Controlling an RGB LED is a simple and common microcontroller project, often the next project done after blinking and fading an LED.
To make this more interesting, I built it using an ATtiny13, one of the smallest and simplest AVRs, which required "high-voltage programming." I also used a 3W LED because I wanted to learn how to make a constant-current driver. Then I added a low-battery indicator, since that seemed like a useful skill for other projects, and mounted the PCB onto acrylic. (I'd never worked with acrylic and heard it was difficult to drill and cut, but had no issues with either.) Finally, I made a diffuser out of polymorph - I had tried polymorph a few weeks earlier to make the cover for my electronic candle, and I wanted more practice shaping it.
This was made from parts I had lying around. To buy all the components would be about $25, with most of that for the $15 high-power LED. Because this build was to practice with various electronics concepts, I'll use each step to discuss what I learned, hoping that information might be useful for one of your projects.
Step 1: Build & Program It
With the exception of the power regulation, this is essentially the same circuit repeated three times, one for red, green, and blue. Three of the ATtiny's pins - PB0, PB1, PB2 - are configured as outputs and are used to rapidly turn on and off a transistor using PWM, which in turn turns on and off the red, green, or blue LED attached to it. (An RGB LED is really just three LEDs mounted in a single package.) The other three pins on the ATtiny13 - PB3, PB4, and PB5 - are configured as inputs and run through the chip's analog-to-digital converter. Each pin is attached to a 10K potentiometer which acts as a voltage divider, providing from 0 to 5 volts depending on whether the knob is turned all the way left or right. The microcontroller converts this voltage to a value from 0-255, which it then uses to determine what percentage of the time the corresponding output pin is on, thus turning the blue knob's position into the blue LED's brightness, etc.
My Eagle schematic and board layout are attached, as well as the print-out of the board's traces which I used to etch the PCB. There are a few jumper wires on top of the board since I've yet to try double-sided etching. The three long wires from the POTs to the ATtiny are also shown on top of the board, although to make it look nicer, I put them on the back of the board using insulated wire.
The RGB LED comes mounted to a heatsink star, but Sparkfun's documentation says that if all three LEDs are fully on, then additional heat sinking is needed. Figuring I'd certainly want to turn all three knobs up and see how bright of a white light this can produce, I decided to attach the LED's star to a larger 1" aluminum square (1/8" thick). The LED isn't really attached as much as held in place by the six power wires, although I did place thermal compound between the two plates, which also keeps it in place.
The LM317's get pretty hot as well -- they are each dissipating around 2-watts -- so I screwed TO-220 heatsinks with thermal grease onto each of those as well.
The C source code is attached. Since the ATtiny13 only has two hardware PWM pins, the PWM here is done "manually." That is, every time the timer counter register overflows, the interrupt routine checks to see whether it's time to turn off the red, blue, or green LED. The chip is running at its default speed of 9.6MHz, which means the overflow interrupt triggers at 37.5KHz (9.6MHz/256), which means the PWM speed is only 146Hz (37500/256). There are ways to update this much faster, but I didn't see any reason to since 146Hz is still well above people's flicker fusion threshold.
Step 2: Disabling RESET to Get One More I/O Pin
The ATtiny13 has just 8 pins, 2 of which are needed for power and 1 of which is normally reserved for reset. With three analog inputs and three PWM outputs, this uses every possible pin, and requires disabling the reset pin.
In one sense, dedicating a microcontroller pin for reset seems wasteful. How often does a device actually need to be reset as opposed to just momentarily cutting the power? With 40 or more pins on most microprocessors, why not have a dedicated reset pin? But with only six, that's giving up more than 15% of your data possibilities. Of course, I could just step up to the next bigger chip - after all, the 14-pin ATtiny24 series is only a few cents more than the 8-pin series, the 20-pin series only a few cents more than that, and the 28-pin ATmegas only slightly more. Then again, using all six pins on an 8-pin chip does seem more elegant, and it seemed like a fun bonus challenge.
The challenge is that In-System Programming (ISP) requires a reset to interrupt the chip and put it into programming mode. But if we disable the reset pin (i.e. turn it into an ordinary I/O pin by clearing bit 0 of the high fuse), then how will we re-program the chip if any changes are needed? One easy option: Don't -- program it right the first time, and if you need to re-program it, get another chip - they're cheaper than a Snickers bar. But the more interesting way is to use high-voltage programming.
That sounds cooler and more exotic than it is. AVR chips run at 1.8 - 5.5 volts, but if you apply 12 volts to the reset pin, rather than being fried, the chip will go into a special programming mode even if the reset pin has been "disabled" into a regular I/O pin. An ISP programmer, like Adafruit's USBtinyISP, is connected to your computer's 5v USB port and can't generate the 12 volts, but ATmel makes several programmers that can.
Or for a fun DIY option, there are several ways to build a high-voltage programmer, or at least a high-voltage fuse resetter that can return an AVR to its default state of having a normal reset pin so that you can then use your ISP. Mnedix has a great instructable for doing this, although I used this Arduino-based one by Paul Willoughby. I had a Sparkfun Arduino-clone that I'd never used, so in just a few minutes I hooked it up to my power-supply and was able to "recover" AVRs that had their fuses locked into non-ISP friendly configurations.
Step 3: Constant Current Driver Using LM317
This project is similar to Project #3 in Dhananjay Gadre's TinyAVR book. Gadre's one uses a regular 5mm RGB LED, so it doesn't require any transistors. I had a 3W RGB LED, so I figured I'd use that and make a much brighter light. (Sadly, in the process of figuring out this driver, I managed to burn out my $15 LED and needed to buy another one.)
The best way to use a high-powered LED is to drive a fixed current through it, which in the case of this LED is up to 350 milliamps for each color. In my first instructable, I used four 3W white LEDs and didn't bother making a constant current driver. I didn't know how and figured it wasn't necessary since these were strobe lights that were only on for a few milliseconds at a time, and they still work fine years later. The problem with using the normal LED scheme of just adding a resistor in series is that you don't know the exact resistance of the resistor (most are plus or minus 5%), you don't know the exact voltage drop of the LED (typically plus or minus .2 volts), and you don't know your starting voltage (which could be plus or minus several volts as the battery goes from fully charged to fully drained). So the current you picked a resistor for might end up being double what you expected, which might burn out a bright LED over time. (This is presumably true for regular 5mm LEDs too, but if you're trying to run 20 milliamps through them and end up running 40, they seem to be able to handle the extra heat. And even if not, they only costs a few cents.)
The easiest way I could find to make a constant-current driver is to use an LM317 adjustable voltage regulator. This chip always tries to pump enough current on its output pin so as to be 1.25 volts higher than the voltage it sees on its adjustment pin. This is primarily used for making a variable voltage regulator by setting up a voltage divider around the fixed 1.25V, but it can also be used to make a constant current driver by inserting a resistor across that 1.25V drop. For example, by using a 3.9 ohm resistor, the LM317 outputs 320 milliamps (1.25V / 3.9 ohms = .320 amps).
Artificial Intelligence has a terrific instructable that shows how to do this for a fixed brightness. To dim the LED (and produce different shades of red, green, and blue), we need to add PWM so we're rapidly turning the LED on and off. Using PWM means we need a transistor between the microcontroller and the LM317. The most common way to do this is to add one on the regulator's output pin, as shown in the first schematic. But that means the transistor needs to be able to handle 320 milliamps, which means it will likely be slightly larger (TO-220) and more expensive than small signal transistors.
In Project #7 of Simon Monk's 30 Arduino Projects book, he shows a clever workaround for achieving the same effect with a small, logic-level transistor. He uses the 2N7000 N-channel MOSFET and attaches it to the adjustment pin, turning off that pin (which has a high impedance and thus very little current on it) rather than the output pin. This way only the LM317 needs to be able to handle high-power, rather than the LM317 plus the PWM transistor. I originally didn't understand how this works, but he graciously explained via e-mail:
"The more conventional schematic you sent needs a high power MOSFET to switch the already regulated current. The trick in my circuit is that I use the LM317 itself as both the current regulator and the high current switch. This means I can use a really low current MOSFET."
As in his schematic, I also added the 1K resistors between the PWM pin and the transistor's gate, even though I thought they were unnecessary with MOSFETs (as opposed to BJTs). Again, he explained:
"While that is true about the gate impedance, and the circuit would probably be just fine without the resistor, it is generally considered good practice to use a gate resistor, for this reason. There is actually quite a significant gate capacitance, so when you first turn the gate on, a momentary high current flows out of the Arduino output to 'fill' the 'gate capacitor'. In theory, this could be enough to damage the Arduino, especially if switching fast with PWM. However, in practice the small resistance of the wires leading to the gate and the general robustness of the ATMega outputs means that you can usually get away without the resistor."
Finally note that the resistor the LM317 uses to set its current also needs to handle a lot of power (i.e. dissipate a lot of heat), which is why those are the larger 1W variety. The 1K resistors on the adjustment pin and the controlling transistor can be much smaller.
Step 4: Low Battery Indicator
As in my first two instructables, I'm once again using a 2-cell LiPo battery, which is nominally 7.4V, but actually starts out at 8.4V. The reason I used a 2S battery is that the blue and green components of the LED require as much as 3.5V, and the LM317 has about a 3V drop when supplying 320 milliamps, so I needed a voltage source that was at least 6.5V.
One downside of multi-cell LiPo batteries is they have no protection circuitry, either against over-current or over-discharging. Allowing a huge amount of current is actually a feature of these batteries - they are designed for RC vehicles, and if your helicopter suddenly needs a lot of amps to stay in the air, it's better to have the battery able to deliver it (even if it ruins the battery) then to have the drone fall out of the sky.
But over-discharging is annoying for every day use. It means if I let the battery run until it dies, there's a very good chance it won't be able to be recharged. I've already had this happen to two of these batteries, and so I end up just plugging them into a charger every so often to recharge, even if they are probably still near full.
Since I might leave this LED for a while, I figured it'd be nice to add an indicator LED that lit up once the battery drops below a threshold so I know it's time to recharge. Since these batteries are 7.4V and my circuit needs about 6.5V to work correctly, I set up the low-battery LED to turn on when the voltage drops to 6.5V.
The simplest low-battery indicator circuit I found is by Swagatam Majumdar on the Homemade Circuits & Schematics blog. It uses just two NPN transistors and a few resistors and consumes only a tiny bit of current (less than 1 milliamp). The first transistor is normally on, which keeps the second transistor turned off and thus the indicator LED off. If the first transistor turns off, the second transistors turns on, which lights the LED. The first transistor will be on as long as it has about 0.6 volts on its base pin. So we set up the resistors controlling the first transistor's base as a voltage divider so that when the battery drops below the target threshold, the base voltage will be lower than 0.62 volts. (I figured out the exact cut-off voltage for my transistor by attaching it to a variable power supply and turning down the voltage until the transistor turned off.)
There are many combinations of resistors that will create the voltage divider, but we want the values to be large enough so that the circuit draws very little current normally, yet small enough that there's still enough current going into the base of the first transistor to turn on (after all, BJTs are current-driven devices). I used the same 33K resistors for the top value as he did in the blog post. Since .62 volts is 9.5% of the 6.5 volt threshold, I needed the bottom resistor to be 9.5% of the total, which meant about 3600 ohms. The standard resistor value of 3.3K is thus too low and 3.9K is too high, so I used a 3.3K resistor in series with a 270 ohm one to create 3570 ohms, which works perfectly when I test it with my power supply. (These resistors are all +/- 5%, although I tested them with a multimeter to get as close as possible.)
Step 5: Polymorph Diffuser
This LED is painful to look directly at. In Gadre's book, he suggests cutting a ping-pong ball in half and putting it over the RGB LED, and that's just for a regular LED running 20 milliamps for each color rather than 320. I didn't have any ping pong balls, but I did have a bag of polymorph that I wanted to practice shaping.
Polymorph is a thermal plastic with a very low melting point. To work with it, you heat the balls to 125-175 degrees Fahrenheit. Some people do this in the microwave, but I found the technique of just adding them to near-boiling water much more effective. The balls clump together so you don't have to worry about them dissolving. As they melt, the form a clear plastic glob that looks and feels like thick clear silly putty.
You can shape it with your fingers as it cools down, gradually turning more opaque as it does. Once it's back to fully white, it's hard enough to saw and drill! Unfortunately, I found it difficult to get precise shapes just by hand - I have the same problem trying to make anything interesting with my daughter's PlayDough.
To actually fit the polymorph over my aluminum square, I decided to create a mold (the aluminum itself would've been a good one, but it was already stuck on the PCB). So I cut a block of wood the same dimensions as the heat sink, heated up the polymorph and then stretched it over the wood. (The first version I did wasn't nearly tall enough since to be a good diffuser, it has to sit a centimeter or more above the LED. So I cut a block out of thicker wood, reheated the polymorph, and did it again.) The polymorph stuck to the wood a little as it hardened, but I was able to pry it off in one piece. The unevenness of my finger prints and stretch marks create a bit more diffusion, which is nice in parts, although I wish I had been more uniform about it. One bonus of using polymorph is I didn't have to glue it to the PCB - I just heated up the bottom edge again by holding it in hot water and then stuck the soft clear edge right onto the PCB, with the hardening plastic acting as a weak glue.
I've attached a video of the final product, although as with most LED projects, the iPhone's camera doesn't do it much justice.
As always, I'm very eager to hear any comments, questions, corrections, tips or suggestions, whether about the electronics, source code, or anything else. Thanks for reading.