From 5ee7a85b33f83131fd42ce229d3aadaf2054f44a Mon Sep 17 00:00:00 2001 From: alexmoon Date: Tue, 29 Mar 2022 15:18:43 -0400 Subject: [PATCH] Async USB HID class --- embassy-usb-hid/Cargo.toml | 18 ++ embassy-usb-hid/src/fmt.rs | 225 ++++++++++++++ embassy-usb-hid/src/lib.rs | 529 ++++++++++++++++++++++++++++++++ examples/nrf/Cargo.toml | 6 +- examples/nrf/src/bin/usb_hid.rs | 131 ++++++++ 5 files changed, 907 insertions(+), 2 deletions(-) create mode 100644 embassy-usb-hid/Cargo.toml create mode 100644 embassy-usb-hid/src/fmt.rs create mode 100644 embassy-usb-hid/src/lib.rs create mode 100644 examples/nrf/src/bin/usb_hid.rs diff --git a/embassy-usb-hid/Cargo.toml b/embassy-usb-hid/Cargo.toml new file mode 100644 index 00000000..dc3d3cd8 --- /dev/null +++ b/embassy-usb-hid/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "embassy-usb-hid" +version = "0.1.0" +edition = "2021" + +[features] +default = ["usbd-hid"] +usbd-hid = ["dep:usbd-hid", "ssmarshal"] + +[dependencies] +embassy = { version = "0.1.0", path = "../embassy" } +embassy-usb = { version = "0.1.0", path = "../embassy-usb" } + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } +usbd-hid = { version = "0.5.2", optional = true } +ssmarshal = { version = "1.0", default-features = false, optional = true } +futures-util = { version = "0.3.21", default-features = false } diff --git a/embassy-usb-hid/src/fmt.rs b/embassy-usb-hid/src/fmt.rs new file mode 100644 index 00000000..06697081 --- /dev/null +++ b/embassy-usb-hid/src/fmt.rs @@ -0,0 +1,225 @@ +#![macro_use] +#![allow(unused_macros)] + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} diff --git a/embassy-usb-hid/src/lib.rs b/embassy-usb-hid/src/lib.rs new file mode 100644 index 00000000..c1f70c32 --- /dev/null +++ b/embassy-usb-hid/src/lib.rs @@ -0,0 +1,529 @@ +#![no_std] +#![feature(generic_associated_types)] +#![feature(type_alias_impl_trait)] + +//! Implements HID functionality for a usb-device device. + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +use core::mem::MaybeUninit; + +use embassy::channel::signal::Signal; +use embassy::time::Duration; +use embassy_usb::driver::{EndpointOut, ReadError}; +use embassy_usb::{ + control::{ControlHandler, InResponse, OutResponse, Request, RequestType}, + driver::{Driver, Endpoint, EndpointIn, WriteError}, + UsbDeviceBuilder, +}; +use futures_util::future::{select, Either}; +use futures_util::pin_mut; +#[cfg(feature = "usbd-hid")] +use ssmarshal::serialize; +#[cfg(feature = "usbd-hid")] +use usbd_hid::descriptor::AsInputReport; + +const USB_CLASS_HID: u8 = 0x03; +const USB_SUBCLASS_NONE: u8 = 0x00; +const USB_PROTOCOL_NONE: u8 = 0x00; + +// HID +const HID_DESC_DESCTYPE_HID: u8 = 0x21; +const HID_DESC_DESCTYPE_HID_REPORT: u8 = 0x22; +const HID_DESC_SPEC_1_10: [u8; 2] = [0x10, 0x01]; +const HID_DESC_COUNTRY_UNSPEC: u8 = 0x00; + +const HID_REQ_SET_IDLE: u8 = 0x0a; +const HID_REQ_GET_IDLE: u8 = 0x02; +const HID_REQ_GET_REPORT: u8 = 0x01; +const HID_REQ_SET_REPORT: u8 = 0x09; +const HID_REQ_GET_PROTOCOL: u8 = 0x03; +const HID_REQ_SET_PROTOCOL: u8 = 0x0b; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ReportId { + In(u8), + Out(u8), + Feature(u8), +} + +impl ReportId { + fn try_from(value: u16) -> Result { + match value >> 8 { + 1 => Ok(ReportId::In(value as u8)), + 2 => Ok(ReportId::Out(value as u8)), + 3 => Ok(ReportId::Feature(value as u8)), + _ => Err(()), + } + } +} + +pub struct State<'a, const IN_N: usize, const OUT_N: usize, const FEATURE_N: usize> { + control: MaybeUninit>, + out_signal: Signal<(usize, [u8; OUT_N])>, + feature_signal: Signal<(usize, [u8; FEATURE_N])>, +} + +impl<'a, const IN_N: usize, const OUT_N: usize, const FEATURE_N: usize> + State<'a, IN_N, OUT_N, FEATURE_N> +{ + pub fn new() -> Self { + State { + control: MaybeUninit::uninit(), + out_signal: Signal::new(), + feature_signal: Signal::new(), + } + } +} + +pub struct HidClass< + 'd, + D: Driver<'d>, + const IN_N: usize, + const OUT_N: usize, + const FEATURE_N: usize, +> { + input: ReportWriter<'d, D, IN_N>, + output: ReportReader<'d, D, OUT_N>, + feature: ReportReader<'d, D, FEATURE_N>, +} + +impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize, const FEATURE_N: usize> + HidClass<'d, D, IN_N, OUT_N, FEATURE_N> +{ + /// Creates a new HidClass. + /// + /// poll_ms configures how frequently the host should poll for reading/writing + /// HID reports. A lower value means better throughput & latency, at the expense + /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for + /// high performance uses, and a value of 255 is good for best-effort usecases. + /// + /// This allocates two endpoints (IN and OUT). + /// See new_ep_in (IN endpoint only) and new_ep_out (OUT endpoint only) to only create a single + /// endpoint. + pub fn new( + builder: &mut UsbDeviceBuilder<'d, D>, + state: &'d mut State<'d, IN_N, OUT_N, FEATURE_N>, + report_descriptor: &'static [u8], + request_handler: Option<&'d dyn RequestHandler>, + poll_ms: u8, + ) -> Self { + let ep_out = Some(builder.alloc_interrupt_endpoint_out(64, poll_ms)); + let ep_in = Some(builder.alloc_interrupt_endpoint_in(64, poll_ms)); + Self::new_inner( + builder, + state, + report_descriptor, + request_handler, + ep_out, + ep_in, + ) + } + + /// Creates a new HidClass with the provided UsbBus & HID report descriptor. + /// See new() for more details. + pub fn new_ep_in( + builder: &mut UsbDeviceBuilder<'d, D>, + state: &'d mut State<'d, IN_N, OUT_N, FEATURE_N>, + report_descriptor: &'static [u8], + request_handler: Option<&'d dyn RequestHandler>, + poll_ms: u8, + ) -> Self { + let ep_out = None; + let ep_in = Some(builder.alloc_interrupt_endpoint_in(64, poll_ms)); + Self::new_inner( + builder, + state, + report_descriptor, + request_handler, + ep_out, + ep_in, + ) + } + + /// Creates a new HidClass with the provided UsbBus & HID report descriptor. + /// See new() for more details. + pub fn new_ep_out( + builder: &mut UsbDeviceBuilder<'d, D>, + state: &'d mut State<'d, IN_N, OUT_N, FEATURE_N>, + report_descriptor: &'static [u8], + request_handler: Option<&'d dyn RequestHandler>, + poll_ms: u8, + ) -> Self { + let ep_out = Some(builder.alloc_interrupt_endpoint_out(64, poll_ms)); + let ep_in = None; + Self::new_inner( + builder, + state, + report_descriptor, + request_handler, + ep_out, + ep_in, + ) + } + + fn new_inner( + builder: &mut UsbDeviceBuilder<'d, D>, + state: &'d mut State<'d, IN_N, OUT_N, FEATURE_N>, + report_descriptor: &'static [u8], + request_handler: Option<&'d dyn RequestHandler>, + ep_out: Option, + ep_in: Option, + ) -> Self { + let control = state.control.write(Control::new( + report_descriptor, + &state.out_signal, + &state.feature_signal, + request_handler, + )); + + control.build(builder, ep_out.as_ref(), ep_in.as_ref()); + + Self { + input: ReportWriter { ep_in }, + output: ReportReader { + ep_out, + receiver: &state.out_signal, + }, + feature: ReportReader { + ep_out: None, + receiver: &state.feature_signal, + }, + } + } + + /// Gets the [`ReportWriter`] for input reports. + /// + /// **Note:** If the `HidClass` was created with [`new_ep_out()`](Self::new_ep_out) + /// this writer will be useless as no endpoint is availabe to send reports. + pub fn input(&mut self) -> &mut ReportWriter<'d, D, IN_N> { + &mut self.input + } + + /// Gets the [`ReportReader`] for output reports. + pub fn output(&mut self) -> &mut ReportReader<'d, D, OUT_N> { + &mut self.output + } + + /// Gets the [`ReportReader`] for feature reports. + pub fn feature(&mut self) -> &mut ReportReader<'d, D, FEATURE_N> { + &mut self.feature + } + + /// Splits this `HidClass` into seperate readers/writers for each report type. + pub fn split( + self, + ) -> ( + ReportWriter<'d, D, IN_N>, + ReportReader<'d, D, OUT_N>, + ReportReader<'d, D, FEATURE_N>, + ) { + (self.input, self.output, self.feature) + } +} + +pub struct ReportWriter<'d, D: Driver<'d>, const N: usize> { + ep_in: Option, +} + +pub struct ReportReader<'d, D: Driver<'d>, const N: usize> { + ep_out: Option, + receiver: &'d Signal<(usize, [u8; N])>, +} + +impl<'d, D: Driver<'d>, const N: usize> ReportWriter<'d, D, N> { + /// Tries to write an input report by serializing the given report structure. + /// + /// Panics if no endpoint is available. + #[cfg(feature = "usbd-hid")] + pub async fn serialize(&mut self, r: &IR) -> Result<(), WriteError> { + let mut buf: [u8; N] = [0; N]; + let size = match serialize(&mut buf, r) { + Ok(size) => size, + Err(_) => return Err(WriteError::BufferOverflow), + }; + self.write(&buf[0..size]).await + } + + /// Writes `report` to its interrupt endpoint. + /// + /// Panics if no endpoint is available. + pub async fn write(&mut self, report: &[u8]) -> Result<(), WriteError> { + assert!(report.len() <= N); + + let ep = self + .ep_in + .as_mut() + .expect("An IN endpoint must be allocated to write input reports."); + + let max_packet_size = usize::from(ep.info().max_packet_size); + let zlp_needed = report.len() < N && (report.len() % max_packet_size == 0); + for chunk in report.chunks(max_packet_size) { + ep.write(chunk).await?; + } + + if zlp_needed { + ep.write(&[]).await?; + } + + Ok(()) + } +} + +impl<'d, D: Driver<'d>, const N: usize> ReportReader<'d, D, N> { + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + assert!(buf.len() >= N); + if let Some(ep) = &mut self.ep_out { + let max_packet_size = usize::from(ep.info().max_packet_size); + + let mut chunks = buf.chunks_mut(max_packet_size); + + // Wait until we've received a chunk from the endpoint or a report from a SET_REPORT control request + let (mut total, data) = { + let chunk = unwrap!(chunks.next()); + let fut1 = ep.read(chunk); + pin_mut!(fut1); + match select(fut1, self.receiver.wait()).await { + Either::Left((Ok(size), _)) => (size, None), + Either::Left((Err(err), _)) => return Err(err), + Either::Right(((size, data), _)) => (size, Some(data)), + } + }; + + if let Some(data) = data { + buf[0..total].copy_from_slice(&data[0..total]); + Ok(total) + } else { + for chunk in chunks { + let size = ep.read(chunk).await?; + total += size; + if size < max_packet_size || total == N { + break; + } + } + Ok(total) + } + } else { + let (total, data) = self.receiver.wait().await; + buf[0..total].copy_from_slice(&data[0..total]); + Ok(total) + } + } +} + +pub trait RequestHandler { + /// Read the value of report `id` into `buf` returning the size. + /// + /// Returns `None` if `id` is invalid or no data is available. + fn get_report(&self, id: ReportId, buf: &mut [u8]) -> Option { + let _ = (id, buf); + None + } + + /// Set the idle rate for `id` to `dur`. + /// + /// If `id` is `None`, set the idle rate of all input reports to `dur`. If + /// an indefinite duration is requested, `dur` will be set to `Duration::MAX`. + fn set_idle(&self, id: Option, dur: Duration) { + let _ = (id, dur); + } + + /// Get the idle rate for `id`. + /// + /// If `id` is `None`, get the idle rate for all reports. Returning `None` + /// will reject the control request. Any duration above 1.020 seconds or 0 + /// will be returned as an indefinite idle rate. + fn get_idle(&self, id: Option) -> Option { + let _ = id; + None + } +} + +pub struct Control<'d, const OUT_N: usize, const FEATURE_N: usize> { + report_descriptor: &'static [u8], + out_signal: &'d Signal<(usize, [u8; OUT_N])>, + feature_signal: &'d Signal<(usize, [u8; FEATURE_N])>, + request_handler: Option<&'d dyn RequestHandler>, + hid_descriptor: [u8; 9], +} + +impl<'a, const OUT_N: usize, const FEATURE_N: usize> Control<'a, OUT_N, FEATURE_N> { + fn new( + report_descriptor: &'static [u8], + out_signal: &'a Signal<(usize, [u8; OUT_N])>, + feature_signal: &'a Signal<(usize, [u8; FEATURE_N])>, + request_handler: Option<&'a dyn RequestHandler>, + ) -> Self { + Control { + report_descriptor, + out_signal, + feature_signal, + request_handler, + hid_descriptor: [ + // Length of buf inclusive of size prefix + 9, + // Descriptor type + HID_DESC_DESCTYPE_HID, + // HID Class spec version + HID_DESC_SPEC_1_10[0], + HID_DESC_SPEC_1_10[1], + // Country code not supported + HID_DESC_COUNTRY_UNSPEC, + // Number of following descriptors + 1, + // We have a HID report descriptor the host should read + HID_DESC_DESCTYPE_HID_REPORT, + // HID report descriptor size, + (report_descriptor.len() & 0xFF) as u8, + (report_descriptor.len() >> 8 & 0xFF) as u8, + ], + } + } + + fn build<'d, D: Driver<'d>>( + &'d mut self, + builder: &mut UsbDeviceBuilder<'d, D>, + ep_out: Option<&D::EndpointOut>, + ep_in: Option<&D::EndpointIn>, + ) { + let len = self.report_descriptor.len(); + let if_num = builder.alloc_interface_with_handler(self); + + builder.config_descriptor.interface( + if_num, + USB_CLASS_HID, + USB_SUBCLASS_NONE, + USB_PROTOCOL_NONE, + ); + + // HID descriptor + builder.config_descriptor.write( + HID_DESC_DESCTYPE_HID, + &[ + // HID Class spec version + HID_DESC_SPEC_1_10[0], + HID_DESC_SPEC_1_10[1], + // Country code not supported + HID_DESC_COUNTRY_UNSPEC, + // Number of following descriptors + 1, + // We have a HID report descriptor the host should read + HID_DESC_DESCTYPE_HID_REPORT, + // HID report descriptor size, + (len & 0xFF) as u8, + (len >> 8 & 0xFF) as u8, + ], + ); + + if let Some(ep) = ep_out { + builder.config_descriptor.endpoint(ep.info()); + } + if let Some(ep) = ep_in { + builder.config_descriptor.endpoint(ep.info()); + } + } +} + +impl<'d, const OUT_N: usize, const FEATURE_N: usize> ControlHandler + for Control<'d, OUT_N, FEATURE_N> +{ + fn reset(&mut self) {} + + fn control_out(&mut self, req: embassy_usb::control::Request, data: &[u8]) -> OutResponse { + trace!("HID control_out {:?} {=[u8]:x}", req, data); + if let RequestType::Class = req.request_type { + match req.request { + HID_REQ_SET_IDLE => { + if let Some(handler) = self.request_handler.as_ref() { + let id = req.value as u8; + let id = (id != 0).then(|| ReportId::In(id)); + let dur = u64::from(req.value >> 8); + let dur = if dur == 0 { + Duration::MAX + } else { + Duration::from_millis(4 * dur) + }; + handler.set_idle(id, dur); + } + OutResponse::Accepted + } + HID_REQ_SET_REPORT => match ReportId::try_from(req.value) { + Ok(ReportId::In(_)) => OutResponse::Rejected, + Ok(ReportId::Out(_id)) => { + let mut buf = [0; OUT_N]; + buf[0..data.len()].copy_from_slice(data); + self.out_signal.signal((data.len(), buf)); + OutResponse::Accepted + } + Ok(ReportId::Feature(_id)) => { + let mut buf = [0; FEATURE_N]; + buf[0..data.len()].copy_from_slice(data); + self.feature_signal.signal((data.len(), buf)); + OutResponse::Accepted + } + Err(_) => OutResponse::Rejected, + }, + HID_REQ_SET_PROTOCOL => { + if req.value == 1 { + OutResponse::Accepted + } else { + warn!("HID Boot Protocol is unsupported."); + OutResponse::Rejected // UNSUPPORTED: Boot Protocol + } + } + _ => OutResponse::Rejected, + } + } else { + OutResponse::Rejected // UNSUPPORTED: SET_DESCRIPTOR + } + } + + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + trace!("HID control_in {:?}", req); + match (req.request_type, req.request) { + (RequestType::Standard, Request::GET_DESCRIPTOR) => match (req.value >> 8) as u8 { + HID_DESC_DESCTYPE_HID_REPORT => InResponse::Accepted(self.report_descriptor), + HID_DESC_DESCTYPE_HID => InResponse::Accepted(&self.hid_descriptor), + _ => InResponse::Rejected, + }, + (RequestType::Class, HID_REQ_GET_REPORT) => { + let size = match ReportId::try_from(req.value) { + Ok(id) => self + .request_handler + .as_ref() + .and_then(|x| x.get_report(id, buf)), + Err(_) => None, + }; + + if let Some(size) = size { + InResponse::Accepted(&buf[0..size]) + } else { + InResponse::Rejected + } + } + (RequestType::Class, HID_REQ_GET_IDLE) => { + if let Some(handler) = self.request_handler.as_ref() { + let id = req.value as u8; + let id = (id != 0).then(|| ReportId::In(id)); + if let Some(dur) = handler.get_idle(id) { + let dur = u8::try_from(dur.as_millis() / 4).unwrap_or(0); + buf[0] = dur; + InResponse::Accepted(&buf[0..1]) + } else { + InResponse::Rejected + } + } else { + InResponse::Rejected + } + } + (RequestType::Class, HID_REQ_GET_PROTOCOL) => { + // UNSUPPORTED: Boot Protocol + buf[0] = 1; + InResponse::Accepted(&buf[0..1]) + } + _ => InResponse::Rejected, + } + } +} diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index aa30f3fa..e944c171 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -6,13 +6,14 @@ version = "0.1.0" [features] default = ["nightly"] -nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embassy-usb-serial"] +nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embassy-usb-serial", "embassy-usb-hid"] [dependencies] embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] } embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true } embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"], optional = true } +embassy-usb-hid = { version = "0.1.0", path = "../../embassy-usb-hid", features = ["defmt"], optional = true } defmt = "0.3" defmt-rtt = "0.3" @@ -23,4 +24,5 @@ panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } rand = { version = "0.8.4", default-features = false } embedded-storage = "0.3.0" - +usbd-hid = "0.5.2" +serde = { version = "1.0.136", default-features = false } diff --git a/examples/nrf/src/bin/usb_hid.rs b/examples/nrf/src/bin/usb_hid.rs new file mode 100644 index 00000000..1fd056d0 --- /dev/null +++ b/examples/nrf/src/bin/usb_hid.rs @@ -0,0 +1,131 @@ +#![no_std] +#![no_main] +#![feature(generic_associated_types)] +#![feature(type_alias_impl_trait)] + +#[path = "../example_common.rs"] +mod example_common; + +use core::mem; +use defmt::*; +use embassy::executor::Spawner; +use embassy::time::{Duration, Timer}; +use embassy_nrf::interrupt; +use embassy_nrf::pac; +use embassy_nrf::usb::Driver; +use embassy_nrf::Peripherals; +use embassy_usb::{Config, UsbDeviceBuilder}; +use embassy_usb_hid::{HidClass, ReportId, RequestHandler, State}; +use futures::future::join; +use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; + +#[embassy::main] +async fn main(_spawner: Spawner, p: Peripherals) { + let clock: pac::CLOCK = unsafe { mem::transmute(()) }; + let power: pac::POWER = unsafe { mem::transmute(()) }; + + info!("Enabling ext hfosc..."); + clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); + while clock.events_hfclkstarted.read().bits() != 1 {} + + info!("Waiting for vbus..."); + while !power.usbregstatus.read().vbusdetect().is_vbus_present() {} + info!("vbus OK"); + + // Create the driver, from the HAL. + let irq = interrupt::take!(USBD); + let driver = Driver::new(p.USBD, irq); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Tactile Engineering"); + config.product = Some("Testy"); + config.serial_number = Some("12345678"); + config.max_power = 100; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 16]; + let request_handler = MyRequestHandler {}; + + let mut state = State::<5, 0, 0>::new(); + + let mut builder = UsbDeviceBuilder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut control_buf, + ); + + // Create classes on the builder. + // let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + let mut hid = HidClass::new( + &mut builder, + &mut state, + MouseReport::desc(), + Some(&request_handler), + 60, + ); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let hid_fut = async { + loop { + Timer::after(Duration::from_millis(500)).await; + hid.input() + .serialize(&MouseReport { + buttons: 0, + x: 0, + y: 4, + wheel: 0, + pan: 0, + }) + .await + .unwrap(); + + Timer::after(Duration::from_millis(500)).await; + hid.input() + .serialize(&MouseReport { + buttons: 0, + x: 0, + y: -4, + wheel: 0, + pan: 0, + }) + .await + .unwrap(); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, hid_fut).await; +} + +struct MyRequestHandler {} + +impl RequestHandler for MyRequestHandler { + fn get_report(&self, id: ReportId, _buf: &mut [u8]) -> Option { + info!("Get report for {:?}", id); + None + } + + fn set_idle(&self, id: Option, dur: Duration) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle(&self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +}