diff --git a/rp2040/Cargo.toml b/rp2040/Cargo.toml index 42dc6fd..22abcb5 100644 --- a/rp2040/Cargo.toml +++ b/rp2040/Cargo.toml @@ -14,11 +14,12 @@ embedded-hal ="0.2.5" fugit = "0.3.5" nb = "1.0.0" smart-leds = "0.3.0" +smart-leds-trait = "0.2.1" ws2812-pio = "0.6.0" usbd-human-interface-device = "0.4.2" usb-device = "0.2" packed_struct = { version = "0.10", default-features = false } -keypad = "0.2.2" +pio = "0.2.0" [features] # This is the set of features we enable by default diff --git a/rp2040/src/button_matrix.rs b/rp2040/src/button_matrix.rs new file mode 100644 index 0000000..ff1f021 --- /dev/null +++ b/rp2040/src/button_matrix.rs @@ -0,0 +1,98 @@ +use core::convert::Infallible; +use cortex_m::delay::Delay; +use embedded_hal::digital::v2::*; + +pub const DEBOUNCE_COUNT: u8 = 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], + debounce_counter: [u8; N], +} + +impl<'a, const R: usize, const C: usize, const N: usize> ButtonMatrix<'a, R, C, N> { + pub fn new( + rows: &'a [&'a dyn InputPin; R], + cols: &'a mut [&'a mut dyn OutputPin; C], + delay: &'a mut Delay, + ) -> Self { + Self { + rows, + cols, + delay, + state: [false; N], + state_raw: [false; N], + debounce_counter: [0; N], + } + } + + 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) { + for col_index in 0..self.cols.len() { + self.cols[col_index].set_low().unwrap(); + self.delay.delay_us(10); + self.process_column(col_index); + self.cols[col_index].set_high().unwrap(); + self.delay.delay_us(10); + } + } + + fn process_column(&mut self, col_index: usize) { + 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] { + 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; + } + } + } + + // 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 + } +} diff --git a/rp2040/src/layout.rs b/rp2040/src/layout.rs index 7fcab37..b8de35c 100644 --- a/rp2040/src/layout.rs +++ b/rp2040/src/layout.rs @@ -3,8 +3,8 @@ // | 0 | 1 | 2 | 3 | 4 | 5 | | 6 | 7 | 8 | 9 | 10 | 11 | // | 12 | 13 | 14 | 15 | 16 | 17 | | 18 | 19 | 20 | 21 | 22 | 23 | // | 24 | 25 | 26 | 27 | 28 | 29 | | 30 | 31 | 32 | 33 | 34 | 35 | -// ------------------| 36 | 37 | 38 | | 39 | 40 | 41 |------------------ -// ------------------- ------------------- +// ------------------| 39 | 40 | 41 | | 42 | 43 | 44 |------------------ +// 36 37 38 ------------------- ------------------- 45 46 47 // // Swedish keymap conversion table: // US Swedish @@ -26,13 +26,15 @@ use crate::NUMBER_OF_KEYS; use usbd_human_interface_device::page::Keyboard; // Function (Fn) buttons index (need two buttons) -pub const FN_BUTTONS: [u8; 3] = [37, 40, 41]; - +// Button 41 are used both as Fn and RightAlt (AltGr) for better ergonomics. +// This means that RightAlt is only available on layer 1 keys. +pub const FN_BUTTONS: [u8; 3] = [40, 43, 44]; // Button map to HID key (three Function layers) pub const MAP: [[Keyboard; NUMBER_OF_KEYS]; 3] = [ [ // Function layer 0 // HID Key // Button Index + // ----------------------------------------- Keyboard::Tab, // 0 Keyboard::Q, // 1 Keyboard::W, // 2 @@ -44,7 +46,7 @@ pub const MAP: [[Keyboard; NUMBER_OF_KEYS]; 3] = [ Keyboard::I, // 8 Keyboard::O, // 9 Keyboard::P, // 10 - Keyboard::LeftBrace, // 11 // Å + Keyboard::LeftBrace, // 11 å Keyboard::LeftControl, // 12 Keyboard::A, // 13 Keyboard::S, // 14 @@ -55,8 +57,8 @@ pub const MAP: [[Keyboard; NUMBER_OF_KEYS]; 3] = [ Keyboard::J, // 19 Keyboard::K, // 20 Keyboard::L, // 21 - Keyboard::Semicolon, // 22 // Ö - Keyboard::Apostrophe, // 23 // Ä + Keyboard::Semicolon, // 22 ö + Keyboard::Apostrophe, // 23 ä Keyboard::LeftShift, // 24 Keyboard::Z, // 25 Keyboard::X, // 26 @@ -67,18 +69,25 @@ pub const MAP: [[Keyboard; NUMBER_OF_KEYS]; 3] = [ Keyboard::M, // 31 Keyboard::Comma, // 32 Keyboard::Dot, // 33 - Keyboard::ForwardSlash, // 34 // - + Keyboard::ForwardSlash, // 34 - Keyboard::RightShift, // 35 - Keyboard::LeftAlt, // 36 - Keyboard::NoEventIndicated, // 37 // Fn - Keyboard::Space, // 38 - Keyboard::ReturnEnter, // 39 - Keyboard::NoEventIndicated, // 40 // Fn - Keyboard::RightAlt, // 41 + Keyboard::NoEventIndicated, // 36 no button connected + Keyboard::NoEventIndicated, // 37 no button connected + Keyboard::NoEventIndicated, // 38 no button connected + Keyboard::LeftAlt, // 39 + Keyboard::NoEventIndicated, // 40 Fn (= will never trigg this layer) + Keyboard::Space, // 41 + Keyboard::ReturnEnter, // 42 + Keyboard::NoEventIndicated, // 43 Fn (= will never trigg this layer) + Keyboard::NoEventIndicated, // 44 Fn (= will never trigg this layer) + Keyboard::NoEventIndicated, // 45 no button connected + Keyboard::NoEventIndicated, // 46 no button connected + Keyboard::NoEventIndicated, // 47 no button connected ], [ // Function layer 1 // HID Key // Button Index + // ----------------------------------------- Keyboard::Escape, // 0 Keyboard::F1, // 1 Keyboard::F2, // 2 @@ -109,29 +118,36 @@ pub const MAP: [[Keyboard; NUMBER_OF_KEYS]; 3] = [ Keyboard::Keyboard8, // 27 Keyboard::Keyboard9, // 28 Keyboard::Keyboard0, // 29 - Keyboard::NonUSBackslash, // 30 // < - Keyboard::Equal, // 31 // ´ - Keyboard::Backslash, // 32 // ' - Keyboard::RightBrace, // 33 // ^ - Keyboard::Minus, // 34 // + + Keyboard::NonUSBackslash, // 30 < + Keyboard::Equal, // 31 ´ + Keyboard::Backslash, // 32 ' + Keyboard::RightBrace, // 33 ^ + Keyboard::Minus, // 34 + Keyboard::RightShift, // 35 - Keyboard::LeftAlt, // 36 - Keyboard::NoEventIndicated, // 37 // Fn - Keyboard::DeleteBackspace, // 38 - Keyboard::ReturnEnter, // 39 - Keyboard::NoEventIndicated, // 40// Fn - Keyboard::RightAlt, // 41 + Keyboard::NoEventIndicated, // 36 no button connected + Keyboard::NoEventIndicated, // 37 no button connected + Keyboard::NoEventIndicated, // 38 no button connected + Keyboard::LeftAlt, // 39 + Keyboard::NoEventIndicated, // 40 Fn + Keyboard::DeleteBackspace, // 41 + Keyboard::ReturnEnter, // 42 + Keyboard::NoEventIndicated, // 43 Fn + Keyboard::RightAlt, // 44 Fn + Keyboard::NoEventIndicated, // 45 no button connected + Keyboard::NoEventIndicated, // 46 no button connected + Keyboard::NoEventIndicated, // 47 no button connected ], [ // Function layer 2 // HID Key // Button Index + // ----------------------------------------- Keyboard::F11, // 0 Keyboard::F12, // 1 Keyboard::F13, // 2 Keyboard::F14, // 3 Keyboard::F15, // 4 Keyboard::F16, // 5 - Keyboard::Grave, // 6 // § + Keyboard::Grave, // 6 § Keyboard::NoEventIndicated, // 7 Keyboard::LeftGUI, // 8 Keyboard::NoEventIndicated, // 9 @@ -161,11 +177,17 @@ pub const MAP: [[Keyboard; NUMBER_OF_KEYS]; 3] = [ Keyboard::End, // 33 Keyboard::Insert, // 34 Keyboard::RightShift, // 35 - Keyboard::LeftAlt, // 36 - Keyboard::NoEventIndicated, // 37 // Fn - Keyboard::LeftGUI, // 38 - Keyboard::ReturnEnter, // 39 - Keyboard::NoEventIndicated, // 40 // Fn - Keyboard::RightAlt, // 41 + Keyboard::NoEventIndicated, // 36 no button connected + Keyboard::NoEventIndicated, // 37 no button connected + Keyboard::NoEventIndicated, // 38 no button connected + Keyboard::LeftAlt, // 39 + Keyboard::NoEventIndicated, // 40 Fn + Keyboard::LeftGUI, // 41 + Keyboard::ReturnEnter, // 42 + Keyboard::NoEventIndicated, // 43 Fn + Keyboard::NoEventIndicated, // 44 Fn + Keyboard::NoEventIndicated, // 45 no button connected + Keyboard::NoEventIndicated, // 46 no button connected + Keyboard::NoEventIndicated, // 47 no button connected ], ]; diff --git a/rp2040/src/main.rs b/rp2040/src/main.rs index ffaf97d..7d8c829 100644 --- a/rp2040/src/main.rs +++ b/rp2040/src/main.rs @@ -5,13 +5,21 @@ #![no_std] #![no_main] +mod button_matrix; +mod layout; +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; use panic_halt as _; -use smart_leds::{SmartLedsWrite, RGB8}; +use rp2040_hal::{ + gpio::{Function, FunctionConfig, PinId, ValidPinMode}, + pio::StateMachineIndex, +}; use usb_device::class_prelude::*; use usb_device::prelude::*; use usbd_human_interface_device::page::Keyboard; @@ -28,23 +36,20 @@ use waveshare_rp2040_zero::{ }, Pins, XOSC_CRYSTAL_FREQ, }; -use ws2812_pio::Ws2812; - -mod layout; pub const KEY_ROWS: usize = 4; pub const KEY_COLS: usize = 12; -pub const NUMBER_OF_KEYS: usize = 42; +pub const NUMBER_OF_KEYS: usize = KEY_ROWS * KEY_COLS; #[derive(Copy, Clone)] -struct ButtonMatrix { - current_state: bool, - previous_state: bool, - fn_mode: u8, +pub struct KeyboardButton { + pub current_state: bool, + pub previous_state: bool, + pub fn_mode: u8, } -impl ButtonMatrix { - fn default() -> Self { +impl KeyboardButton { + pub fn default() -> Self { Self { current_state: false, previous_state: false, @@ -104,17 +109,13 @@ fn main() -> ! { // Configure the status LED let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); - let mut status_led = Ws2812::new( + let mut status_led = Ws2812StatusLed::new( pins.neopixel.into_mode(), &mut pio, sm0, clocks.peripheral_clock.freq(), - timer.count_down(), ); - let mut button_matrix: [ButtonMatrix; NUMBER_OF_KEYS] = - [ButtonMatrix::default(); NUMBER_OF_KEYS]; - let button_matrix_row_pins: &[&dyn InputPin; KEY_ROWS] = &[ &pins.gp0.into_pull_up_input(), &pins.gp1.into_pull_up_input(), @@ -137,6 +138,11 @@ fn main() -> ! { &mut pins.gp11.into_push_pull_output(), ]; + let mut button_matrix: ButtonMatrix = + ButtonMatrix::new(button_matrix_row_pins, button_matrix_col_pins, &mut delay); + + 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()); @@ -146,79 +152,44 @@ fn main() -> ! { let mut indicator_count_down = timer.count_down(); indicator_count_down.start(250.millis()); - let mut status_led_onoff: bool = false; let mut caps_lock_active: bool = false; let mut fn_mode: u8 = 0; - let status_led_colors: [RGB8; 6] = [ - (0, 0, 0).into(), // Off - (10, 7, 0).into(), // Green - (10, 4, 10).into(), // Blue - (5, 10, 0).into(), // Orange - (2, 20, 0).into(), // Red - (0, 10, 10).into(), // Purple - ]; - // Set all column pins to output and high - init_button_matrix_pins(button_matrix_col_pins, &mut delay); + 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 { + status_led.update(StatusMode::BOOTLOADER); + let gpio_activity_pin_mask = 0; + let disable_interface_mask = 0; + rp2040_hal::rom_data::reset_to_usb_boot(gpio_activity_pin_mask, disable_interface_mask); + } loop { if indicator_count_down.wait().is_ok() { - // Set status LED colour based on function layer and capslock - // 0 = green, 1 = blue, 2 = orange, capslock active = flashing. - if caps_lock_active == true && status_led_onoff == true { - status_led - .write([status_led_colors[0]].iter().copied()) - .unwrap(); - status_led_onoff = false; - } else { - status_led - .write([status_led_colors[usize::from(fn_mode) + 1]].iter().copied()) - .unwrap(); - status_led_onoff = true; - } + update_status_led(&mut status_led, fn_mode, caps_lock_active); } if report_count_down.wait().is_ok() { // Scan keyboard matrix - let pressed_keys = - get_pressed_buttons(button_matrix_row_pins, button_matrix_col_pins, &mut delay); + let pressed_keys = button_matrix.get_button_state(); - // Check if all four corners are pressed, if so, reset to USB boot mode - if pressed_keys[0] == true - && pressed_keys[11] == true - && pressed_keys[24] == true - && pressed_keys[35] == true - { - status_led - .write([status_led_colors[5]].iter().copied()) - .unwrap(); - delay.delay_us(100); - let gpio_activity_pin_mask = 0; - let disable_interface_mask = 0; - rp2040_hal::rom_data::reset_to_usb_boot( - gpio_activity_pin_mask, - disable_interface_mask, - ); - } // Get current function layer fn_mode = get_fn_mode(pressed_keys); - // Set status LED colour based on function layer and capslock - // 0 = green, 1 = blue, 2 = orange, capslock active = flashing. if caps_lock_active == false { - status_led - .write([status_led_colors[usize::from(fn_mode) + 1]].iter().copied()) - .unwrap(); - status_led_onoff = true; + 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() { - button_matrix[index].current_state = *key; + buttons[index].current_state = *key; } // Generate keyboard report - let keyboard_report = get_keyboard_report(&mut button_matrix, layout::MAP, fn_mode); + let keyboard_report = get_keyboard_report(&mut buttons, layout::MAP, fn_mode); match keyboard.device().write_report(keyboard_report) { Err(UsbHidError::WouldBlock) => {} @@ -232,6 +203,8 @@ fn main() -> ! { //Tick once per ms if tick_count_down.wait().is_ok() { + button_matrix.scan_matrix(); + match keyboard.tick() { Err(UsbHidError::WouldBlock) => {} Ok(_) => {} @@ -261,54 +234,36 @@ fn main() -> ! { } } -// Initialise keyboard matrix pins -fn init_button_matrix_pins(cols: &mut [&mut dyn OutputPin], delay: &mut Delay) { - for col in cols.iter_mut() { - col.set_high().unwrap(); - } - delay.delay_us(10); -} - -// Scan keyboard matrix for pressed keys and return a bool array -// representing the state of each key (true = pressed) -fn get_pressed_buttons( - rows: &[&dyn InputPin], - cols: &mut [&mut dyn OutputPin], - delay: &mut Delay, -) -> [bool; NUMBER_OF_KEYS] { - // Scan keyboard matrix for pressed keys - let mut pressed_keys: [bool; NUMBER_OF_KEYS] = [false; NUMBER_OF_KEYS]; - for (col_index, col) in cols.iter_mut().enumerate() { - // Activate column - col.set_low().unwrap(); - delay.delay_us(10); - - // Read rows - for (row_index, row) in rows.iter().enumerate() { - // Do not check unconnected keys in the matrix - if row_index == 3 && (col_index < 3 || col_index > 8) { - continue; - } - if row_index < 3 && row.is_low().unwrap() { - pressed_keys[col_index + (row_index * KEY_COLS)] = true; - } else if row.is_low().unwrap() { - // Correct index for unconnected keys - pressed_keys[col_index + (row_index * KEY_COLS) - 3] = true; - } +// Set status LED colour based on function layer and capslock +// 0 = green, 1 = blue, 2 = orange, capslock active = flashing red. +fn update_status_led( + status_led: &mut Ws2812StatusLed, + fn_mode: u8, + caps_lock_active: bool, +) where + P: PIOExt + FunctionConfig, + I: PinId, + Function

: ValidPinMode, + SM: StateMachineIndex, +{ + if caps_lock_active == true { + status_led.update(StatusMode::WARNING); + } else { + match fn_mode { + 0 => status_led.update(StatusMode::OK), + 1 => status_led.update(StatusMode::ACTIVE1), + 2 => status_led.update(StatusMode::ACTIVE2), + _ => status_led.update(StatusMode::ERROR), } - col.set_high().unwrap(); - delay.delay_us(10); } - // Return scan result - pressed_keys } // Get current 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 layout::FN_BUTTONS.iter() { - if pressed_keys[usize::from(*button_id)] == true { + for button_id in crate::layout::FN_BUTTONS.iter() { + if pressed_keys[*button_id as usize] == true { fn_mode += 1; } } @@ -320,7 +275,7 @@ fn get_fn_mode(pressed_keys: [bool; NUMBER_OF_KEYS]) -> u8 { // Generate keyboard report based on pressed keys and Fn mode (0, 1 or 2) fn get_keyboard_report( - matrix_keys: &mut [ButtonMatrix; NUMBER_OF_KEYS], + matrix_keys: &mut [KeyboardButton; NUMBER_OF_KEYS], layout_map: [[Keyboard; NUMBER_OF_KEYS]; 3], fn_mode: u8, ) -> [Keyboard; NUMBER_OF_KEYS] { @@ -335,7 +290,7 @@ fn get_keyboard_report( key.previous_state = key.current_state; if key.current_state == true { - keyboard_report[index] = layout_map[usize::from(key.fn_mode)][index]; + keyboard_report[index] = layout_map[key.fn_mode as usize][index]; } } // Return report diff --git a/rp2040/src/status_led.rs b/rp2040/src/status_led.rs new file mode 100644 index 0000000..4fff509 --- /dev/null +++ b/rp2040/src/status_led.rs @@ -0,0 +1,81 @@ +use rp2040_hal::{ + gpio::{Function, FunctionConfig, Pin, PinId, ValidPinMode}, + pio::{PIOExt, StateMachineIndex, UninitStateMachine, PIO}, +}; +use smart_leds::{SmartLedsWrite, RGB8}; +use ws2812_pio::Ws2812Direct; + +#[derive(PartialEq, Eq, Copy, Clone)] +pub enum StatusMode { + OFF = 0, + OK = 1, + ACTIVE1 = 2, + ACTIVE2 = 3, + WARNING = 4, + ERROR = 5, + BOOTLOADER = 6, +} + +pub struct Ws2812StatusLed +where + I: PinId, + P: PIOExt + FunctionConfig, + Function

: ValidPinMode, + SM: StateMachineIndex, +{ + ws2812_direct: Ws2812Direct, + state: bool, +} + +impl Ws2812StatusLed +where + I: PinId, + P: PIOExt + FunctionConfig, + Function

: ValidPinMode, + SM: StateMachineIndex, +{ + /// Creates a new instance of this driver. + pub fn new( + pin: Pin>, + pio: &mut PIO

, + sm: UninitStateMachine<(P, SM)>, + clock_freq: fugit::HertzU32, + ) -> Self { + // prepare the PIO program + let ws2812_direct = Ws2812Direct::new(pin, pio, sm, clock_freq); + let state = false; + Self { + ws2812_direct, + state, + } + } + + pub fn update(&mut self, mode: StatusMode) { + let colors: [RGB8; 7] = [ + (0, 0, 0).into(), // Off + (10, 7, 0).into(), // Green + (10, 4, 10).into(), // Blue + (5, 10, 0).into(), // Orange + (2, 20, 0).into(), // Red + (2, 20, 0).into(), // Red + (0, 10, 10).into(), // Purple + ]; + + if mode == StatusMode::WARNING && self.state == false { + self.ws2812_direct + .write([colors[mode as usize]].iter().copied()) + .unwrap(); + self.state = true; + } else if mode == StatusMode::WARNING || mode == StatusMode::OFF { + self.ws2812_direct + .write([colors[0]].iter().copied()) + .unwrap(); + self.state = false; + } else { + self.ws2812_direct + .write([colors[mode as usize]].iter().copied()) + .unwrap(); + self.state = true; + } + } +}