Part of my project with the Arduino Portenta requires some lighting and I wanted to be able to individually control them. I asked a model railroad mate and he suggested NeoPixels. They only require 3 wires, run on 5v and can be controlled from a range of microcontrollers. The standard ones do RGB values but there is also a variant with an additional white LED. I found some on a small breakout that did RGBW and could be bought in packs of 10.
After experimenting with a couple of different libraries in Micropython, I failed to get the pixels to work. The best option seemed to be a library that used a SPI port to run the pixels but it was not quite clear if the SPI buses were configured correctly. https://github.com/JanBednarik/micropython-ws2812
So I swapped back to C having the idea that I could use one core to run the lights and the other to act as a high-level controller. This in turn seems to have its own challenges but I think they can be resolved.
There are a number of libraries for Neopixels including an AdaFruit one, FastLED and FabLED. These all have fine-tuned code to make the pixels work well on AVR platforms. However, after looking at the AdaFruit one it had some examples for STM architecture (same as the Portenta) and also examples where the "show" function was separated out into a separate file. This helped me work out how to integrate Portenta so I forked that library - https://github.com/Workshopshed/Adafruit_NeoPixel and did some experiments with a simple digitalwrite. For the timing I dug into the mbed library and found a wait_ns function. That was configured with the delays as per the examples in the other variants. That didn't give me a working result so I dug into Josh Levine's post NeoPixels Revealed and found the timings out. When I looked at my timings with a scope, there was a much large delay than I was specifying. I suspected that digitalwrite was the culprit and swapped that for a lower level call I found via an_dreh in their Fast GPIO toggle example on the Arduino forum. I suspect even lower level options are available but this combined with some adjustments to my timings meant that the NeoPixels were displaying.
I created a simple loop to test the brightness of the white LEDs and found that I was getting a green LED lighting up on the first pixel. I had initially thought this was because the first pixed was getting the signal at 3.3v and added a simple level shifter to the circuit. However this did not make the problem go away.
So I made some tweeks to the library to ensure the IO pin was initially low and that there was a gap before the first data byte. And that fixed the first pixel being green. Finally, I knew that brightness of LEDs was not a linear function so borrowed some code from Diarmuid Mac Namara and made the LED brightness non-linear depending on an exponential function.
Testcode for fading white part of the RGBW pixel.
#include <Adafruit_NeoPixel.h>
// Which pin on the Arduino is connected to the NeoPixels?
#define PIN 0 // Only works with D0 at the moment
// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS 2 // Popular NeoPixel ring size
// When setting up the NeoPixel library, we tell it how many pixels,
// and which pin to use to send signals. Note that for older NeoPixel
// strips you might need to change the third parameter -- see the
// strandtest example for more information on possible values.
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRBW + NEO_KHZ800);
#define DELAYVAL 50 // Time (in milliseconds) to pause between steps
//From https://diarmuid.ie/blog/pwm-exponential-led-fading-on-arduino-or-other-platforms
// The number of Steps between the output being on and off
const int pwmIntervals = 100;
// The R value in the graph equation
float R;
void setup() {
// These lines are specifically to support the Adafruit Trinket 5V 16 MHz.
// Any other board, you can remove this part (but no harm leaving it):
pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
pixels.clear(); // Set all pixel colors to 'off'
// Calculate the R variable (only needs to be done once at setup)
R = (pwmIntervals * log10(2))/(log10(255));
}
void loop() {
int brightness = 0;
for (int interval = 0; interval <= pwmIntervals; interval++) {
// Calculate the required PWM value for this interval step
brightness = pow (2, (interval / R)) - 1;
// Set the LED output to the calculated brightness
for(int i=0; i<NUMPIXELS; i++) { // For each pixel...
pixels.setPixelColor(i, pixels.Color(0, 0, 0, brightness));
}
pixels.show(); // Send the updated pixel colors to the hardware.
delay(DELAYVAL); // Pause before next pass through loop
}
}
Library work in progress: https://github.com/Workshopshed/Adafruit_NeoPixel