Chapter 11.2: Control RGB LEDs with an LED Controller Part 2
04/117/2016 | 09:59 AM
In the last section, we covered some LED driver basics and how to limit the current of an LED without the need for inline current limiting resistors. In this section we will connect the TLC5940 LED driver breakout board to the Starter Kit and then begin to develop a serial interface software driver. We will also define some data structures to help deal with the serial interface data stream.
Hardware Connections
In order to communicate with the TLC5940, we need eight wires: Vcc, Ground, a data pin, a clock pin, a pin to latch the serial data called XLAT in the specification, a pin to select the mode called VPRG, a global enable pin called BLANK, and a pin to serve as a grayscale clock, GSCLK. We can choose to use any of the UART, LEUART, or USART peripherals on the MCU. I will choose USART1, Location #1, since it is available on the header pins of the Wonder Gecko Starter Kit. We have used these pins in earlier lessons for SPI and serial data. Connect the board as follows:
Note that we are only using two pins of the four available on USART1. We will not be using US1_RX or US1_CS. There is no rule that we have to use all four pins of a peripheral.
If you wanted to connect a second TLC5940 breakout board in series with the first, you would simply solder the pins on the right side of the first board to the pins on the left side of the second board. You could repeat this over and over to add more and more individually-addressable channel capacity to the design. We will talk about how that can be accomplished in software later in the chapter.
When I first found this part and reviewed the spec, I thought that it only required three wires because of this line: “Only 3 pins are needed to input data into the device.” But only after I began to experiment with the chip (and could not get it working) did I figure out that in addition to the three pins necessary to load data into the device, namely SIN, SCLK, and XLAT, you need three more pins to actually perform the grayscale function: VPRG, BLANK and GSCLK. I was really surprised. This is exactly why you should build breadboard prototypes with any new parts before you spend money on a PCB!
Another surprise that I found upon working with this board is that it was getting hot after being connected to the Starter Kit. The problem was that Sparkfun decided to run VPRG through a “solder jumper trace,” which is way to connect the VPRG pin to ground but allow you to later cut the trace if you decide to control that line from the MCU. I had assumed that the trace would be open by default, and they would leave it up to me to populate a resistor to close the connection to ground. So when I drove PD3 high, it was being connected to ground on the TLC5940 breakout board. Once I cut the trace, the problem was solved, and VPRG is now able to be controlled by the MCU.
You must cut this trace on the back of your TLC5940 breakout board to use the code for this chapter. If you don’t, your board will get slightly warm, and you won’t be able to control the dot compensation (channel current control).
Peripheral Software Configuration
We are going to configure all pins with code rather than using the Configurator tool to generate the code for us, just as a succinct reminder of what is needed to activate the GPIO pins. Remember, whenever we want to activate a peripheral, we need to do the steps listed below. The order is important. If we don’t enable the peripheral clocks before we configure the peripheral, the peripheral will not receive any of the configuration register writes. If we route the peripheral through to the GPIO before configuring the peripheral, then we will could get some glitches on the GPIO pins as the peripheral is activated. In summary:
Enable the GPIO clock
Enable the peripheral clock (in this case, USART1)
Configure and enable the peripheral
Route the pins used by the peripheral through to the GPIO
Configure the pins used by the peripheral in the GPIO (push-pull, input, etc.)
Here is the resulting code to enable the USART and GPIO peripherals:
#define CONTROL_PORT gpioPortD
#define XLAT_PIN 1
#define VPRG_PIN 3
#define BLANK_PIN 4
#define SIN_PIN 0
#define SCLK_PIN 2
#define GSCLK_PIN 7
// Turn on the peripheral clocks
CMU_ClockSelectSet(cmuClock_HF, cmuSelect_HFXO);
CMU_ClockEnable(cmuClock_GPIO, true);
CMU_ClockEnable(cmuClock_USART1, true);
// Configure the USART peripheral
USART_InitSync_TypeDef init = USART_INITSYNC_DEFAULT;
init.baudrate = 50000;
init.msbf = true; // MSB first, per the TLC5940 spec
USART_InitSync(USART1, &init);
// Route the peripheral to the GPIO block
USART1->ROUTE = USART_ROUTE_TXPEN | // US1_TX
USART_ROUTE_CLKPEN | // US1_CLK
USART_ROUTE_LOCATION_LOC1; // Location #1
// Configure both the USART and control pins in GPIO
GPIO_DriveModeSet(gpioPortD, gpioDriveModeLowest);
GPIO_PinModeSet(CONTROL_PORT, SIN_PIN, gpioModePushPullDrive, 0);
GPIO_PinModeSet(CONTROL_PORT, SCLK_PIN, gpioModePushPullDrive, 0);
GPIO_PinModeSet(CONTROL_PORT, XLAT_PIN, gpioModePushPullDrive, 0);
GPIO_PinModeSet(CONTROL_PORT, VPRG_PIN, gpioModePushPullDrive, 0);
GPIO_PinModeSet(CONTROL_PORT, BLANK_PIN, gpioModePushPullDrive, 0);
GPIO_PinModeSet(CONTROL_PORT, GSCLK_PIN, gpioModePushPullDrive, 0);
LED Packet Data Structure
The TLC5940 implements a shift register that expects an exact 96- or 192-bit serial data stream in order to program the current limits (dot correction) and PWM values (grayscale) for all channels. The type of serial stream at the input to the TLC5940 (dot correction versus grayscale) is selected with the VPRG pin. The dot correction fields are 6 bits multiplied by 16 channels, which gives us the 96 bits. The grayscale fields are 12 bits multiplied by 16 channels, which gives us the 192 bits.
The bit sizes for grayscale and dot correction are a little problematic for us. They are not nice byte-sized values that we have come to know with the uint8_t and uint16_t data types that we have used on the USARTs thus far. If we were to store the 6-bit values as uint8_t and the 12-bit values as uint16_t, then transfer these values into the USART peripheral, the result would be padded with extra zeroes, which would not work for this chip. Things need to be packed into a serial stream exactly as the TLC5940 specification dictates.
Instead, we will define a structure that has bitfields. These bitfields don’t guarantee that the values will be stored in memory in the nicely packed structure that we want, but they will take care of truncating any values stored in the structure for us. They also remind us of the size of each field that the TLC5940 expects.
#define MAX_CHANNELS 16
#define DC_BIT_LEN 6
#define GS_BIT_LEN 12
// Structs for LED driver
typedef struct LED_bit_fields
{ // Number after colon is field width
uint8_t dot_correction : DC_BIT_LEN;
uint16_t grayscale : GS_BIT_LEN;
} LED_data_struct;
typedef struct LED_stream
{
LED_data_struct channel[MAX_CHANNELS];
bool mode;
} LED_stream_struct;
LED_stream_struct stream;
Warning: There is a compiler directive called “packed” that will help save memory space but does not guarantee that the structure will be stored in the correct order in memory, because the way that the C compiler packs the structure is not always predictable. Don’t rely on any specific ordering; instead, use shifts and masks to build the final serial data stream. We will cover how to do that in the next section.
With the above code, I have created my own type called LED_data_struct, which is then used inside of another new type definition that is called LED_stream_struct. This enables me to create a single variable that will hold all of the programming data for the TLC5940 in a single top-level global variable called stream. I can now set the stream with this kind of syntax:
Chapter 11.2: Control RGB LEDs with an LED Controller Part 2
In the last section, we covered some LED driver basics and how to limit the current of an LED without the need for inline current limiting resistors. In this section we will connect the TLC5940 LED driver breakout board to the Starter Kit and then begin to develop a serial interface software driver. We will also define some data structures to help deal with the serial interface data stream.
Hardware Connections
In order to communicate with the TLC5940, we need eight wires: Vcc, Ground, a data pin, a clock pin, a pin to latch the serial data called XLAT in the specification, a pin to select the mode called VPRG, a global enable pin called BLANK, and a pin to serve as a grayscale clock, GSCLK. We can choose to use any of the UART, LEUART, or USART peripherals on the MCU. I will choose USART1, Location #1, since it is available on the header pins of the Wonder Gecko Starter Kit. We have used these pins in earlier lessons for SPI and serial data. Connect the board as follows:
Note that we are only using two pins of the four available on USART1. We will not be using US1_RX or US1_CS. There is no rule that we have to use all four pins of a peripheral.
If you wanted to connect a second TLC5940 breakout board in series with the first, you would simply solder the pins on the right side of the first board to the pins on the left side of the second board. You could repeat this over and over to add more and more individually-addressable channel capacity to the design. We will talk about how that can be accomplished in software later in the chapter.
When I first found this part and reviewed the spec, I thought that it only required three wires because of this line: “Only 3 pins are needed to input data into the device.” But only after I began to experiment with the chip (and could not get it working) did I figure out that in addition to the three pins necessary to load data into the device, namely SIN, SCLK, and XLAT, you need three more pins to actually perform the grayscale function: VPRG, BLANK and GSCLK. I was really surprised. This is exactly why you should build breadboard prototypes with any new parts before you spend money on a PCB!
Another surprise that I found upon working with this board is that it was getting hot after being connected to the Starter Kit. The problem was that Sparkfun decided to run VPRG through a “solder jumper trace,” which is way to connect the VPRG pin to ground but allow you to later cut the trace if you decide to control that line from the MCU. I had assumed that the trace would be open by default, and they would leave it up to me to populate a resistor to close the connection to ground. So when I drove PD3 high, it was being connected to ground on the TLC5940 breakout board. Once I cut the trace, the problem was solved, and VPRG is now able to be controlled by the MCU.
You must cut this trace on the back of your TLC5940 breakout board to use the code for this chapter. If you don’t, your board will get slightly warm, and you won’t be able to control the dot compensation (channel current control).
Peripheral Software Configuration
We are going to configure all pins with code rather than using the Configurator tool to generate the code for us, just as a succinct reminder of what is needed to activate the GPIO pins. Remember, whenever we want to activate a peripheral, we need to do the steps listed below. The order is important. If we don’t enable the peripheral clocks before we configure the peripheral, the peripheral will not receive any of the configuration register writes. If we route the peripheral through to the GPIO before configuring the peripheral, then we will could get some glitches on the GPIO pins as the peripheral is activated. In summary:
Here is the resulting code to enable the USART and GPIO peripherals:
LED Packet Data Structure
The TLC5940 implements a shift register that expects an exact 96- or 192-bit serial data stream in order to program the current limits (dot correction) and PWM values (grayscale) for all channels. The type of serial stream at the input to the TLC5940 (dot correction versus grayscale) is selected with the VPRG pin. The dot correction fields are 6 bits multiplied by 16 channels, which gives us the 96 bits. The grayscale fields are 12 bits multiplied by 16 channels, which gives us the 192 bits.
The bit sizes for grayscale and dot correction are a little problematic for us. They are not nice byte-sized values that we have come to know with the uint8_t and uint16_t data types that we have used on the USARTs thus far. If we were to store the 6-bit values as uint8_t and the 12-bit values as uint16_t, then transfer these values into the USART peripheral, the result would be padded with extra zeroes, which would not work for this chip. Things need to be packed into a serial stream exactly as the TLC5940 specification dictates.
Instead, we will define a structure that has bitfields. These bitfields don’t guarantee that the values will be stored in memory in the nicely packed structure that we want, but they will take care of truncating any values stored in the structure for us. They also remind us of the size of each field that the TLC5940 expects.
Warning: There is a compiler directive called “packed” that will help save memory space but does not guarantee that the structure will be stored in the correct order in memory, because the way that the C compiler packs the structure is not always predictable. Don’t rely on any specific ordering; instead, use shifts and masks to build the final serial data stream. We will cover how to do that in the next section.
With the above code, I have created my own type called LED_data_struct, which is then used inside of another new type definition that is called LED_stream_struct. This enables me to create a single variable that will hold all of the programming data for the TLC5940 in a single top-level global variable called stream. I can now set the stream with this kind of syntax:
stream.mode = mode;
stream.channel[channel_number].grayscale = grayscale_value;
stream.channel[channel_number].dot_correction = dot_correction_value;
In the next section, we will complete the LED software driver and use it to bring some light to our test LEDs on the breakout board.
PREVIOUS | NEXT