WS2812 Driver for Electron/Photon using SPI and DMA

Hi Folks,

I’m looking to build a WS2812 driver for an Electron based project, but all the libraries I have been able to find are incredibly CPU intensive, since they seem to do the timing by bit-bashing using NOP opcodes for the delays. I’ve had an idea about how this could be made more efficient, but I was wondering whether anyone else had war stories they may want to share. I’ll probably give the implementation a go over the Easter break, but I figured I should check here before I start in-case I’ve overlooked something blitheringly obvious.

I was looking yesterday at trying to use a hardware serial module to generate the required pattern on an output pin. If I can make it work, it should mean I can build the desired command string, then hand it off to one of the DMA-based methods for serial output and have the transmission taken care of in the background without tying up my CPU doing delays.

My basic logic is this: Each ‘bit’ in the WS2812’s protocol is broken into three approximately equal sections. The first third is always a logic 1, the last third is always a logic 0, the third in the middle is either a 1 or a 0 based on which bit value you’re trying to send. By my logic, you should therefore be able to generate the pattern using an SPI module, UART or I2C won’t work since they automatically inject start and stop bits which will screw with everything, but the MOSI pin on an SPI connection just generates the high/low bit pattern you’re transmitting, I’m only going to use that one pin, I can ignore the SCK and MISO connections.

Now the WS2812 bit width is 1.25 uS, which means each of the 3 mini-bits is 416 nS, which corresponds to a 2.4 MHz SPI clock. The nearest I can get to that using a Photon will be 1.875MHz, but it looks like that will allow me to meet tolerance on at-least two of the three parameters:
For a 1.875 MHz SPI clock, I get mini-bit times of 533 nSec, which should be OK for the ‘short’ pulse limits on the WS2812. Two mini-bits is 1066 nSec, which is OK for the ‘long’ pulse limit on the WS2812, and the overall bit time is 1600 nSec, which is a smidge too long to be technically correct, but I’m hoping it will be OK.

These timings are my main concern, the bit-packing method is pretty straightforward, since the SPI is seeing the mini-bits, then you’ll need to transmit 9 bytes of data per WS2812, which will have the 24 bits worth of RGB data surrounded by the requisite padding. Importantly there’s the SPI.transfer(void*, void*, size_t, std::function) method that allows you to offload actual transmission of the packet to the DMA system and keep running your other code.

Has anyone else tried to produce something like this? If not, would anyone be interested in me writing it up as a particle library? I’ve never done one before, but if this works then it’d be a bit rude not to share. :wink: I’m assuming the main reason there aren’t libs out there that do it this way is that Arduinos and equivalent don’t have the same CPU power and DMA functionality as a particle, and therefore they don’t really see the benefits to doing it this way.

– Dave W

I guess I would look at APA102 style LEDs instead of WS2812’s. They are essentially SPI devices that are chainable and can be driven directly by the SPI of most micros. Adafruit calls them DotStar LEDs. That would make your code trivial instead of difficult.

1 Like

100% agree. DotStar LEDs are superior to NeoPixels in every way.