diff --git a/Cargo.lock b/Cargo.lock index 77fe2a7..c9f7ad4 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" @@ -77,6 +89,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" @@ -133,7 +151,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", ] @@ -155,7 +173,7 @@ checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -219,7 +237,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.111", ] [[package]] @@ -230,7 +248,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -268,7 +286,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -372,7 +390,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -402,6 +420,23 @@ dependencies = [ "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.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b2739fbcf6cd206ae08779c7d709087b16577d255f2ea4a45bc4bbbf305b3f" +dependencies = [ + "embassy-futures", + "embassy-net-driver", + "embassy-sync 0.7.2", +] + [[package]] name = "embassy-rp" version = "0.4.0" @@ -505,6 +540,21 @@ dependencies = [ "heapless", ] +[[package]] +name = "embassy-usb" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e651b9b7b47b514e6e6d1940a6e2e300891a2c33641917130643602a0cb6386" +dependencies = [ + "embassy-futures", + "embassy-net-driver-channel", + "embassy-sync 0.6.2", + "embassy-usb-driver", + "heapless", + "ssmarshal", + "usbd-hid", +] + [[package]] name = "embassy-usb-driver" version = "0.1.1" @@ -589,6 +639,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" @@ -679,6 +735,15 @@ 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.16.1" @@ -708,7 +773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", ] [[package]] @@ -729,15 +794,18 @@ dependencies = [ "defmt 0.3.100", "defmt-rtt", "embassy-executor", + "embassy-futures", "embassy-rp", "embassy-sync 0.6.2", "embassy-time", + "embassy-usb", "embedded-hal 1.0.0", "embedded-hal-async", "embedded-io", "embedded-io-async", "embedded-storage", "panic-probe", + "usbd-hid", ] [[package]] @@ -862,9 +930,15 @@ checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "panic-probe" version = "0.3.2" @@ -986,9 +1060,15 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + [[package]] name = "precomputed-hash" version = "0.1.1" @@ -1014,7 +1094,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -1149,6 +1229,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 2.0.111", +] + [[package]] name = "sha2-const-stable" version = "0.1.0" @@ -1195,6 +1304,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" @@ -1219,6 +1338,17 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.111" @@ -1265,7 +1395,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] [[package]] @@ -1292,6 +1422,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 = [ + "heapless", + "portable-atomic", +] + +[[package]] +name = "usbd-hid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f291ab53d428685cc780f08a2eb9d5d6ff58622db2b36e239a4f715f1e184c" +dependencies = [ + "serde", + "ssmarshal", + "usb-device", + "usbd-hid-macros", +] + +[[package]] +name = "usbd-hid-descriptors" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee54712c5d778d2fb2da43b1ce5a7b5060886ef7b09891baeb4bf36910a3ed" +dependencies = [ + "bitfield 0.14.0", +] + +[[package]] +name = "usbd-hid-macros" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb573c76e7884035ac5e1ab4a81234c187a82b6100140af0ab45757650ccda38" +dependencies = [ + "byteorder", + "hashbrown 0.13.2", + "log", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "usbd-hid-descriptors", +] + [[package]] name = "vcell" version = "0.1.3" @@ -1370,5 +1547,5 @@ checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.111", ] diff --git a/Cargo.toml b/Cargo.toml index 1227f48..d76571f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,11 +23,14 @@ embedded-storage = "0.3.1" cortex-m-rt = "0.7.3" -embassy-executor = { version = "0.7", features = ["task-arena-size-1024", "arch-cortex-m", "executor-thread", "defmt", "executor-interrupt"] } +embassy-executor = { version = "0.7", features = ["task-arena-size-65536", "arch-cortex-m", "executor-thread", "defmt", "executor-interrupt"] } embassy-sync = { version = "0.6" } embassy-time = { version = "0.4", features = ["defmt", "defmt-timestamp-uptime"] } cortex-m = { version = "0.7.7" } embassy-rp = { version = "0.4", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp2040"] } +embassy-usb = "0.4.0" +usbd-hid = "0.8.2" +embassy-futures = "0.1.2" [profile.release] debug = 2 lto = true diff --git a/src/main.rs b/src/main.rs index d302406..39efbac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,15 @@ #![no_std] #![no_main] -use defmt::info; +use core::sync::atomic::{AtomicBool, Ordering}; + +use defmt::{info, warn}; use embassy_executor::Spawner; -use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_futures::join::join; +use embassy_rp::{bind_interrupts, gpio::{Input, Level, Output, Pull}, peripherals::USB, usb::{Driver, InterruptHandler}}; use embassy_time::{Duration, Timer}; +use embassy_usb::{Builder, Config, Handler, class::hid::{HidReaderWriter, ReportId, RequestHandler, State}, control::OutResponse}; +use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; use {defmt_rtt as _, panic_probe as _}; pub struct Debouncer<'a> { @@ -17,11 +22,26 @@ impl<'a> Debouncer<'a> { Self { input, debounce } } - pub async fn debounce(&mut self) -> Level { + pub async fn debounce_low(&mut self) -> Level { loop { let l1 = self.input.get_level(); - self.input.wait_for_any_edge().await; + self.input.wait_for_low().await; + + Timer::after(self.debounce).await; + + let l2 = self.input.get_level(); + if l1 != l2 { + break l2; + } + } + } + + pub async fn debounce_high(&mut self) -> Level { + loop { + let l1 = self.input.get_level(); + + self.input.wait_for_high().await; Timer::after(self.debounce).await; @@ -33,21 +53,170 @@ impl<'a> Debouncer<'a> { } } +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + #[embassy_executor::main] -async fn main(_spawner: Spawner) -> ! { +async fn main(_spawner: Spawner) -> () { let p = embassy_rp::init(Default::default()); + + let driver = Driver::new(p.USB, Irqs); + + let mut config = Config::new(0x1234, 0xabcd); + config.manufacturer = Some("Lukrecja"); + config.product = Some("kb-prototype"); + config.serial_number = Some("00000000"); + config.composite_with_iads = false; + config.device_class = 0x00; + config.device_sub_class = 0x00; + config.device_protocol = 0x00; + + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + let mut request_handler = KbRequestHandler {}; + let mut device_handler = KbDeviceHandler::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); + + let config = embassy_usb::class::hid::Config { + report_descriptor: KeyboardReport::desc(), + request_handler: None, + poll_ms: 60, + max_packet_size: 64, + }; + + let hid = HidReaderWriter::<_, 1, 8>::new(&mut builder, &mut state, config); + + let mut usb = builder.build(); + + let usb_fut = usb.run(); + let mut led = Output::new(p.PIN_25, Level::Low); let mut switch_input = Debouncer::new(Input::new(p.PIN_0, Pull::Up), Duration::from_millis(20)); switch_input.input.set_schmitt(true); - loop { - defmt::info!("switch off"); - led.set_low(); - switch_input.debounce().await; + let (reader, mut writer) = hid.split(); - info!("switch on"); - led.set_high(); + let in_fut = async { + loop { + defmt::info!("waiting for low"); + led.set_low(); + + switch_input.debounce_low().await; + + info!("got low"); + led.set_high(); + + let report = KeyboardReport { + keycodes: [4, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + + match writer.write_serialize(&report).await { + Ok(()) => info!("report sent"), + Err(e) => warn!("failed to send: {:?}", e), + }; + + switch_input.debounce_high().await; + info!("got high"); + + let report = KeyboardReport { + keycodes: [0, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + + match writer.write_serialize(&report).await { + Ok(()) => info!("report sent"), + Err(e) => warn!("failed to send: {:?}", e), + }; + } + }; - switch_input.debounce().await; + let out_fut = async { + reader.run(false, &mut request_handler).await; + }; + join(usb_fut, join(in_fut, out_fut)).await; +} + +struct KbRequestHandler {} + +impl RequestHandler for KbRequestHandler { + fn get_report(&mut self, _id: ReportId, _buf: &mut [u8]) -> Option { + info!("get report"); + None + } + + fn set_report(&mut self, _id: ReportId, data: &[u8]) -> OutResponse { + info!("set report: {=[u8]}", data); + OutResponse::Accepted + } + + fn set_idle_ms(&mut self, _id: Option, dur: u32) { + info!("set idle rate to {}", dur); + } + + fn get_idle_ms(&mut self, _id: Option) -> Option { + info!("get idle rate"); + None + } +} + +struct KbDeviceHandler { + configured: AtomicBool, +} + +impl KbDeviceHandler { + fn new() -> Self { + Self { + configured: AtomicBool::new(false), + } + } +} + +impl Handler for KbDeviceHandler { + 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, 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") + } else { + info!("device no longer configured"); + } } }