From 937b3312eb8c0e5b376cb268ad5dcba7ad48fe40 Mon Sep 17 00:00:00 2001 From: Brandon Maier Date: Mon, 13 Jul 2026 08:34:37 -0500 Subject: [PATCH] usb: Move USB handling to separate module --- src/keyboard.rs | 2 +- src/main.rs | 151 ++----------------------------------- src/usb.rs | 193 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 147 deletions(-) create mode 100644 src/usb.rs diff --git a/src/keyboard.rs b/src/keyboard.rs index 6a249b0..80c96e0 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -10,7 +10,7 @@ use embassy_usb::{ }; use usbd_hid::descriptor::KeyboardReport; -use crate::HID_PROTOCOL_MODE; +use crate::usb::HID_PROTOCOL_MODE; const WRITE_N: usize = 8; diff --git a/src/main.rs b/src/main.rs index e291baf..d39ae1c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,10 +3,10 @@ mod heartbeat; mod keyboard; +mod usb; use crate::heartbeat::heartbeat; use crate::keyboard::Keyboard; -use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use defmt::*; use embassy_executor::Spawner; use embassy_futures::join::join; @@ -14,12 +14,6 @@ use embassy_rp::bind_interrupts; use embassy_rp::gpio::{Input, Level, Output, Pull}; use embassy_rp::peripherals::USB; use embassy_rp::usb::{Driver, InterruptHandler}; -use embassy_usb::class::hid::{ - HidBootProtocol, HidProtocolMode, HidReaderWriter, HidSubclass, ReportId, RequestHandler, State, -}; -use embassy_usb::control::OutResponse; -use embassy_usb::{Builder, Config, Handler}; -use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; use {defmt_rtt as _, panic_probe as _}; @@ -27,59 +21,14 @@ bind_interrupts!(struct Irqs { USBCTRL_IRQ => InterruptHandler; }); -static HID_PROTOCOL_MODE: AtomicU8 = AtomicU8::new(HidProtocolMode::Boot as u8); - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); + // Create the driver, from the HAL. let driver = Driver::new(p.USB, Irqs); - - // Create embassy-usb Config - let mut config = Config::new(0xc0de, 0xcafe); - config.manufacturer = Some("Nicholas Mello"); - config.product = Some("Pi Pico Arcade Keyboard"); - config.serial_number = Some("00000001"); - config.max_power = 100; - config.max_packet_size_0 = 64; - config.composite_with_iads = false; - config.device_class = 0; - config.device_sub_class = 0; - config.device_protocol = 0; - - // Create embassy-usb DeviceBuilder using the driver and config. - // It needs some buffers for building the descriptors. - let mut config_descriptor = [0; 256]; - let mut bos_descriptor = [0; 256]; - // You can also add a Microsoft OS descriptor. - let mut msos_descriptor = [0; 256]; - let mut control_buf = [0; 64]; - let mut request_handler = MyRequestHandler {}; - let mut device_handler = MyDeviceHandler::new(); - - let mut state = State::new(); - - let mut builder = Builder::new( - driver, - config, - &mut config_descriptor, - &mut bos_descriptor, - &mut msos_descriptor, - &mut control_buf, - ); - - builder.handler(&mut device_handler); - - // Create classes on the builder. - let config = embassy_usb::class::hid::Config { - report_descriptor: KeyboardReport::desc(), - request_handler: None, - poll_ms: 60, - max_packet_size: 64, - hid_subclass: HidSubclass::Boot, - hid_boot_protocol: HidBootProtocol::Keyboard, - }; - let hid = HidReaderWriter::<_, 1, 8>::new(&mut builder, &mut state, config); + let mut usb_buf = usb::UsbKeyboardBuf::new(); + let (usbkey, writer) = usb::UsbKeyboard::create_usb(&mut usb_buf, driver); // Set up the signal pin that will be used to trigger the keyboard. let mut signal_pin = Input::new(p.PIN_16, Pull::None); @@ -87,8 +36,6 @@ async fn main(_spawner: Spawner) { // Enable the schmitt trigger to slightly debounce. signal_pin.set_schmitt(true); - let (reader, writer) = hid.split(); - let mut keyboard = Keyboard::<_, 1>::new(writer); match keyboard.add_key(signal_pin, 'a') { Ok(()) => info!("Key 'a' registered"), @@ -97,95 +44,7 @@ async fn main(_spawner: Spawner) { } } - let out_fut = async { - reader.run(false, &mut request_handler).await; - }; - - let mut usb = builder.build(); let led = Output::new(p.PIN_25, Level::Low); - join( - join(usb.run(), heartbeat(led)), - join(keyboard.process(), out_fut), - ) - .await; -} - -struct MyRequestHandler {} - -impl RequestHandler for MyRequestHandler { - fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option { - info!("Get report for {:?}", id); - None - } - - fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse { - info!("Set report for {:?}: {=[u8]}", id, data); - OutResponse::Accepted - } - - fn get_protocol(&self) -> HidProtocolMode { - let protocol = HidProtocolMode::from(HID_PROTOCOL_MODE.load(Ordering::Relaxed)); - info!("The current HID protocol mode is: {}", protocol); - protocol - } - - fn set_protocol(&mut self, protocol: HidProtocolMode) -> OutResponse { - info!("Switching to HID protocol mode: {}", protocol); - HID_PROTOCOL_MODE.store(protocol as u8, Ordering::Relaxed); - OutResponse::Accepted - } - - fn set_idle_ms(&mut self, id: Option, dur: u32) { - info!("Set idle rate for {:?} to {:?}", id, dur); - } - - fn get_idle_ms(&mut self, id: Option) -> Option { - info!("Get idle rate for {:?}", id); - None - } -} - -struct MyDeviceHandler { - configured: AtomicBool, -} - -impl MyDeviceHandler { - fn new() -> Self { - MyDeviceHandler { - configured: AtomicBool::new(false), - } - } -} - -impl Handler for MyDeviceHandler { - fn enabled(&mut self, enabled: bool) { - self.configured.store(false, Ordering::Relaxed); - if enabled { - info!("Device enabled"); - } else { - info!("Device disabled"); - } - } - - fn reset(&mut self) { - self.configured.store(false, Ordering::Relaxed); - info!("Bus reset, the Vbus current limit is 100mA"); - } - - fn addressed(&mut self, addr: u8) { - self.configured.store(false, Ordering::Relaxed); - info!("USB address set to: {}", addr); - } - - fn configured(&mut self, configured: bool) { - self.configured.store(configured, Ordering::Relaxed); - if configured { - info!( - "Device configured, it may now draw up to the configured current limit from Vbus." - ) - } else { - info!("Device is no longer configured, the Vbus current limit is 100mA."); - } - } + join(join(usbkey.run(), heartbeat(led)), keyboard.process()).await; } diff --git a/src/usb.rs b/src/usb.rs new file mode 100644 index 0000000..cb67916 --- /dev/null +++ b/src/usb.rs @@ -0,0 +1,193 @@ +use core::cell::OnceCell; +use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; +use defmt::*; +use embassy_futures::join::join; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::Driver; +use embassy_usb::UsbDevice; +use embassy_usb::class::hid::{ + HidBootProtocol, HidProtocolMode, HidReader, HidReaderWriter, HidSubclass, HidWriter, ReportId, + RequestHandler, State, +}; +use embassy_usb::control::OutResponse; +use embassy_usb::{Builder, Config, Handler}; +use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; + +pub static HID_PROTOCOL_MODE: AtomicU8 = AtomicU8::new(HidProtocolMode::Boot as u8); + +pub struct UsbKeyboardBuf<'d> { + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + config_descriptor: [u8; 256], + bos_descriptor: [u8; 256], + // You can also add a Microsoft OS descriptor. + msos_descriptor: [u8; 256], + control_buf: [u8; 64], + + state: OnceCell>, + device_handler: OnceCell, +} + +impl<'d> UsbKeyboardBuf<'d> { + pub const fn new() -> Self { + Self { + config_descriptor: [0; 256], + bos_descriptor: [0; 256], + msos_descriptor: [0; 256], + control_buf: [0; 64], + state: OnceCell::new(), + device_handler: OnceCell::new(), + } + } +} + +pub struct UsbKeyboard<'d> { + usb: UsbDevice<'d, Driver<'d, USB>>, + reader: HidReader<'d, Driver<'d, USB>, 1>, +} + +impl<'d> UsbKeyboard<'d> { + // HidWriter<'_, Driver<'_, USB>, 8> + pub fn create_usb( + buf: &'d mut UsbKeyboardBuf<'d>, + driver: Driver<'d, USB>, + ) -> (Self, HidWriter<'d, Driver<'d, USB>, 8>) { + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Nicholas Mello"); + config.product = Some("Pi Pico Arcade Keyboard"); + config.serial_number = Some("00000001"); + config.max_power = 100; + config.max_packet_size_0 = 64; + config.composite_with_iads = false; + config.device_class = 0; + config.device_sub_class = 0; + config.device_protocol = 0; + + if buf.state.set(State::new()).is_err() { + defmt::panic!("USB State already initalized"); + } + + let mut builder = Builder::new( + driver, + config, + &mut buf.config_descriptor, + &mut buf.bos_descriptor, + &mut buf.msos_descriptor, + &mut buf.control_buf, + ); + + if buf.device_handler.set(MyDeviceHandler::new()).is_err() { + defmt::panic!("USB Device Handler already initialized"); + } + + builder.handler(buf.device_handler.get_mut().unwrap()); + + // Create classes on the builder. + let config = embassy_usb::class::hid::Config { + report_descriptor: KeyboardReport::desc(), + request_handler: None, + poll_ms: 60, + max_packet_size: 64, + hid_subclass: HidSubclass::Boot, + hid_boot_protocol: HidBootProtocol::Keyboard, + }; + let hid = + HidReaderWriter::<_, 1, 8>::new(&mut builder, buf.state.get_mut().unwrap(), config); + + let (reader, writer) = hid.split(); + + let usb = builder.build(); + + (Self { usb, reader }, writer) + } + + pub async fn run(mut self) { + let mut request_handler = MyRequestHandler {}; + + let out_fut = async { + self.reader.run(false, &mut request_handler).await; + }; + + join(self.usb.run(), out_fut).await; + } +} + +struct MyRequestHandler {} + +impl RequestHandler for MyRequestHandler { + fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option { + info!("Get report for {:?}", id); + None + } + + fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse { + info!("Set report for {:?}: {=[u8]}", id, data); + OutResponse::Accepted + } + + fn get_protocol(&self) -> HidProtocolMode { + let protocol = HidProtocolMode::from(HID_PROTOCOL_MODE.load(Ordering::Relaxed)); + info!("The current HID protocol mode is: {}", protocol); + protocol + } + + fn set_protocol(&mut self, protocol: HidProtocolMode) -> OutResponse { + info!("Switching to HID protocol mode: {}", protocol); + HID_PROTOCOL_MODE.store(protocol as u8, Ordering::Relaxed); + OutResponse::Accepted + } + + fn set_idle_ms(&mut self, id: Option, dur: u32) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle_ms(&mut self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +} + +struct MyDeviceHandler { + configured: AtomicBool, +} + +impl MyDeviceHandler { + fn new() -> Self { + MyDeviceHandler { + configured: AtomicBool::new(false), + } + } +} + +impl Handler for MyDeviceHandler { + fn enabled(&mut self, enabled: bool) { + self.configured.store(false, Ordering::Relaxed); + if enabled { + info!("Device enabled"); + } else { + info!("Device disabled"); + } + } + + fn reset(&mut self) { + self.configured.store(false, Ordering::Relaxed); + info!("Bus reset, the Vbus current limit is 100mA"); + } + + fn addressed(&mut self, addr: u8) { + self.configured.store(false, Ordering::Relaxed); + info!("USB address set to: {}", addr); + } + + fn configured(&mut self, configured: bool) { + self.configured.store(configured, Ordering::Relaxed); + if configured { + info!( + "Device configured, it may now draw up to the configured current limit from Vbus." + ) + } else { + info!("Device is no longer configured, the Vbus current limit is 100mA."); + } + } +}