Hardware Interactions: Part 3 – RGB LED Driver
This is the next installment of our blog post series on Raspberry Pi hardware interactions. In the past two posts we’ve looked at inputs; buttons and rotary encoders. This time, we’ll implement an output by setting the colours displayed on a strip of addressable LEDs. During this guide, we will explain:
- How to wire up the circuit (including how to deal with differing logic levels);
- How to configure the Raspberry Pi to allow for communication on the Serial Peripheral Interface bus (SPI),
- How this type of LED works; and,
- How these factors impact the software design decisions made when interacting this kind of hardware
In keeping with previous posts in the series, this post will delve into the technical details of the hardware we’re interacting with and will end with a demonstration—a Java application which allows you to set the colours displayed by an LED strip. As the code for this demonstration is a little more involved than the last two posts, it has been made available on GitHub. If you would prefer to jump straight to the implementation of this demonstration, head over to the project repository.
What You Will Need
To try this yourself, you’ll need the following,
- Raspberry Pi (with power supply)
- RGB LEDs (APA102 or similar)
- Logic level shifter (74AHCT125 or similar)
- Breakout boards for the LEDs
- 10KΩ resistors
We’re using Pin 2 to provide power (5V), and Pin 6 as the common ground between components in our circuit. To communicate with the LEDs in our circuit, we will be using the SPI bus—that is, Pin 19 (Master out Slave in) for data, and Pin 23 for clock. Alternatives for the GPIO pins used in the example circuit can be found here. By consulting the technical documentation of the Raspberry Pi GPIO pins, we can see that there is an incompatible voltage level between the SPI communication voltage of the Raspberry Pi and the LEDs. To account for this, we will use a logic level shifter.
A logic level shifter is a circuit that can change one logic level to another, allowing for compatibility between components. Specifically, our LEDs require SPI communication at 5V whilst the Raspberry Pi operates SPI on the 3.3V level. We are using the 74AHCT125 to provide the compatibility shift. It should be noted that this component does not interfere with the communication, as far as our components are concerned—they do not know the logic level converter between them exists. To connect the Raspberry Pi to the logic level shifter, please use the circuit below (Figure 1) for reference.
Finally, we can connect our LEDs to the logic level shifter. We connect the respective data, clock, power, and ground pins from the logic level shifter to our first LED. You may be wondering how do we add more LEDs to the circuit? Do we need multiple logic level shifters? It seems we may run out of pins that way.
Introducing daisy chaining! Daisy chaining allows for LEDs to be connected one after the other, like you see in an LED strip. In this context, the data and clock out of one LED is passed into (daisy chained) the data and clock in for the next and they can all share the same power and ground lines. It is theoretically possible to use as many LEDs as you like when using this type of communication, however, there is a limit to the amount of power the Raspberry Pi can safely supply. For our final circuit, please use the circuit below (Figure 2) for reference.
Raspberry Pi Configuration
SPI devices operate using four lines and need to be attached to specific pins on the Raspberry Pi, namely Pin 19 for data and Pin 23 for the clock. Multiple ground and power pins are available. There’s one last piece of setup we need to go through before we can start to interact with these LEDs. SPI also needs to be enabled via the interfacing options available through the command,
will open a configuration menu and once it has we can, select option 5, Interfacing Options, then select option 4, SPI, and answer ’Yes’ to enable the use of the SPI bus. Use the right arrow to select Finish. Once the Pi has rebooted we can use the following command to test that the config has been successfully updated.
$ ls /dev/*spi*
Which should respond thusly,
Usually the Pi is limited to these two SPI devices but, the daisy chain we’re using in this example lets us get around this limitation.
How the RGB LEDs Work
As mentioned previously, the Raspberry Pi can communicate with the LEDs via the Serial Peripheral Interface bus. We can send encoded data containing information about what colour and brightness each LED should be, and we can expect each LED to read its own colour and pass information on to the rest of the LEDs in the chain. Let’s look at how this process works.
When we want to communicate with LEDs, we write a packet of data onto the bus. This packet contains LED colour data and control data used for transmission. The control data is used to activate specific modes on the LED.
let’s look at the modes the LED can be in:
- Data forwarding: this is the default mode of each LED. In this mode, the LED will write everything it reads back out (from Din pin to Dout pin).
- Data storing: this mode is activated upon receiving 4 bytes of 0x00 (0000 0000). In this mode, the LED will store the next 4 bytes of data read, while simultaneously writing 4 bytes of 0x00 out (daisy chaining). Once 4 bytes of data has been stored, the LED will return to data forwarding mode.
Using our knowledge of these modes, we can start to construct our packet. We know that it must start with 4 bytes of 0s, which we call the start frame, and that we can write individual messages to each LED by simply putting them in the correct order, as shown above, but what do we want these messages to contain?
Within the SPI packet that is sent to each LED, there is an encoded colour and brightness data. This data is 4 bytes in length, the first byte contains information about the brightness, and the remaining 3 bytes contain information about the colour.
brightness byte must start with three 1s, leaving the remaining 5 bits of the byte configurable. This means we have 32 different levels of brightness (25), configurable via a 5 bit number we can encode into the packet, from completely off (00000) to full brightness (11111).
The 3 colour bytes, red, blue, and green, contain information about the specific intensity of that particular colour in the LED. Each colour byte is fully configurable, meaning we have 256 different levels of intensity of the particular colour (28), from no colour (00000000 or 0x00) to full colour (11111111 or 0xFF).
Pulse Width Modulation
Each LED houses three smaller LEDs, which you may have guessed, are the colours red, green and blue (RGB). From the encoded colour data, the LED uses its own internal mechanisms to read and translate it into three pulse width modulated (PWM) signals. These signals rapidly turn the three individual LEDs on and off. The percentage of time a light is on (duty cycle) represents the intensity of that light. The relative intensity of the three colours (red, green and blue) determines the overall colour you see. This neat RGB trick is widely used in phone, computer, and television screens. It is very likely that right now, the colours you see on your screen are a result of the colours of millions of pixels, all containing a red, blue and green colour.
End Frame Issue
The final piece of the puzzle is the end frame which needs to be of varying size with respect to the number of LED frames in the packet. This is due to, the design of the APA102 data communication protocol; the switching between the data forwarding and data storing modes we discussed earlier. For each LED in a strip the SPI clock will become out of sync by half a cycle, or put more simply, in order for a packet of data to reach the last LED in the strip, we need to send 1 bit of dummy data for every 2 LEDs. This needs to be done because while there is no data on the SPI bus, the SPI clock will remain idle, and thus the LEDs will not be driven.
This gives us a completed packet which looks like this,
It’s time to checkout the project repository and try this for yourself. With the example code provided you will be able to communicate over the SPI bus to set the colours on your LED strip. Using your knowledge from the previous post on rotary encoders, you could create an implementation of a LED strip with an encoder that will step through the colours of the rainbow as the dial is rotated. Or if you’re feeling a little more adventurous, why not try extending the driver available in this repository to add animations to your LED projects?
In part 4 of this series we will look at another protocol commonly used to communicate with hardware peripherals as we hook up a temperature sensor to the Pi’s I2C bus.
Header image courtesy of Alexander S
Thanks to Matt Hannah, Alex Cummaudo, Shannon Pace, Maryna Vlasiuk, and Andy Vouliotis for their feedback.