Hi gang,
I just did some work getting a Maple Mini to drive a strip of 120 WS28128-based LEDs and I thought I'd share the core code because it was pretty tedious.
I looked at some code provided by Adafruit to come up with this implementation, including borrowing an inline-assembly delay function from them. The delay values were determined empirically using time cursors on a storage oscilloscope.
This code uses three loops because I keep my frame buffers in RGB byte order, and the WS2812 wants data in GRB order. This method is part of one of my classes. The only external thing you'll need is a constant called kNumLEDs that says how many RGB-tuples are pointed to by the rgb argument (i.e. rgb is a pointer to an array of kNumLEDs * 3 bytes).
Hope this helps someone out.
-Bryan
http://anthrolume.com
// These values were determined empirically
#define kSpinCountZeroBitHigh 4 // these give 330ns high and 800ns low
#define kSpinCountZeroBitLow 9
#define kSpinCountOneBitHigh 9 // these give 750 high and 360 low
#define kSpinCountOneBitLow 4
// WS2812 chip - NeoPixel, etc.
void Strand::_present(byte* rgb /* = NULL */)
{
if (rgb == NULL) rgb = getWriteBuffer();
// Be sure we leave 50+ microseconds for data latch
while((micros() - _endTime) < 50L);
volatile uint32 *setResetRegister = &(PIN_MAP[_pin].gpio_device->regs->BSRR);
uint32 highValue = (1 << PIN_MAP[_pin].gpio_bit);
uint32 lowValue = (1 << PIN_MAP[_pin].gpio_bit + 16);
byte *end = rgb + 3 * kNumLEDs;
byte r, g, b, mask;
// Turn off interrupts until we're done with our loop
noInterrupts();
while (rgb < end)
{
byte r = *rgb++;
byte g = *rgb++;
byte b = *rgb++;
// Our frame buffers are RGB, but WS2812s want colors in GRB order, so we
// do that reording here.
// Emit green
for (mask = 0x80; mask; mask >>= 1)
{
*setResetRegister = highValue;
if (g & mask)
{
spinDelay(kSpinCountOneBitHigh);
*setResetRegister = lowValue;
spinDelay(kSpinCountOneBitLow);
}
else
{
spinDelay(kSpinCountZeroBitHigh);
*setResetRegister = lowValue;
spinDelay(kSpinCountZeroBitLow);
}
}
// Emit red
for (mask = 0x80; mask; mask >>= 1)
{
*setResetRegister = highValue;
if (r & mask)
{
spinDelay(kSpinCountOneBitHigh);
*setResetRegister = lowValue;
spinDelay(kSpinCountOneBitLow);
}
else
{
spinDelay(kSpinCountZeroBitHigh);
*setResetRegister = lowValue;
spinDelay(kSpinCountZeroBitLow);
}
}
// Emit blue
for (mask = 0x80; mask; mask >>= 1)
{
*setResetRegister = highValue;
if (b & mask)
{
spinDelay(kSpinCountOneBitHigh);
*setResetRegister = lowValue;
spinDelay(kSpinCountOneBitLow);
}
else
{
spinDelay(kSpinCountZeroBitHigh);
*setResetRegister = lowValue;
spinDelay(kSpinCountZeroBitLow);
}
}
}
interrupts();
_endTime = micros();
}