//! Board bring-up and peripheral wiring for the CMDR Keyboard firmware. //! //! This module owns the RP2040 peripheral initialisation lifecycle so that the //! rest of the firmware can rely on already-configured clocks, GPIO, timers, //! USB, and LED control. Nothing outside this module should directly touch raw //! PAC peripherals; callers interact with the strongly typed `BoardParts` //! returned after initialisation. use crate::{hardware, ButtonMatrix, MatrixPins, StatusLed}; use cortex_m::delay::Delay; use cortex_m::interrupt; use rp2040_hal::{ Clock, clocks::init_clocks_and_plls, gpio::Pins, pac, pio::PIOExt, sio::Sio, timer::Timer, watchdog::Watchdog, }; use usb_device::class_prelude::UsbBusAllocator; pub type KeyboardMatrix = ButtonMatrix< MatrixPins<{ hardware::KEY_ROWS }, { hardware::KEY_COLS }>, { hardware::KEY_ROWS }, { hardware::KEY_COLS }, { hardware::NUMBER_OF_KEYS }, >; pub type KeyboardStatusLed = StatusLed; /// Aggregates the peripherals required to run the keyboard firmware. pub struct Board { pub button_matrix: KeyboardMatrix, pub status_led: KeyboardStatusLed, pub delay: Delay, pub timer: Timer, usb_bus: &'static UsbBusAllocator, } /// Components returned to the application after initialization. pub struct BoardParts { pub button_matrix: KeyboardMatrix, pub status_led: KeyboardStatusLed, pub delay: Delay, pub timer: Timer, pub usb_bus: &'static UsbBusAllocator, } impl Board { pub fn new() -> Self { // Acquire RP2040 peripheral handles before board bring-up. let mut pac = pac::Peripherals::take().unwrap(); let core = pac::CorePeripherals::take().unwrap(); let mut watchdog = Watchdog::new(pac.WATCHDOG); // Bring up the primary system and USB clocks using the external crystal. 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 sio = Sio::new(pac.SIO); // Split the GPIO bank into the strongly typed board pins. let pins = Pins::new( pac.IO_BANK0, pac.PADS_BANK0, sio.gpio_bank0, &mut pac.RESETS, ); let (rows, cols, status_pin) = hardware::split_board_pins(pins); let matrix_pins = MatrixPins::new(rows, cols); // Create the debounced button matrix scanner with firmware thresholds. let mut button_matrix = ButtonMatrix::new( matrix_pins, hardware::MATRIX_DEBOUNCE_SCANS_PRESS, hardware::MATRIX_DEBOUNCE_SCANS_RELEASE, hardware::MIN_PRESS_SPACING_SCANS, hardware::RELEASE_GRACE_PERIOD_SCANS, ); button_matrix.init_pins(); let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); let status_led_pin = status_pin; // Configure the WS2812 status LED using a dedicated PIO state machine. let status_led = StatusLed::new( status_led_pin, &mut pio, sm0, clocks.peripheral_clock.freq(), ); // Prepare shared timer peripherals and a blocking delay helper. let timer = Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); let delay = Delay::new(core.SYST, clocks.system_clock.freq().to_Hz()); // Allocate the global USB bus for the HID device class. let usb_bus = usb_allocator( pac.USBCTRL_REGS, pac.USBCTRL_DPRAM, clocks.usb_clock, &mut pac.RESETS, ); Self { button_matrix, status_led, delay, timer, usb_bus, } } pub fn into_parts(self) -> BoardParts { BoardParts { button_matrix: self.button_matrix, status_led: self.status_led, delay: self.delay, timer: self.timer, usb_bus: self.usb_bus, } } } fn usb_allocator( usbctrl_regs: pac::USBCTRL_REGS, usbctrl_dpram: pac::USBCTRL_DPRAM, usb_clock: rp2040_hal::clocks::UsbClock, resets: &mut pac::RESETS, ) -> &'static UsbBusAllocator { // Lazily create and share the USB bus allocator between HID classes. static USB_BUS: static_cell::StaticCell> = static_cell::StaticCell::new(); interrupt::free(|_| { USB_BUS.init_with(|| { UsbBusAllocator::new(rp2040_hal::usb::UsbBus::new( usbctrl_regs, usbctrl_dpram, usb_clock, true, resets, )) }) }) }