//! 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 fmt; mod layout; mod status_led; mod usb_joystick_device; 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 usb_joystick_device::{JoystickConfig, JoystickReport}; 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 = 5; pub const KEY_COLS: usize = 5; 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.gp9.into_pull_up_input(), &pins.gp10.into_pull_up_input(), &pins.gp11.into_pull_up_input(), &pins.gp12.into_pull_up_input(), &pins.gp13.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.gp4.into_push_pull_output(), &mut pins.gp5.into_push_pull_output(), &mut pins.gp6.into_push_pull_output(), &mut pins.gp7.into_push_pull_output(), &mut pins.gp8.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 joystick = UsbHidClassBuilder::new() .add_device(JoystickConfig::default()) .build(&usb_bus); let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x1209, 0x0002)) .manufacturer("CMtec") .product("CMDR Joystick") .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 fn_mode: u8; // 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 first 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); } status_led.update(StatusMode::Normal); loop { // if status_led_count_down.wait().is_ok() { // update_status_led(&mut status_led, &caps_lock_active, &gui_lock_state); // } if usb_hid_report_count_down.wait().is_ok() { let pressed_keys = button_matrix.buttons_pressed(); fn_mode = get_fn_mode(pressed_keys); for (index, key) in pressed_keys.iter().enumerate() { buttons[index].pressed = *key; } match joystick .device() .write_report(&get_joy_report(&mut buttons, fn_mode)) { Err(UsbHidError::WouldBlock) => {} Err(UsbHidError::SerializationError) => { status_led.update(StatusMode::Bootloader); } Err(UsbHidError::UsbError(UsbError::BufferOverflow)) => { status_led.update(StatusMode::Bootloader); } Ok(_) => {} Err(e) => { status_led.update(StatusMode::Error); core::panic!("Failed to write joystick report: {:?}", e) } }; } if usb_tick_count_down.wait().is_ok() { button_matrix.scan_matrix(&mut delay); } if usb_dev.poll(&mut [&mut joystick]) {} } } /// 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_state: &u8, ) where P: PIOExt + FunctionConfig, I: PinId, Function

: ValidPinMode, SM: StateMachineIndex, { if *caps_lock_active { status_led.update(StatusMode::Warning); } else if *gui_lock_state != 0 { status_led.update(StatusMode::Activity); } else { status_led.update(StatusMode::Normal); } } /// Get current Fn mode (0, 1, 2 or 3) /// layout::FN_BUTTONS contains the button types /// /// # 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 fn_mode: u8 = 0; let mut fn_l_active: bool = false; let mut fn_r_active: bool = false; for (index, key) in pressed_keys.iter().enumerate() { if *key && layout::MAP[0][index] == layout::ButtonType::FnL { fn_l_active = true; } if *key && layout::MAP[0][index] == layout::ButtonType::FnR { fn_r_active = true; } } if fn_l_active && fn_r_active { fn_mode = 3; } else if fn_l_active { fn_mode = 2; } else if fn_r_active { fn_mode = 1; } fn_mode } /// 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 fn get_joy_report( matrix_keys: &mut [KeyboardButton; NUMBER_OF_KEYS], fn_mode: u8, ) -> JoystickReport { let mut x: u16 = 0x03ff; let mut y: u16 = 0x03ff; let mut z: u16 = 0x03ff; let mut rx: u16 = 0x03ff; let mut ry: u16 = 0x03ff; let mut rz: u16 = 0x03ff; let mut buttons: u32 = 0; let mut hat1: u8 = 0xf; let mut hat2: u8 = 0xf; let mut hat3: u8 = 0xf; let mut hat4: u8 = 0xf; // Filter report based on Fn mode and pressed keys for (index, key) in matrix_keys.iter_mut().enumerate() { // Set fn mode for the pressed button if key.pressed != key.previous_pressed && key.pressed { key.fn_mode = fn_mode; } key.previous_pressed = key.pressed; // Skip key if defined as NoEventIndicated if layout::MAP[key.fn_mode as usize][index] == layout::ButtonType::NotConnected { continue; } // Update button state if key.pressed && layout::MAP[fn_mode as usize][index] as usize <= layout::ButtonType::B20 as usize { buttons |= 1 << layout::MAP[fn_mode as usize][index] as usize; } // Update hat state if key.pressed && layout::MAP[fn_mode as usize][index] as usize >= layout::ButtonType::Hat1U as usize && layout::MAP[fn_mode as usize][index] as usize <= layout::ButtonType::Hat4D as usize { match layout::MAP[fn_mode as usize][index] { layout::ButtonType::Hat1U => hat1 = 0, layout::ButtonType::Hat1R => hat1 = 2, layout::ButtonType::Hat1D => hat1 = 4, layout::ButtonType::Hat1L => hat1 = 6, layout::ButtonType::Hat2U => hat2 = 0, layout::ButtonType::Hat2R => hat2 = 2, layout::ButtonType::Hat2D => hat2 = 4, layout::ButtonType::Hat2L => hat2 = 6, layout::ButtonType::Hat3U => hat3 = 0, layout::ButtonType::Hat3R => hat3 = 2, layout::ButtonType::Hat3D => hat3 = 4, layout::ButtonType::Hat3L => hat3 = 6, layout::ButtonType::Hat4U => hat4 = 0, layout::ButtonType::Hat4R => hat4 = 2, layout::ButtonType::Hat4D => hat4 = 4, layout::ButtonType::Hat4L => hat4 = 6, _ => {} } } } JoystickReport { x, y, z, rx, ry, rz, hat1, hat2, hat3, hat4, buttons, } }