260 lines
9.2 KiB
Rust
260 lines
9.2 KiB
Rust
//! Project: CMtec CMDR Keyboard 42
|
|
//! Date: 2025-03-09
|
|
//! Author: Christoffer Martinsson
|
|
//! Email: cm@cmtec.se
|
|
//! License: Please refer to LICENSE in root directory
|
|
|
|
#![no_std]
|
|
#![no_main]
|
|
|
|
use cmdr_keyboard_42::hardware::{self, timers};
|
|
use cmdr_keyboard_42::{ButtonMatrix, KeyboardState, StatusLed, StatusMode};
|
|
use cortex_m::delay::Delay;
|
|
use embedded_hal::digital::{InputPin, OutputPin};
|
|
use embedded_hal_0_2::timer::CountDown;
|
|
use fugit::ExtU32;
|
|
use panic_halt as _;
|
|
use rp2040_hal::{
|
|
Sio,
|
|
clocks::{Clock, init_clocks_and_plls},
|
|
gpio::Pins,
|
|
pac,
|
|
pio::PIOExt,
|
|
timer::Timer,
|
|
watchdog::Watchdog,
|
|
};
|
|
use usb_device::class_prelude::*;
|
|
use usb_device::prelude::*;
|
|
use usb_device::device::UsbDeviceState;
|
|
use usbd_human_interface_device::prelude::*;
|
|
|
|
#[unsafe(link_section = ".boot2")]
|
|
#[unsafe(no_mangle)]
|
|
#[used]
|
|
pub static BOOT2_FIRMWARE: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080;
|
|
|
|
#[rp2040_hal::entry]
|
|
fn main() -> ! {
|
|
let mut pac = pac::Peripherals::take().unwrap();
|
|
|
|
let mut watchdog = Watchdog::new(pac.WATCHDOG);
|
|
|
|
let clocks = init_clocks_and_plls(
|
|
hardware::XTAL_FREQ_HZ,
|
|
pac.XOSC,
|
|
pac.CLOCKS,
|
|
pac.PLL_SYS,
|
|
pac.PLL_USB,
|
|
&mut pac.RESETS,
|
|
&mut watchdog,
|
|
)
|
|
.ok()
|
|
.unwrap();
|
|
|
|
let core = pac::CorePeripherals::take().unwrap();
|
|
let sio = Sio::new(pac.SIO);
|
|
|
|
let pins = Pins::new(
|
|
pac.IO_BANK0,
|
|
pac.PADS_BANK0,
|
|
sio.gpio_bank0,
|
|
&mut pac.RESETS,
|
|
);
|
|
|
|
let button_matrix_row_pins: &mut [&mut dyn InputPin<Error = core::convert::Infallible>;
|
|
hardware::KEY_ROWS] = &mut [
|
|
&mut cmdr_keyboard_42::get_pin!(pins, button_row_0).into_pull_up_input(),
|
|
&mut cmdr_keyboard_42::get_pin!(pins, button_row_1).into_pull_up_input(),
|
|
&mut cmdr_keyboard_42::get_pin!(pins, button_row_2).into_pull_up_input(),
|
|
&mut cmdr_keyboard_42::get_pin!(pins, button_row_3).into_pull_up_input(),
|
|
];
|
|
|
|
let button_matrix_col_pins: &mut [&mut dyn OutputPin<Error = core::convert::Infallible>;
|
|
hardware::KEY_COLS] = &mut [
|
|
&mut cmdr_keyboard_42::get_pin!(pins, button_col_0).into_push_pull_output(),
|
|
&mut cmdr_keyboard_42::get_pin!(pins, button_col_1).into_push_pull_output(),
|
|
&mut cmdr_keyboard_42::get_pin!(pins, button_col_2).into_push_pull_output(),
|
|
&mut cmdr_keyboard_42::get_pin!(pins, button_col_3).into_push_pull_output(),
|
|
&mut cmdr_keyboard_42::get_pin!(pins, button_col_4).into_push_pull_output(),
|
|
&mut cmdr_keyboard_42::get_pin!(pins, button_col_5).into_push_pull_output(),
|
|
&mut cmdr_keyboard_42::get_pin!(pins, button_col_6).into_push_pull_output(),
|
|
&mut cmdr_keyboard_42::get_pin!(pins, button_col_7).into_push_pull_output(),
|
|
&mut cmdr_keyboard_42::get_pin!(pins, button_col_8).into_push_pull_output(),
|
|
&mut cmdr_keyboard_42::get_pin!(pins, button_col_9).into_push_pull_output(),
|
|
&mut cmdr_keyboard_42::get_pin!(pins, button_col_10).into_push_pull_output(),
|
|
&mut cmdr_keyboard_42::get_pin!(pins, button_col_11).into_push_pull_output(),
|
|
];
|
|
|
|
let mut button_matrix: ButtonMatrix<
|
|
{ hardware::KEY_ROWS },
|
|
{ hardware::KEY_COLS },
|
|
{ hardware::NUMBER_OF_KEYS },
|
|
> = ButtonMatrix::new(
|
|
button_matrix_row_pins,
|
|
button_matrix_col_pins,
|
|
hardware::MATRIX_DEBOUNCE_SCANS_PRESS,
|
|
hardware::MATRIX_DEBOUNCE_SCANS_RELEASE,
|
|
);
|
|
|
|
let mut keyboard_state = KeyboardState::new();
|
|
|
|
let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS);
|
|
let mut status_led = StatusLed::new(
|
|
cmdr_keyboard_42::get_pin!(pins, status_led).into_function(),
|
|
&mut pio,
|
|
sm0,
|
|
clocks.peripheral_clock.freq(),
|
|
);
|
|
status_led.update(StatusMode::Error);
|
|
|
|
let timer = Timer::new(pac.TIMER, &mut pac.RESETS, &clocks);
|
|
let mut delay = Delay::new(core.SYST, clocks.system_clock.freq().to_Hz());
|
|
|
|
let mut usb_tick_count_down = timer.count_down();
|
|
usb_tick_count_down.start(timers::USB_TICK_INTERVAL_US.micros());
|
|
|
|
let mut status_led_count_down = timer.count_down();
|
|
status_led_count_down.start(timers::STATUS_LED_INTERVAL_MS.millis());
|
|
let mut status_time_ms: u32 = 0;
|
|
let mut usb_initialized = false;
|
|
let mut usb_suspended = false;
|
|
let mut wake_on_input = false;
|
|
let mut last_activity_ms: u32 = 0;
|
|
|
|
button_matrix.init_pins();
|
|
|
|
for _ in 0..hardware::INITIAL_SCAN_PASSES {
|
|
button_matrix.scan_matrix(&mut delay);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
let usb_bus = UsbBusAllocator::new(rp2040_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(hardware::usb::VID, hardware::usb::PID))
|
|
.strings(&[StringDescriptors::default()
|
|
.manufacturer(hardware::usb::MANUFACTURER)
|
|
.product(hardware::usb::PRODUCT)
|
|
.serial_number(hardware::usb::SERIAL_NUMBER)])
|
|
.unwrap()
|
|
.build();
|
|
|
|
loop {
|
|
if status_led_count_down.wait().is_ok() {
|
|
status_time_ms = status_time_ms.saturating_add(timers::STATUS_LED_INTERVAL_MS);
|
|
let idle_elapsed = status_time_ms.saturating_sub(last_activity_ms);
|
|
let idle_mode = usb_initialized && idle_elapsed >= timers::IDLE_TIMEOUT_MS;
|
|
let usb_active = usb_initialized && !idle_mode;
|
|
status_led.apply_summary(
|
|
keyboard_state.status_summary(usb_initialized, usb_active, usb_suspended, idle_mode),
|
|
status_time_ms,
|
|
);
|
|
}
|
|
|
|
// Skip high-frequency scanning when suspended to save power
|
|
// Only scan periodically to detect wake-up inputs
|
|
let should_scan = if usb_suspended {
|
|
// When suspended, reduce scan frequency by factor of 20 (every ~5ms instead of 250μs)
|
|
static mut SUSPENDED_SCAN_COUNTER: u8 = 0;
|
|
unsafe {
|
|
SUSPENDED_SCAN_COUNTER = (SUSPENDED_SCAN_COUNTER + 1) % 20;
|
|
SUSPENDED_SCAN_COUNTER == 0
|
|
}
|
|
} else {
|
|
true
|
|
};
|
|
|
|
if usb_tick_count_down.wait().is_ok() && should_scan {
|
|
button_matrix.scan_matrix(&mut delay);
|
|
let pressed_keys = button_matrix.buttons_pressed();
|
|
|
|
// Check for input activity
|
|
if pressed_keys.iter().any(|pressed| *pressed) {
|
|
last_activity_ms = status_time_ms;
|
|
|
|
// Wake from USB suspend if input detected
|
|
if wake_on_input && usb_suspended {
|
|
// TODO: Implement remote wakeup if supported by host
|
|
wake_on_input = false;
|
|
}
|
|
}
|
|
|
|
let keyboard_report = keyboard_state.process_scan(pressed_keys);
|
|
|
|
// Only transmit USB reports when not suspended
|
|
if !usb_suspended {
|
|
match keyboard.device().write_report(keyboard_report) {
|
|
Err(UsbHidError::WouldBlock) | Err(UsbHidError::Duplicate) => {}
|
|
Ok(_) => {
|
|
usb_initialized = true;
|
|
}
|
|
Err(_) => {
|
|
keyboard_state.mark_stopped();
|
|
usb_initialized = false;
|
|
}
|
|
};
|
|
}
|
|
|
|
match keyboard.tick() {
|
|
Err(UsbHidError::WouldBlock) | Ok(_) => {}
|
|
Err(_) => {
|
|
keyboard_state.mark_stopped();
|
|
usb_initialized = false;
|
|
}
|
|
};
|
|
}
|
|
|
|
if usb_dev.poll(&mut [&mut keyboard]) {
|
|
match keyboard.device().read_report() {
|
|
Err(UsbError::WouldBlock) => {}
|
|
Err(_) => {
|
|
keyboard_state.mark_stopped();
|
|
usb_initialized = false;
|
|
}
|
|
Ok(leds) => {
|
|
keyboard_state.update_caps_lock(leds.caps_lock);
|
|
keyboard_state.mark_started();
|
|
usb_initialized = true;
|
|
last_activity_ms = status_time_ms;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check USB device state for suspend/resume handling
|
|
let usb_state = usb_dev.state();
|
|
let was_suspended = usb_suspended;
|
|
usb_suspended = usb_state == UsbDeviceState::Suspend;
|
|
|
|
// Handle USB resume transition
|
|
if was_suspended && !usb_suspended {
|
|
// Device was suspended and is now resumed
|
|
last_activity_ms = status_time_ms;
|
|
wake_on_input = false;
|
|
}
|
|
|
|
// Handle USB suspend transition
|
|
if !was_suspended && usb_suspended {
|
|
// Device has just been suspended - enter power saving mode
|
|
wake_on_input = true;
|
|
}
|
|
}
|
|
}
|