Support multiple strips in one Python program

Previously, a necessary delay at the end of pixel transmission was not enforced.

The new test program is intended for a braincraft hat with a 96-pixel LED strip
on each of the two GPIO connectors, but can be modified for other uses.

Closes: #3
This commit is contained in:
Jeff Epler 2025-01-22 11:09:26 -06:00
parent 7643c3f0d1
commit f8847a5aac
2 changed files with 85 additions and 7 deletions

View file

@ -0,0 +1,55 @@
import adafruit_pixelbuf
import board
from adafruit_led_animation.animation.rainbow import Rainbow
from adafruit_led_animation.animation.rainbowchase import RainbowChase
from adafruit_led_animation.animation.rainbowcomet import RainbowComet
from adafruit_led_animation.animation.rainbowsparkle import RainbowSparkle
from adafruit_led_animation.sequence import AnimationSequence
from adafruit_raspberry_pi5_neopixel_write import neopixel_write
NEOPIXEL1 = board.D13
NEOPIXEL2 = board.D12
num_pixels = 96
class Pi5Pixelbuf(adafruit_pixelbuf.PixelBuf):
def __init__(self, pin, size, **kwargs):
self._pin = pin
super().__init__(size=size, **kwargs)
def _transmit(self, buf):
neopixel_write(self._pin, buf)
pixels1 = Pi5Pixelbuf(NEOPIXEL1, num_pixels, auto_write=True, byteorder="BGR")
pixels2 = Pi5Pixelbuf(NEOPIXEL2, num_pixels, auto_write=True, byteorder="BGR")
def make_animation(pixels):
rainbow = Rainbow(pixels, speed=0.02, period=2)
rainbow_chase = RainbowChase(pixels, speed=0.02, size=5, spacing=3)
rainbow_comet = RainbowComet(pixels, speed=0.02, tail_length=7, bounce=True)
rainbow_sparkle = RainbowSparkle(pixels, speed=0.02, num_sparkles=15)
animations = AnimationSequence(
rainbow,
rainbow_chase,
rainbow_comet,
rainbow_sparkle,
advance_interval=5,
auto_clear=True,
random_order=True,
)
return animations
animation1 = make_animation(pixels1)
animation2 = make_animation(pixels2)
try:
while True:
animation1.animate()
animation2.animate()
finally:
pixels1.fill(0)
pixels1.show()
pixels2.fill(0)
pixels2.show()

View file

@ -1,4 +1,5 @@
#include <iostream>
#include <time.h>
#include <pybind11/pybind11.h>
#include "piolib.h"
@ -14,6 +15,19 @@ static int sm{-1};
static int offset{-1};
static int last_gpio{-1};
static size_t last_size{};
static struct timespec deadline;
constexpr auto NS_PER_SECOND = 1'000'000'000l;
constexpr auto NS_PER_MS = 1'000'000l;
static void timespec_add_ns(struct timespec &out, const struct timespec &in, long ns) {
out = in;
out.tv_nsec += ns;
if(out.tv_nsec > NS_PER_SECOND) {
out.tv_nsec -= NS_PER_SECOND;
out.tv_sec += 1;
}
}
static void neopixel_write(py::object gpio_obj, py::buffer buf) {
int gpio = py::getattr(gpio_obj, "_pin", gpio_obj).attr("id").cast<int>();
@ -39,15 +53,15 @@ static void neopixel_write(py::object gpio_obj, py::buffer buf) {
offset = pio_add_program(pio, &ws2812_program);
pio_sm_clear_fifos(pio, sm);
pio_sm_set_clkdiv(pio, sm, 1.0);
last_gpio = -1;
}
if (gpio != last_gpio) {
ws2812_program_init(pio, sm, offset, gpio, 800000.0, true);
last_gpio = gpio;
} else {
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &deadline, NULL);
if (gpio != last_gpio) {
ws2812_program_init(pio, sm, offset, gpio, 800000.0, true);
}
}
last_gpio = gpio;
size_t size = info.size * info.itemsize;
@ -75,6 +89,15 @@ static void neopixel_write(py::object gpio_obj, py::buffer buf) {
if (pio_sm_xfer_data(pio, sm, PIO_DIR_TO_SM, data_size, &vec[0])) {
throw std::runtime_error("pio_sm_xfer_data() failed");
}
// Track the earliest time at which we can start a fresh neopixel transmission.
// This needs to be long enough that all bits have been clocked out (the FIFO can
// contain 16 entries of 32 bits each, taking 640us to transmit) plus the ws2812
// required idle time ("RET code") of 50usmin. These sum to around 700us, so the 1ms
// delay is generous.
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
timespec_add_ns(deadline, ts, NS_PER_MS);
}
static void free_pio(void) {