157 lines
4.9 KiB
Rust

//! 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<pac::PIO0, rp2040_hal::pio::SM0, hardware::StatusLedPin>;
/// 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<rp2040_hal::usb::UsbBus>,
}
/// 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<rp2040_hal::usb::UsbBus>,
}
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<rp2040_hal::usb::UsbBus> {
// Lazily create and share the USB bus allocator between HID classes.
static USB_BUS: static_cell::StaticCell<UsbBusAllocator<rp2040_hal::usb::UsbBus>> =
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,
))
})
})
}