Closed Gadgetoid closed 5 months ago
Note: All my claimed "FPS" measurements don't include the processing time for the RGB effect, but the transaction time gains are significant enough for this not to matter.
Before this change I counted roughly 6 I2C transactions per pixel, for a total of 96 at 100KHz, giving a 60-80ms transaction time, for an absolute best-case 16FPS.
After this change, it's three I2C transactions - one to set the bank to the current frame, one to transmit the data and a final one to set the new frame (we're flipping back and forth between frame 0 and frame 1) to be visible. This takes a total of ~6ms- or a 10x speedup.
I can drive my Keybow 2040 up to ~650KHz for ~200 updates/second, though I'm not going to push a change to do that based on a sample size of 1 😆
I've split this into multiple commits to hopefully - somewhat - document my reasoning.
I have opted not to delete is31fl3731_pixelbuf.py
since it still works, but it's effectively redundant. Let me know if I should do that.
Did you also try the is31fl3731 driver that is built into CP?
Interesting, I did not know about this! Is it possible to subclass so I can handle all the weird pixel ordering? 🤔 though I guess a wrapper won’t hurt. I’d expect this to be a little faster so it’s definitely worth a try!
No need to subclass:
Parameters: [...] mapping
(Tuple[int, ...])
– mapping of matrix locations to LEDs
Haha I had not read up, since I was on my phone. Looking into it now! Thank you.
Edit: Ah it's only available in the "Adafruit LED Glasses Driver nRF52840" build, though that doesn't preclude it from being added to Keybow 2040 for this very purpose. I'll look into building a firmware and see if it's worth the effort- if it is I'll catch ImportError
and fall back to my Python version.
Edit 2: Okay I have had absolutely no luck getting it up and running. Managed to built it into a custom CircuitPython build, but looking over the code it doesn't seem to enable the LEDs or quite do what I'm after. A shame!
Okay, despite failing to get anywhere with the C driver I've dramatically simplified the hot path and removed the double-buffering which should be doing effectively nothing (if the i2c transfer takes <4ms we're not going to see any tearing anyway).
This gets us to a very stable < 4ms conversion and transfer time for a theoretical (static lighting) 250FPS. According to my very technical "using a stopwatch on my phone" tests we're now getting roughly 60FPS in the RGB fade effect.
Changing from i2c.write(bytes([_COLOR_OFFSET + 17]) + self.out_buffer[17:140])
to i2c.write(out)
using some classic tricks (priming the buffer with the correct address, and sizing it correctly) also dramatically reduces the variation I was seeing in the update times bringing it to ~.1ms deviation where previously I was seeing up to 1ms.
I also refactored out the multiply and then rewrote the result for clarity and brevity by iterating the address lookup and unpacking it to o, r, g, b
.
I think I need to stop optimising this now... 😆 I've practically optimised out all the code.
Replace slow RGB wrapper for is31fl3731 driver with a custom implementation which double-buffers with two frames and writes pixels in batches (or all 144 in bulk).
Speeds up default RGB breath effect from ~12FPS to ~60FPS.
Fixes #859.