Actions

Difference between revisions of "Driving a large WS2811 LED string with an ATtiny13 and nothing else"

From Just in Time

(Created page with "After creating code to drive a WS2811/WS2812 LED string with an 8Mhz AVR, some of the comments on that page triggered me to...")
 
Line 13: Line 13:
  
 
Data is terminated by either a zero Jump value or a zero Count value, except for the first Jump, which can be zero if the first LED in the string should be lit.
 
Data is terminated by either a zero Jump value or a zero Count value, except for the first Jump, which can be zero if the first LED in the string should be lit.
 +
 +
For example, suppose we want to create the following 20-LED string, where the leftmost LED is the first one:
 +
 +
{| class="wikitable" border="1"
 +
! colspan="5"| 5 unlit
 +
! colspan="2"| 2 lit
 +
! colspan="8" | 8 unlit
 +
! 1 lit
 +
! colspan="4" | 2 unlit
 +
 +
|-
 +
| style="background-color:black;color:white;" | black
 +
| style="background-color:black;color:white;" | black
 +
| style="background-color:black;color:white;" | black
 +
| style="background-color:black;color:white;" | black
 +
| style="background-color:black;color:white;" | black
 +
| style="background-color:blue;color:white;"  | blue
 +
| style="background-color:red;color:black;"  | red
 +
| style="background-color:black;color:white;" | black
 +
| style="background-color:black;color:white;" | black
 +
| style="background-color:black;color:white;" | black
 +
| style="background-color:black;color:white;" | black
 +
| style="background-color:black;color:white;" | black
 +
| style="background-color:black;color:white;" | black
 +
| style="background-color:black;color:white;" | black
 +
| style="background-color:black;color:white;" | black
 +
| style="background-color:yellow;color:black;" | yellow
 +
| style="background-color:black;color:white;" | black
 +
| style="background-color:black;color:white;" | black
 +
| style="background-color:black;color:white;" | black
 +
| style="background-color:black;color:white;" | black
 +
|}
 +
 +
We can represent this with the following buffer contents:
 +
 +
{| class="wikitable"
 +
! Jump
 +
! Count
 +
! colspan = "3"|LED (GRB)
 +
! colspan = "3"|LED (GRB)
 +
! Jump
 +
! Count
 +
! colspan = "3"|LED (GRB)
 +
! Jump
 +
! terminate
 +
|-
 +
| 5    || 2   
 +
|style="background-color:blue;color:white;"  | 0
 +
|style="background-color:blue;color:white;"  | 0
 +
|style="background-color:blue;color:white;"  | 255
 +
|style="background-color:red;color:black;"  | 0
 +
|style="background-color:red;color:black;"  | 255
 +
|style="background-color:red;color:black;"  | 0
 +
| 8 || 1
 +
| style="background-color:yellow;color:black;" | 255
 +
| style="background-color:yellow;color:black;" | 255
 +
| style="background-color:yellow;color:black;" | 0
 +
| 4 || 0
 +
|}
 +
 +
The memory mapped representation would require 60 bytes, the sparse representation takes only 15 bytes.

Revision as of 13:33, 9 March 2014

After creating code to drive a WS2811/WS2812 LED string with an 8Mhz AVR, some of the comments on that page triggered me to create a version of that code for AVRs that run at 9.6Mhz. In itself this was a lot easier, since these MCUs have more clock cycles per bit available than their 8 Mhz counterparts. However, when talking about 9.6 Mhz AVRs, we're actually talking about the ATtiny13. This MCU is fast, small and dirt-cheap, but is also limited in its resources: 64 bytes of RAM and 1024 bytes (= 512 instructions) flash memory.

Our WS2811 driver, like all others that I've seen, is "memory mapped", which means that for every LED in an LED string the driver requires 3 bytes in memory and every such triplet directly maps onto one of the LEDs in the string. For a 64-byte MCU this means there is a theoretical limit of 21 LEDs in a string. In practice this is even less, because a typical program will use some stack space. I found that a simple color cycle demo could drive at most 13 LEDs. At the same time, most of the demos that I created only light up a few LEDs of the string, so the actual information content is much less than the n-times-3 for n LEDs. Wouldn't it be nice if we could drive such a sparsely lit string with only the bytes we need to describe the LEDs that are actually lit and maybe some bytes describing where these LEDs are?

It turns out that not only is this possible, the resulting driver code is considerably smaller than our original 9.6 Mhz driver code as well! ust likee

Sparse data representation

The new driver reads an array of bytes and sends out a ws2811 serial signal, just like the old one did. However, where the previous driver just read a sequence of green, red, blue-values from memory, the new one expects a sequence of bytes that describe blocks of consecutive LEDs, with for each block:

  1. a byte containing er count of unlit LEDs preceding the block (the "Jump")
  2. a byte containing the count of lit LEDs in the block (the "Count")
  3. for each LED in the block a G, R and B value (three bytes per LED)

Data is terminated by either a zero Jump value or a zero Count value, except for the first Jump, which can be zero if the first LED in the string should be lit.

For example, suppose we want to create the following 20-LED string, where the leftmost LED is the first one:

5 unlit 2 lit 8 unlit 1 lit 2 unlit
black black black black black blue red black black black black black black black black yellow black black black black

We can represent this with the following buffer contents:

Jump Count LED (GRB) LED (GRB) Jump Count LED (GRB) Jump terminate
5 2 0 0 255 0 255 0 8 1 255 255 0 4 0

The memory mapped representation would require 60 bytes, the sparse representation takes only 15 bytes.