From 2a81cf33d59e6666743b3cd40836bc4016363f3a Mon Sep 17 00:00:00 2001 From: Androo Frowns Date: Fri, 17 Jul 2026 02:45:39 -0400 Subject: [PATCH] fix(ledmatrix): prevent redundant I2C redraws during commands --- fl16-inputmodules/src/control.rs | 10 ++-- fl16-inputmodules/src/lib.rs | 2 + fl16-inputmodules/src/matrix.rs | 2 +- fl16-inputmodules/src/matrix_sync.rs | 84 ++++++++++++++++++++++++++++ fl16-inputmodules/src/patterns.rs | 58 ------------------- ledmatrix/src/main.rs | 30 +++++----- 6 files changed, 110 insertions(+), 76 deletions(-) create mode 100644 fl16-inputmodules/src/matrix_sync.rs diff --git a/fl16-inputmodules/src/control.rs b/fl16-inputmodules/src/control.rs index 42961486..2073a21d 100644 --- a/fl16-inputmodules/src/control.rs +++ b/fl16-inputmodules/src/control.rs @@ -34,6 +34,8 @@ use crate::games::snake; #[cfg(feature = "ledmatrix")] use crate::matrix::*; #[cfg(feature = "ledmatrix")] +use crate::matrix_sync::*; +#[cfg(feature = "ledmatrix")] use crate::patterns::*; #[cfg(feature = "ledmatrix")] use is31fl3741::PwmFreq; @@ -515,7 +517,7 @@ pub fn handle_generic_command(command: &Command) -> Option<[u8; 32]> { pub fn handle_command( command: &Command, state: &mut LedmatrixState, - matrix: &mut Foo, + display: &mut MatrixSync, random: u8, ) -> Option<[u8; 32]> { use crate::games::game_of_life; @@ -528,7 +530,7 @@ pub fn handle_command( } Command::SetBrightness(br) => { //let _ = serial.write("Brightness".as_bytes()); - set_brightness(state, *br, matrix); + state.brightness = *br; None } Command::Percentage(p) => { @@ -545,7 +547,7 @@ pub fn handle_command( PatternVals::ZigZag => state.grid = zigzag(), PatternVals::FullBrightness => { state.grid = percentage(100); - set_brightness(state, BRIGHTNESS_LEVELS, matrix); + state.brightness = BRIGHTNESS_LEVELS; } PatternVals::DisplayPanic => state.grid = display_panic(), PatternVals::DisplayLotus2 => state.grid = display_lotus2(), @@ -618,7 +620,7 @@ pub fn handle_command( } Command::SetPwmFreq(arg) => { state.pwm_freq = *arg; - matrix.device.set_pwm_freq(state.pwm_freq.into()).unwrap(); + display.set_pwm_freq(state.pwm_freq); None } Command::GetPwmFreq => { diff --git a/fl16-inputmodules/src/lib.rs b/fl16-inputmodules/src/lib.rs index df107681..84014410 100644 --- a/fl16-inputmodules/src/lib.rs +++ b/fl16-inputmodules/src/lib.rs @@ -22,6 +22,8 @@ pub mod animations; #[cfg(feature = "ledmatrix")] pub mod matrix; #[cfg(feature = "ledmatrix")] +pub mod matrix_sync; +#[cfg(feature = "ledmatrix")] pub mod patterns; #[cfg(feature = "b1display")] diff --git a/fl16-inputmodules/src/matrix.rs b/fl16-inputmodules/src/matrix.rs index 71a625fd..f775b3e4 100644 --- a/fl16-inputmodules/src/matrix.rs +++ b/fl16-inputmodules/src/matrix.rs @@ -8,7 +8,7 @@ pub const WIDTH: usize = 9; pub const HEIGHT: usize = 34; pub const LEDS: usize = WIDTH * HEIGHT; -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct Grid(pub [[u8; HEIGHT]; WIDTH]); impl Default for Grid { fn default() -> Self { diff --git a/fl16-inputmodules/src/matrix_sync.rs b/fl16-inputmodules/src/matrix_sync.rs new file mode 100644 index 00000000..307bf284 --- /dev/null +++ b/fl16-inputmodules/src/matrix_sync.rs @@ -0,0 +1,84 @@ +use crate::control::PwmFreqArg; +use crate::led_hal as bsp; +use crate::matrix::{Grid, LedmatrixState, HEIGHT, WIDTH}; +use crate::patterns::BRIGHTNESS_LEVELS; +use is31fl3741::devices::LedMatrix; +use rp2040_hal::{ + gpio::{ + bank0::{Gpio26, Gpio27}, + FunctionI2C, PullUp, + }, + pac::I2C1, +}; + +pub type Foo = LedMatrix< + bsp::hal::I2C< + I2C1, + ( + bsp::hal::gpio::Pin, + bsp::hal::gpio::Pin, + ), + >, +>; + +pub struct MatrixSync { + matrix: Foo, + sent_grid: Grid, + sent_brightness: u8, +} + +impl MatrixSync { + pub fn new(matrix: Foo) -> Self { + Self { + matrix, + sent_grid: Grid::default(), + sent_brightness: 0, + } + } + + /// Fills the grid pixels but only sends the I2C commands if we expect it to update displayed state + pub fn flush_if_dirty(&mut self, state: &LedmatrixState) { + if state.grid != self.sent_grid || state.brightness != self.sent_brightness { + fill_grid_pixels(state, &mut self.matrix); + self.sent_grid = state.grid.clone(); + self.sent_brightness = state.brightness; + } + } + + pub fn set_pwm_freq(&mut self, freq: PwmFreqArg) { + self.matrix.device.set_pwm_freq(freq.into()).unwrap(); + } +} + +/// Just sends two I2C commands for the entire grid +fn fill_grid_pixels(state: &LedmatrixState, matrix: &mut Foo) { + // 0xB4 LEDs on the first page, 0xAB on the second page + let mut brightnesses = [0x00; 0xB4 + 0xAB]; + for y in 0..HEIGHT { + for x in 0..WIDTH { + let (register, page) = (matrix.device.calc_pixel)(x as u8, y as u8); + brightnesses[(page as usize) * 0xB4 + (register as usize)] = + ((state.grid.0[x][y] as u64) * (state.brightness as u64) + / (BRIGHTNESS_LEVELS as u64)) as u8; + } + } + matrix.device.fill_matrix(&brightnesses).unwrap(); +} + +/// Same as fill_grid_pixels but does each pixel individually +/// So it's much slower because it has to send 306 I2C commands +pub fn _fill_grid(grid: &Grid, matrix: &mut Foo) { + for y in 0..HEIGHT { + for x in 0..WIDTH { + matrix.device.pixel(x as u8, y as u8, grid.0[x][y]).unwrap(); + } + } +} + +pub fn full_brightness(matrix: &mut Foo) { + // Fills every pixel individually + //matrix.fill_brightness(0xFF).unwrap(); + + // Fills full page at once + matrix.device.fill(0xFF).unwrap(); +} diff --git a/fl16-inputmodules/src/patterns.rs b/fl16-inputmodules/src/patterns.rs index e8ff33d6..09bc04a7 100644 --- a/fl16-inputmodules/src/patterns.rs +++ b/fl16-inputmodules/src/patterns.rs @@ -1,15 +1,5 @@ -use rp2040_hal::{ - gpio::{ - bank0::{Gpio26, Gpio27}, - FunctionI2C, PullUp, - }, - pac::I2C1, -}; - -use crate::led_hal as bsp; use crate::mapping::*; use crate::matrix::*; -use is31fl3741::devices::LedMatrix; /// Bytes needed to represent all LEDs with a single bit /// math.ceil(WIDTH * HEIGHT / 8) @@ -18,16 +8,6 @@ pub const DRAW_BYTES: usize = 39; /// Maximum number of brightneses levels pub const BRIGHTNESS_LEVELS: u8 = 255; -pub type Foo = LedMatrix< - bsp::hal::I2C< - I2C1, - ( - bsp::hal::gpio::Pin, - bsp::hal::gpio::Pin, - ), - >, ->; - pub fn draw(bytes: &[u8; DRAW_BYTES]) -> Grid { let mut grid = Grid::default(); @@ -308,44 +288,6 @@ pub fn double_gradient() -> Grid { grid } -/// Same as fill_grid_pixels but does each pixel individually -/// So it's much slower because it has to send 306 I2C commands -pub fn _fill_grid(grid: &Grid, matrix: &mut Foo) { - for y in 0..HEIGHT { - for x in 0..WIDTH { - matrix.device.pixel(x as u8, y as u8, grid.0[x][y]).unwrap(); - } - } -} - -pub fn set_brightness(state: &mut LedmatrixState, brightness: u8, matrix: &mut Foo) { - state.brightness = brightness; - fill_grid_pixels(state, matrix); -} - -/// Just sends two I2C commands for the entire grid -pub fn fill_grid_pixels(state: &LedmatrixState, matrix: &mut Foo) { - // 0xB4 LEDs on the first page, 0xAB on the second page - let mut brightnesses = [0x00; 0xB4 + 0xAB]; - for y in 0..HEIGHT { - for x in 0..WIDTH { - let (register, page) = (matrix.device.calc_pixel)(x as u8, y as u8); - brightnesses[(page as usize) * 0xB4 + (register as usize)] = - ((state.grid.0[x][y] as u64) * (state.brightness as u64) - / (BRIGHTNESS_LEVELS as u64)) as u8; - } - } - matrix.device.fill_matrix(&brightnesses).unwrap(); -} - -pub fn full_brightness(matrix: &mut Foo) { - // Fills every pixel individually - //matrix.fill_brightness(0xFF).unwrap(); - - // Fills full page at once - matrix.device.fill(0xFF).unwrap(); -} - pub fn zigzag() -> Grid { let mut grid = Grid::default(); diff --git a/ledmatrix/src/main.rs b/ledmatrix/src/main.rs index 9337f053..ed9c3156 100644 --- a/ledmatrix/src/main.rs +++ b/ledmatrix/src/main.rs @@ -146,6 +146,7 @@ use heapless::String; use fl16_inputmodules::control::*; use fl16_inputmodules::games::{pong, snake}; use fl16_inputmodules::matrix::*; +use fl16_inputmodules::matrix_sync::*; use fl16_inputmodules::patterns::*; use fl16_inputmodules::serialnum::{device_release, get_serialnum}; @@ -298,7 +299,8 @@ fn main() -> ! { matrix.device.set_pwm_freq(state.pwm_freq.into()).unwrap(); - fill_grid_pixels(&state, &mut matrix); + let mut display = MatrixSync::new(matrix); + display.flush_if_dirty(&state); let mut animation_timer = timer.get_counter().ticks(); let mut game_timer = timer.get_counter().ticks(); @@ -385,7 +387,7 @@ fn main() -> ! { handle_sleep( sleep_reason, &mut state, - &mut matrix, + &mut display, &mut delay, &mut led_enable, ); @@ -402,7 +404,7 @@ fn main() -> ! { } } - fill_grid_pixels(&state, &mut matrix); + display.flush_if_dirty(&state); if state.animate { for x in 0..WIDTH { state.grid.0[x].rotate_right(1); @@ -445,7 +447,7 @@ fn main() -> ! { // Handle bootloader command without any delay // No need, it'll reset the device anyways (Some(c @ Command::BootloaderReset), _) => { - handle_command(&c, &mut state, &mut matrix, random); + handle_command(&c, &mut state, &mut display, random); } (Some(command), _) => { if let Command::Sleep(go_sleeping) = command { @@ -473,7 +475,7 @@ fn main() -> ! { handle_sleep( sleep_reason, &mut state, - &mut matrix, + &mut display, &mut delay, &mut led_enable, ); @@ -486,7 +488,7 @@ fn main() -> ! { sleep_timer = timer.get_counter().ticks(); if let Some(response) = - handle_command(&command, &mut state, &mut matrix, random) + handle_command(&command, &mut state, &mut display, random) { let _ = serial.write(&response); }; @@ -501,7 +503,7 @@ fn main() -> ! { .unwrap(); // let _ = serial.write(text.as_bytes()); - fill_grid_pixels(&state, &mut matrix); + display.flush_if_dirty(&state); } (None, _) => {} } @@ -613,7 +615,7 @@ fn assign_sleep_reason( fn handle_sleep( sleep_reason: Option, state: &mut LedmatrixState, - matrix: &mut Foo, + display: &mut MatrixSync, delay: &mut Delay, led_enable: &mut gpio::Pin, ) { @@ -628,7 +630,8 @@ fn handle_sleep( loop { delay.delay_ms(100); brightness = brightness.saturating_sub(5); - set_brightness(state, brightness, matrix); + state.brightness = brightness; + display.flush_if_dirty(state); if brightness == 0 { break; } @@ -637,7 +640,7 @@ fn handle_sleep( if debug_mode(state) { state.grid = display_sleep_reason(sleep_reason); - fill_grid_pixels(state, matrix); + display.flush_if_dirty(state); } else { // Turn LED controller off to save power led_enable.set_low().unwrap(); @@ -651,7 +654,7 @@ fn handle_sleep( // If debug mode is enabled, then make sure the latest sleep reason is displayed if debug_mode(state) { state.grid = display_sleep_reason(sleep_reason); - fill_grid_pixels(state, matrix); + display.flush_if_dirty(state); } } // Sleeping and need to wake up @@ -659,7 +662,7 @@ fn handle_sleep( // Restore back grid before sleeping state.sleeping = SleepState::Awake; state.grid = old_grid; - fill_grid_pixels(state, matrix); + display.flush_if_dirty(state); // Power LED controller back on if !debug_mode(state) { @@ -676,7 +679,8 @@ fn handle_sleep( } else { brightness + 5 }; - set_brightness(state, brightness, matrix); + state.brightness = brightness; + display.flush_if_dirty(state); if brightness == old_brightness { break; }