A stability-focused LED controller designed for the Raspberry Pi 5. This engine uses unrolled bit-packing and a supplemental threaded rendering class to ensure smooth, non-blocking animations. Optimized to handle the specific SPI clock characteristics of the Pi 5 to prevent color shifting and data corruption.
By leveraging the hardware SPI bus (spidev), this engine offloads the strict timing requirements of addressable LEDs to the hardware, eliminating the flicker and color shifting common with software-based bit-banging.
Leading/Trailing Padding: Injects 1KB of zero-byte "silence" to ensure a clean Reset signal for every frame.
Single-Shot Transfers: Sends the entire frame in one ioctl call to prevent timing gaps between LED data.
Unrolled Bit Packing: Optimized loops ensure maximum timing consistency and CPU efficiency.
DMA Alignment: Utilizes posix_memalign and mlock for page-aligned memory, ensuring the Pi 5's I/O controller can access the buffer without interruption.
LED Type: WS2811 / WS2812 / WS2812B (Addressable RGB)
Data Pin (DIN): Connect to Pin 19 (GPIO 10 / MOSI)
Ground: Ensure the LED strip and the Raspberry Pi share a common ground.
Power: Always power high-density LED strips with an external power supply; do not rely on the Pi's 5V rail for large arrays.
- Enable SPI The Raspberry Pi's SPI interface must be enabled via the configuration tool:
Bash
sudo raspi-config
Navigate to Interface Options > SPI > Yes. Reboot if prompted.
- Increase SPI Buffer Size Standard Linux SPI buffers are often limited to 4KB. If you are driving more than ~170 LEDs, you must increase the buffer size.
Edit the boot configuration:
Bash
sudo nano /boot/firmware/cmdline.txt
Add the following to the end of the existing line (do not create a new line):
Plaintext
spidev.bufsiz=65536
Verify after reboot:
Bash
cat /sys/module/spidev/parameters/bufsiz
Should output: 65536
Since the core logic is contained within WS2812Spi, getting your first animation running is straightforward.
- Create a simple main.cpp
C++ #include "render_leds.h" #include
int main() { int num_leds = 60; // Set your LED count WS2812Spi strip(num_leds, "/dev/spidev0.0");
// Optional: Enable Real-Time Priority (requires sudo)
strip.setRealTimePriority();
// Create a color buffer (RRGGBB)
std::vector<uint32_t> colors(num_leds);
while (true) {
for (int i = 0; i < num_leds; i++) {
// Fill with a simple color (e.g., Red)
colors[i] = 0xFF0000;
strip.show(colors);
usleep(50000); // 50ms delay
// Clear for next frame
colors[i] = 0x000000;
}
}
return 0;
}
- Compilation Compile your project using g++. Ensure you include the helper files and link the pthread library for timing and threading support.
Bash
g++ -O3 main.cpp render_leds.cpp render_leds_helper.cpp -o led_engine -lpthread
- Running the Engine Because the engine uses mlock for memory stability and sets SCHED_FIFO for real-time priority, it must be run with root privileges:
Bash
sudo ./led_engine
render_leds.h / .cpp: The core WS2812Spi driver.
render_leds_helper.h / .cpp: Supplemental classes for high-precision timing (FLED_TIME), color management (CRGB), and frame-rate limiting (TIMED_IS_READY).
RENDER_LEDS_CLASS: (Supplemental) A threaded wrapper for non-blocking rendering. Useful for complex applications where LED updates should not stall the main logic.
I haven't tested the included example. I just wanted to put the code out there for anyone interested. The example 'stress_test.cpp' in the source folder should work though. Although, it does have a bug in it. wheel_cycle(uint8_t pos) function may work as well.
The WS2812Spi class is all that is needed to run LED light strips and it is included in the render_leds.h and the render_leds.cpp files.
To get the lights running in a tread, you will need to use the RENDER_LEDS_CLASS. I havent finished self containing it, but the meat of the code is there, as well as most of the function calls included in the render_leds_helper. If i get any demand for it I can zip it up to run. If you don't want to wait, im sure any AI chat box can help you get it working out of the box.
The code isn't perfect. There is plenty of things in there that can be refined, cleaned, dropped, and changed. The thing is, I ran it for a good 8hrs in a harsh enviornment and it worked without issuse. I'm a bit afraid of changing it. If its not broke, right?
However, there is plenty of fat that can be trimmed out. Enjoy.
If you mod, fork, dupe, and/or improve this code, send me a message. I can either just use your code, or pull the changes into mine.