Adafruit Neopixel Library [PORTED]

Live in the Particle Libraries - NeoPixel v1.0.0

Important:

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.

NeoPixel Github Repo

GitHub - technobly/Particle-NeoPixel: An Implementation of Adafruit's NeoPixel Library for Particle devices: Core, Photon, P1, Electron, Argon, Boron, Xenon and RedBear Duo.


Original post:

Hands down, Adafruit's neopixel leds are amazing. What would be even more amazing is running them from a Spark Core! Make this library a reality! The Magic of NeoPixels | Adafruit NeoPixel Ăśberguide | Adafruit Learning System

8 Likes

EDIT:

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.

https://github.com/technobly/SparkCore-NeoPixel

…
…
…


Original Post:

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? :wink:

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.

5 Likes

That’s awesome work. It’s working (and the final piece to get working) on my Buttons for SparkCore shield.

5 Likes

Ok I have a full port of the Adafruit NeoPixel Library for Spark Core up and running!
https://github.com/technobly/SparkCore-NeoPixel

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.

8 Likes

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.

2 Likes

I’m honestly amazed. Good work man!

I have 2x 1meter 60led strips coming in the mail this week; I’ll be sure to test the crap out of it! You made my week.

2 Likes

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

Yes! I will definitely be supporting the WS2811 and WS2812B. You have the WS2811 right?

1 Like

I'm new to spark and ARM gcc, but how about

asm volatile("mov r0,r0");

to burn one cycle

1 Like

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.

1 Like

Thanks for your work. I was implementing control of WS2811(2) right at the same time and ended up almost in the same place.
My test setup has 8 LEDs and I’m feeding them red colour at ~60fps. While it worked most of the time, sometimes it can pause during sending pulses for a few us, which makes some LEDs flickering in different colour.
see
https :// dl.dropboxusercontent.com/u/2485286/WS2811%20on%20Spark%20Core%201.png
or
https :// dl.dropboxusercontent.com/u/2485286/WS2811%20on%20Spark%20Core%202.png

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)?

Do you have the end result of your code tweaks handy so BDub can see what you did differently?

I’m not using updated Adafruit_NeoPixel class, but my own.
My changes are:

  1. replace noInterrupts(); with __disable_irq(); and interrupts(); with __enable_irq();

  2. 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?

Are you seeing something different?

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:

It seems like the timing might be a tad off: https://vine.co/v/h3T0jqMUJgK

After awhile it starts to do this: https://vine.co/v/h3TEI07ViPV

This is the strip: http://www.adafruit.com/products/1138

Excited to hear your thoughts on how we can fix it! Keep up the great work man.

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.

I’ve never messed with level converters before but I’ll post a few more pics on my current setup.