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:
- "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)
- "all 50 50 0" - sets all to a dim yellow (using decimal RGB values)
- "red; 20,0,80; blue; green; 50,50,200" - sets all 5 LEDs using a mix of names and rgb values
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:
typedef struct {
const uint8_t name_len;
const uint8_t r;
const uint8_t g;
const uint8_t b;
} Color;
const Color colors[] PROGMEM = {
{3,0,0,0}, //off
{9,240,248,255}, //aliceblue
{12,250,235,215}, //antiquewhite
{4,0,255,255}, //aqua
...
};
const char COLOR_NAMES[] PROGMEM = "offaliceblueantiquewhiteaquaaquamarineazurebeigebisqueblackblanchedalmondbluebluevioletbrownburlywoodcadetbluechartreusechocolatecoralcornflowerbluecornsilkcrimsoncyan ..." ;
const uint8_t name_len;
const uint8_t r;
const uint8_t g;
const uint8_t b;
} Color;
const Color colors[] PROGMEM = {
{3,0,0,0}, //off
{9,240,248,255}, //aliceblue
{12,250,235,215}, //antiquewhite
{4,0,255,255}, //aqua
...
};
const char COLOR_NAMES[] PROGMEM = "offaliceblueantiquewhiteaquaaquamarineazurebeigebisqueblackblanchedalmondbluebluevioletbrownburlywoodcadetbluechartreusechocolatecoralcornflowerbluecornsilkcrimsoncyan ..." ;
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!).
How many volts are the LEDs? If 12v I have a very strong use case I want to try. Can I buy one from you?
ReplyDelete