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)
  • "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
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:

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 ..." ;


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!).

Sunday, March 25, 2012

Simple USB LED Controller - Part 1

Back when Next Make built the Next House Party Lighting System, we designed the LED controllers to connect on a shared RS-485 network over CAT5 cable.  This was a great solution for that system since the controllers were far apart (RS-485 uses differential signaling so it's pretty robust over longer distances), and we had 24 separate controllers to connect so we wanted to be able to chain them together on a single network.

But if you wanted to set up a smaller scale LED system with just 1 or 2 sets of LEDs, those controllers were a bit overkill - you needed a separate USB->RS-485 converter and then had to string them together with CAT5.  So I set out to design a simpler high power LED controller that had a USB port directly on it (I'm calling it SULC - the Simple USB LED Controller).

Instead of using an FTDI (USB->serial converter IC) along with a microcontroller, I wanted to try out the ATMega8/16/32U2 family of AVRs which has USB support built-in.  Unfortunately there's no through-hole version of those chips, so I had to design a PCB to try them out - my first experience laying out a PCB from scratch.  I used the open source KiCad EDA for the schematic design and pcb layout.  After a weekend of work, I had a PCB ready to send off to production:


I ordered the PCBs from SeeedStudio which offers an amazing deal: Fusion PCB Service - $10 for 10 boards that are 5cm x 5cm, with 2 layers of copper, soldermask and silkscreen on both sides.  The boards arrived about 2 weeks after I placed the order (mostly shipping time from China), and looked great:


The next step was soldering the components to the board.  Since most of the components were surface mount, I decided to try out "frying pan reflow" - you first spread solder paste on each pad on the PCB, then line up all of the components on top of that, and finally stick it in a frying pan to melt and reflow the solder.  SparkFun has a great article about low-cost reflow soldering.  

But how do you get the solder paste cleanly onto the pads when they're only ~0.01" wide? You can buy solder paste syringes to squirt the paste onto each pad individually, but that seemed like a lot of work with ~150 pads, and tricky to get the right amount onto each pad.  Instead, I used a solder-paste stencil to apply the paste - SparkFun also has a great tutorial on solder paste stencils.  You can order solder paste stencils online from places like Pololu, but to go the full DIY approach, I made my own.  I bought 3 mil mylar on McMaster and had my friend laser cut holes in it for the pads.  Here's what the stencil looks like:


A few of the smaller holes didn't get completely cut, so I had to use a pin to clean them up:

(notice the little bits of mylar stuck on the upper side of those holes)

After cleaning up the stencil, it was time to apply the solder paste.  I used the technique described by SparkFun - use other PCBs to hold the one you're working with in place, and spread the paste with a putty knife:

Spreading the paste across the stencil


Unfortunately the solder paste didn't apply very cleanly - probably because I didn't hold the stencil tight enough and because it was warm when I applied it, so the paste was more liquid than I would have liked.  I went ahead and placed each component on top of the solder paste:

Solder paste and components placed

In order to reflow the solder paste, I stuck the PCB inside a rectangular aluminum extrusion and placed that on a small electric stove/hot plate:

PCB placed inside aluminum extrusion to help spread the heat


When reflowing solder, there's a specific heat curve that you're supposed to follow to get it to melt and make good connections.  A number of people have modified toaster ovens with PID control loops to get the temperature to follow a specific curve precisely.  I just used a thermocouple with my multimeter to measure the temperature and used the stove's knob to make adjustments - pretty simple and it worked fine.


Fresh out of the oven!


The reflow process was mostly successful - all of the small discrete components like resistors, capacitors, and LEDs aligned themselves and were soldered in place perfectly.  There were a couple solder bridges though between pins on the TLC5940s and on the ATMega32u2:


A nasty solder bridge on a TLC5940 (top) and a minor one on the ATMega32u2 (bottom)

After a bit of cleanup with solder wick and flux, everything looked good to go.  I soldered up the through-hole components and then came the moment of truth - plugging the board in.  To my surprise, it actually lit up the first time!


And even better than seeing that beautiful blue light was the output of lsusb:

Bus 004 Device 126: ID 03eb:2ff0 Atmel Corp.

Yes!  The board shows up as a USB device (running Atmel's DFU bootloader)!  

I wrote a quick test program and loaded onto the device over USB using dfu-programmer.  It works!  It flashes each of the 4 debug LEDs on the board:


That's as far as I've gotten so far, but I think it's pretty awesome progress for my first ever custom PCB and first time working with surface mount components.  Next I need to reprogram the fuses on the microcontroller to get the full 16MHz clock speed, and then I can try using LUFA to make the board show up as a USB virtual serial device, and finally I can see if the TLC5940 LED drivers are connected correctly to drive high power LEDs.

The board designs are on github - https://github.com/scottbez1/sulc - although beware that I haven't finished testing the board, so there may be errors still.