WS2811 protocol


Full time elf
May 16, 2010
Perth, WA
Hello All

Just a quick one with some info that hopefully help some one else.

I got myself some ws2811 strings and set about getting them to work.

After some headaches, I got it going last night.

The timing according to the data sheet is correct.

As the 2811 is a three wire system, it implements a NRZ protocol, much like the TM1804.

The timing is:
0: 500ns high / 2us low
1: 2ns High / 500ns low.

After the frame of data is sent for all pixels a reset code is required to display the data and is simply holding the data line low for more than 24us (50us is recommended).

If you are developing with a hardware SPI like the TP3244 then you only use the data line and will need to do some data conversion and lookups.

I will try to explain clearly how I have done it. There maybe a cleaner way.

Some terms I will use.
SPI bit, byte or data I am referring to the actual data coming out the controller
WS2811 bit, byte or data I am referring to bit or byte that make up the ws2811 data.
DMX bit, byte or data is the dmx data coming into the controller

Firstly I needed to use 5 SPI bits to make up one WS2811 bit.

SPI bits 1 2 3 4 5
_ _ _ _
For 1: output | | _ |

For 0: output | | _ _ _ _ |

Set the SPI port to have a 500ns clock.

I have 5 lookup tables each table have 4 elements.

So from the dmx byte, I have masked off the 8th and 7th bits then perform a lookup on first table.
output it to SPI.
then lookup on second table the 7th and 6th bit and output
third table, 5th and 4th bit and output
forth table, 3rd and 2nd bit, out
finally fifth table, 2nd and 1st bit and output.

The entire 40 bits or one WS2811 byte looks like this:
Top Line are the DMX bits from a single byte
second line is how I make up the WS2811 bit, 1's and 0's are fixed, X's are the bits that change

8th 7th 7th 6th 5th 4th 3rd 2nd 2nd 1st
1 X X X 0 1 X X X 0 1 X X X 0 1 X X X 0 1 X X X 0 1 X X X 0 1 X X X 0 1 X X X 0
| | | | | |
First Lookup Second Third Forth Fifth

Repeat this for all dmx bytes and your done.

Rereading through this it is a bit confusing, but hopefully you can work it out.

Again this is just how I got the protocol to work. I am sure there is a more eliquent way.


Apprentice elf
Sep 19, 2010
Indianapolis, IN
The timing values you give are for the slow speed (400kbps). The 2811s that I have seen so far are all set for high speed (800kbps). Just curious where you found 2811 pixels that are set for the slower speed.


New elf
Nov 23, 2012
Perhaps I'm misunderstanding, but I worked out the table of byte codes in the 5-bit SPI protocol, and it looks like your Xs and 1/0s are exactly inverted. For mine, it came out like this:

X000XX00 0XX000XX 000XX000 XX000XX0 00XX000X

What am I missing?


New elf
Nov 23, 2012
Oh. My. God. Never mind. I had my 1 as 00001 in my calculation, instead of 11110. More details on the lookup table to follow to round out this discussion.


New elf
May 21, 2013
Hi, new to the forum but came across it in a search for controlling WS2811 LED drivers. I've got a string I recently purchased and am looking to make a controller for it.

I have a question about how you accomplished masking the 5 WS2811 bits into 8-bits. The concept you presented looks simple enough to follow and I know how to bit-mask, but beyond that I'm a little stumped. Is there any way you could provide some sample code or the 4 elements in each lookup table?

[SIZE=small]Thanks in advance for any help! I've been pulling my hair out for the past few months trying to figure this out :(

It always happens like this, as soon as I post a question to a forum I miraculously seem to find an answer. IDK if it's the right answer, but here's what I came up with.[SIZE=small] The tables were chosen based on the 4 possible outcomes of each SPI 8-bit send. [/SIZE]

[SIZE=small]unsigned char LUT_SPI_1[4] = {0x84, 0x87, 0xF4, 0xF7};[/SIZE]
[SIZE=small]unsigned char LUT_SPI_2[4] = {0x21, 0x3D, 0xA1, 0xBD};[/SIZE]
[SIZE=small]unsigned char LUT_SPI_3[4] = {0x08, 0x0F, 0xE8, 0xEF};[/SIZE]
[SIZE=small]unsigned char LUT_SPI_4[4] = {0x42, 0x43, 0x7A, 0x7B};[/SIZE]
[SIZE=small]unsigned char LUT_SPI_5[4] = {0x10, 0x1E, 0xD0, 0xDE};[/SIZE]

For example: The first SPI byte consists of 1XXXX01XX, where the first set of (3) X's is the 8th DMX bit and the 2nd set of (2) X's is part of the 7th DMX bit. The 4 possible outcomes of 2 DMX bits (8 & 7) are 00, 01, 10, 11. So if we fill the table with the 4 possible values we get:

[SIZE=small]00 = 10000100 = 0x84[/SIZE]
[SIZE=small]01 = 11110111 = 0x87[/SIZE]
[SIZE=small]10 = 11110100 = 0xF4[/SIZE]
[SIZE=small]11 = 11110111 = 0xF7[/SIZE]

As you can see, these are the 4 values of my first LUT. The same is then applied to the remaining 4 SPI bytes:

[SIZE=small]byte 2 = DMX 7 & 6 = X01XXX01[/SIZE]
[SIZE=small]byte 3 = DMX 5 & 4 = XXX01XXX[/SIZE]
[SIZE=small]byte 4 = DMX 3 & 2 = 01XXX01X[/SIZE]
[SIZE=small]byte 5 = DMX 2 & 1 = XX01XXX0[/SIZE]

Next I took the byte color value and isolated the 2 bits used in each SPI out. So again, in the first example that would be bits 8 & 7. To do this, I bit shifted it to the right 6 places, then reset (masked) bits 7-2. This isn't necessary for the first SPI byte, but is for the remaining bytes. Once bit shifted, the remaining value turns out to be the index # of the SPI byte in the corresponding LUT. So here's how I got my SPI bytes:

[SIZE=small]SPI_Data = LUT_SPI_1[(color >> 6) & 0x03]; // SPI Byte 1[/SIZE]
[SIZE=small]// Output SPI[/SIZE]
[SIZE=small]SPI_Data = LUT_SPI_1[(color >> 5) & 0x03]; // SPI Byte 2[/SIZE]
[SIZE=small]// Output SPI[/SIZE]
[SIZE=small]SPI_Data = LUT_SPI_1[(color >> 3) & 0x03]; // SPI Byte 3[/SIZE]
/[SIZE=small]/ Output SPI[/SIZE]
[SIZE=small]SPI_Data = LUT_SPI_1[(color >> 1) & 0x03]; // SPI Byte 4[/SIZE]
[SIZE=small]// Output SPI[/SIZE]
[SIZE=small]SPI_Data = LUT_SPI_1[color & 0x03]; // SPI Byte 5[/color][/SIZE]
[SIZE=small]// Output SPI[/SIZE]

[SIZE=small]SPI_Data is the SPI data byte and color is the 0-255 color value. By masking with an & and 0x03, I'm resetting bits 7-2 so the only value remaining is 00, 01, 10 and 11. I'm using a PIC chip for this and could probably just set the SPI buffer register directly rather than assigning it to a variable, which would put the lookup and send in a single line. I haven't tested this directly yet, but wrote a sample program in C++ that output the SPI bytes and seemed to work fine.[/SIZE]
I setup my RGB data by creating a structure containing 3 chars {R,G,B}, then creating an array of structures sized to the # of LEDs in my strip. You can choose to fill the array how you like to make whatever designs and patterns. For my output function I created 2 for loops: the outer loop cycles through the array and the inner loop cycles through each RGB value, then the code above in inside the 2nd for loop.