From 967d544bbc3143bd69727edee4ed5bea1a854e70 Mon Sep 17 00:00:00 2001 From: Christoffer Martinsson Date: Sat, 1 Jul 2023 16:28:56 +0200 Subject: [PATCH] Code cleanup --- rp2040/src/button_matrix.rs | 82 ++++++------- rp2040/src/main.rs | 223 ++++++++++++++++++++---------------- rp2040/src/status_led.rs | 20 ++++ 3 files changed, 179 insertions(+), 146 deletions(-) diff --git a/rp2040/src/button_matrix.rs b/rp2040/src/button_matrix.rs index ff1f021..715911d 100644 --- a/rp2040/src/button_matrix.rs +++ b/rp2040/src/button_matrix.rs @@ -2,47 +2,63 @@ use core::convert::Infallible; use cortex_m::delay::Delay; use embedded_hal::digital::v2::*; -pub const DEBOUNCE_COUNT: u8 = 5; - +/// Button matrix driver +/// +/// # Example +/// ``` +/// let button_matrix: ButtonMatrix<4, 6, 48> = ButtonMatrix::new(row_pins, col_pins, 5); pub struct ButtonMatrix<'a, const R: usize, const C: usize, const N: usize> { rows: &'a [&'a dyn InputPin; R], cols: &'a mut [&'a mut dyn OutputPin; C], - delay: &'a mut Delay, - state: [bool; N], - state_raw: [bool; N], + pressed: [bool; N], + debounce: u8, debounce_counter: [u8; N], } impl<'a, const R: usize, const C: usize, const N: usize> ButtonMatrix<'a, R, C, N> { + /// Creates a new button matrix. + /// + /// # Arguments + /// + /// * `rows` - An array of references to the row pins. + /// * `cols` - An array of references to the column pins. + /// * `debounce` - The debounce time in number of scans. pub fn new( rows: &'a [&'a dyn InputPin; R], cols: &'a mut [&'a mut dyn OutputPin; C], - delay: &'a mut Delay, + debounce: u8, ) -> Self { Self { rows, cols, - delay, - state: [false; N], - state_raw: [false; N], + pressed: [false; N], + debounce, debounce_counter: [0; N], } } + /// Initializes the button matrix. + /// This should be called once before scanning the matrix. pub fn init_pins(&mut self) { for col in self.cols.iter_mut() { col.set_high().unwrap(); } - self.delay.delay_us(10); } - pub fn scan_matrix(&mut self) { + /// Scans the button matrix and updates the pressed state of each button. + /// This should be called at regular intervals. + /// Allow at least 5 times the delay compared to the needed button latency. + /// + /// # Arguments + /// + /// * `delay` - A mutable reference to a delay object. + pub fn scan_matrix(&mut self, delay: &mut Delay) { for col_index in 0..self.cols.len() { self.cols[col_index].set_low().unwrap(); - self.delay.delay_us(10); + delay.delay_us(10); self.process_column(col_index); self.cols[col_index].set_high().unwrap(); - self.delay.delay_us(10); + delay.delay_us(10); } } @@ -50,49 +66,21 @@ impl<'a, const R: usize, const C: usize, const N: usize> ButtonMatrix<'a, R, C, for row_index in 0..self.rows.len() { let button_index: usize = col_index + (row_index * C); let current_state = self.rows[row_index].is_low().unwrap(); - self.state_raw[button_index] = current_state; - if current_state == self.state[button_index] { + if current_state == self.pressed[button_index] { self.debounce_counter[button_index] = 0; continue; } self.debounce_counter[button_index] += 1; - if self.debounce_counter[button_index] >= DEBOUNCE_COUNT { - self.state[button_index] = current_state; + if self.debounce_counter[button_index] >= self.debounce { + self.pressed[button_index] = current_state; } } } - // pub fn scan_matrix(&mut self) { - // for (col_index, col) in self.cols.iter_mut().enumerate() { - // col.set_low().unwrap(); - // self.delay.delay_us(10); - // - // for (row_index, row) in self.rows.iter().enumerate() { - // let button_index: usize = col_index + (row_index * C); - // let current_state = row.is_low().unwrap(); - // self.state_raw[button_index] = current_state; - // - // if current_state == self.state[button_index] { - // self.debounce_counter[button_index] = 0; - // } else { - // self.debounce_counter[button_index] += 1; - // if self.debounce_counter[button_index] >= DEBOUNCE_COUNT { - // self.state[button_index] = current_state; - // } - // } - // } - // col.set_high().unwrap(); - // self.delay.delay_us(10); - // } - // } - - pub fn get_button_state(&mut self) -> [bool; N] { - self.state - } - - pub fn get_button_state_raw(&mut self) -> [bool; N] { - self.state_raw + /// Returns an array of booleans indicating whether each button is pressed. + pub fn buttons_pressed(&mut self) -> [bool; N] { + self.pressed } } diff --git a/rp2040/src/main.rs b/rp2040/src/main.rs index 7d8c829..a2d2cca 100644 --- a/rp2040/src/main.rs +++ b/rp2040/src/main.rs @@ -12,6 +12,7 @@ mod status_led; use crate::status_led::{StatusMode, Ws2812StatusLed}; use button_matrix::ButtonMatrix; use core::convert::Infallible; +use cortex_m::delay::Delay; use embedded_hal::digital::v2::*; use embedded_hal::timer::CountDown; use fugit::ExtU32; @@ -37,33 +38,26 @@ use waveshare_rp2040_zero::{ Pins, XOSC_CRYSTAL_FREQ, }; +// Public constants pub const KEY_ROWS: usize = 4; pub const KEY_COLS: usize = 12; pub const NUMBER_OF_KEYS: usize = KEY_ROWS * KEY_COLS; -#[derive(Copy, Clone)] +// Public types +#[derive(Copy, Clone, Default)] pub struct KeyboardButton { - pub current_state: bool, - pub previous_state: bool, + pub pressed: bool, + pub previous_pressed: bool, pub fn_mode: u8, } -impl KeyboardButton { - pub fn default() -> Self { - Self { - current_state: false, - previous_state: false, - fn_mode: 0, - } - } -} - #[entry] fn main() -> ! { let mut pac = pac::Peripherals::take().unwrap(); let core = pac::CorePeripherals::take().unwrap(); let mut watchdog = Watchdog::new(pac.WATCHDOG); + // Configure clocks and PLLs let clocks = init_clocks_and_plls( XOSC_CRYSTAL_FREQ, pac.XOSC, @@ -76,6 +70,7 @@ fn main() -> ! { .ok() .unwrap(); + // Configure GPIOs let sio = Sio::new(pac.SIO); let pins = Pins::new( pac.IO_BANK0, @@ -84,38 +79,7 @@ fn main() -> ! { &mut pac.RESETS, ); - let usb_bus = UsbBusAllocator::new(waveshare_rp2040_zero::hal::usb::UsbBus::new( - pac.USBCTRL_REGS, - pac.USBCTRL_DPRAM, - clocks.usb_clock, - true, - &mut pac.RESETS, - )); - - let mut keyboard = UsbHidClassBuilder::new() - .add_device( - usbd_human_interface_device::device::keyboard::NKROBootKeyboardConfig::default(), - ) - .build(&usb_bus); - - let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x1209, 0x0001)) - .manufacturer("usbd-human-interface-device") - .product("CMDR keyboard") - .serial_number("0001") - .build(); - - let timer = Timer::new(pac.TIMER, &mut pac.RESETS); - let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); - - // Configure the status LED - let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); - let mut status_led = Ws2812StatusLed::new( - pins.neopixel.into_mode(), - &mut pio, - sm0, - clocks.peripheral_clock.freq(), - ); - + // Create button matrix let button_matrix_row_pins: &[&dyn InputPin; KEY_ROWS] = &[ &pins.gp0.into_pull_up_input(), &pins.gp1.into_pull_up_input(), @@ -139,28 +103,68 @@ fn main() -> ! { ]; let mut button_matrix: ButtonMatrix = - ButtonMatrix::new(button_matrix_row_pins, button_matrix_col_pins, &mut delay); + ButtonMatrix::new(button_matrix_row_pins, button_matrix_col_pins, 5); + // Configure USB + let usb_bus = UsbBusAllocator::new(waveshare_rp2040_zero::hal::usb::UsbBus::new( + pac.USBCTRL_REGS, + pac.USBCTRL_DPRAM, + clocks.usb_clock, + true, + &mut pac.RESETS, + )); + + let mut keyboard = UsbHidClassBuilder::new() + .add_device( + usbd_human_interface_device::device::keyboard::NKROBootKeyboardConfig::default(), + ) + .build(&usb_bus); + + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x1209, 0x0001)) + .manufacturer("CMtec") + .product("CMDR keyboard") + .serial_number("0001") + .build(); + + // Create status LED + let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); + let mut status_led = Ws2812StatusLed::new( + pins.neopixel.into_mode(), + &mut pio, + sm0, + clocks.peripheral_clock.freq(), + ); + + // Create keyboard button array let mut buttons: [KeyboardButton; NUMBER_OF_KEYS] = [KeyboardButton::default(); NUMBER_OF_KEYS]; - let mut report_count_down = timer.count_down(); - report_count_down.start(10.millis()); + // Create timers/delays + let timer = Timer::new(pac.TIMER, &mut pac.RESETS); + let mut delay = Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); - let mut tick_count_down = timer.count_down(); - tick_count_down.start(1.millis()); + let mut usb_hid_report_count_down = timer.count_down(); + usb_hid_report_count_down.start(10.millis()); - let mut indicator_count_down = timer.count_down(); - indicator_count_down.start(250.millis()); + let mut usb_tick_count_down = timer.count_down(); + usb_tick_count_down.start(1.millis()); + let mut status_led_count_down = timer.count_down(); + status_led_count_down.start(250.millis()); + + // Create variables to track caps lock and fn mode let mut caps_lock_active: bool = false; let mut fn_mode: u8 = 0; - // Set all column pins to output and high + // Initialize button matrix button_matrix.init_pins(); - // Check if esc pressed while power on. If yes then enter bootloader - button_matrix.scan_matrix(); - if button_matrix.get_button_state_raw()[0] == true { + // Scan matrix to get initial state + for _ in 0..10 { + button_matrix.scan_matrix(&mut delay); + } + + // Check if esc key is pressed while power on. If yes then enter bootloader + if button_matrix.buttons_pressed()[0] { status_led.update(StatusMode::BOOTLOADER); let gpio_activity_pin_mask = 0; let disable_interface_mask = 0; @@ -168,47 +172,43 @@ fn main() -> ! { } loop { - if indicator_count_down.wait().is_ok() { + if status_led_count_down.wait().is_ok() { update_status_led(&mut status_led, fn_mode, caps_lock_active); } - if report_count_down.wait().is_ok() { - // Scan keyboard matrix - let pressed_keys = button_matrix.get_button_state(); + if usb_hid_report_count_down.wait().is_ok() { + let pressed_keys = button_matrix.buttons_pressed(); - // Get current function layer fn_mode = get_fn_mode(pressed_keys); if caps_lock_active == false { update_status_led(&mut status_led, fn_mode, caps_lock_active); } - - // Copy result from scanned keys to matrix struct for (index, key) in pressed_keys.iter().enumerate() { - buttons[index].current_state = *key; + buttons[index].pressed = *key; } - // Generate keyboard report - let keyboard_report = get_keyboard_report(&mut buttons, layout::MAP, fn_mode); + let keyboard_report = get_keyboard_report(&mut buttons, fn_mode); match keyboard.device().write_report(keyboard_report) { Err(UsbHidError::WouldBlock) => {} Err(UsbHidError::Duplicate) => {} Ok(_) => {} Err(e) => { + status_led.update(StatusMode::ERROR); core::panic!("Failed to write keyboard report: {:?}", e) } }; } - //Tick once per ms - if tick_count_down.wait().is_ok() { - button_matrix.scan_matrix(); + if usb_tick_count_down.wait().is_ok() { + button_matrix.scan_matrix(&mut delay); match keyboard.tick() { Err(UsbHidError::WouldBlock) => {} Ok(_) => {} Err(e) => { + status_led.update(StatusMode::ERROR); core::panic!("Failed to process keyboard tick: {:?}", e) } }; @@ -216,26 +216,33 @@ fn main() -> ! { if usb_dev.poll(&mut [&mut keyboard]) { match keyboard.device().read_report() { - Err(UsbError::WouldBlock) => { - //do nothing - } + Err(UsbError::WouldBlock) => {} Err(e) => { + status_led.update(StatusMode::ERROR); core::panic!("Failed to read keyboard report: {:?}", e) } Ok(leds) => { - if leds.caps_lock == true { - caps_lock_active = true; - } else { - caps_lock_active = false; - } + caps_lock_active = leds.caps_lock; } } } } } -// Set status LED colour based on function layer and capslock -// 0 = green, 1 = blue, 2 = orange, capslock active = flashing red. +/// Update status LED colour based on function layer and capslock +/// +/// # Arguments +/// * `status_led` - Reference to status LED +/// * `fn_mode` - Current function layer +/// * `caps_lock_active` - Is capslock active +/// +/// # Results +/// +/// Fn0 = green (OK) +/// Fn1 = blue (ACTIVE1) +/// Fn2 = orange (ACTIVE2) +/// Capslock active = flashing red (WARNING) +/// Error = steady red (ERROR) fn update_status_led( status_led: &mut Ws2812StatusLed, fn_mode: u8, @@ -246,7 +253,7 @@ fn update_status_led( Function

: ValidPinMode, SM: StateMachineIndex, { - if caps_lock_active == true { + if caps_lock_active { status_led.update(StatusMode::WARNING); } else { match fn_mode { @@ -258,41 +265,59 @@ fn update_status_led( } } -// Get current Fn mode (0, 1 or 2) +/// Get current Fn mode (0, 1 or 2) +/// layout::FN_BUTTONS contains the keycodes for each Fn key +/// +/// # Arguments +/// +/// * `pressed_keys` - Array of pressed keys +/// +/// # Results +/// +/// * Fn mode (0, 1 or 2) fn get_fn_mode(pressed_keys: [bool; NUMBER_OF_KEYS]) -> u8 { - // Check Fn mode - let mut fn_mode: u8 = 0; - for button_id in crate::layout::FN_BUTTONS.iter() { - if pressed_keys[*button_id as usize] == true { - fn_mode += 1; - } + // Check how many Fn keys are pressed + let mut active_fn_keys = layout::FN_BUTTONS + .iter() + .filter(|button_id| pressed_keys[**button_id as usize]) + .count() as u8; + + // Limit Fn mode to 2 + if active_fn_keys > 2 { + active_fn_keys = 2; } - if fn_mode > 2 { - fn_mode = 2; - } - fn_mode + active_fn_keys } -// Generate keyboard report based on pressed keys and Fn mode (0, 1 or 2) +/// Generate keyboard report based on pressed keys and Fn mode (0, 1 or 2) +/// layout::MAP contains the keycodes for each key in each Fn mode +/// +/// # Arguments +/// +/// * `matrix_keys` - Array of pressed keys +/// * `fn_mode` - Current function layer +/// +/// # Results +/// +/// * Keyboard report fn get_keyboard_report( matrix_keys: &mut [KeyboardButton; NUMBER_OF_KEYS], - layout_map: [[Keyboard; NUMBER_OF_KEYS]; 3], fn_mode: u8, ) -> [Keyboard; NUMBER_OF_KEYS] { - // Create default report let mut keyboard_report: [Keyboard; NUMBER_OF_KEYS] = [Keyboard::NoEventIndicated; NUMBER_OF_KEYS]; + // Filter report based on Fn mode and pressed keys for (index, key) in matrix_keys.iter_mut().enumerate() { - if key.current_state != key.previous_state && key.current_state == true { + if key.pressed != key.previous_pressed && key.pressed { key.fn_mode = fn_mode; } - key.previous_state = key.current_state; - if key.current_state == true { - keyboard_report[index] = layout_map[key.fn_mode as usize][index]; + key.previous_pressed = key.pressed; + + if key.pressed { + keyboard_report[index] = layout::MAP[key.fn_mode as usize][index]; } } - // Return report keyboard_report } diff --git a/rp2040/src/status_led.rs b/rp2040/src/status_led.rs index 4fff509..cf41da5 100644 --- a/rp2040/src/status_led.rs +++ b/rp2040/src/status_led.rs @@ -5,6 +5,7 @@ use rp2040_hal::{ use smart_leds::{SmartLedsWrite, RGB8}; use ws2812_pio::Ws2812Direct; +/// Status LED modes #[derive(PartialEq, Eq, Copy, Clone)] pub enum StatusMode { OFF = 0, @@ -35,6 +36,13 @@ where SM: StateMachineIndex, { /// Creates a new instance of this driver. + /// + /// # Arguments + /// + /// * `pin` - PIO pin + /// * `pio` - PIO instance + /// * `sm` - PIO state machine + /// * `clock_freq` - PIO clock frequency pub fn new( pin: Pin>, pio: &mut PIO

, @@ -50,6 +58,18 @@ where } } + /// Update status LED + /// Depending on the mode, the LED will be set to a different colour + /// + /// * OFF = off + /// * OK = green + /// * ACTIVE1 = blue + /// * ACTIVE2 = orange + /// * WARNING = red (flashing) + /// * ERROR = red + /// * BOOTLOADER = purple + /// + /// Make sure to call this function regularly to keep the LED flashing pub fn update(&mut self, mode: StatusMode) { let colors: [RGB8; 7] = [ (0, 0, 0).into(), // Off