micropython-lib icon indicating copy to clipboard operation
micropython-lib copied to clipboard

drivers/led/neopixel: Add brightness control.

Open tmountjr opened this issue 1 year ago • 15 comments

New functionality to specify a brightness for the strip when creating the instance, and also via set_brightness. Also refactored the fill() method to avoid duplicating the logic used in __setitem__. Existing functionality is that the write() method must be called to see the changes, so while set_brightness does change the values for all the pixels in the instance, it does not write that back out to the strip immediately.

This should be completely backwards-compatible with the previous version in that the brightness is by default 100%.

tmountjr avatar Sep 29 '23 13:09 tmountjr

Do all implementations now have floating point enabled? (#define MICROPY_FLOAT_IMPL MICROPY_FLOAT_IMPL_FLOAT )

ricksorensen avatar Sep 29 '23 15:09 ricksorensen

I had just assumed that float was a built-in type. It seems to be referenced that way here - https://docs.micropython.org/en/latest/genrst/builtin_types.html.

If that's not the case, what's the process for handling floating point numbers on systems that don't have that type?

tmountjr avatar Sep 29 '23 18:09 tmountjr

I am not sure how to check. Look for a type exception maybe?. As micropython has matured, more boards now have at least software floating point which can be optionally turned off to save flash space, but I think are a couple of older boards that do not enable floating point by default CC3200, powerpc, pic16bit, nrf (NRF51) The designers would be able to offer better guidance on the direction here. Note that some display drivers require floating point also - for rotations and positioning- and may offer some guidance on how to handle missing floating point type.

On Fri, Sep 29, 2023 at 1:27 PM Tom Mount @.***> wrote:

I had just assumed that float was a built-in type. It seems to be referenced that way here - https://docs.micropython.org/en/latest/genrst/builtin_types.html.

If that's not the case, what's the process for handling floating point numbers on systems that don't have that type?

— Reply to this email directly, view it on GitHub https://github.com/micropython/micropython-lib/pull/739#issuecomment-1741320244, or unsubscribe https://github.com/notifications/unsubscribe-auth/AA5QPQD5PVRDOIMEYRS5WXLX44HKTANCNFSM6AAAAAA5MQEXV4 . You are receiving this because you commented.Message ID: @.***>

-- Rick Sorensen @.*** 651-470-5511

ricksorensen avatar Sep 29 '23 20:09 ricksorensen

Thanks @tmountjr

@ricksorensen is correct that not all boards have floating point enabled. The bigger issue though is that using floating point is very expensive in MicroPython because floats get heap-allocated so you want to absolutely avoid using them unless necessary.

For example, on a PYBV11, the following line:

n.fill((255,0,0,0))

takes 2058 microseconds before this PR, but now takes 24686 microseconds.

But I think this can probably be almost entirely addressed:

  • Make brightness optional (and None if not set).
  • Only calculate the adjusted tuple once (right now it's doing it for every pixel).
  • Keep fill as it was (implementing it in terms of __setitem__ is slow).

See e.g. https://gist.github.com/jimmo/472bcf9213bae97fc915fd71d85048b9 which is back to ~2048 microseconds when brightness is not set and 2238 microseconds when set to 0.5.

I'm also not sure about the behavior of __getitem__ though. It's a bit weird that if you set a pixel and read it back, you get the adjusted value? I realise it's difficult to make this work any other way though.

jimmo avatar Oct 04 '23 05:10 jimmo

Good idea only conditionally applying brightness if it's already set. Also didn't consider that I'd be recalculating the values for every single pixel if I simplified the fill() method.

The use of __getitem__ in the set_brightness method was a bit of a shot in the dark for me too - I basically thought of it from a high level - when you set the brightness, you need to iterate over each pixel and recalculate what the new tuple should be based on the new brightness. __getitem__ already handles getting the right component value for each "pixel" in the bytearray, so the left half takes care of itself; and __setitem__ handles calculating the brightness as a function of setting the component values, so that's the right half too. I agree though that self[i] = self[i] seems kinda redundant but unless there are performance concerns with using the under methods it may qualify as elegant. 😄 It could use some documentation though, if only to acknowledge the fact that it looks really odd.

tmountjr avatar Oct 04 '23 13:10 tmountjr

@jimmo Thanks for all your feedback! You seem to have access to some hardware that I don't that catches corner cases...would you be able to take a look at the latest version of this PR on that hardware and let me know if the performance is where it should be?

tmountjr avatar Oct 04 '23 14:10 tmountjr

You seem to have access to some hardware

Nope just any board running MicroPython:

t = time.ticks_us()
... do some code ...
print(time.ticks_diff(time.ticks_us(), t))

catches corner cases

Nothing special going on here, was just calling fill() for neopixel objects with and without the brightness set.

It would be also worth testing code that sets pixels directly (which I imagine is more representative of what people typically do with neopixels).


I'm on the fence about this... I agree it's a useful feature -- it's nice to be able to e.g. fade in/out a strip or just separate your pixel generating code from brightness control. And it's good that it doesn't affect performance significantly if you don't use it (although set pixel will now be slower because it has to check self.brightness). I am still not super excited about the fact that set and get pixel are no longer symmetric (but again I don't have a better suggestion... so maybe it's a reasonable compromise).

The other thing to think about is code size increase. I spent a bit of time trying to minimise neopixel.py as much as possible (which is why the style is a bit weird). neopixel.mpy is currently 616 bytes. This PR increases it to 925. I think we can get it down to 819 without any functional changes. I'll add some comments to the PR to explain.

jimmo avatar Oct 05 '23 02:10 jimmo

Did some tests on my Pico W...

  • fill() time with the changes: 658us without a brightness, 1093us with a brightness
  • changing brightness with brightness(): 4294us <-- that felt a little concerning to me so I'm going to refactor and post the "after" (it's way faster now)

tmountjr avatar Oct 06 '23 15:10 tmountjr

Okay, the refactor of brightness() - I figured that since we didn't really care if there were three or four channels per pixel (we're changing all the pixels, which means all the channels of each pixel), we could just go over the buffer byte by byte and calculate the new value for whatever channel of whatever pixel we happened to be on for that iteration. Cut it from 4000 ticks to 1000 (ish).

Original Timing New Timing
Startup 517 756
fill() 556 898 (brightness=1)
627 (brightness=None)
brightness(0.25) n/a 1093

tmountjr avatar Oct 06 '23 15:10 tmountjr

(See related https://github.com/micropython/micropython/pull/3623 and https://github.com/micropython/micropython/pull/4815)

dpgeorge avatar Oct 09 '23 05:10 dpgeorge

What are the next steps with this PR? I see the two closed PRs on the main micropython repo but I'm not clear if I need to make any changes in light of those. Thanks!

tmountjr avatar Oct 26 '23 13:10 tmountjr

Hi, I don't understand something... I have just add a brightness function to your lib, and I have only 173 us for a fill() and a write() without brightness function, and 256 with brightness function, on a pyboard v1.1... On a feather M4, I have 249 us without brightness function, and 283 with it. On other way, I have problems to use strip, which contains severals neopixels, with the classical ws2812 lib (spi) too... Electrical problems I think... Your lib with the brightness function is therehttps://www.cosmocratie.fr/data.html

IHOXOHI avatar Oct 31 '23 12:10 IHOXOHI

@IHOXOHI any chance you can put that code in a gist for me? the link you provided looked a little sketchy.

Is your question why the timing is different? I'm not sure I know what you're asking for help with.

tmountjr avatar Nov 13 '23 15:11 tmountjr

Just commenting that I went looking for this functionality today only to wind up at this PR. It isn't too much of a lift to simply implement equivalent functionality for myself for the time being, but I would love to see this PR eventually get merged.

dlareau avatar Jan 30 '24 19:01 dlareau

Ok, I will post It on GitHub... M'y first. M'y question is about the color which depends of a frequency, interfere with the frequency which controls each place of each neopixel... I presume. .. Is there anybody who succeeded to use a big strip of bigs neopixels? More than 5...

The lib modified: github.com/IHOXOHI/neopixel-brightness-function/blob/main/README.md

IHOXOHI avatar Jan 31 '24 09:01 IHOXOHI