Scope of this post
The scope of this post is to describe an implementation of a LED driver for LED stripe of the type WS2812. As an Atmega lover I wanted to implement the driver for that kind of chip. It should work for all type in that family; obviously with restriction based on the memory of your processor, the power supply used, crystal,… I will describe as clear as possible the project up to the application level. It is just to give an example of how to use the driver. For a particular application it is maybe good to rewrite patterns and application to match your need. Please refer to the code. Any comment is welcome.
There are seemingly some libraries available on the internet for this purpose but I did not want only to set a LED to a certain color but also to implement a color gradient and some effects around it. That is, the possibility to let the diode smoothly fade from one color to the next in the RGB coordinate and using the Bresenham algorithm in 3D; all this running in an ATmega. That was my initial requirements. My preference goes to ATMEGA1284P since it has 128K of program memory (to store e.g. some predefined sequences), 16K of internal RAM and it can work at 20MHz. At the end this project is aimed at giving a light effect for my wife’s snow globe collection.
The LED driver and led.h is the piece of code that speaks to the LED stripe itself. It provides the communication interface with the LEDs , an initialisation function and a shared memory. This shared memory can be used by the upper levels of the software to put a given diode at the given color.
The LED driver works in an interruption. TIMER0 is used to generate the interruption TIMER0_OVERFLOW on regular basis (e.g. 10ms). During this interruption, the shared memory (Frame Buffer) is read and the RGB values are sent over the serial bus to the LED stripes. This way the upper levels can write asynchronously in the memory without to worry about any frame refresh. It reads the RGB data structure and sends the data in the sequence GRB, MSB first as the datasheet specifies.
At the beginning of a frame refresh, a reset signal is sent on bus, it consists of a low level for more than 50us. Then the GRB value for every diode is sent one by one.
Every G, R and B byte is split into bits, the corresponding high/low levels timing are set on the bus to ensure clean/error free transmission. Note that the bit test is not done in a for loop, but in a succession of if (byte&(1<<[0..7])) instead. This is to increase the speed; otherwise, the timings cannot be in spec.
Data transfer time( TH+TL=1.25μs±600ns)
|T0H||0 code ,high voltage time||0.35us||±150ns|
|T0L||0 code , low voltage time||0.8us||±150ns|
|T1H||1 code ,high voltage time||0.7us||±150ns|
|T1L||1 code ,low voltage time||0.6us||±150ns|
Please find below, the timings for a one and zero, then the frame refresh (for 10 diodes and 12MHz crystal). X distance is visible in the upper-left box under BX-AX.
Timing H for bit one
Timing L for bit one
Timing H for bit zero
Timing L for bit zero
10 Diodes frame
The handler is in charge of providing a mechanism to instantiate a colour gradient (LED_Set_gradient(Color_gradient LED_gradient,uint8_t LED_Pos);) and a mechanism to update the colors on a regular basis. Typically it is the interface between the driver and the application. This set of functions is the only ones to use the Bresenham algorithm to smoothly move from color to the next in an RGB frame of reference. A Colour gradient is a structure with members: color start, color stop and speed.
To achieve this, like the LED driver, the LED handler uses a timer overflow (the TIMER3_OVERFLOW) interruption to update the colors on a regular basis, and at the given speed.
From that point on one should not consider anymore that we give a diode a color but a gradient. A single color is from now on a gradient for which the start color is the same as the stop color.
The Bresenham algorithm was developed in 1962 by Jack Elton Bresenham to draw a line in a grid. There are implementations of 3D versions on the internet. The Bresenham implementation here is based on one of those (I don’t remember which one so I cannot give credit) with some variations. The picture below illustrates how a line is drawn in a grid. The same will be applied to the colors.
Usually, the Bresenham implementation does a loop in which it calls a kind of plot(x,y,color) function to draw a line in one shot. But in our case, the loop must be outside since we want to have the color to go softly at the given speed from start color to end color. Bresenham.c provides the function Bresenham3D_init who set the init color and init parameters and returns a type tBresenham that will be used as argument to Bresenham3D_next function that will compute the next color. That function returns 1 whenever there is still at least one step to the end color.
The set of patterns as well as a sequence parser are implemented in patterns.c. It can be seen as a starting point to implement more features, imagination is the only limit.
Only one pin is needed. In Led_define.h set the suitable port and led arrangement. Since the diodes are connected in a chain and that there is a reshaping feature on each of them, the connection to atmega can be straight; just connect the pin you want to the middle pin of the stripe. Think also to put the pin direction properly (here DDRC register).
#define LED_PORT PORTC
#define LED_PORT_bit 1
#define LED_ROWS 5
#define LED_COLS 22
#define LED_NR LED_ROWS*LED_COLS
The cabling is straight forward since each diode reshapes the signal. Nevertheless it is advised to repeat the power supply every 18-20 diodes. According to my measures each diode takes 40mA when at full white; therefore take a strong enough power supply. A normal USB can drive like 20 diodes.
Demo on the snowball shelf