//! Project: CMtec CMDR Keyboard 42 //! Date: 2023-07-01 //! Author: Christoffer Martinsson //! Email: cm@cmtec.se //! License: Please refer to LICENSE in root directory #![no_std] #![no_main] mod button_matrix; mod layout; mod status_led; 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 rp2040_hal::{ gpio::{Function, FunctionConfig, PinId, ValidPinMode}, pio::StateMachineIndex, }; use status_led::{StatusMode, Ws2812StatusLed}; use usb_device::class_prelude::*; use usb_device::prelude::*; use usbd_human_interface_device::page::Keyboard; use usbd_human_interface_device::prelude::*; use waveshare_rp2040_zero::entry; use waveshare_rp2040_zero::{ hal::{ clocks::{init_clocks_and_plls, Clock}, pac, pio::PIOExt, timer::Timer, watchdog::Watchdog, Sio, }, 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; // Public types #[derive(Copy, Clone, Default)] pub struct KeyboardButton { pub pressed: bool, pub previous_pressed: bool, pub fn_mode: u8, } #[entry] fn main() -> ! { // Grab our singleton objects let mut pac = pac::Peripherals::take().unwrap(); // Set up the watchdog driver - needed by the clock setup code let mut watchdog = Watchdog::new(pac.WATCHDOG); // Configure clocks and PLLs let clocks = init_clocks_and_plls( XOSC_CRYSTAL_FREQ, pac.XOSC, pac.CLOCKS, pac.PLL_SYS, pac.PLL_USB, &mut pac.RESETS, &mut watchdog, ) .ok() .unwrap(); let core = pac::CorePeripherals::take().unwrap(); // The single-cycle I/O block controls our GPIO pins let sio = Sio::new(pac.SIO); // Set the pins to their default state let pins = Pins::new( pac.IO_BANK0, pac.PADS_BANK0, sio.gpio_bank0, &mut pac.RESETS, ); // Setting up array with pins connected to button rows let button_matrix_row_pins: &[&dyn InputPin; KEY_ROWS] = &[ &pins.gp0.into_pull_up_input(), &pins.gp1.into_pull_up_input(), &pins.gp29.into_pull_up_input(), &pins.gp28.into_pull_up_input(), ]; // Setting up array with pins connected to button columns let button_matrix_col_pins: &mut [&mut dyn OutputPin; KEY_COLS] = &mut [ &mut pins.gp12.into_push_pull_output(), &mut pins.gp13.into_push_pull_output(), &mut pins.gp14.into_push_pull_output(), &mut pins.gp15.into_push_pull_output(), &mut pins.gp26.into_push_pull_output(), &mut pins.gp27.into_push_pull_output(), &mut pins.gp7.into_push_pull_output(), &mut pins.gp8.into_push_pull_output(), &mut pins.gp6.into_push_pull_output(), &mut pins.gp9.into_push_pull_output(), &mut pins.gp10.into_push_pull_output(), &mut pins.gp11.into_push_pull_output(), ]; // Create button matrix object that scans all the PCB buttons let mut button_matrix: ButtonMatrix = 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]; // 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 usb_hid_report_count_down = timer.count_down(); usb_hid_report_count_down.start(10.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; let mut gui_lock_active: bool = false; let mut gui_lock_trigger_index: u8 = 0; // Initialize button matrix button_matrix.init_pins(); // 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: u32 = 0; let disable_interface_mask: u32 = 0; rp2040_hal::rom_data::reset_to_usb_boot(gpio_activity_pin_mask, disable_interface_mask); } loop { if status_led_count_down.wait().is_ok() { update_status_led(&mut status_led, caps_lock_active, gui_lock_active); } if usb_hid_report_count_down.wait().is_ok() { let pressed_keys = button_matrix.buttons_pressed(); fn_mode = get_fn_mode(pressed_keys); if !caps_lock_active { update_status_led(&mut status_led, caps_lock_active, gui_lock_active); } for (index, key) in pressed_keys.iter().enumerate() { buttons[index].pressed = *key; } let keyboard_report = get_keyboard_report( &mut buttons, fn_mode, &mut gui_lock_active, &mut gui_lock_trigger_index, ); 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) } }; } 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) } }; } if usb_dev.poll(&mut [&mut keyboard]) { match keyboard.device().read_report() { Err(UsbError::WouldBlock) => {} Err(e) => { status_led.update(StatusMode::Error); core::panic!("Failed to read keyboard report: {:?}", e) } Ok(leds) => { caps_lock_active = leds.caps_lock; } } } } } /// Update status LED colour based on function layer and capslock /// /// Normal = green (NORMAL) /// GUI lock = blue (GUI LOCK) /// Capslock active = flashing red (WARNING) /// Error = steady red (ERROR) /// /// # Arguments /// * `status_led` - Reference to status LED /// * `caps_lock_active` - Is capslock active fn update_status_led( status_led: &mut Ws2812StatusLed, caps_lock_active: bool, gui_lock_active: bool, ) where P: PIOExt + FunctionConfig, I: PinId, Function

: ValidPinMode, SM: StateMachineIndex, { if caps_lock_active { status_led.update(StatusMode::Warning); } else if gui_lock_active { status_led.update(StatusMode::Activity); } else { status_led.update(StatusMode::Normal); } } /// 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 fn get_fn_mode(pressed_keys: [bool; NUMBER_OF_KEYS]) -> u8 { // 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; } active_fn_keys } /// 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 /// * `gui_lock` - Is GUI lock active /// * `gui_lock_index` - Index of the key pressed after GUI lock was activated fn get_keyboard_report( matrix_keys: &mut [KeyboardButton; NUMBER_OF_KEYS], fn_mode: u8, gui_lock_active: &mut bool, gui_lock_trigger_index: &mut u8, ) -> [Keyboard; NUMBER_OF_KEYS] { 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.pressed != key.previous_pressed && key.pressed { key.fn_mode = fn_mode; } // Check if GUI lock button is pressed if key.pressed != key.previous_pressed && key.pressed && index == layout::GUI_LOCK_BUTTON[0] as usize && !(*gui_lock_active) && fn_mode == layout::GUI_LOCK_BUTTON[1] { *gui_lock_active = true; key.previous_pressed = key.pressed; continue; } key.previous_pressed = key.pressed; /// Index of GUI key in keyboard report /// Index 36, 37, 38, 45, 46, 47 are not used by any other keys const GUI_REPORT_INDEX: usize = 47; // If GUI lock is active, set LeftGUI key to pressed // when next button is pressed. Keep LeftGUI pressed // until next button is released if *gui_lock_active && key.pressed { *gui_lock_trigger_index = index as u8; keyboard_report[GUI_REPORT_INDEX] = Keyboard::LeftGUI; } else if *gui_lock_trigger_index as usize == index && key.pressed { keyboard_report[GUI_REPORT_INDEX] = Keyboard::LeftGUI; } else if *gui_lock_trigger_index as usize == index && !key.pressed { *gui_lock_active = false; } if key.pressed { keyboard_report[index] = layout::MAP[key.fn_mode as usize][index]; } } keyboard_report }