KMKfw / kmk_firmware

Clackety Keyboards Powered by Python
https://kmkfw.zulipchat.com
Other
1.45k stars 485 forks source link

Keybow 2040: Speed up RGB. #982

Closed Gadgetoid closed 5 months ago

Gadgetoid commented 5 months ago

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.

Gadgetoid commented 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.

Gadgetoid commented 5 months ago

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!

xs5871 commented 5 months ago

No need to subclass:

Parameters: [...] mapping (Tuple[int, ...]) – mapping of matrix locations to LEDs

Gadgetoid commented 5 months ago

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!

Gadgetoid commented 5 months ago

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.