circuitpython: (Large numbers of) Neopixel and Dotstar are slow

Both Neopixel and Dotstar take a lot of cycles to update pixels. There’s two different things going on that slow down both. The first is that converting from Int or RGB[W] to the bytearray is complex code that makes updating pixels slow. The penalty for more pixels is O(N) (as one would expect).

The second issue is that when brightness<1, extra work is done during .show() - the bytearray is copied and every pixel value recomputed. This probably also causes unnecessary garbage collection, and has helped me run out of memory later on in runtime through apparent fragmentation.

This technically isn’t a core CircuitPython issue, but because it requires a C helper to address, some work is likely needed in CircuitPython to add helpers to speed up these tasks.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 38 (9 by maintainers)

Commits related to this issue

Most upvoted comments

Move the .show() out of the loop:

for i in range(num_pixels):
     pixels[i] = (100, 0, 0, 0)
pixels.show()

At some point I hope my life becomes less busy, otherwise I’ll work on this during sprints at PyCon. https://github.com/rhooper/CircuitPython_Pixel_Driver is the WIP repo that has neopixel.py and adafruit_dotstar.py drivers. Memory-wise it should be similar to the current implementations, unless you use the single buffered mode that doesn’t refresh the brightness of all pixels when brightness is changed. That functionality isn’t exposed in the backwards compatible APIs in the above linked repo.

Hit me up on Discord with questions if you like.

@paulmand3l You’re right, we forgot to do a release after fixing the re issue. I just made https://github.com/adafruit/Adafruit_CircuitPython_Pypixelbuf/releases/tag/1.0.3, which has the re fix.

PixelBuf speeds up RGB pixels by using C to do math on all the pixels such as the pre-transmit application of brightness. The libraries need to inherit from PixelBuf in order to get this speed-up though.

How does this actually work behind the scenes to create a faster library?

😕 I’m surprised 5.0.0 stable release appears might not include new _pixelbuf driven NeoPixel?! Expected that would be one of its main features.

For those who want to try out _pixelbuf accelerated neopixels with 5.0.0 without building / compiling CircuitPython…

You can easily:

  • Download neopixel.py from the pixelbuf branch.
  • Rename neopixel.py to pixelbuf_neopixel.py (or anything different)
  • copy it to /lib folder (create if needed) on your circuitpython board
  • to use: import pixelbuf_neopixel as neopixel

Simple example:

import board
import pixelbuf_neopixel as neopixel
# import neopixel # uncomment to compare speed
pixels = neopixel.NeoPixel(board.NEOPIXEL, 10) # pin, # of neopixels

col = [127,255,64]

max_range = max(col)

while True:
    for i in range(max_range + 1):
        # gamma 2.0 adjust
        c = [max(0, n-i)**2//255 for n in col]
        pixels.fill(c)
    for i in range(max_range, 0, -1):
        # gamma 2.0 adjust
        c = [max(0, n-i)**2//255 for n in col]
        pixels.fill(c)

Ok I compiled the pixbuf tree and it works a lot faster now! https://twitter.com/timonsku/status/1213599823116591105

Still a lot slower than the Arduino library but it now makes 100-200 pixel strips usable in combination with other stuff, which before was just impossible! Thanks for all the work!

@kevinjwalters _pixelbuf has a native fill that the previously mentioned forks/branches of adafruit_dotstar.py and neopixel.py can use.

@PTS93 the latest changes to _pixelbuf were just merged into master yesterday. If you’re good with building CircuitPython yourself, you can try the _pixelbuf variants of neopixel.py and adafruit_dotstar.py over at https://github.com/rhooper/Adafruit_CircuitPython_DotStar/tree/pixelbuf and https://github.com/rhooper/Adafruit_CircuitPython_NeoPixel/tree/pixelbuf

If you’re using one of the new nRF boards like the Feather nRF52840 https://www.adafruit.com/product/4062 DMA is used for neopixel_write (if possible).

There’s probably no reason that the M4 boards couldn’t use the techniques the NRF neopixel_write and https://github.com/adafruit/Adafruit_NeoPXL8/blob/master/Adafruit_NeoPXL8.cpp use to do DMA (PWM) for more boards. I’m guessing there’s likely not enough space/memory to do the DMA technique for M0.

What is the current state of this? I would love to do some animations with a 120px Neopixel strip but the refresh rate is abysmal with the default library 😕 I could only find the api doc but not really sure on how this is supposed to be used, does it tie into the standard neopixel library?

shark, turn off auto_write, also use fill()

My thoughts on how to handle low memory boards is to keep the logic in neopixel and dotstar, but also check if CircuitPython has the pixelbuf helpers present. If present, it’ll use the fast implementation, if not, it’ll use the pure python.

This will let us exclude the code from boards where it’s not useful or doesn’t fit.