From abe5e688b62ba53a21cccec2895171ced433b9bc Mon Sep 17 00:00:00 2001 From: ROMemories Date: Fri, 12 Jan 2024 11:01:59 +0100 Subject: [PATCH] feat(example): add a USB HID keyboard example --- Cargo.lock | 78 ++++++++++++- examples/embassy-usb-keyboard/Cargo.toml | 17 +++ examples/embassy-usb-keyboard/README.md | 16 +++ examples/embassy-usb-keyboard/laze.yml | 5 + examples/embassy-usb-keyboard/src/main.rs | 135 ++++++++++++++++++++++ examples/embassy-usb-keyboard/src/pins.rs | 12 ++ examples/laze.yml | 1 + 7 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 examples/embassy-usb-keyboard/Cargo.toml create mode 100644 examples/embassy-usb-keyboard/README.md create mode 100644 examples/embassy-usb-keyboard/laze.yml create mode 100644 examples/embassy-usb-keyboard/src/main.rs create mode 100644 examples/embassy-usb-keyboard/src/pins.rs diff --git a/Cargo.lock b/Cargo.lock index 75ab83a91..1afa142c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -155,9 +155,9 @@ checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" [[package]] name = "byteorder" -version = "1.5.0" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cast" @@ -743,6 +743,8 @@ dependencies = [ "embassy-sync 0.5.0", "embassy-usb-driver", "heapless 0.8.0", + "ssmarshal", + "usbd-hid", ] [[package]] @@ -751,6 +753,21 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fc247028eae04174b6635104a35b1ed336aabef4654f5e87a8f32327d231970" +[[package]] +name = "embassy-usb-keyboard" +version = "0.1.0" +dependencies = [ + "embassy-executor", + "embassy-nrf", + "embassy-sync 0.5.0", + "embassy-time", + "embassy-usb", + "riot-rs", + "riot-rs-boards", + "static_cell", + "usbd-hid", +] + [[package]] name = "embedded-dma" version = "0.1.2" @@ -855,6 +872,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.1" @@ -2211,6 +2234,16 @@ dependencies = [ "lock_api", ] +[[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.0" @@ -2455,6 +2488,47 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "usb-device" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f6cc3adc849b5292b4075fc0d5fdcf2f24866e88e336dd27a8943090a520508" + +[[package]] +name = "usbd-hid" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975bd411f4a939986751ea09992a24fa47c4d25c6ed108d04b4c2999a4fd0132" +dependencies = [ + "serde", + "ssmarshal", + "usb-device", + "usbd-hid-macros", +] + +[[package]] +name = "usbd-hid-descriptors" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbee8c6735e90894fba04770bc41e11fd3c5256018856e15dc4dd1e6c8a3dd1" +dependencies = [ + "bitfield", +] + +[[package]] +name = "usbd-hid-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261079a9ada015fa1acac7cc73c98559f3a92585e15f508034beccf6a2ab75a2" +dependencies = [ + "byteorder", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "usbd-hid-descriptors", +] + [[package]] name = "vcell" version = "0.1.3" diff --git a/examples/embassy-usb-keyboard/Cargo.toml b/examples/embassy-usb-keyboard/Cargo.toml new file mode 100644 index 000000000..707517e69 --- /dev/null +++ b/examples/embassy-usb-keyboard/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "embassy-usb-keyboard" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +publish = false + +[dependencies] +riot-rs = { path = "../../src/riot-rs", features = ["time", "usb"] } +riot-rs-boards = { path = "../../src/riot-rs-boards" } +embassy-executor = { workspace = true, default-features = false } +embassy-nrf = { workspace = true, default-features = false } +embassy-sync = { workspace = true } +embassy-usb = { workspace = true, features = ["usbd-hid"] } +embassy-time = { workspace = true, default-features = false } +static_cell = { workspace = true } +usbd-hid = { version = "0.6.1"} diff --git a/examples/embassy-usb-keyboard/README.md b/examples/embassy-usb-keyboard/README.md new file mode 100644 index 000000000..cdb06b20a --- /dev/null +++ b/examples/embassy-usb-keyboard/README.md @@ -0,0 +1,16 @@ +# embassy-usb-keyboard + +## About + +This application is testing basic +[embassy](https://github.com/embassy-rs/embassy) _USB HID_ usage with RIOT-rs. + +## How to run + +In this folder, run + + laze build -b nrf52840dk run + +With the device USB cable connected, pressing ButtonĀ 1 should send the keycode +0x04 ('a') to the connected computer, and pressing ButtonĀ 2 should send keycode +0x05 ('b'). diff --git a/examples/embassy-usb-keyboard/laze.yml b/examples/embassy-usb-keyboard/laze.yml new file mode 100644 index 000000000..bece9350c --- /dev/null +++ b/examples/embassy-usb-keyboard/laze.yml @@ -0,0 +1,5 @@ +apps: + - name: embassy-usb-keyboard + context: nrf52840dk + selects: + - ?release diff --git a/examples/embassy-usb-keyboard/src/main.rs b/examples/embassy-usb-keyboard/src/main.rs new file mode 100644 index 000000000..7ed824965 --- /dev/null +++ b/examples/embassy-usb-keyboard/src/main.rs @@ -0,0 +1,135 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(used_with_arg)] + +use embassy_time::Duration; +use embassy_usb::class::hid::{self, HidReaderWriter}; +use riot_rs::embassy::{make_static, usb::UsbDriver}; +use riot_rs::linkme::distributed_slice; +use riot_rs::rt::debug::println; + +use usbd_hid::descriptor::KeyboardReport; + +mod pins; + +// TODO: wrap in macro +use riot_rs::embassy::delegate::Delegate; +static USB_BUILDER_HOOK: Delegate = Delegate::new(); + +#[distributed_slice(riot_rs::embassy::usb::USB_BUILDER_HOOKS)] +#[linkme(crate=riot_rs::embassy::linkme)] +static _USB_BUILDER_HOOK: &Delegate = &USB_BUILDER_HOOK; + +#[embassy_executor::task] +async fn usb_keyboard(button_peripherals: pins::Buttons) { + let mut buttons = Buttons::new(button_peripherals); + + let config = embassy_usb::class::hid::Config { + report_descriptor: ::desc(), + request_handler: None, + poll_ms: 60, + max_packet_size: 64, + }; + + let hid_state = make_static!(hid::State::new()); + let hid_rw: HidReaderWriter<'static, UsbDriver, 1, 8> = USB_BUILDER_HOOK + .with(|usb_builder| hid::HidReaderWriter::new(usb_builder, hid_state, config)) + .await; + + let (_hid_reader, mut hid_writer) = hid_rw.split(); + + loop { + for (i, button) in buttons.get_mut().iter_mut().enumerate() { + if button.is_pressed() { + println!("Button #{} pressed", i + 1); + + let report = keyboard_report(KEYCODE_MAPPING[i]); + if let Err(e) = hid_writer.write_serialize(&report).await { + println!("Failed to send report: {:?}", e); + } + let report = keyboard_report(KEY_RELEASED); + if let Err(e) = hid_writer.write_serialize(&report).await { + println!("Failed to send report: {:?}", e); + } + } + } + + // Debounce events + embassy_time::Timer::after(Duration::from_millis(50)).await; + } +} + +// TODO: macro up this +use riot_rs::embassy::{arch::OptionalPeripherals, Spawner}; +#[riot_rs::embassy::distributed_slice(riot_rs::embassy::EMBASSY_TASKS)] +#[linkme(crate = riot_rs::embassy::linkme)] +fn __init_usb_keyboard(spawner: &Spawner, peripherals: &mut OptionalPeripherals) { + spawner + .spawn(usb_keyboard(pins::Buttons::take_from(peripherals).unwrap())) + .unwrap(); +} + +use crate::buttons::{Buttons, KEY_COUNT}; + +// Assuming a QWERTY US layout, see https://docs.qmk.fm/#/how_keyboards_work +// and https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf +const KC_A: u8 = 0x04; +const KC_C: u8 = 0x06; +const KC_G: u8 = 0x0a; +const KC_T: u8 = 0x17; + +const KEY_RELEASED: u8 = 0x00; + +fn keyboard_report(keycode: u8) -> KeyboardReport { + KeyboardReport { + keycodes: [keycode, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + } +} + +// Maps physical buttons to keycodes/characters +const KEYCODE_MAPPING: [u8; KEY_COUNT as usize] = [KC_A, KC_C, KC_G, KC_T]; + +mod buttons { + use embassy_nrf::gpio::{AnyPin, Input, Pin, Pull}; + + use crate::pins; + + pub const KEY_COUNT: u8 = 4; + + pub struct Button(Input<'static>); + + impl Button { + pub fn new(button: AnyPin) -> Self { + Self(Input::new(button, Pull::Up)) + } + + pub fn is_pressed(&mut self) -> bool { + self.0.is_low() + } + } + + pub struct Buttons([Button; KEY_COUNT as usize]); + + impl Buttons { + pub fn new(button_peripherals: pins::Buttons) -> Self { + Self([ + Button::new(button_peripherals.btn1.degrade()), + Button::new(button_peripherals.btn2.degrade()), + Button::new(button_peripherals.btn3.degrade()), + Button::new(button_peripherals.btn4.degrade()), + ]) + } + + pub fn get(&self) -> &[Button] { + &self.0 + } + + pub fn get_mut(&mut self) -> &mut [Button] { + &mut self.0 + } + } +} diff --git a/examples/embassy-usb-keyboard/src/pins.rs b/examples/embassy-usb-keyboard/src/pins.rs new file mode 100644 index 000000000..8d344f194 --- /dev/null +++ b/examples/embassy-usb-keyboard/src/pins.rs @@ -0,0 +1,12 @@ +use embassy_nrf::peripherals; +use riot_rs::define_peripherals; + +#[cfg(builder = "nrf52840dk")] +define_peripherals! { + Buttons { + btn1: P0_11, + btn2: P0_12, + btn3: P0_24, + btn4: P0_25, + } +} diff --git a/examples/laze.yml b/examples/laze.yml index 755c6606e..3fbb52486 100644 --- a/examples/laze.yml +++ b/examples/laze.yml @@ -10,6 +10,7 @@ subdirs: - embassy-http-server - embassy-net-tcp - embassy-net-udp + - embassy-usb-keyboard - hello-world - minimal - threading