psBASIC Displays System Stats with LEDs

Written by ChipMaster on 2024-10-25 21:18:41 updated 2024-10-25 21:18:41

The WallCHIPv2 Control Panel

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.

WallCHIPv2 Control Panel

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!