Neopixel/WS2812 driver for Raspberry Pi Pico running Kaluma
The Raspberry Pi Pico is a cheap (but powerful) microcontroller board from Raspberry Pi (important: the Raspberry Pi Pico does not run Linux). Kaluma is a JavaScript runtime for the RasPi Pico with a web-based IDE, which may be run on any modern browser that supports the "Web Serial API" (important: you do not have to sign-in in order to use the IDE)
Surprisingly, Kaluma does not yet have a library that may be used to drive Neopixel displays (or other strips of WS2812 LEDs) - this little contribution shall fill this gap.
Just a small note: if you like this work and plan to use it, consider "starring" this repository (you will find the "Star" button on the top right of this page), so that I know which of my repositories to take most care of.
This code uses the first of the "Serial Programming Interfaces" (SPI) the RasPi Pico provides. With proper timing, such an SPI can be used to drive Neopixel displays (or compatible LED strips) - ioprog has a good article describing the technical background.
From the possible signals an SPI provides, only "Master Out Slave In" (MOSI) is used. The RasPi Pico allows that signal to be routed to multiple output pins (but not to any of them): by default, pin GP3 wil be used - alternatively, you may also choose pin GP19 if you modify the examples accordingly.
Nota bene: the Pimoroni Tiny 2040 already uses pin GP19 for its own built-in RGB LED - thus, on such a device, the default pin GP3 should be preferred.
Sometimes, LED stripes (which assume to be powered with 5V) may be directly wired to the RasPi Pico output pin (which provides 3v3 levels only) but usually, such a connection does not work reliably and may produce wrong LED patterns from time to time.
In such a case, a level shifter should be inserted between RasPi Pico and LED Stripe. This device does not have to be bidirectional but definitely must be fast since communication effectively runs with 800kHz. For that reason, a dedicated level shifting chip should be used (instead of the brilliant and, thus, widely used circuit consisting of a simple MOSFET and two resistors only). If it still has to be the latter one, you may try to "short-circuit" the MOSFET with a small ceramic capacitor (e.g., 22pF) but success is not guaranteed.
Internally, the driver allocates 3 bytes per Neopixel data byte in order to prepare the bit pattern to be sent through the SPI - thus, for each RGB LED a total of 9 bytes is used internally.
The library directly supports linear stripes as well as matrices wired in zigzag fashion. The assumed byte order of connected LEDs is GRB - but you do not have to care and may specify any color values in RGB order.
The "library" consists of a single function SPIDisplay
which should be invoked to setup a driver for a given MOSI pin and LED geometry:
SPIDisplay (Pin, Width, Height)
prepares an internal display storage for a LED matrix with the given dimension (omitHeight
if you have a linear stripe only) which is connected to the givenPin
(setPin
tonull
or3
if you want to use pin GP3 or19
for pin GP19)
The output of this function is an object containing a few methods which may be used to prepare a display and send it to the LED stripe.
clear ()
fills the internal display storage with the SPI bit pattern for dark LEDssetPixelRGB (x,y, R,G,B)
sets the LED at the given coordinate (x = 0...Width-1, y = 0...Height-1
) to the given RGB values (R = 0...255, G = 0...255, B = 0...255
)setPixelHSL (x,y, H,S,L)
sets the LED at the given coordinate (x = 0...Width-1, y = 0...Height-1
) to RGB values which correspond to the given "Hue" (H = 0...1
), "Saturation" (S = 0...1
) and "Luminosity" (L = 0...1
) values. Internally, this method uses the same conversion asHSLtoRGB
and has been provided for your convenience onlyHSLtoRGB (H,S,L)
converts the given "Hue" (H = 0...1
), "Saturation" (S = 0...1
) and "Luminosity" (L = 0...1
) values to corresponding RGB values (R = 0...255, G = 0...255, B = 0...255
) and returns them as an array. The same conversion is also used bysetPixelHSL
, but has been provided as a separate method in case that you want to process the resulting RGB values furthershow ()
sends the current contents of the internal display storage to the connected LED stripe
In the simplest case, the contents of file SPIDisplay.js
may just be copied into the Kaluma Web IDE and your code added below:
// <<<< insert SPIDisplay.js here
let Display = SPIDisplay(null, 16,16);
Display.clear();
Display.setPixelRGB(0,0, 16,0,0); // red
Display.setPixelRGB(2,0, 0,16,0); // green
Display.setPixelRGB(4,0, 0,0,16); // blue
Display.show();
The following image illustrates the expected wiring (without level shifter) for the tests and examples that come with this driver.
Please note, that the shown Neopixel display is just an example - please, consider the individual code examples for their actually expected LED geometry.
The following tests assume a LED string connected to Pin 19. Just copy them into your clipboard, paste them into the Kaluma Web IDE and "Upload":
RGB-Test.js
sets pixel 0 to red, pixel 2 to green, 4 to blue and pixel 10 to yellow, 12 to magenta, 14 to cyanHSL-Test.js
fills pixels 0...15 with different colors
All examples assume a 16x16 RGB LED matrix connected to Pin 19. Just copy them into your clipboard, paste them into the Kaluma Web IDE and "Upload":
Intensity-Test-linear-non-dimmed.js
fills a 16x16 LED matrix with a variety of colors with linearly increasing RGB values ranging from 0 to 255. Neopixel LEDs usually shine quite bright, perhaps too bright - try this example to see what I meanIntensity-Test-linear-dimmed.js
fills a 16x16 LED matrix with a variety of colors and linearly increasing RGB values ranging from 0 to 1/16th of the potential maximum. This example is like the previous one, but does not exhaust the complete intensity range of Neopixel LEDsIntensity-Test-non-linear-non-dimmed.js
fills a 16x16 LED matrix with a variety of colors and quadratically increasing RGB values ranging from 0 to 256. Linearly increasing the RGB values of a Neopixel LED does not lead to a linearly increasing brightness - this example therefore uses quadratically increasing RGB values which seem to do a better jobIntensity-Test-non-linear-dimmed.js
fills a 16x16 LED matrix with a variety of colors and quadratically increasing intensity ranging from 0 to 1/16th of the potential maximum. This example is like the previous one, but does not exhaust the complete intensity range of Neopixel LEDs