From ea36834de54370e4d4ebac1c123299ea22b5b9b1 Mon Sep 17 00:00:00 2001 From: Nick Mello Date: Sat, 11 Jul 2026 14:37:28 -0500 Subject: [PATCH] Add USB support to hit the 'a' key Signed-off-by: Nicholas Mello --- Cargo.lock | 210 +++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 3 + src/main.rs | 244 ++++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 430 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f5a90d..1035e36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,18 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -16,12 +28,15 @@ name = "arcade-keyboard" version = "0.1.0" dependencies = [ "cortex-m-rt", - "defmt", + "defmt 1.1.1", "defmt-rtt", "embassy-executor", + "embassy-futures", "embassy-rp", "embassy-time", + "embassy-usb", "panic-probe", + "usbd-hid", ] [[package]] @@ -81,6 +96,12 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + [[package]] name = "bitflags" version = "1.3.2" @@ -136,7 +157,7 @@ version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ - "defmt", + "defmt 1.1.1", "num-traits", "pure-rust-locales", ] @@ -168,7 +189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" dependencies = [ "bare-metal", - "bitfield", + "bitfield 0.13.2", "embedded-hal 0.2.7", "volatile-register", ] @@ -274,6 +295,15 @@ version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80a4af69c60438a1a82af89d362f4729fd38db7b73f305a237636fad31ceb2bf" +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.1.1", +] + [[package]] name = "defmt" version = "1.1.1" @@ -312,7 +342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05144944c117db54127a8ac17a3c9a28d55e7047bfddb950bcd20b4a94c79b10" dependencies = [ "critical-section", - "defmt", + "defmt 1.1.1", ] [[package]] @@ -394,6 +424,9 @@ name = "embassy-futures" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" +dependencies = [ + "defmt 1.1.1", +] [[package]] name = "embassy-hal-internal" @@ -412,10 +445,27 @@ checksum = "568659fc53866d3d85c60fa33723fb751aa69e71507634fc2c19e7649432fb75" dependencies = [ "cortex-m", "critical-section", - "defmt", + "defmt 1.1.1", "num-traits", ] +[[package]] +name = "embassy-net-driver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524eb3c489760508f71360112bca70f6e53173e6fe48fc5f0efd0f5ab217751d" + +[[package]] +name = "embassy-net-driver-channel" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a07d2eb9f05a6fc876500949856ea1be40773d866d8cb99384f72d0ae4568c16" +dependencies = [ + "embassy-futures", + "embassy-net-driver", + "embassy-sync", +] + [[package]] name = "embassy-rp" version = "0.10.0" @@ -427,7 +477,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt", + "defmt 1.1.1", "document-features", "embassy-embedded-hal", "embassy-futures", @@ -467,7 +517,7 @@ dependencies = [ "embedded-io-async 0.7.0", "futures-core", "futures-sink", - "heapless", + "heapless 0.9.3", ] [[package]] @@ -502,7 +552,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168297bf80aaf114b3c9ad589bf38b01b3009b9af7f97cd18086c5bbf96f5693" dependencies = [ "embassy-executor-timer-queue", - "heapless", + "heapless 0.9.3", +] + +[[package]] +name = "embassy-usb" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a25746d8b152b72fbf2a217f489a083dbbe243f281f09184a1f2cfbe9bbb245f" +dependencies = [ + "bitflags 2.13.0", + "defmt 1.1.1", + "embassy-futures", + "embassy-net-driver-channel", + "embassy-sync", + "embassy-time", + "embassy-usb-driver", + "embedded-io-async 0.7.0", + "heapless 0.9.3", + "ssmarshal", + "usbd-hid", ] [[package]] @@ -511,7 +580,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa675c5f4349b6aa0fcffc4bf9b241f18cd11b97c1f8323273fb9a5449937fbd" dependencies = [ - "defmt", + "defmt 1.1.1", "embedded-io-async 0.6.1", "embedded-io-async 0.7.0", ] @@ -605,6 +674,12 @@ dependencies = [ "log", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "equivalent" version = "1.0.2" @@ -698,12 +773,31 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + [[package]] name = "heapless" version = "0.9.3" @@ -727,7 +821,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.17.1", ] [[package]] @@ -914,7 +1008,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" dependencies = [ "cortex-m", - "defmt", + "defmt 1.1.1", ] [[package]] @@ -1025,6 +1119,12 @@ dependencies = [ "syn", ] +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + [[package]] name = "precomputed-hash" version = "0.1.1" @@ -1068,7 +1168,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "869675ad2d7541aea90c6d88c81f46a7f4ea9af8cd0395d38f11a95126998a0d" dependencies = [ - "defmt", + "defmt 1.1.1", ] [[package]] @@ -1206,6 +1306,35 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha2-const-stable" version = "0.1.0" @@ -1267,6 +1396,16 @@ dependencies = [ "rgb", ] +[[package]] +name = "ssmarshal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850" +dependencies = [ + "encode_unicode", + "serde", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -1434,6 +1573,53 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "defmt 0.3.100", + "heapless 0.8.0", + "portable-atomic", +] + +[[package]] +name = "usbd-hid" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68beab087e4971a2fe76f631478b0e91d39593f58efd2775026ce6dc07a7bac6" +dependencies = [ + "defmt 0.3.100", + "usb-device", + "usbd-hid-macros", +] + +[[package]] +name = "usbd-hid-descriptors" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b297f021719c4308d5d0c61b6c1e7c6b3ba383deba774b49aa5484f996bdb8f1" +dependencies = [ + "bitfield 0.14.0", +] + +[[package]] +name = "usbd-hid-macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "011a3219e0933f5b3ad7dc90d9a66541a967d084c98c067deed1cd608e557ed7" +dependencies = [ + "byteorder", + "hashbrown 0.13.2", + "log", + "proc-macro2", + "quote", + "serde", + "syn", + "usbd-hid-descriptors", +] + [[package]] name = "valuable" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index d793ab6..61df29f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,9 @@ cortex-m-rt = "0.7.5" defmt = "1.1.1" defmt-rtt = "1.3.0" embassy-executor = { version = "0.10.0", features = ["executor-thread", "platform-cortex-m"] } +embassy-futures = { version = "0.1.2", features = ["defmt"] } embassy-rp = { version = "0.10.0", features = ["chrono", "defmt", "rp2040", "rt", "time-driver", "critical-section-impl"] } embassy-time = "0.5.1" +embassy-usb = { version = "0.6.0", features = ["defmt"] } panic-probe = { version = "1.0.0", features = ["defmt"] } +usbd-hid = { version = "0.10.0", features = ["defmt"] } diff --git a/src/main.rs b/src/main.rs index 60fc45a..14bac41 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,29 +1,243 @@ -//! This example test the RP Pico on board LED. -//! -//! It does not work with the RP Pico W board. See wifi_blinky.rs. - #![no_std] #![no_main] +use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; + use defmt::*; use embassy_executor::Spawner; -use embassy_rp::gpio; -use embassy_time::Timer; -use gpio::{Level, Output}; +use embassy_futures::join::join; +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_time::{Duration, Timer}; +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 _}; +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()); - let mut led = Output::new(p.PIN_25, Level::Low); + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); - loop { - info!("led on!"); - led.set_high(); - Timer::after_secs(1).await; + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("HID keyboard example"); + config.serial_number = Some("12345678"); + 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; - info!("led off!"); - led.set_low(); - Timer::after_secs(1).await; + // 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); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Set up the signal pin that will be used to trigger the keyboard. + let mut signal_pin = Input::new(p.PIN_16, Pull::None); + + // Enable the schmitt trigger to slightly debounce. + signal_pin.set_schmitt(true); + + let (reader, mut writer) = hid.split(); + + // Do stuff with the class! + let in_fut = async { + loop { + info!("Waiting for HIGH on pin 16"); + signal_pin.wait_for_high().await; + info!("HIGH DETECTED"); + + if HID_PROTOCOL_MODE.load(Ordering::Relaxed) == HidProtocolMode::Boot as u8 { + match writer.write(&[0, 0, 4, 0, 0, 0, 0, 0]).await { + Ok(()) => {} + Err(e) => warn!("Failed to send boot report: {:?}", e), + }; + } else { + // Create a report with the A key pressed. (no shift modifier) + let report = KeyboardReport { + keycodes: [4, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + // Send the report. + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + } + + signal_pin.wait_for_low().await; + info!("LOW DETECTED"); + if HID_PROTOCOL_MODE.load(Ordering::Relaxed) == HidProtocolMode::Boot as u8 { + match writer.write(&[0, 0, 0, 0, 0, 0, 0, 0]).await { + Ok(()) => {} + Err(e) => warn!("Failed to send boot report: {:?}", e), + }; + } else { + let report = KeyboardReport { + keycodes: [0, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + } + } + }; + + let out_fut = async { + reader.run(false, &mut request_handler).await; + }; + + let heartbeat = async { + let mut led = Output::new(p.PIN_25, Level::Low); + + loop { + info!("led on!"); + led.set_high(); + Timer::after(Duration::from_millis(1000)).await; + + info!("led off!"); + led.set_low(); + Timer::after(Duration::from_millis(1000)).await; + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(join(usb_fut, heartbeat), join(in_fut, 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."); + } } }