If you have an old app that you would like to update please remove the NeoPixel library from your app and re-apply it to update the included library to v1.0.0. If you are creating a new app, simply add the NeoPixel library to your app and you will have the latest v1.0.0.
All, the Spark Core NeoPixel Library now supports 800 KHz and 400kHz bitstream NeoPixels! WS2812, WS2812B and WS2811. See the example for how to select between these different LEDs. Technically there is two timing schemes, both the WS2812 and WS2812B are the same unified 800kHz timing. WS2811 uses the slower 400kHz timing.
It’s been on my list but I keep getting sidetracked. It might require some inline assembly to get the timing perfect and glitch-free. Not something I generally look forward to, but have done plenty in the past and present. We might be able to get by with C++ and some bit-banging though.
Is anyone else thinking of working on this? Report back with whatever progress you are making here if you do, k?
I’ll probably start with just experimenting with timing and bit-banging to see if that even works, before reconstructing the library (open-face tuna fish style).
It’s not pretty yet, but it’s working! Just a proof of concept that it can be done, pretty easily too:
(EDIT: see my next post for the port I made of adafruit’s library for the spark core on github)
Code:
uint32_t rgb;
uint16_t neoPin = D0;
void setPixel(uint32_t grb) {
uint8_t i;
uint8_t grb_bit = 23;
uint32_t grb_mask = 0x00800000;
for (i = 0; i < 24; i++) {
if (grb & (1 << grb_bit)) {
// 700ns HIGH (meas. 680ns)
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
// 600ns LOW (meas. 612ns)
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
} else {
// 350ns HIGH (meas. 348ns)
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
PIN_MAP[neoPin].gpio_peripheral->BSRR = PIN_MAP[neoPin].gpio_pin; // HIGH
// 800ns LOW (meas. 808ns)
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
PIN_MAP[neoPin].gpio_peripheral->BRR = PIN_MAP[neoPin].gpio_pin; // LOW
}
grb_bit--;
grb_mask = grb_mask >> 1;
}
}
// Packs and sets a 32-bit color from individual colors
void setRGB(uint8_t red, uint8_t green, uint8_t blue) {
uint32_t grb = ((uint32_t) red << 16) + ((uint32_t) green << 16) + blue;
setPixel(grb);
}
// Reorders and sets a 32-bit packed color
void setColor(uint32_t color) {
uint32_t grb = ((uint32_t)(color >> 8) & 0x0000FF00) + (uint32_t)((color << 8) & 0x00FF0000) + (uint32_t)(color & 0x000000FF);
setPixel(grb);
}
// Slightly different, this makes the rainbow equally distributed throughout
void rainbowCycle(uint8_t wait) {
uint16_t j;
for (j = 0; j < 256; j++) { // 1 cycle of all colors on wheel
setColor(Wheel(j));
delayMicroseconds(wait * 1000);
}
}
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(uint8_t WheelPos) {
if (WheelPos < 85) {
return packColor(WheelPos * 3, 255 - WheelPos * 3, 0);
} else if (WheelPos < 170) {
WheelPos -= 85;
return packColor(255 - WheelPos * 3, 0, WheelPos * 3);
} else {
WheelPos -= 170;
return packColor(0, WheelPos * 3, 255 - WheelPos * 3);
}
}
// Convert separate R,G,B into packed 32-bit RGB color.
// Packed format is always RGB, regardless of LED strand color order.
uint32_t packColor(uint8_t r, uint8_t g, uint8_t b) {
uint32_t rgb = (((uint32_t) r << 16) + ((uint32_t) g << 8) + b);
return rgb;
}
void setup() {
pinMode(neoPin, OUTPUT);
}
void loop() {
rgb = 0x00000FF;
//setColor(rgb);
rainbowCycle(20);
}
As you can see, I didn’t spend a lot of time on the “timing” part… running the bit-banging part over and over again was a dirty way to do it. However it works well as you can see from the measured timing and demo. I think there are little glitches every no and then because of interrupts that should probably be turned off during the critical timing… or, driving at 3.3V. I seem to recall this 3.3V issue before was the reason it was a tad glitchy.
If anyone can point me a better way to burn 1 cycle, please let me know. I tried Nop(); and nop(); to no avail.
Anyhoo, I said I’d keep you up to date on anything that’s happening… so you got it! Down and dirty.
Currently supports WS2812 GRB 800kHz style pixels, strips and sticks!
Please give it a try and let me know if it works with your strips! Currently I’m only testing with one pixel, but if I set it for more than one I can see all of the timing and it looks good for now. Later after I learn a little more about the inline assembly I’ll convert the whole 24-bit routine to assembly.
Excellent work BDub, I’ve just tried it on a 10 led strip and it was perfect. I’ve got a 50 strip floating around somewhere so I’ll try that one next.
I tested the code on a 240 LED strip and it works, unfortunately the strip is only an older strip and hence it should have the 400Khz timing. Due to this the strip flickers quite a lot.
Do you see any chance in also building a 400 Khz version for the older strips ?
Thanks
Rudy
Thanks @mdma, if you look at my port of the Adafruit NeoPixel Library above, you’ll see I found the commands I was looking for. I actually used a little bit of “mov r0, r0” and also “nop”. I was finding all kinds of weird compiler optimizing that was affecting the timing, so this mashup of commands ultimately worked the best. Once I learn a bit more asm for this stm32 I’ll write the whole 24-bit output bit-banger in assembly.
Yes, it is WS2811 that I tested with and I have the experience with the NeoPixel library that I need to clock them at 400Khz. I’ll be looking forward to your updates and will test your code when available.
noInterrupts() on spark core does not disable all interrupts as it was on arduino, but only user-accessible ones.
I ended up using __disable_irq()/__enable_irq() intrinsics instead. This solved timing issues, but not sure how this will work with other parts of software.
BTW, does anyone considered porting OctoWS2812 instead (DMA and timers are available on spark core too, so should be possible)?
I’m not using updated Adafruit_NeoPixel class, but my own.
My changes are:
replace noInterrupts(); with __disable_irq(); and interrupts(); with __enable_irq();
move 24th iteration out of the loop and do mask = 0x1000000; // reset the mask, start 1 higher than g = *ptr++; // Next green byte value r = *ptr++; // Next red byte value b = *ptr++; // Next blue byte value i--;
instead of some nops to prevent stretching last bit waveform.
see https://gist.github.com/Jahor/16665933c78c2422598e for the show function
Hi @jagor! Thanks I’ll have to try and play with the irq() and see what that does for timing.
As for the unrolling of the 24th bit, while I love to keep timing nice and tight… I think it’s probably unnecessary due to how the WS2812 works. If you look at the following chart and imagine the LOW LSB of each 24bit color pattern stretching out a little bit, it should not be seen as a reset code of >= 50us.
The delay comes from when the code loops back around to the c = ((uint32_t)g << 16) | ((uint32_t)r << 8) | b; calculation. Which is why it only stretches out the trailing low part of the lsb. I don’t recall how long this stretch is but I would guess no more than 1us?
Right, it’s not very long. I was trying to fix my problem without disabling all interrupts first, so tried to make waveform as ideal as possible, but that did not help. c = ((uint32_t)g << 16) | ((uint32_t)r << 8) | b; can be moved out as well.
But probably that is not required.
@BDub so the good news is that I got my lights in the mail today. I received a nice 120 led 2m adafruit neopixel strip that you recommended. I tried out the library and here are the results:
Ok, looks like you have the 5V going to Vin, and GND to GND… I would bet money that the timing is good enough, so I’m thinking it may be an issue of driving the WS2812 with a 3.3V Digital Output when the VCC to the WS2812 is supplied with 5V.
The WS2812 spec says DIN voltage level HIGH is 0.7*VDD or 3.5V.
Basically you would need to level convert the D0 output to 5V to see if that theory is correct.
I gotta leave work but I’ll chime back in when I get home… with level converter ideas.