For this Instructable we need a WS2812 LED strip, also known as NeoPixel, and any MCU that could be programmed via Arduino. I use Digispark, it is a very nice tiny MCU based on ATtiny85. It has a special bootloader called Micronucleus which provides the ability to upload a program directly trough USB. This means that if you have Digispark board you may start using it without any other hardware, just follow the instructions at http://digistump.com/wiki/digispark
List of Components:
- Digispark (any Arduino will do)
- WS2812 LED strip (NeoPixel)
Please note that NeoPixel library needs enough memory to hold color data for all pixels. You need 3 bytes for each LED in your WS2812 strip (and some more for other variables). For example, ATtiny85 has only 512 bytes of memory and I was able to control up to 100 LEDs. If you want to do longer strips, you need more powerful MCU (ATmega328 is a decent choice).
Step 1: Wiring
Most WS2812 strips comes with three-wire connectors. I removed the plastic casing of the connector and connect wires to pins directly (they fit well). White is GND, red is +5V and green is data input.
Most LED strips have additional power lines on both ends. This wires are usually naked, so make sure to isolate it (otherwise you may accidentally short-circuit it).
Please note that LEDs require a lot of power. If you have a strip of 30 LEDs, you can run it on full brightness directly from any PC or USB power supply (it will consume less that 500mA). About 100 LEDs will work as well on low brightness, but if you want more, you'd better off with a separate power. For long strips it is recommended to apply power from both ends, for extra-long strips, connect additional power lines each 100-200 LEDs.
WS2812 receives color data serially, so you can control virtually any number of LEDs with just one data pin. You can use any digital output pin (do not forget to put correct number in the code).
Once we connected a strip, let's get to the coding part.
Step 2: Coding
To quick start, find the #define PIN 0 and replace 0 with number of pin which you connected to data input of NeoPixel strip.
You probably would also like to adjust other settings, which are explained in code comments:
- #define NUMPIXELS 30 – sets number of pixels to control
- #define RNDPIN 2 – set to any pin with analog input (it is used only once to initialize random number generator)
- #define BRIGHTNESS 64 – maximum LED brightness (1 to 255). Please note that high brightness requires lots of power, so start with low values
- #define FOCUS 65 – shape of color spots (increase to get narrow spots, decrease to get wider spots)
- #define DELAY 4000 – set the speed of animation: decrease to speed up, increase to slow down (it's not a delay actually)
- #define DEBUG 0 – set to 1 to display FPS rate
Upload the code using you usual approach. Enjoy!
Step 3: Troubleshooting
If you do not see anything at all, check your connections, polarity and data pin.
If some LEDs are blinking or stuck – it is likely a power issue. Try lowering the #define BRIGHTNESS setting and make sure you have enough power.
If you see any bugs in code or would like to improve – you are welcome!
Step 4: How It Works
For this project we have three "color spots" which runs back and forth along the strip with different speeds. When spots run across each other, superposition of red, green and blue results in various colors. We keep position and speed of each spot in variables and we need to recalculate a color for every LED on each redraw. While we can do it many times per second, the animation will look smooth.
Each color component of a pixel is proportional to exp(-d*d), where d is distance between the pixel and the center of spot of corresponding color. In other words, pixel colors represent a normal (Gaussian) distribution – it is exactly the same as using "Gaussian blur" filter in graphic editor.
The exponent function included in Arduino library appeared to be very slow, so I made an approximation using only two multiplications and one division: 1.0/(1.0-(0.634-1.344*x)*x). Please note that this approximation is suitable only for x < 0.
We calculate color of each pixel in loop and send it to a memory buffer by calling strip.setPixelColor(). When we looped over each pixel we call strip.show() and NeoPixel library synchronously sends all data to specified pin – all pixels, one by one, 24 bits of color data for each pixel. First NeoPixel reads first 24 bits of data, stores it locally in a register and passes all other data through to next pixels. Second "pixel" takes its data and sends remainder to the next one – and so on and so forth until every pixel gets it's data. You can safely send less data than number LEDs you have in a strip – only first NUMPIXELS will work then.
The code is well documented (I hope!), and please ask questions in comments here!
Step 5: Tweaking
Of course, there are lots of ways to modify this idea and create other projects.
For example, I made a metal ring for one of my projects and glued one of the strips to it. Now it is a nice decoration light you can put on a wall or a ceiling. Putting a strip near a window pane results in a nice reflection (as you could see on a photo).
In technical aspects, the code may be improved a lot by rewriting math in fixed-point calculations. I am using float to keep things descriptive and easy to understand, but it is very slow. I would probably publish an optimized version later – or, if someone would like to do – you are welcome!
One of other ideas I have is to use a microphone to adjust animation to sound. It would probably require something more powerful than ATtiny.
Finally, you can plug Digispark into any powerbank and enjoy your gadget in portable mode. Glue the strip to a handbag, a garment, or a bicycle and use it as a unique personal accessory .)