Thursday, March 29, 2012

Simple USB LED Controller - Part 1.5

I've been working on a simple usb led controller (read Part 1), but unfortunately ran into a bit a of snag - it turns out that the surface-mount package of the TLC5940 has different pin assignments than the through-hole version I've used before - even though it has the same number of pins in the same physical arrangement, the pin assignments are shifted over by 7 pins, which means my original PCB designs don't work. Lesson learned: double check the datasheet! I've updated the PCB design and sent off v0.2 to have new PCBs made, so now I just have to wait a few weeks for them to arrive.

In the meantime though, I was able to get the LUFA usb library up and running, port the Arduino TLC5940 library to work on the ATMega32U2, and get a good portion of the led controlling firmware written. In order to test this out, I programmed the controller board I built, but had to use led drivers on a separate breadboard. It's ugly, but it works:

The goal of SULC is to make controlling high power RGB LEDs really simple, so the firmware I'm writing can parse several different formats to set the colors of the LEDs. It shows up as a virtual serial device, and you can send simple messages to set all LED colors. Here are some examples:

"all purple" - set all 5 RGB LEDs to purple

"red, green, blue, yellow, teal" - sets the LEDs to different colors

",,red,,yellow" - sets the 3rd LED to red and the 5th to yellow (leaving the others unchanged)

I'm planning to add support for hex colors (e.g. "#FF0000" or "#ff0") along with a more efficient binary protocol for programmatically setting the colors quickly (see protocol.txt for details).

Getting the microcontroller to be able to parse all ~147 standard web color names was a bit tricky. The ATMega32U2 only has 1KB of RAM, and a good chunk of that is being used for actually running the program, so there's no room to store a table of color names as a global variable in RAM. Instead, I used avr-gcc's PROGMEM macro to specify that particular data structures should live in flash program memory instead (Dean Camera wrote a nice tutorial on PROGMEM). I defined two main data structures: one giant string with every color name concatenated together, along with an array of structs that holds a color's name-length and its rgb values:

Reading from PROGMEM structures is a little different than normal variables - instead of getting a value with syntax like:

uint8_t len = colors[5].name_len;

you need to use a macro to read a byte from program memory:

uint8_t len = pgm_read_byte(&colors[5].name_len);

The reason for the difference is that program memory and RAM are distinct - so the array colors is a pointer in program memory address space. Indexing into an array the normal way (e.g. colors[5]) would be looking up that address in RAM, which obviously won't work because the data isn't in RAM! There are also functions for reading a float, word, or dword defined in avr/pgmspace.h.

To interpret a color name, the parser first scans through the colors array looking for a color with the same length, and whenever it finds a color of the right length, it compares the input string buffer to COLOR_NAMES to see if they match. Of course there are plenty of possible optimizations - using better data structures to make lookups faster, or compression techniques to make the color names take up less space - but it's currently "fast enough" and with 32K of program memory available, size isn't a huge concern right now either.

I'll post another entry once the new PCB's get here (assuming they work this time!).