Controlling WS2812 LEDs - How to do faster?


#1

Hello forum! I’ve got example code working to control a WS2812 LED strip. I used SPI at 15MHz to get the WS2812 to pay attention and work with the 150ns timing. Works well but I’m not savvy enough at squirrel to really get this code running efficiently.

The full example code is here: https://gist.github.com/nseidle/66d8966463aabee7e6ac#file-gistfile1-txt

Here is the main updateStrip() function:

`
//Sends the pixel array to the strip
//Converts colors (red = 255) to ‘bits’ that WS2812 can understand
function updateStrip(){

const ONBIT = 0xFC; //1.2 / .25us = 1.45us, 83% on time
const OFFBIT = 0x80; //0.5 / .91us = 1.41us, 35% on time

//Each pixel requires 24 bits
//Each "bit" going to the WS2812 is actually a byte (0xFC or 0x80)
stringData <- blob( (sizeOfStrip * 24) + 2);

//The 0x00 at the beginning gets the SPI hardware working correctly
//so that the first bit of actual data has proper timing
stringData.writen(0x00, 'b');

//Step through all the bytes in the pixel array, three bytes per pixel
for(local j = 0 ; j < sizeOfStrip * 3; j++)
{
    local myByte = pixels[j];
    
    //Convert this one part of a pixel (r g or b data) into ON/OFFBITs
    for(local i = 0 ; i < 8 ; ++i)
    {
        if(myByte & 0x80) 
            stringData.writen(ONBIT, 'b')
        else
            stringData.writen(OFFBIT, 'b');
        
        myByte = myByte << 1;
    }
}

//The 0x00 at the end will keep the data line low when done.
stringData.writen(0x00, 'b');

hardware.spi257.write(stringData);

}
`

Currently it takes ~95ms between updates to a 120 pixel strip. This is obviously not great (update rate of 10 frames a second). Looking at the example code for the WS2801 (http://devwiki.electricimp.com/doku.php?id=writingefficientsquirrel) I bet there’s a slicker way to do the updateStrip() but I’m at a loss.

Do you have a recommendation that would maintain the output of bit values but speed up the preparation of the array?


#2

The key to going faster is to remove the inner loop. You can pack four bits’ worth of output data into an integer, so the inner loop becomes just two look-ups. Also, for everything referred to in the outer loop, copy it into a (fast) local variable. On my imp here, your code runs in 83ms and the code below runs in 21ms. (I’ve got a nasty feeling that I got the endian-ness wrong in the pixints array, but if so, hopefully the fix is obvious.)

`pixels <- blob(sizeOfStrip*3);

pixints <- [
0x80808080, 0x808080FC, 0x8080FC80, 0x8080FCFC,
0x80FC8080, 0x80FC80FC, 0x80FCFC80, 0x80FCFCFC,
0xFC808080, 0xFC8080FC, 0xFC80FC80, 0xFC80FCFC,
0xFCFC8080, 0xFCFC80FC, 0xFCFCFC80, 0xFCFCFCFC];

function updateStrip()
{
const ONBIT = 0xFC; //1.2 / .25us = 1.45us, 83% on time
const OFFBIT = 0x80; //0.5 / .91us = 1.41us, 35% on time

//Each pixel requires 24 bits
//Each “bit” going to the WS2812 is actually a byte (0xFC or 0x80)
local stringData = blob( (sizeOfStrip * 24) + 2);
local max = sizeOfStrip*3;
local pi = pixints;
local srcpix = pixels;

//The 0x00 at the beginning gets the SPI hardware working correctly
//so that the first bit of actual data has proper timing
stringData.writen(0x00, ‘b’);

//Step through all the bytes in the pixel array, three bytes per pixel
for(local j = 0 ; j < max; j++)
{
local myByte = srcpix[j];
stringData.writen(pi[myByte>>4], ‘i’);
stringData.writen(pi[myByte&15], ‘i’);
}

//The 0x00 at the end will keep the data line low when done.
stringData.writen(0x00, ‘b’);

hardware.spi257.write(stringData);
}
`

Peter


#3

You may want to take a look at https://github.com/beardedinventor/electricimp/blob/master/NeoPixel/neopixel.device.nut (You should note that this requires a firmware release newer than what’s publicly available to work very reliably)

There’s been a few posts on the forum about the LEDs as well. See http://forums.electricimp.com/discussion/1534/the-c-word/p1 and http://forums.electricimp.com/discussion/361/imp-digital-clock#Item_25


#4

The code in my repo uses a giant blog that we index into. It’s basically the values we need to write to the SPI bus at a specific frequency for 0-255 using their silly notation. It’s stored as a big const so that it gets shoved into flash - which is nice.

That code still needs to be cleaned up a bit, but it should form a good starting point for people wanting to play with the WS2812 / NeoPixels.


#5

@Peter - with your improvements it significantly increased the max update rate! Thank you and nice trick, I would not have seen that myself. I now get about 37ms between frame updates with 120 pixels.

@deldrid1 - Dah! I was searching for WS2812 not Neopixel (what Adafruit decided to arbitrarily name them). Thanks for those links. They help.

@beardedinventor - Your code works well! Neat trick with the blob. I measured about 30ms between frame updates at 120 pixels. Thanks for sharing!


#6

I have tried running the code and though it checks out and downloads, it doesn’t seem to be working, what am I missing?


#7

That code has always had the imp.enableblinkup() function in it… I think most likely because @beardedinventor was working with it at the time. You can take it out.


#8

The most up to date version of the code is in the Electric Imp repo (properly named as ws2812):

https://github.com/electricimp/reference/tree/master/hardware/ws2812

Is that the code you’re working with @back_ache?

What does not working mean? Is nothing happening when you run the code, is it ‘mostly working’ but occasionally glitching, or it displaying totally random static


#9

Yes, but it is still not lighting up. I added a server call into the code to check it is running and have added a peizo across the data line and can hear something is being sent, what I cannot see in the code is how you tell it what pin I am connected to (7).

I have just noticed something in the picture I took, it’s a WS2811 not a WS2812, now to go and find the difference

http://rgb-123.com/ws2812b-vs-ws2811/

Ah, the protocol is the same but half the speed and “The duty cycle for the “0” and “1” bits is slightly different” for one of them 0=0.5us/1=1.2us and the other is 0=2.0us/1=1.3us

Not sure how to change the code for all of this though.


#10

Hi all,

I was testing the code with the electric imp 002 and it first randomly turned on some leds. Has this been working on a 002 for somebody?

I get this error in the logs:

2014-02-11 11:34:45 UTC-1: [Device] ERROR: slice out of range
2014-02-11 11:34:45 UTC-1: [Device] ERROR: at writePixel:30
2014-02-11 11:34:45 UTC-1: [Device] ERROR: from AnimationLoop:60
2014-02-11 11:34:45 UTC-1: [Device] ERROR: from main:66

I am using an 8x8 RGB for RGB123 - this here to be exact:
http://rgb-123.com/product/88-8x8-rgb-led-matrix/

So I inititally had the number of RGBs configures to 64, which also did not work and showed the same error. Any ideas?


#11

Mhh, tried it on an imp 001 and the results seem to be the same: random. So the RGB LEDS all seeem to be of type WS281B on the rgb123 products. Is the code supposed to work for these?

The RGB led board works with the Adafruit NeoPixel library if initialised like this:

// Parameter 1 = number of pixels in strip
// Parameter 2 = pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
// NEO_KHZ400 400 KHz (classic ‘v1’ (not v2) FLORA pixels, WS2811 drivers)
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
Adafruit_NeoPixel strip = Adafruit_NeoPixel(64, PIN, NEO_GRB + NEO_KHZ800);

Would be great if someone had an idea what might be the issue, and better even how to fix it. Thx!


#12

We actually updated the NeoPixel code late yesterday to work with both WS2812 and WS2812B. Make sure you have the absolute latest version of the code from GitHub, and try again.

Also - if you could post (or link to) your code it would be helpful so we can see how you’re initializing, etc.


#13

If the aim is for the code to cover the family is there any chance the library will be extended to cover the ws2811 as well?


#14

Looking at datasheets, it should work with WS2811 if you set it to high speed mode? I’m not sure if we have any around to test though…


#15

Do you need line 24 now? local i = 0;


#16

The way I read the doc’s it’s half the speed and the length of the 1 and 0’s are different.


#17

Sorry, no luck here. I really copy and pasted the RAW version of the lib and animation loop example into the electric imp editor device screen.

https://raw.github.com/electricimp/reference/master/hardware/ws2812/neopixel.device.nut

When I run it, I still get an error:
2014-02-13 14:15:05 UTC+1: [Device] ERROR: slice out of range
2014-02-13 14:15:05 UTC+1: [Device] ERROR: at writePixel:29
2014-02-13 14:15:05 UTC+1: [Device] ERROR: from AnimationLoop:59
2014-02-13 14:15:05 UTC+1: [Device] ERROR: from main:65

This is again on an Electric Imp 001. The RGB123-8x8 RGB array is connected to 5V & GND, the data line is connected to PIN7 and in addition I joined the GND of the electric imp and the gnd that powers the LED array.

It’s a bit strange I get a runtime / code error, I must be doing somethign really strange. I just copied the code into the device editor, that’s what I am supposed to do, right?


#18

I’ve added my script to this snipped repo:
http://snipt.org/Ehtf2

I am wondering what you mean with “initializing”… Isn’t the snipped provided on GitHub supposed to a small full example? Am I missing the initialization, what does it mean?


#19

Sorry for all confusion, but I just cannot understand what goes on here. I’ve removed the AnimationLoop and began to experiment with setting individual pixels on my 8x8 board.

const NUMPIXELS = 64;
const DELAY = 0.025;

spi <- hardware.spi257;
spi.configure(MSB_FIRST, 7500);
pixelStrip <- neoPixels(spi, NUMPIXELS);

pixelStrip.clearFrame();
//pixelStrip.writePixel(0, 0,0,100); //two RGB leds are on , 0 and 1 with blue and green color?
//pixelStrip.writePixel(0, 255,0,0); //error slice out of range
//pixelStrip.writePixel(0, 254,0,0); //error slice out of range
pixelStrip.writePixel(0, 200,0,0); //1st rgb pixel on, yellow
pixelStrip.writePixel(1, 200,0,0); //2nd rgb pixel on, red
pixelStrip.writePixel(2, 200,0,0); //3d rgb pixel on, red
pixelStrip.writeFrame();

I get very crazy results, for most of my experiments. For example:

//pixelStrip.writePixel(0, 0,0,100); //two RGB leds are on , 0 and 1 with blue and green color?

This really turned on two LEDs, although I clearly just addressed LED 0 - and then the colors are green and blue. So I assume some data is piped to the LEDS, but it is clearly messed up.

I’ve started to log the bits.len(), too, which is 2304 in my case. Is that correct?


#20

Could it be possible to modify the library so that the definition of a one, a zero, the bus speed, number of pixels and color-order be defined at the top the code? (with examples for the different chips in the family) .

Or perhaps have the per chip variables defined in a separate includes? (or forks)