adafruit / Adafruit_Blinka

Add CircuitPython hardware API and libraries to MicroPython & CPython devices
https://learn.adafruit.com/circuitpython-on-raspberrypi-linux
MIT License
447 stars 335 forks source link

Add support for more than one strip #103

Closed glenneroo closed 3 years ago

glenneroo commented 5 years ago

When trying to control two WS2812 strips with AdaFruit_NeoPixel on two separate PWM GPIOs (Board.D13 and Board.D18) I get the following RuntimeError:

Error in atexit._run_exitfuncs: Traceback (most recent call last): File "na.py", line 1287, in exit_handler led_strip.shutdown() File "na.py", line 891, in shutdown self.color_wipe(LED.color(0, 0, 0)) File "na.py", line 934, in color_wipe self.pixels2.show() File "/usr/local/lib/python3.5/dist-packages/neopixel.py", line 230, in show neopixel_write(self.pin, self.buf) File "/usr/local/lib/python3.5/dist-packages/neopixel_write.py", line 24, in neopixel_write return _neopixel.neopixel_write(gpio, buf) File "/usr/local/lib/python3.5/dist-packages/adafruit_blinka/microcontroller/bcm283x/neopixel.py", line 70, in neopixel_write raise RuntimeError("Raspberry Pi neopixel support is for one strip only!") RuntimeError: Raspberry Pi neopixel support is for one strip only! swig/python detected a memory leak of type 'ws2811_t *', no destructor found.

Here is how I initialize:

pixels1 = neopixel.NeoPixel(
    board.D18, LED.LED_COUNT, brightness=1, auto_write=False, pixel_order=neopixel.GRB)
pixels2 = neopixel.NeoPixel(
    board.D13, LED.LED_COUNT, brightness=1, auto_write=False, pixel_order=neopixel.GRB)

RuntimeErrors are thrown whenever I call this:

pixels1.fill(color)
pixels1.show()
pixels2.fill(color)
pixels2.show()

Is there a reason only one strip is supported?

ladyada commented 5 years ago

hmm haven't tried it, can you use two neopixel strips with https://github.com/jgarff/rpi_ws281x ? that's what we're basing off of

glenneroo commented 5 years ago

I can run the "test" program in 2 terminals: sudo ./test -s grb -g 18 -c sudo ./test -s grb -g 21 -c

And they both run, albeit the strips running off of GPIO21 seem to act a bit weird (regardless if running simultaneously or not).

ladyada commented 5 years ago

ok i thought it was just one at a time, well, we dont have support yet here is all

georgebohnisch commented 4 years ago

Would also love to see support for multiple strips at once.

tomkeuper commented 3 years ago

I currently use neopixel with python 2.7 and it works with more than 1 strip. But I wanted to use asyncio and I need to upgrade to 3.5+ for that.

makermelissa commented 3 years ago

I've been playing with this and I think it is possible with a workaround and a bit of a rewrite. There's a couple of possible strategies. While the _rpi_ws281x module only natively supports one strip, I'm not sure if that's 1 strip per instance or shared.

One strategy is to initialize a separate instance per GPIO (if it supports multiple instances) and switch between them based on the GPIO passed in. The other strategy is to have a single instance and have it reinitialize and switch every time the gpio changes.

makermelissa commented 3 years ago

Ok, I just came across this: https://github.com/rpi-ws281x/rpi-ws281x-python/blob/master/examples/multistrandtest.py. It looks like it may now support multiple strips.

makermelissa commented 3 years ago

There seems to be a pretty big bug with the rpi_ws281x library that will write the data to both GPIOs once they have been initialized as separate strips. This happened when using multiple instances and adapting the multistrand example above. I think strategy 2 (reinitializing between each write) might be the way to go.

makermelissa commented 3 years ago

Ok after trying a number of different strategies, I'm concluding that it really isn't possible. Here's some background of what I tried: I was running into issues with NeoPixels on 2 different pins responding to the same changes. This is because there are 2 PWM channels on the Raspberry Pi that share a number of pins and both of the ones I was using are on PWM0.

The specific parameter options for this dtoverlay can be found with the following command:

dtoverlay -h pwm-2chan

So I enabled the second PWM channel using the pwm-2chan dtoverlay by adding the following line to my /boot/config.txt

dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4

The pin functionality can be found on page 102 of the data sheet at https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2835/BCM2835-ARM-Peripherals.pdf.

I confirmed they were set in PWM output with the following command:

raspi-gpio get 12,13

and got the following output:

GPIO 12: level=0 fsel=4 alt=0 func=PWM0_0 pull=DOWN
GPIO 13: level=0 fsel=4 alt=0 func=PWM0_1 pull=DOWN

I ran my test script and got the following error:

RuntimeError: ws2811_init failed with code -11 (Selected GPIO not possible)
Segmentation fault

I did notice that after running the script, the raspi-gpio command now outputs the following:

GPIO 12: level=0 fsel=4 alt=0 func=PWM0_0 pull=NONE
GPIO 13: level=0 fsel=1 func=OUTPUT pull=NONE

This tells me that the rpi_ws281x library is handling changing the pin muxing already and the dtoverlay was probably not necessary. However, it just refuses to cooperate and the issue lies outside of Blinka. Also, the benefit of 1 additional strand is probably not worth it since strands can be chained together already, so I'm going to close this issue.