Modifications added to this fork and how to customize it are below
fastNP is an STM32 library for refreshing multiple chains of addressable LEDs in parallel. It works with WS2812B LEDs and their clones like SK6812, etc (often called NeoPixels). Up to 16 chains of LEDs can be refreshed in parallel, without overclocking the CPU. I have developed and tested this on an STM32F103 microcontroller (on a so-called "Blue Pill" board) but it should be possible to port it to other STM32 micros as well.
This library builds on the concepts of Martin Hubáček's WS2812B DMA Library, who uses a brilliant technique for refreshing multiple LED chains using one Timer and three DMA channels. In fastNP, we try to take this a step further, by heavily optimizing the code that generates the intermediate DMA bit-buffers, which is basically the bottleneck when trying to refresh multiple channels. With a little bit of inline assembly, we can generate a bitbuffer for all sixteen channels, within the time it takes to perform one half of a DMA transfer.
-
Grab an STM32F103 board (I'm using Blue Pill but anything similar should work).
-
If your board uses anything other than an 8MHz HSC oscillator, modify the CubeMX project (or clock settings in main.c) so that the CPU and Timers still run at 72MHz.
-
Connect strips of WS2812B LEDs to any pin(s) on PORTB. By default, LED data will be sent to GPIOs B0-B15.
-
Grab the code and compile:
make
(assuming you're on Linux) -
Flash the board
make flash
if you have the st-flash utility on Linux, or use whatever way you prefer -
Watch the pretty lights scroll by. See demo_main.c for more info.
NOTE: If writing your own Makefile (or using some other build system), an optimization level of at least -O1 is required. Otherwise, GCC generates code that is too slow.
First, you'll want to customize ws2812_led.h for your project. If you are using fewer than 16 LED channels, set WS2812_NUM_CHANNELS to the appropriate number. In practice it's usually okay to leave this as-is (you can specify zero-length framebuffers for the unused channels) but this is more optimal.
All your LED channels must be connected to the same GPIO bank (ie, all LEDs must be on GPIOA, or on GPIOB, but you can't mix the two). If using fewer than 16 channels, you can also decide which channels map to which GPIO numbers within the GPIO bank. By default, channel 0 goes to GPIO0 (A0 or B0 or whatever bank you're using) but you can change this relationship in ws2812_led.h if necessary.
Next, you'll need to set up your channel array. This is an array of struct led_channel_info and its length must be WS2812_NUM_CHANNELS. This array contains the framebuffer pointer for each channel, as well as the length of the framebuffer for that channel. Unused channels should have their length set to 0. Note that framebuffer lengths are in bytes, rather than in terms of number of pixels. This is because some LEDs want 3 bytes (R, G, B) and other types of LEDs want 4 bytes (R, G, B, W). The library doesn't actually care what kind of LEDs you're talking to, as long as you've laid out your framebuffers in a way that your LEDs expect.
Next, you'll want to call ws2812_init(). This sets up Timer2 and all the DMA channels, but it doesn't send out any data yet.
Then, we disable interrupts, call ws2812_refresh(), passing in our channel array, and the GPIO bank we want to use. Finally, we may (optionally) re-enable interrupts.
See the following code snippet:
// Declare channel array
struct led_channel_info channels[WS2812_NUM_CHANNELS];
// Clear it out
memset(channels, 0, sizeof(channels));
channels[0].framebuffer = <pointer to some byte array>;
channels[0].length = <framebuffer length in bytes>
channels[1].framebuffer = <pointer to some other byte array>;
channels[1].length = <framebuffer length in bytes>
...
// Send out the image data!
__disable_irq();
ws2812_refresh(channels, GPIOB); // If using some other GPIO bank, change it here
__enable_irq();
For more info, take a look at demo_main.c.
Written with StackEdit.
- Modified to work as Adalight compatible with Prismatik
- Using USB CDC code based on philrawlings' bluepill-usb-cdc-test
- Added heartbeat LED code (idea inspired from ArduinISP)
The code programmed for two output channels (2 WS2812 LED ribbons), on PB0 and PB1 resp., the number of LEDs in each output is defined by NUM_LEDS_OUTPUT_1 and NUM_LEDS_OUTPUT_2 resp., the number of channels is defined by WS2812_NUM_CHANNELS.
- Change the number of channels to 1
// ws2812_led.h line 45
#define WS2812_NUM_CHANNELS 1
- You can optionally (since it's only on startup and won't have any further influenc) remove the definitions of the second channel and all code related to it
// main.c, around line 206
#define NUM_LEDS_OUTPUT_2 80
...
// main.c, line 209
struct pixel leds2[NUM_LEDS_OUTPUT_2];
...
// main.c, line 216
channels[1].framebuffer = &leds2;
channels[1].length = NUM_LEDS_OUTPUT_2 * sizeof(struct pixel);
// main.c, line 231
FillPixels(channels[1], colors[i]);
The output will be PB2
- Define the number of LEDs in this channel (let's say 50 LEDs)
// main.c, around line 207
#define NUM_LEDS_OUTPUT_2 50
- Define another struct
// main.c, around line 210
struct pixel leds3[NUM_LEDS_OUTPUT_3];
- Add the defined structure to channels
// main.c, line 218
channels[2].framebuffer = &leds3;
channels[2].length = NUM_LEDS_OUTPUT_3 * sizeof(struct pixel);
- Change the number of channels to 3
// ws2812_led.h line 45
#define WS2812_NUM_CHANNELS 3
- Add initialization colors (optional)
// main.c, line 232
FillPixels(channels[2], colors[i]);