diff --git a/rp2040/src/bootloader.rs b/rp2040/src/bootloader.rs
new file mode 100644
index 0000000..f429449
--- /dev/null
+++ b/rp2040/src/bootloader.rs
@@ -0,0 +1,77 @@
+//! Bootloader entry helpers shared between power-on checks and runtime combos.
+
+use crate::{layout, status::StatusMode, StatusLed, NUMBER_OF_KEYS};
+use cortex_m::asm;
+use rp2040_hal::{
+ gpio::AnyPin,
+ pio::{PIOExt, StateMachineIndex},
+};
+use usbd_human_interface_device::page::Keyboard;
+
+/// Returns true when the runtime bootloader chord is held.
+///
+/// The chord requires two Fn buttons, both Shift buttons and the primary Ctrl.
+pub fn chord_requested(pressed_keys: &[bool; NUMBER_OF_KEYS]) -> bool {
+ if !modifier_pressed(pressed_keys, Keyboard::LeftShift)
+ || !modifier_pressed(pressed_keys, Keyboard::RightShift)
+ {
+ return false;
+ }
+
+ if !modifier_pressed(pressed_keys, Keyboard::LeftControl) {
+ return false;
+ }
+
+ let active_fn = layout::FN_BUTTONS
+ .iter()
+ .filter(|index| pressed_keys[**index as usize])
+ .count();
+
+ active_fn >= 2
+}
+
+fn modifier_pressed(pressed_keys: &[bool; NUMBER_OF_KEYS], key: Keyboard) -> bool {
+ layout::MAP[0]
+ .iter()
+ .enumerate()
+ .any(|(index, mapped)| *mapped == key && pressed_keys[index])
+}
+
+/// Puts the RP2040 into the ROM bootloader.
+pub fn enter
(status_led: &mut StatusLed
) -> !
+where
+ P: PIOExt,
+ SM: StateMachineIndex,
+ I: AnyPin,
+{
+ 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 {
+ asm::nop();
+ }
+}
+
+#[cfg(all(test, feature = "std"))]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn chord_requires_all_modifier_keys_and_two_fn() {
+ let mut pressed = [false; NUMBER_OF_KEYS];
+ for (index, key) in layout::MAP[0].iter().enumerate() {
+ match key {
+ Keyboard::LeftShift | Keyboard::RightShift | Keyboard::LeftControl => {
+ pressed[index] = true;
+ }
+ _ => {}
+ }
+ }
+ pressed[layout::FN_BUTTONS[0] as usize] = true;
+ assert!(!chord_requested(&pressed));
+
+ pressed[layout::FN_BUTTONS[1] as usize] = true;
+ assert!(chord_requested(&pressed));
+ }
+}
diff --git a/rp2040/src/lib.rs b/rp2040/src/lib.rs
index 1a66698..5aeaf98 100644
--- a/rp2040/src/lib.rs
+++ b/rp2040/src/lib.rs
@@ -6,6 +6,7 @@
//! details, status handling, and keyboard processing modular.
pub mod button_matrix;
+pub mod bootloader;
pub mod hardware;
pub mod keyboard;
pub mod layout;
diff --git a/rp2040/src/main.rs b/rp2040/src/main.rs
index ea44f75..b2ca642 100644
--- a/rp2040/src/main.rs
+++ b/rp2040/src/main.rs
@@ -8,7 +8,7 @@
#![no_main]
use cmdr_keyboard_42::hardware::{self, timers};
-use cmdr_keyboard_42::{ButtonMatrix, KeyboardState, StatusLed, StatusMode};
+use cmdr_keyboard_42::{bootloader, ButtonMatrix, KeyboardState, StatusLed, StatusMode};
use cortex_m::delay::Delay;
use embedded_hal::digital::{InputPin, OutputPin};
use embedded_hal_0_2::timer::CountDown;
@@ -26,6 +26,7 @@ use rp2040_hal::{
use usb_device::class_prelude::*;
use usb_device::prelude::*;
use usb_device::device::UsbDeviceState;
+use usbd_human_interface_device::page::Keyboard;
use usbd_human_interface_device::prelude::*;
#[unsafe(link_section = ".boot2")]
@@ -127,11 +128,9 @@ fn main() -> ! {
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 initial_pressed = button_matrix.buttons_pressed();
+ if initial_pressed[0] {
+ bootloader::enter(&mut status_led);
}
let usb_bus = UsbBusAllocator::new(rp2040_hal::usb::UsbBus::new(
@@ -186,6 +185,26 @@ fn main() -> ! {
button_matrix.scan_matrix(&mut delay);
let pressed_keys = button_matrix.buttons_pressed();
+ if bootloader::chord_requested(&pressed_keys) {
+ if !usb_suspended {
+ let mut attempts: u8 = 0;
+ while attempts < 3 {
+ let clear_report = [Keyboard::NoEventIndicated; hardware::NUMBER_OF_KEYS];
+ match keyboard.device().write_report(clear_report) {
+ Ok(_) => break,
+ Err(UsbHidError::WouldBlock) | Err(UsbHidError::Duplicate) => {
+ let _ = keyboard.tick();
+ }
+ Err(_) => break,
+ }
+ attempts = attempts.saturating_add(1);
+ }
+ }
+
+ delay.delay_ms(5);
+ bootloader::enter(&mut status_led);
+ }
+
// Check for input activity
if pressed_keys.iter().any(|pressed| *pressed) {
last_activity_ms = status_time_ms;