psBASIC Displays System Stats with LEDs
The WallCHIPv2 Control Panel
The Project
Building on the previous episode I will periodically put the gathered system statistic on 8 LEDs to simulate the blinken light displays of yesteryear's computing big iron. Those lights would blink like crazy as the various components of their CPUs processed instructions. Since our SBCs don't have physical access to such information I will update my LEDs with the bit-pattern of a busyness counter.
In my steampunk computer I wanted the blinken lights and a set of toggle switches reminiscent of the DDP-24 we had in the backyard for a year or two. Discussion of the switches will be reserved for a future episode.
I wanted to have my LEDs and switches on the door of my cabinet connected to an SBC mounted inside the cabinet. Originally I chose to power this computer with a C.H.I.P SBC. Unfortunately NTC, the maker of the C.H.I.P went out of business, so I decided to reserve my limited stable of parts for other things. So I converted to a Raspberry Pi 2B (rPi2b) someone gave me.
To minimize the wires being routed between door and SBC and to reserve the I/O on the rPi2b I opted to use a bare-metal ATMega328P as an 8 in and 8 out I2C attached GPIO expander. That gives me 8 switches and 8 LEDs multiplexed on just 4 wires: 2 for power and 2 for I2C. I used the Arduino IDE to program the part with my firmware and to configure it to use the integrated R/C oscillator at 8Mhz, instead of an external crystal. This freed up two whole I/O port registers to allow transferring the 8 ins or 8 outs with a single 1 byte move instruction. It also reduced the complexity of my interface board to the ATMega328P, power decouple cap and 8 resistors to power-limit the LEDs. Switch pull-ups are in the chip.
The next bit of code
You can read the previous episode to see how I gather stats for display. I made them as simple subroutines using the classic GOSUB
and RETURN
mechanism. Both subroutines update the variable BUSYCT
with 8 bits of busyness data. This allows me to change the stat being shown simply by choosing the subroutine to call.
My ATMega's I2C protocol is dead-simple. Any byte coming in is written to the LEDs. Any byte read request returns the switches states, 1 bit per switch packed in a single byte. All we need to do now is: open an I2C port, read a BUSYCT
, put it out to the port, delay and do it again.
The I2C write code is this:
open "B",#1,"/dev/i2c-1:17" : ' Open Door controller: I2C1 ID 11H
' ...
10100 s_$=chr$(BUSYCT) : ' Pack a 1 byte write
put #1,,s_$
BUSYCTP=BUSYCT
return
The OPEN
command recognizes I2C bus devices if the filename starts with the absolute "/dev/" path. It then expects hardware parameters for the device to follow. The device ID of the slave you want to talk with is required. So an I2C device path name will always be followed by a colon (:) and the device ID in decimal. Run HELP "i2c"
in psBASIC for the details.
The PUT
command writes the exact number of bytes contained in the string variable s_$
out the I2C bus destined for the specified slave. The chr$()
function produces exactly one byte so one byte will be written to slave ID 17 on I2C bus #1.
The BUSYCTP
part is used for redundant write elimination in the main loop.
What if you don't have a stack of LEDs on I2C?
The more traditional way to blink LEDs on something like a Raspberry Pi is to tie LEDs direct to GPIO outputs. So lets write a new version of the 10100 subroutine with that in mind.
data 3,0,7,1,199,198,6,11
dim led%(8)
mat read led%
' ...
10100 v=BUSYCT
for l=1 to 8
out led%(l),v and 1 : ' put out the LSB on a pin
v=v\2 : ' shift-right 1 bit
next
return
To tell us which bits should be output on which GPIO pins I use an array named led%
. This is loaded from a DATA
statement with the MAT READ
command. IT reads elements 1-8 into the array. For historical reasons the MAT
command ignore element 0. I loop through all the possible LEDs putting the LSB out on the pin, and then shifting my work variable (v
) right by one for each LED.
The main loop with status read and LED write is:
200 gosub 20100 : ' CPU time
if BUSYCT<>BUSYCTP then gosub 10100 : ' Write door LEDs
'REST A BIT AND THEN DO IT AGAIN
sleep 0.05 : ' We don't need to drive the CPU at 100% load
goto 200
' We should never exit the loop
That calls the subroutine we saw in the previous episode. The new BUSYCT
values is compared to the previous (BUSYCTP
) and if they differ its written to the LED controller on the I2C bus.
The loop could have just as easily been written with DO ... LOOP
instead of a line number and GOTO
. Even though DO ... LOOP
doesn't need a bogus test, as in other languages, I still find myself using GOTO
for infinite loops. Old dogs ... I guess.
Putting it all together
'*** GLOBALS ***
100 defint A-Z
dim field$(20)
delimit #10," "+string$(2,0)+"1" : ' Parse variable space separated ASCII tables
BUSYCT=0 : ' The value shown on the door LEDs
BUSYCTP=255 : ' Previous BUSYCT
open "B",#1,"/dev/i2c-1:17" : ' Open Door controller: I2C1 ID 11H
'*** Output loop ***
200 gosub 20100 : ' CPU time
if BUSYCT<>BUSYCTP then gosub 10100 : ' Write door LEDs
'REST A BIT AND THEN DO IT AGAIN
sleep 0.05 : ' We don't need to drive the CPU at 100% load
goto 200
' We should never exit the loop
'*** Door Write ***
10100 s_$=chr$(BUSYCT) : ' Pack a 1 byte write
put #1,,s_$
BUSYCTP=BUSYCT
return
'*** CPU Work load ***
20100 BUSYCT=0
open "I",#10,"/proc/stat"
mat input #10,field$
close #10
BUSYCT=(VAL(field$(2))+val(field$(4))) and 255
return
And there you have it! The 8 LSBs of the CPU usage counter being displayed on LEDs. They will flutter slowly in a familiar binary progression when the system is bored and then blink pretty furiously when it gets busy!
You can replace the 10100 subroutine with the second version shown above to toggle LEDs tied to GPIO pins. Don't forget to merge the initialization code in the top too.
Get your copy of Pi Shack BASIC here!
In the next episode we'll put those switches to use!