Introduction: A $1 LED Mood Lamp With ATtiny13 and WS2812
This is a low-cost mood lamp with four modes.
1. Rainbow spark. A spark of light moves upwards time after time and gradually changes the colour.
2. Rainbow glow. A stable glow which gradually changes the colour.
3. Candle fire simulation.
You may switch modes by tapping a touch button on the top. The current mode is saved in EEPROM memory after powering off.
How tiny is ATtiny13?
The idea was to get maximal features from minimum hardware, something more complex than automated switch or thermometer, a project close to the edge of this tiny microcontroller. After all, restrictions make you think creative, right? Well, it looked like it in the beginning.
The most challenging in this project was to shove all the code into ATtiny13. The microcontroller has 1K bytes flash and just 64 bytes RAM. Yes, when I say “bytes”, I mean those consisting of eight bits. 64 bytes for for all your local variables and call stack. To make it clear, consider we have to control 8 RGB LEDs. Each of them is defined by 3 bytes (one for red, green and blue channel respectively). So, just to store the state of 8 LEDs, we’ll need to implement an array of 8 structures 3 bytes each and a pointer to the beginning of this array would take one more byte. Thus, 25 of 64 bytes are out. We’ve just used 39% of RAM and haven’t really started yet. Additionally, to store seven basic rainbow colours, you’ll need 7×3 = 21 bytes, so 72% of RAM are out. Well, as for basic colours, I exaggerate: we don’t need all of them at the same time in RAM and they never change, so they may be implemented as a constant array to be stored in flash instead of RAM. Anyway, it gives an overall impression about used hardware.
Remembering Knuth’s statement about premature optimization, I began from prototyping three lamp modes separately to see what happens. I have tested them separately to make sure they work properly and each one fits my microcontroller. It took a couple of evenings to accomplish it and everything went well… until I tried to put them together inside switch statement. avr-size utility reported a 1.5 Kb text section size (with -s flag of avr-gcc). At that moment my original intention was to grab some ATtiny25 with 2Kb flash and that could have been the happy end of this story.
But somehow I felt that after considerable optimization I could manage to shrink that crappy code into 1Kb. However, it took one more week to realize that it’s impossible and one more week to accomplish it anyway. I had to cut a rainbow to five basic colours (without significant visual difference). I got rid of case statements and used a chain of if-then-if to decrease binary code size. Fire animation needs a pseudorandom number generator which is pretty much bulky, so I implemented a simplified version of LFSR with constant initial value. I don’t care about PRNG full cycle length and just looking for a descent balance between code size and “realistic fire animation”. I also implemented a lot of minor optimizations I can’t remember right now and even managed to flash all the modes apart from fire into the chip. When I ran out of ideas, my total code was about 1200 bytes.
I took timeout and had been reading a lot about AVR code optimizing. I was close to giving up and rewriting everything in assembly language but gave it the last chance. During the final optimization rush, I’ve cut a rainbow to three basic colours and made others to be calculated on the fly, I inspected everything and followed AVR optimizing recommendations and finally…
avrdude: writing flash (1004 bytes):
Writing | ################################################## | 100% 0.90s
There is no need to say that I used nearly all the RAM and just one byte of EEPROM to store current mode. I do not imply that this is an ideal and ultimate implementation. It just works and fits the microcontroller. I’m sure, you could do it better. I really am. I just want to share the fun of solving an apparently impractical problem you consider nearly impossible at the beginning. “Thus, hacking means exploring the limits of what is possible...” --Richard Stallman.
1x ATtiny13 MCU ($0.28 = $0.24 for MCU in SOP-8 package and $0.04 for DIP8 Adapter)
8x WS2812 RGB LEDs (I recommend a board or a piece of LED stripe) ($0.42)
1x TTP223 Touch button ($0.10)
1x Micro USB to DIP Adapter ($0.14)
1x 10kΩ resistor (<$0.01)
1x 100nF ceramic capacitor (<$0.01)
1x 10–47µF electrolytic capacitor (<$0.01)
Step 1: Software Setup
You’ll need avr-gcc toolchain for compiling the source code and avrdude utility for uploading microcontroller’s ROM. The installation process is pretty much simple and straightforward, but it depends on your operating system. If you use some sort of GNU/Linux, you probably already have proper packages in your repository tree. The source code of this project may be downloaded here:
You’ll also need a light_ws2812 library:
Once you’ve got avr-gcc toolchain and project sources, run your terminal and type the following code:
Step 2: Programming the Microcontroller
If you have some sort of USBASP programmer, just connect it to Attiny according to its pinout. Usually it would look like this but I strongly recommend checking out your actual pinout!
Alternatively, you may use an Arduino board as a programmer. Open Arduino IDE and find the Arduino ISP example in “File→Examples” menu. After uploading the sketch, your Arduino board acts as a programmer. The comments in the sketch code would give you a clue to pinout.
to flash the MCU and
to set fuse bits.
Step 3: Schematics
You may check everything on a breadboard before soldering.
Step 4: Soldering
It really depends on your actual components size, but try to solder it as compact as possible and follow the schematics. You may omit the capacitors, which are needed for voltage smoothing, but I strictly do not recommend doing so as long as you have enough space. I’ve attached the MCU board to the back of WS2812 board with thermoglue and fastened the touch button on the top and USB port on the bottom of the construction. Now you may turn on and test it.
Step 5: Container
You’ll need some suitable semi-transparent tube or a glass tube cowered with white paint A piece of plastic bottle would also work well. You are likely to find something suitable among your scraps. I made a tube with semi-transparent clay, namely Cernit translucent with night glow effect. It gives a nice fading effect when LEDs are turned off. I also made a basement with regular clay and covered it with metal acrylic paint. As for the upped end of the tube, I closed it with a proper-sized coin. Don’t forget to separate it from direct contact with touch button’s contacts with a piece of insulator tape. The button has a reasonable proximity range by default so it should work well.
Alternatively, you may use a 3D printer to make a case you like.
Step 6: Enjoy!
Plug the USB cable and touch the button on the top to turn it on.
Step 7: Improve It
I’m sure, you’ll find a way to add new modes or features. The 1Kb is the limit. :)
Participated in the
Colors of the Rainbow Contest