Driving an LED Strip with SPI

Driving Chart for the WS28XX series ICs

WS2811 ws2811 0
0.5μs H
2.0μs L1
1.2μs H
1.3μs LReset Time
Above 50μs
 WS2812B  ws2811 0
0.40μs H
0.85μs L1
0.85μs H
0.40μs LReset Time
Above 50μs

Because I would like to use the Galileo, I’m limited in not being able to use the FastLED library, as its intended for AVR.  One approach is to use SPI at a very high frequency, and send whole Byte sequences to imitate a tight timing sequence for the above:

For the WS2811 1 bit is transmitted in 2.5μs. So if We set up an SPI driver to push out the 8 bit number 255 , 11111111 in binary, at 400khz

ws2911spiex1

Now as a whole pulse that’s useless to us. Because we need to pulse a HIGH and LOW in a specific pattern. To do this, we can mimic the signal by increasing the SPI frequency and sending in BYTES with specific patterns. For example If we double the frequency to 800Khz we can now send in 2 bytes in the same time frame. So if we send in a 255 and a 0:

ws2911spiex2

We get the above. Which is almost to a point where we can start making up masks. In the above example each BYTE, that’s 8 bits, takes 1.25μs to transmit, and looks like a continuous HIGH signal for that amount of time. This means that each BIT takes 1.25μs/8 = 0.15625μs to transmit…

ws2911spiex3

To get a correctly timed pulse for the WS2811 we will need to adjust a few things. 1st we need to find out a nice round number for each Bit to sent out at.  We need to be able to add multiple bits transmition time together to get exactly 1.2μs and 1.3μs. As well as 0.5μs and 2μs. For example…

Time Required for 1 Bit to Transmit = 0.1μs

Time required for 1Byte = 0.8μs

At 1,250,000Hz a single Byte takes 0.8μs to transmit and a Single bit takes 0.1μs to transmit. So…

ws2911spiex4

Above we transmitted 4Bytes over SPI [255,240,0,0] to get a Logic 1

ws2911spiex5And here we transmitted 4Bytes over SPI [248,0,0,0] to get Logic 0

What this means is that we have to transmit 4 Bytes for each BIT in a standard Byte of representation Data. If I have to control 1 LED i need 3 bytes to control Red, Green and Blue. That means I need:

3bytes x 8bits x 4spi-block-bytes = 96Bytes per LED!!!! With my current suit layout that has 288 RGB LEDs, I need 864 Bytes of control data. With the Above protocol, ill need 27,648 Bytes per FRAME….Now what I’m getting at here is that Using SPI to control these drivers is probably not a great idea. It will take 22.1184 milliseconds per frame which is at best around 5 frames per second.

My other concern is that the Galileo probably wont allow such tight control over the SPI frequency. I will be able to set it at 1mhz and 2mhz but not anywhere in between with any kind of certainty. I spent some time on my oscilloscope last night to see if the SPI frequency actually changes in between MHz but saw no difference unless the change was significant .

Here is the same diagrams but using the WS2812B timing, which is the same as the WS2811 but using the double data rate:

Set SPI to 2,500,000Hz Use 4SPI Bytes per LED BYTE:

To send  Logic 1 you send [255,255,128,0]

ws2812sendhighTo send Logic 0 you send [255,0,0,0]

ws2812sendlow

I can confirm that the 144LED strips uses fast mode… This was sent ot me directly from WorldSemi

“The 144LED Strip uses the WS2812B chipset(built-in IC), and HIGH speed mode”

Their Web page states they use the WS2811 chip but they also say that they use High Speed  mode in prepackaged systems… Using High Speed mode I can get maybe 10 frames per second with the same number of LEDs.

Here is some NodeJS code I wrote that will grab an array of Bytes and create a Buffer() of bytes with the appropriate bit pattern to be pushed out to the SPI bus.

var ledBytes = 1*3;
var ledBar = new Array(ledBytes);
var aBuffer = new Buffer(0);
var counter = 0;

init();
runMain();

function init()
{
 //clear the led array
 clearSuit();
}

function runMain()
{
 var testBuffer = WS2811b_SPI();
 
 for(counter=0; counter<testBuffer.length; counter++)
 {
 console.log("Index "+counter+"->"+testBuffer[counter]);
 }
 
}

function WS2811b_SPI()
{
 var spiBuffer = new Buffer(ledBytes*8*4);
 var currentByteSPIBlock = new Buffer(8*4);
 var spiBufferIndex = 0;
 
 for(counter=0; counter<ledBytes; counter++)
 {
 currentByteSPIBlock = createSPIBlockFromByte(ledBar[counter]);
 currentByteSPIBlock.copy(spiBuffer,spiBufferIndex,0,currentByteSPIBlock.length);
 spiBufferIndex+=currentByteSPIBlock.length;
 }
 return spiBuffer;
}

function createSPIBlockFromByte(dataToPush)
{
 var mask=1, bitResult=0, cnt=0, bufferIndex=0; 
 var tempBuffer = new Buffer(4*8);
 var spiBlock = new Buffer(4);
 
 mask = mask << 7; 
 for(cnt=0; cnt<8; cnt++) 
 { 
 bitResult = dataToPush & mask; 
 bitResult = bitResult >> (7-cnt); 
 spiBlock = createSPIBit(bitResult);
 spiBlock.copy(tempBuffer,bufferIndex,0,spiBlock.length);
 bufferIndex+=spiBlock.length;
 mask = mask >> 1; 
 } 
 return tempBuffer;
 
}

function createSPIBit(value)
{
 var spiBitBlock = new Buffer(4);
 
 if(value==1)
 {
 spiBitBlock[0] = 255;
 spiBitBlock[1] = 255;
 spiBitBlock[2] = 128;
 spiBitBlock[3] = 0;
 }
 else
 {
 spiBitBlock[0] = 255;
 spiBitBlock[1] = 0;
 spiBitBlock[2] = 0;
 spiBitBlock[3] = 0;
 }
 return spiBitBlock;
}

function clearSuit()
{
 for(counter=0; counter<ledBytes; counter++)
 {
 ledBar[counter] = 0;
 }
}