[Bounty] Port Adafruit NeoPixel Library


#1

I have been struggling with getting a reliable NeoPixel library working with Bluz. Instead of banging my head on the keyboard any longer, I’ve decided that offering a bounty might be fun and interesting. The challenge is to port the Adafruit NeoPixel library to the Bluz platform. The first person to post a working port will get one Bluz DK (as long as you don’t mind sharing your shipping address with me). Another option (for bonus points) would be to port @BDub’s SparkCore-NeoPixel library. The bonus for that library will be one of my Photon Power Proto boards (it works with Bluz as well!).

That’s a total of one Bluz DK and/or one Photon Proto Power Board (this is a personal bounty–my pockets aren’t that deep). If you port only the SparkCore-NeoPixel library first, you will get both. If you port only the Adafruit library, you will get the Bluz DK. If someone has already ported the Adafruit library and you then port the SparkCore-NeoPixel library, you will get the Photon Power Proto Board (only).

No purchase necessary. Offer ends when all prizes have been claimed. Must love NeoPixels to enter. All participants are awesome people.


Neopixels library!
#2

@wgbartley, i see that the bluz firmware repo has a neo example but it’s broken/not working.

You can grab it here and edit the line 11:

STM32_Pin_Info* PIN_MAP2 = HAL_Pin_Map();
int PIXEL_PIN = PIN_MAP2[D2].gpio_pin;

It compiled using particle compile bluz . for me but i cannot test it as mine is still on it’s way.

Let me know how it goes! i’ll see how we can combine the 2 libraries into 1 :slight_smile:


#3

I’ve been able to get the example to compile and flash, but haven’t gotten any blinky goodness. The example lib doesn’t give you options for specifying either the bus speed (usually 400KHz or 800Khz) or the various types (WS2811, WS2812, WS2812B, etc). The pixels you get from eBay sellers and wherever else seem to be all over the place, so those options are almost a necessity.

This PR for the Adafruit library may be useful, but I haven’t had any luck getting it implemented either.


#4

Sounds like i have some work to do when mine arrives :wink:


#5

The first high speed GPIO I was able to do on BluzDK was been 350ns high, 350ns low in a tight loop. If I integrate this into the neopixel library and remove all of the NOP’s, it was still too slow to do 800Khz strips. What is the system clock speed on BluzDK? Read on for better results…

SYSTEM_MODE(AUTOMATIC);

#undef SCK
#undef MOSI
#undef MISO
#undef SS
#include "pinmap_impl.h"
#include "gpio_hal.h"
#include "nrf_gpio.h"

#if PLATFORM_ID == 0 // Core
  #define pinLO(_pin) (PIN_MAP[_pin].gpio_peripheral->BRR = PIN_MAP[_pin].gpio_pin)
  #define pinHI(_pin) (PIN_MAP[_pin].gpio_peripheral->BSRR = PIN_MAP[_pin].gpio_pin)
#elif (PLATFORM_ID == 6) || (PLATFORM_ID == 8) || (PLATFORM_ID == 10) // Photon (6) or P1 (8) or Electron (10)
  STM32_Pin_Info* PIN_MAP2 = HAL_Pin_Map(); // Pointer required for highest access speed
  #define pinLO(_pin) (PIN_MAP2[_pin].gpio_peripheral->BSRRH = PIN_MAP2[_pin].gpio_pin)
  #define pinHI(_pin) (PIN_MAP2[_pin].gpio_peripheral->BSRRL = PIN_MAP2[_pin].gpio_pin)
#elif (PLATFORM_ID == 103) // BluzDK (103)
  STM32_Pin_Info* PIN_MAP2 = HAL_Pin_Map(); // Pointer required for highest access speed
  #define pinLO(_pin) (nrf_gpio_pin_clear(PIN_MAP2[_pin].gpio_pin))
  #define pinHI(_pin) (nrf_gpio_pin_set(PIN_MAP2[_pin].gpio_pin))
#else
  #error "*** PLATFORM_ID not supported by this library. PLATFORM should be Bluz, Core, Photon, P1 or Electron ***"
#endif
// fast pin access
#define pinSet(_pin, _hilo) (_hilo ? pinHI(_pin) : pinLO(_pin))

void setup() {
  pinMode(D5, OUTPUT);
}

void loop() {
  pinSet(D5, HIGH);
  pinSet(D5, LOW);
  pinSet(D5, HIGH);
  pinSet(D5, LOW);
  pinSet(D5, HIGH);
  pinSet(D5, LOW); 
  pinSet(D5, HIGH);
  pinSet(D5, LOW);
  pinSet(D5, HIGH);
  pinSet(D5, LOW);
  pinSet(D5, HIGH);
  pinSet(D5, LOW);
  pinSet(D5, HIGH);
  pinSet(D5, LOW); 
  pinSet(D5, HIGH);
  pinSet(D5, LOW);
}

If I hardcode the GPIO I can get 100ns high, 100ns low…

SYSTEM_MODE(AUTOMATIC);

#undef SCK
#undef MOSI
#undef MISO
#undef SS
#include "pinmap_impl.h"
#include "gpio_hal.h"
#include "nrf_gpio.h"

#define gpio_pin 28 // D5

void setup() {
  pinMode(D5, OUTPUT);
}

void loop() {
  nrf_gpio_pin_set(gpio_pin);
  nrf_gpio_pin_clear(gpio_pin);
  nrf_gpio_pin_set(gpio_pin);
  nrf_gpio_pin_clear(gpio_pin);
  nrf_gpio_pin_set(gpio_pin);
  nrf_gpio_pin_clear(gpio_pin);
  nrf_gpio_pin_set(gpio_pin);
  nrf_gpio_pin_clear(gpio_pin);
  nrf_gpio_pin_set(gpio_pin);
  nrf_gpio_pin_clear(gpio_pin);
  nrf_gpio_pin_set(gpio_pin);
  nrf_gpio_pin_clear(gpio_pin);
}

Integrating that back into the neopixel library is still too slow… 1.3us high, 2us low per pixel bit. I think with pure assembly code you might be able to pull it off though… You need to mask the 24 bit value to know if you send a 1 or 0 (and maybe add a NOP to one of those), shift the mask, and check for the mask to equal a certain value to know when all 24 bits are done.


#6

@BDub, the system clock on the nRF51822 is 16MHz so that’s about 63ns period. The 100ns is about right then. The PR on the Adafruit library writes directly to the mask and pin output registers which may be the fastest way to go.


#7

Obligatory: It runs on a 16 MHz Arduino!

That being said, I’m sure the nRF has BLE duties per cycle that the Arduino does not.


#8

I also did not find a quick alternative to __irq_disable(); so that needs to be figured out, as I had to comment it out.

Yep! …and the way it’s done there is with pure assembly code.[quote=“peekay123, post:6, topic:291”]

The PR on the Adafruit library writes directly to the mask and pin output registers which may be the fastest way to go.
[/quote]

The second example in my code above is doing exactly that with an inline function wrapper.


#9

Hmm, also looking at that again it uses C code for the logic. I have the same thing essentially in the Particle Neopixel library and without any NOPs it was too slow… so I wonder what clock speed that person who submitted the PR is using.

EDIT: NVM I see they were the Redbear BLE Nano and RFDuino BLE shield, both 16MHz. Perhaps there is more optimization happening with their builds? Also they bitshift each of the 3 bytes instead of one 24bit value. Might be faster.


#10

I pinged the person who made the PR. I honestly haven’t had a chance to look at this, I briefly played with it a few months ago to mixed results, so perhaps they can shed some light


#11

@eric: If you win, do I send you a Photon instead?


#12

Ha, I am not even competing. I would like one of those power boards though, so next time you post a bounty I will have to try and win it fast.


#13

I haven’t had a chance to try this out on my Bluz, however I think it should be pretty straightforward.

I would recommend copying the changes from my PR to Adafruit’s library into SparkCore-NeoPixe, in a new “#if (PLATFORM_ID == 103)” section.

noInterupts() and interrupts() will probably need to be swapped with the appropriate Particle API equivalent, and PIN_MAP from pinmap_hal.c can be used to convert the input pin into an nRF51pin.


#14

Currently our interrupts() and noInterrupts() functions won’t enable/disable ALL interrupts, only the non-BLE ones. I think that may be the source of some of these issues with the neopixel. Were you somehow able to overcome that? From my understanding, disabling all the SoftDevice interrupts is just bad. All around bad.

The timing necessary for neopixels can always be interrupted and I think that may lead to the issues seen. When I did try this months ago, I could get it to work, but some of the neopixels in my 12-pixel ring would flash or not properly change.

@wgbartley is that what you are seeing too? Or did yours not work at all?


#15

On my initial tests a couple of months ago (a 4-pixel strand), I was seeing the same thing. They mostly worked but we’re very flickery. I tried the exact same code again on the “retail” Bluz, and it’s a no-go. I also tried it on some SMD NeoPixels that I soldered and those didn’t work either (but they do work with Photon).


#16

BLE interrupts definitely need to be disabled for NeoPixels to work. I was using the BLE radio in non-interrupt (polling) mode. I understand why Bluz needs the BLE interrupt on all the time, so the software updates still work reliably, where as most other boards are updated via USB.


#17

I have heard in passing about polling mode, but never really looked into it. This isn’t part of the official Nordic release, is it? Is it part of your BLEPeripheral library? Or did it come from somewhere else?

Nordic’s position, as far as I can tell, is that if you don’t use the SoftDevice the way they want you to (the way they provide the SDK) then they basically just say they can’t guarantee it will work. And in fact, they usually just outright say it won’t work.

How reliable is BLE in polling mode? I would imagine it can lead to random disconnects, is that the case? I would also imagine this affects battery life, yes? If you must constantly poll then the CPU can’t sleep and the 16MHz clock can’t be shut off, correct?

I have heard of people using the Timeslot API to do time sensitive tasks. It can give you small windows where there wouldn’t be any BLE interrupts, so that may be a possibility. I imagine the larger the connection interval, the larger the window you would get. This might be worth investigating as well.

Thoughts?


#18

I have heard in passing about polling mode, but never really looked into it. This isn’t part of the official Nordic release, is it? Is it part of your BLEPeripheral library? Or did it come from somewhere else?

BLEPeripheral has a poll method which on the nRF51 just calls “sd_ble_evt_get” to read events from a non-ISR context. As far as I can tell Bluz firmware is calling the same method in the ISR.

Main goal was to not have user callback methods execute in an ISR context.

How reliable is BLE in polling mode? I would imagine it can lead to random disconnects, is that the case? I would also imagine this affects battery life, yes? If you must constantly poll then the CPU can’t sleep and the 16MHz clock can’t be shut off, correct?

It seems pretty solid so far, as long as poll is calling frequently enough. A friend tried to use a 50 length NeoPixel with the RFduino recently, but of course crazy things like this will interfere with BLE. A 16 NeoPixel ring runs without issues.

Correct, CPU does not sleep. However, polling can be used in conjunction with sleeping. Here’s a low power example: https://gist.github.com/sandeepmistry/6a6dfe9021b19bfddd60 - which uses “sd_app_evt_wait” to enter low power mode.

I have heard of people using the Timeslot API to do time sensitive tasks. It can give you small windows where there wouldn’t be any BLE interrupts, so that may be a possibility. I imagine the larger the connection interval, the larger the window you would get. This might be worth investigating as well.

Agreed, for a small NeoPixel count this should be fine.

Again, for the Bluz use case where OTA firmware updates are the main update mechanism, I wouldn’t recommend a polling style. If the user does not poll frequently enough or removes the polling call all together things break big time.


#19

@wgbartley, @eric, I think it’s time to consider SPI and possibly SPI DMA for the neopixel code. The FastLED library may also be worth considering. :wink:


#20

@peekay123, I’ve been considering offloading the LEDs to a NeoPixel “co-processor” using an ATTiny85 over I2C or serial. I haven’t tested this theory, only considered it