add basic usb hid keyboard functionality with debounced input based on embassy-rs examples

This commit is contained in:
2025-12-13 01:41:18 +01:00
parent 66497ba1ce
commit a8b9d3d750
3 changed files with 374 additions and 25 deletions

201
Cargo.lock generated
View File

@@ -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",
]

View File

@@ -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

View File

@@ -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<USB>;
});
#[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<usize> {
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<ReportId>, dur: u32) {
info!("set idle rate to {}", dur);
}
fn get_idle_ms(&mut self, _id: Option<ReportId>) -> Option<u32> {
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");
}
}
}