Skip to content

Commit

Permalink
Keybow 2040: drop double-buffering, simplify buffer output.
Browse files Browse the repository at this point in the history
Remove hot path conversion to bytes and drop double-buffering which had no visible impact.

Reduce the size of the output buffer to remove leading 17 and trailing 4 bytes, and prime
the buffer with the first LED address.

Update time is now ~4ms and much more stable.

Signed-off-by: Phil Howard <github@gadgetoid.com>
  • Loading branch information
Gadgetoid committed Jun 11, 2024
1 parent 281b027 commit 306d123
Showing 1 changed file with 32 additions and 37 deletions.
69 changes: 32 additions & 37 deletions boards/pimoroni/keybow_2040/keybow_2040_rgb.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from adafruit_bus_device.i2c_device import I2CDevice
from adafruit_pixelbuf import PixelBuf

_FRAME_REGISTER = const(0x01)
_SHUTDOWN_REGISTER = const(0x0A)
_CONFIG_BANK = const(0x0B)
_BANK_ADDRESS = const(0xFD)
Expand All @@ -19,43 +18,49 @@ class Keybow2040Leds(PixelBuf):

width = 16
height = 3

# Pixel addresses starting from LED 17,
# then offset by 1 to fit register addr
_pixel_addr = [
(120, 88, 104), # 0, 0
(136, 40, 72), # 1, 0
(112, 80, 96), # 2, 0
(128, 32, 64), # 3, 0
(121, 89, 105), # 0, 1
(137, 41, 73), # 1, 1
(113, 81, 97), # 2, 1
(129, 33, 65), # 3, 1
(122, 90, 106), # 0, 2
(138, 25, 74), # 1, 2
(114, 82, 98), # 2, 2
(130, 17, 66), # 3, 2
(123, 91, 107), # 0, 3
(139, 26, 75), # 1, 3
(115, 83, 99), # 2, 3
(131, 18, 67), # 3, 3
(104, 72, 88), # 0, 0
(120, 24, 56), # 1, 0
(96, 64, 80), # 2, 0
(112, 16, 48), # 3, 0
(105, 73, 89), # 0, 1
(121, 25, 57), # 1, 1
(97, 65, 81), # 2, 1
(113, 17, 49), # 3, 1
(106, 74, 90), # 0, 2
(122, 9, 58), # 1, 2
(98, 66, 82), # 2, 2
(114, 1, 50), # 3, 2
(107, 75, 91), # 0, 3
(123, 10, 59), # 1, 3
(99, 67, 83), # 2, 3
(115, 2, 51), # 3, 3
]

def __init__(self, size: int = 16): # Kept for backward compatibility
def __init__(self, size: int = 16): # size kept for backward compatibility
self.i2c = busio.I2C(board.SCL, board.SDA, frequency=400_000)
self.i2c_device = I2CDevice(self.i2c, 0x74)
self.out_buffer = bytearray(144)
self.out_buffer = bytearray(124)
self._pixels = 16
self._frame = 0
super().__init__(self._pixels, byteorder='RGB')

# First byte of buffer is our first LED address
self.out_buffer[0] = _COLOR_OFFSET + 17

with self.i2c_device as i2c:
for frame in range(2):
i2c.write(bytes([_BANK_ADDRESS, frame]))
i2c.write(bytes([_ENABLE_OFFSET] + [0xFF] * 18)) # Enable all LEDs
i2c.write(bytes([_COLOR_OFFSET] + [0x00] * 144)) # Init all to 0
i2c.write(bytes([_BANK_ADDRESS, 0]))
i2c.write(bytes([_ENABLE_OFFSET] + [0xFF] * 18)) # Enable all LEDs
i2c.write(bytes([_COLOR_OFFSET] + [0x00] * 144)) # Init all to 0

i2c.write(bytes([_BANK_ADDRESS, _CONFIG_BANK]))
i2c.write(bytes([0x00] * 14)) # Clear Mode, Frame, ... etc
i2c.write(bytes([_SHUTDOWN_REGISTER, 0x01])) # 0 == shutdown, 1 == normal

i2c.write(bytes([_BANK_ADDRESS, 0]))

def _transmit(self, buffer):
# Bring these into local scope for a tiny perf improvement ~.7ms
pixel_addr = self._pixel_addr
Expand All @@ -69,16 +74,6 @@ def _transmit(self, buffer):
out[b] = buffer[x * 3 + 2]

with self.i2c_device as i2c:
# Switch to our new (not currently visible) frame
i2c.write(bytes([_BANK_ADDRESS, self._frame]))

# We only actually use 16 * 3 = 48 LEDs out of the 144 total
# but at 400KHz I2C it's cheaper just to write the whole lot
i2c.write(bytes([_COLOR_OFFSET + 17]) + self.out_buffer[17:140])

# Set the newly written frame as the visible one
i2c.write(bytes([_BANK_ADDRESS, _CONFIG_BANK]))
i2c.write(bytes([_FRAME_REGISTER, self._frame]))

# Switch to our other buffer
self._frame = not self._frame
# Write our 124 byte buffer
# byte 0 is prefilled with the register address
i2c.write(self.out_buffer)

0 comments on commit 306d123

Please sign in to comment.