Save your GPIO
If you have any sort of microcomputer and use it as an IO device or controller, you are always in the business of budgeting connection points. With a pint-size device like the Raspberry Pi, this is especially important. If you use all of your dedicated I2C, SPI, and UART IO, you are left with 8 GPIO. Certainly manageable, but if you want a bunch of indicator LEDs or buzzers (like I do), you have to get smart, and you certainly can’t tie up a GPIO for each one.
Protect your Pi
An additional consideration is sourcing current and directly connecting your GPIO to devices. While sourcing current through a diode is fairly safe, stranger things than short-circuits and current reversals have happened, and the GPIO on the Pi are not protected from this sort of error. A great way to blow $35. It’s also possible with the cumulative current of many LEDs to exceed the current sourcing capability of the Pi. What happens when you do this? Well, best case is that your LEDs don’t light up. In reality, because there isn’t current-limiting built into the Pi, you’ll probably attempt to light all your lights and the Pi will reboot. Good stuff.
SPI and shift registers to the rescue
All of the above lead us to an interface that won’t occupy our GPIO, and can isolate our logic output from the current source. SPI is great for this. We can take a single serial output on GPIO and use a handful of cascaded shift registers to turn eight devices on per shift register, and realistically as many as you like once you cascade them.
What’s a shift register? Pretty simple. Send in serial data, in this case one byte, and put each bit of that byte on an output pin. Eight bits high low –> eight pins, each at high or low. Even better, if you send in a second byte, it will push out the first byte to the shift register’s serial output pin. If you have another shift register connected to the output, the second will display the serial data in the same way as the first was before it was pushed out, and the first register will now show the data on the byte that was sent in.
Wire it up
While the shift registers I mention here (74HC595) will only source 70mA total (8.75 per output), this is enough to light most LEDs even with eight lit, you can easily get around the current limitations by using the shift register to drive a BJT or FET. You can run them on the high or low side, since you can sink or source from the shift register. Let’s get to the wiring. The connections are as follows:
- Serial data in (DS, pin 14) – SPI MOSI or from previous shift register
- Serial clock (SHCP, pin 11) – SPI SCLK
- Master reset (MR, pin 10) – 5V
- Storage Clock – (STCP, pin 12) SPI CE (0 or 1)
- Output Enable (OE, pin 13)- GND
- Ground (GND, pin 8) – GND
- Voltage supply (Vcc, pin 16) – 5V
- Serial data out (Q7S, pin 9) – Serial out to next register (optional)
- Parallel (single IO) output (Q0-7, pins 15, 1-7) – to output devices
Here’s a handy pic:
Fire up the python
Alright, so now to control the things. The easiest way to do this is by setting up spidev. A few modules need to be enabled if you’re using a Pi, and you’ll need a couple packages, notably spidev. Good instructions are here: http://tightdev.net/SpiDev_Doc.pdf .
After you’ve got all that set up, the code is pretty simple. The basic command to send a byte is:
import spidev spi = spidev.SpiDev() spi.open(0,1) # Port 0, Chip Select 1 (CE1) spi.xfer2([spisendbyte])
Now if we want to add some usability, we can use an array to translate to our assign bytes. The following is a good base raw write function. It is written for output low (the shift register is connected to the cathode of the LEDs) and it sinks current. For output high you would simply remove the bit inversion line.
def spirawwrite(enabledlists): import spidev spi = spidev.SpiDev() spi.xfer2([spisendbyte]) spi.open(0,1) # Port 0, Chip Select 1 (CE1) # If we have two registers, we set two bytes # They are set in order. First in goes # to the farthest register spiassignments= for enabledlist in enabledlists: bytesum=0 for index,bit in enumerate(enabledlist): bytesum+=bit*(2**index) # We are output low spiassign=255-bytesum spiassignments.append(spiassign) #transfer bytes resp = spi.xfer2(spiassignments) return resp
With a bit more fancy code calling this function (and a board as created here), you can do something like this: