NeoPixel Strips - RGB vs RGBW

I am planning a project using NeoPixels and have a couple of questions. (I asked these questions at Adafruit but didn’t get a response).

From what I have figured out, the RGB version has 3 coloured pixels in each NeoPixel and the RGBW version has 4 (they added a dedicated white pixel). The max current draw is 20ma per LED colour (RGB = 60ma max and RGBW = 80ma max).

  1. If my project uses white primarily, I’m guessing that it will save power to use the RGBW strip because it should only draw 20ma for pure white (assuming the RGB pixels are off).

  2. Does the library WS2812 library support the RGBW strips? It doesn’t look like it from this page “https://electricimp.com/docs/libraries/hardware/ws2812/”.

  3. If the imp goes to sleep and the strip is still powered, will the NeoPixels remain on and retain the last colour sent to them?

Thanks for your help.

Not heard of RGBW strips; I suspect it’s not a WS2812 controller as that one only had 3 outputs that I remember. Do you know what controller are in these?

You’re correct about white being better power with this - some OLEDs (or was it LCDs?) do this type of thing for the same reason.

I will look into it. Here is a link to the RGBW strip I’m thinking of:

Also, is there an Imp Library for DotStars (another product from AdaFruit)?

They look more useful for POV projects (this isn’t what my project is)

IIRC, the DotStars are APA102s, so yes, there’s an EI library

The RGBW “Neopixels” are a different controller, SK6812, but it looks like the actual protocol is very similar (except for having 32 bits per LED instead of 24). It sounds like it’d be worth starting from the WS2812 library and making some modifications.

Peter

Since I’m just beginning the project, has anybody used both of these and could recommend one over the other? My understanding is that the DotStars are much faster and require a clock signal to feed them data (2 inputs + power). Where the NeoPixels have only a data line and require precise timing to communicate with them (1 input + power).

Am I correct in modifying the WS2812 library into an SK6812 library (see below)?

`
// Copyright © 2015 Electric Imp
// This file is licensed under the MIT License
// http://opensource.org/licenses/MIT

class SK6812 {
// This class uses SPI to emulate the SK6812s’ one-wire protocol.
// This requires one byte per bit to send data at 7.5 MHz via SPI.
// These consts define the “waveform” to represent a zero or one

static version = [0,0,1];

static ZERO                    = 0xC0;
static ONE                      = 0xF8;
static BYTES_PER_PIXEL   = 32;

// When instantiated, the SK6812 class will fill this array with blobs to
// represent the waveforms to send the numbers 0 to 255. This allows the
// blobs to be copied in directly, instead of being built for each pixel.

static _bits     = array(256, null);

// Private variables passed into the constructor

_spi             = null;  // imp SPI interface (pre-configured)
_frameSize  = null;  // number of pixels per frame
_frame        = null;  // a blob to hold the current frame

// Parameters:
//    spi          A pre-configured SPI bus (MSB_FIRST, 7500)
//    frameSize    Number of Pixels per frame
//    _draw        Whether or not to initially draw a blank frame
constructor(spiBus, frameSize, _draw = true) {
    // spiBus must be configured
    _spi = spiBus;

    _frameSize = frameSize;
    _frame = blob(_frameSize * BYTES_PER_PIXEL + 1);
    _frame[_frameSize * BYTES_PER_PIXEL] = 0;

    // Used in constructing the _bits array
    local bytesPerColor = BYTES_PER_PIXEL / 3;

    // Fill the _bits array if required
    // (Multiple instance of SK6812 will only initialize it once)
    if (_bits[0] == null) {
        for (local i = 0; i < 256; i++) {
            local valblob = blob(bytesPerColor);
            valblob.writen((i & 0x80) ? ONE:ZERO,'b');
            valblob.writen((i & 0x40) ? ONE:ZERO,'b');
            valblob.writen((i & 0x20) ? ONE:ZERO,'b');
            valblob.writen((i & 0x10) ? ONE:ZERO,'b');
            valblob.writen((i & 0x08) ? ONE:ZERO,'b');
            valblob.writen((i & 0x04) ? ONE:ZERO,'b');
            valblob.writen((i & 0x02) ? ONE:ZERO,'b');
            valblob.writen((i & 0x01) ? ONE:ZERO,'b');
            _bits[i] = valblob;
        }
    }

    // Clear the pixel buffer
    fill([0,0,0,0]);

    // Output the pixels if required
    if (_draw) {
        this.draw();
    }
}

// Configures the SPI Bus
//
// NOTE: If using the configure method, you *must* pass `false` to the
// _draw parameter in the constructor (or else an error will be thrown)
function configure() {
    _spi.configure(MSB_FIRST, 7500);
    return this;
}

// Sets a pixel in the buffer
//   index - the index of the pixel (0 <= index < _frameSize)
//   color - [r,g,b,W] (0 <= r,g,b,W <= 255)
//
// NOTE: set(index, color) replaces v1.x.x's writePixel(p, color) method
function set(index, color) {
    assert(index >= 0 && index < _frameSize);
    assert(color[0] >= 0 && color[0] <= 255);
    assert(color[1] >= 0 && color[1] <= 255);
    assert(color[2] >= 0 && color[2] <= 255);
    assert(color[3] >= 0 && color[3] <= 255);

    _frame.seek(index * BYTES_PER_PIXEL);

    // Red and green are swapped for some reason, so swizzle them back
    _frame.writeblob(_bits[color[1]]);
    _frame.writeblob(_bits[color[0]]);
    _frame.writeblob(_bits[color[2]]);
    _frame.writeblob(_bits[color[3]]);

    return this;
}


// Sets the frame buffer (or a portion of the frame buffer)
// to the specified color, but does not write it to the pixel strip
//
// NOTE: fill([0,0,0,0]) replaces v1.x.x's clear() method
function fill(color, start=0, end=null) {
    // we can't default to _frameSize -1, so we
    // default to null and set to _frameSize - 1
    if (end == null) { end = _frameSize - 1; }

    // Make sure we're not out of bounds
    assert(start >= 0 && start < _frameSize);
    assert(end >=0 && end < _frameSize)
    assert(color[0] >= 0 && color[0] <= 255);
    assert(color[1] >= 0 && color[1] <= 255);
    assert(color[2] >= 0 && color[2] <= 255);
    assert(color[3] >= 0 && color[3] <= 255);

    // Flip start & end if required
    if (start > end) {
        local temp = start;
        start = end;
        end = temp;
    }

    // Create a blob for the color
    local colorBlob = blob(BYTES_PER_PIXEL);
    colorBlob.writeblob(_bits[color[1]]);
    colorBlob.writeblob(_bits[color[0]]);
    colorBlob.writeblob(_bits[color[2]]);
    colorBlob.writeblob(_bits[color[3]]);

    // Write the color blob to each pixel in the fill
    _frame.seek(start*BYTES_PER_PIXEL);
    for (local index = start; index <= end; index++) {
        _frame.writeblob(colorBlob);
    }

    return this;
}

// Writes the frame to the pixel strip
//
// NOTE: draw() replaces v1.x.x's writeFrame() method
function draw() {
    _spi.write(_frame);
    return this;
}

}`

It looks like the BYTES_PER_PIXEL should be divided by 4 maybe?

// Used in constructing the _bits array
local bytesPerColor = BYTES_PER_PIXEL / 4;

I’ve corrected the class to what I believe is now correct with some notes changed to reference where it came from (derived from the WS2812 library). If this is correct, can it be added as an available library for everyone?

`
// Copyright © 2015 Electric Imp
// This file is licensed under the MIT License
// http://opensource.org/licenses/MIT

class SK6812 {
// This class uses SPI to emulate the SK6812s’ one-wire protocol.
// This requires one byte per bit to send data at 7.5 MHz via SPI.
// These consts define the “waveform” to represent a zero or one

// This class was derived from version 2.0.1 of the WS2812 class

static version = [0,0,1];

static ZERO            = 0xC0;
static ONE             = 0xF8;
static BYTES_PER_PIXEL   = 32;

// When instantiated, the SK6812 class will fill this array with blobs to
// represent the waveforms to send the numbers 0 to 255. This allows the
// blobs to be copied in directly, instead of being built for each pixel.

static _bits     = array(256, null);

// Private variables passed into the constructor

_spi             = null;  // imp SPI interface (pre-configured)
_frameSize       = null;  // number of pixels per frame
_frame           = null;  // a blob to hold the current frame

// Parameters:
//    spi          A pre-configured SPI bus (MSB_FIRST, 7500)
//    frameSize    Number of Pixels per frame
//    _draw        Whether or not to initially draw a blank frame
constructor(spiBus, frameSize, _draw = true) {
    // spiBus must be configured
    _spi = spiBus;

    _frameSize = frameSize;
    _frame = blob(_frameSize * BYTES_PER_PIXEL + 1);
    _frame[_frameSize * BYTES_PER_PIXEL] = 0;

    // Used in constructing the _bits array
    local bytesPerColor = BYTES_PER_PIXEL / 4;

    // Fill the _bits array if required
    // (Multiple instance of SK6812 will only initialize it once)
    if (_bits[0] == null) {
        for (local i = 0; i < 256; i++) {
            local valblob = blob(bytesPerColor);
            valblob.writen((i & 0x80) ? ONE:ZERO,'b');
            valblob.writen((i & 0x40) ? ONE:ZERO,'b');
            valblob.writen((i & 0x20) ? ONE:ZERO,'b');
            valblob.writen((i & 0x10) ? ONE:ZERO,'b');
            valblob.writen((i & 0x08) ? ONE:ZERO,'b');
            valblob.writen((i & 0x04) ? ONE:ZERO,'b');
            valblob.writen((i & 0x02) ? ONE:ZERO,'b');
            valblob.writen((i & 0x01) ? ONE:ZERO,'b');
            _bits[i] = valblob;
        }
    }

    // Clear the pixel buffer
    fill([0,0,0,0]);

    // Output the pixels if required
    if (_draw) {
        this.draw();
    }
}

// Configures the SPI Bus
//
// NOTE: If using the configure method, you *must* pass `false` to the
// _draw parameter in the constructor (or else an error will be thrown)
function configure() {
    _spi.configure(MSB_FIRST, 7500);
    return this;
}

// Sets a pixel in the buffer
//   index - the index of the pixel (0 <= index < _frameSize)
//   color - [r,g,b,w] (0 <= r,g,b,w <= 255)
//
// NOTE: set(index, color) method is identical to WS2812 v2.0.1 but with an additional color parameter
function set(index, color) {
    assert(index >= 0 && index < _frameSize);
    assert(color[0] >= 0 && color[0] <= 255);
    assert(color[1] >= 0 && color[1] <= 255);
    assert(color[2] >= 0 && color[2] <= 255);
    assert(color[3] >= 0 && color[3] <= 255);

    _frame.seek(index * BYTES_PER_PIXEL);

    // Red and green are swapped for some reason, so swizzle them back
    _frame.writeblob(_bits[color[1]]);
    _frame.writeblob(_bits[color[0]]);
    _frame.writeblob(_bits[color[2]]);
    _frame.writeblob(_bits[color[3]]);

    return this;
}


// Sets the frame buffer (or a portion of the frame buffer)
// to the specified color, but does not write it to the pixel strip
//
// NOTE: fill([0,0,0,0]) is identical to WS2812 v2.0.1 but with an additional color parameter
function fill(color, start=0, end=null) {
    // we can't default to _frameSize -1, so we
    // default to null and set to _frameSize - 1
    if (end == null) { end = _frameSize - 1; }

    // Make sure we're not out of bounds
    assert(start >= 0 && start < _frameSize);
    assert(end >=0 && end < _frameSize)
    assert(color[0] >= 0 && color[0] <= 255);
    assert(color[1] >= 0 && color[1] <= 255);
    assert(color[2] >= 0 && color[2] <= 255);
    assert(color[3] >= 0 && color[3] <= 255);

    // Flip start & end if required
    if (start > end) {
        local temp = start;
        start = end;
        end = temp;
    }

    // Create a blob for the color
    local colorBlob = blob(BYTES_PER_PIXEL);
    colorBlob.writeblob(_bits[color[1]]);
    colorBlob.writeblob(_bits[color[0]]);
    colorBlob.writeblob(_bits[color[2]]);
    colorBlob.writeblob(_bits[color[3]]);

    // Write the color blob to each pixel in the fill
    _frame.seek(start*BYTES_PER_PIXEL);
    for (local index = start; index <= end; index++) {
        _frame.writeblob(colorBlob);
    }

    return this;
}

// Writes the frame to the pixel strip
//
// NOTE: draw() method is identical to WS2812 v2.0.1 method
function draw() {
    _spi.write(_frame);
    return this;
}

}
`

The “W” is the 4th LED, which is white (soft white 3000K or daylight white 5000K) … you can buy them with a specified Kelvin value. They do this because RGB (255,255,255) is not a very nice “white”.

Correct… and it saves power when pure white is required. I’m not sure which K value comes in the strips ordered from AdaFruit, but I can always warm up the white with a little red if required.

Edit: Correction - AdaFruit only offers “Neutral white color temperature: ~4000-4500K”

Also, my project uses a string of 300 pixels. Will the Imp have any memory problems with a frame that size (frame is about 10Kb)? And can this library class handle an array that long?

I just noticed a problem (in documentation only) regarding the fill function:

// NOTE: fill([0,0,0]) is identical to WS2812 v2.0.1 but with an additional color parameter

The fill function still only requires 3 parameters but the color parameter is now an array of 4 values instead of 3.

I am currently trying to learn the ins/outs of GitHub so that I can submit this as a library myself.

I have forked the WS2812 class and created an SK6812 class on GitHub. Please check it out.

Thanks

If you’re looking for efficiency, use the APA102 based strips. The imp drives the WS2812 ones by making a spi buffer which represents the waveform (1 byte per bit!) and then sends it in one. This gives exact, predictable timing.

The APA102 is essentially a standard SPI peripheral, so 1 bit = 1 bit. They update quicker, aren’t timing critical, and use much less buffer space.

That said, I doubt the memory usage of ~10k for the WS2812/SK6812 is going to be much of a problem. Frame update rate will be significantly slower than an APA string though.

I’m not as concerned with code/communication efficiency as I am with power consumption. Since the 300 NeoPixels will be on white most of the time, 20mA as opposed to 60mA per pixel equates to 6A instead of 18A at full brightness. Equivalent to a 30W light bulb on all the time as opposed to a 100W bulb.

I’m glad to hear that the memory usage won’t be an issue.

Shortly I am going to receive the electronics components I’ve ordered (Imp + RGBW NeoPixel strip). To verify this code works correctly, I will copy it directly into the agent… but eventually I would like to use the SK6812 as a #require just like the WS2812 does.

What is required to be able to use the class I’ve created in my agent code with a line like this?

#require “SK6812.class.nut:1.0.0”

Is there some kind of approval process so that it can be added to the Electric Imp library?

We’d need to get some test hardware to check the library worked internally, but if you have some modified code then definitely post it and we’ll see what we can do.

I’ve made some progress. I began small with just a single NeoPixel but have verified that the following code works to activate each of the 4 leds on the chip at values from 0-255.

The next step will be to use a string of NeoPixels and set each one to a different colour to verify that the fill and set functions work as intended.

`
class SK6812 {
// This class uses SPI to emulate the SK6812s’ one-wire protocol.
// This requires one byte per bit to send data at 7.5 MHz via SPI.
// These consts define the “waveform” to represent a zero or one

// This class was derived from version 2.0.1 of the WS2812 class

static version = [0,0,1];

static ZERO            = 0xC0;
static ONE             = 0xF8;
static BYTES_PER_PIXEL   = 32;

// When instantiated, the SK6812 class will fill this array with blobs to
// represent the waveforms to send the numbers 0 to 255. This allows the
// blobs to be copied in directly, instead of being built for each pixel.

static _bits     = array(256, null);

// Private variables passed into the constructor

_spi             = null;  // imp SPI interface (pre-configured)
_frameSize       = null;  // number of pixels per frame
_frame           = null;  // a blob to hold the current frame

// Parameters:
//    spi          A pre-configured SPI bus (MSB_FIRST, 7500)
//    frameSize    Number of Pixels per frame
//    _draw        Whether or not to initially draw a blank frame
constructor(spiBus, frameSize, _draw = true) {
    // spiBus must be configured
    _spi = spiBus;

    _frameSize = frameSize;
    _frame = blob(_frameSize * BYTES_PER_PIXEL + 1);
    _frame[_frameSize * BYTES_PER_PIXEL] = 0;

    // Used in constructing the _bits array
    local bytesPerColor = BYTES_PER_PIXEL / 4;

    // Fill the _bits array if required
    // (Multiple instance of SK6812 will only initialize it once)
    if (_bits[0] == null) {
        for (local i = 0; i < 256; i++) {
            local valblob = blob(bytesPerColor);
            valblob.writen((i & 0x80) ? ONE:ZERO,'b');
            valblob.writen((i & 0x40) ? ONE:ZERO,'b');
            valblob.writen((i & 0x20) ? ONE:ZERO,'b');
            valblob.writen((i & 0x10) ? ONE:ZERO,'b');
            valblob.writen((i & 0x08) ? ONE:ZERO,'b');
            valblob.writen((i & 0x04) ? ONE:ZERO,'b');
            valblob.writen((i & 0x02) ? ONE:ZERO,'b');
            valblob.writen((i & 0x01) ? ONE:ZERO,'b');
            _bits[i] = valblob;
        }
    }

    // Clear the pixel buffer
    fill([0,0,0,0]);

    // Output the pixels if required
    if (_draw) {
        this.draw();
    }
}

// Configures the SPI Bus
//
// NOTE: If using the configure method, you *must* pass `false` to the
// _draw parameter in the constructor (or else an error will be thrown)
function configure() {
    _spi.configure(MSB_FIRST, 7500);
    return this;
}

// Sets a pixel in the buffer
//   index - the index of the pixel (0 <= index < _frameSize)
//   color - [r,g,b,w] (0 <= r,g,b,w <= 255)
//
// NOTE: set(index, color) method is identical to WS2812 v2.0.1 but with an additional color parameter
function set(index, color) {
    assert(index >= 0 && index < _frameSize);
    assert(color[0] >= 0 && color[0] <= 255);
    assert(color[1] >= 0 && color[1] <= 255);
    assert(color[2] >= 0 && color[2] <= 255);
    assert(color[3] >= 0 && color[3] <= 255);

    _frame.seek(index * BYTES_PER_PIXEL);

    // Red and green are swapped, so need to swizzle them around
    _frame.writeblob(_bits[color[1]]);
    _frame.writeblob(_bits[color[0]]);
    _frame.writeblob(_bits[color[2]]);
    _frame.writeblob(_bits[color[3]]);

    return this;
}


// Sets the frame buffer (or a portion of the frame buffer)
// to the specified color, but does not write it to the pixel strip
//
// NOTE: fill([0,0,0,0]) is identical to WS2812 v2.0.1 but with an additional color parameter
function fill(color, start=0, end=null) {
    // we can't default to _frameSize -1, so we
    // default to null and set to _frameSize - 1
    if (end == null) { end = _frameSize - 1; }

    // Make sure we're not out of bounds
    assert(start >= 0 && start < _frameSize);
    assert(end >=0 && end < _frameSize)
    assert(color[0] >= 0 && color[0] <= 255);
    assert(color[1] >= 0 && color[1] <= 255);
    assert(color[2] >= 0 && color[2] <= 255);
    assert(color[3] >= 0 && color[3] <= 255);

    // Flip start & end if required
    if (start > end) {
        local temp = start;
        start = end;
        end = temp;
    }

    // Create a blob for the color (swapped for GRBW)
    local colorBlob = blob(BYTES_PER_PIXEL);
    colorBlob.writeblob(_bits[color[1]]);
    colorBlob.writeblob(_bits[color[0]]);
    colorBlob.writeblob(_bits[color[2]]);
    colorBlob.writeblob(_bits[color[3]]);

    // Write the color blob to each pixel in the fill
    _frame.seek(start*BYTES_PER_PIXEL);
    for (local index = start; index <= end; index++) {
        _frame.writeblob(colorBlob);
    }

    return this;
}

// Writes the frame to the pixel strip
//
// NOTE: draw() method is identical to WS2812 v2.0.1 method
function draw() {
    _spi.write(_frame);
    return this;
}

};
`

I verified that the code works and was able to independently set and fill sections of an RGBW strip as intended.

One thing I noticed is that the circuit example has a mistake in it.

The connections for the +5V and Gnd for the Neopixel strip are both connected to the same pin on the level shifter.

I believe it should look like this: