Addressable LED strips and general performance

Questions and discussion about running MicroPython on a micro:bit board.
Target audience: MicroPython users with a micro:bit.
Post Reply
tobiasbp
Posts: 1
Joined: Sat Feb 13, 2021 12:28 pm

Addressable LED strips and general performance

Post by tobiasbp » Sat Feb 13, 2021 12:59 pm

Hello forum.

I'm experimenting with MicroPython on the Micro:bit V1. Playing around with some NeoPixel/WS2812 LED strips, I see much slower update speeds than I expect. I can implement similar things to what I'm doing in my Python script on https://makecode.microbit.org/, and they will run MUCH quicker than my Python code?

Here is an example fading LEDs out. I expect the fading to occur MUCH faster than it actally does. Even precomputing the values I calculate by multipying, do not help (much). Seems like the for loops are inherently slow??

Code: Select all

import neopixel
import microbit as mb

led_strip = neopixel.NeoPixel(mb.pin0, 128)

while True:
    # All LEDs on
    for i in range(len(led_strip)):
        led_strip[i] = (0x0F, 0x00, 0x00)
    led_strip.show()

    # Fade out
    for n in range(10):
        for i in range(len(led_strip)):
            led_strip[i] = ( int(led_strip[i][0]*0.8), 0, 0) 
        led_strip.show()

This is my setup:
  • This is how I upload the code: uflash my_code.py
  • I'm running Linux
These are my concerns:
  • My Python code is inappropriate for use on a microcontroller
  • MicroPython is too slow for my purposes
  • The neopixel library i'm using, is very slow, and I should use another
So, what should I do to increase performance? Any thoughts appreciated.

Regards,
Tobias

User avatar
jimmo
Posts: 2754
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia
Contact:

Re: Addressable LED strips and general performance

Post by jimmo » Mon Feb 15, 2021 2:52 am

tobiasbp wrote:
Sat Feb 13, 2021 12:59 pm
These are my concerns:
My Python code is inappropriate for use on a microcontroller
MicroPython is too slow for my purposes
The neopixel library i'm using, is very slow, and I should use another
So, what should I do to increase performance? Any thoughts appreciated.
I'd be curious to see how the makecode API implementation is so much faster -- do they have a dedicated "fade" function for example?

There are a couple of quirks in the micro:bit MicroPython neopixel API that unfortunately do make it slower than it needs to be.

The way that it access pixels via r,g,b tuples means that there's a lot of allocation of tuples happening.

So when you write
led_strip[n] = (r, g, b)

that creates a new tuple. Similarly, so does accessing an element. So this line...

Code: Select all

led_strip[i] = ( int(led_strip[i][0]*0.8), 0, 0)
...allocates two tuples. Additionally, floating point requires allocations too.

So you can speed this up a little bit with three changes:
- Use a pre-allocated list rather than a tuple for the new pixel values.
- Don't use floating point
- Avoid querying pixel values from the neopixel object.

The third thing only works in your case, because you set all the pixels to the same value. But this wouldn't work if you were fading out an arbritrary pattern.

Code: Select all

import neopixel
import microbit as mb

led_strip = neopixel.NeoPixel(mb.pin0, 128)

pixel = [0,0,0]

while True:
    pixel[0] = 0x0F
    pixel[1] = 0x00
    pixel[2] = 0x00

    # All LEDs on
    for i in range(len(led_strip)):
        led_strip[i] = pixel
    led_strip.show()

    # Fade out
    for n in range(10):
        pixel[0] = pixel[0] * 8 // 10
        for i in range(len(led_strip)):
            led_strip[i] = pixel
        led_strip.show()
In my tests this goes from about 1050ms for the ten-step fade to about 260ms.

If you did want to support fading from an arbritrary pattern, especially if it's single-color, then you could do something like keeping an array of the 128 values, and access that instead.

i.e.

r = [0] * len(led_strip)

whenever you set a pixel, update r

then fade can work entirely on r:

Code: Select all

def fade():
    # Fade out
    for n in range(10):
        for i in range(len(led_strip)):
            r[i] = r[i] * 8 // 10
            pixel[0] = r[i]
            led_strip[i] = pixel
        led_strip.show()

Post Reply