aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoralexmoon <[email protected]>2022-03-29 15:18:43 -0400
committerDario Nieuwenhuis <[email protected]>2022-04-06 05:38:11 +0200
commit5ee7a85b33f83131fd42ce229d3aadaf2054f44a (patch)
treef20b27dfedab8f97b7fb0f5f969c8ac41a41bab2
parent8fe3b44d82f4f53491520898148c4ad337073593 (diff)
Async USB HID class
-rw-r--r--embassy-usb-hid/Cargo.toml18
-rw-r--r--embassy-usb-hid/src/fmt.rs225
-rw-r--r--embassy-usb-hid/src/lib.rs529
-rw-r--r--examples/nrf/Cargo.toml6
-rw-r--r--examples/nrf/src/bin/usb_hid.rs131
5 files changed, 907 insertions, 2 deletions
diff --git a/embassy-usb-hid/Cargo.toml b/embassy-usb-hid/Cargo.toml
new file mode 100644
index 000000000..dc3d3cd88
--- /dev/null
+++ b/embassy-usb-hid/Cargo.toml
@@ -0,0 +1,18 @@
1[package]
2name = "embassy-usb-hid"
3version = "0.1.0"
4edition = "2021"
5
6[features]
7default = ["usbd-hid"]
8usbd-hid = ["dep:usbd-hid", "ssmarshal"]
9
10[dependencies]
11embassy = { version = "0.1.0", path = "../embassy" }
12embassy-usb = { version = "0.1.0", path = "../embassy-usb" }
13
14defmt = { version = "0.3", optional = true }
15log = { version = "0.4.14", optional = true }
16usbd-hid = { version = "0.5.2", optional = true }
17ssmarshal = { version = "1.0", default-features = false, optional = true }
18futures-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 000000000..066970813
--- /dev/null
+++ b/embassy-usb-hid/src/fmt.rs
@@ -0,0 +1,225 @@
1#![macro_use]
2#![allow(unused_macros)]
3
4#[cfg(all(feature = "defmt", feature = "log"))]
5compile_error!("You may not enable both `defmt` and `log` features.");
6
7macro_rules! assert {
8 ($($x:tt)*) => {
9 {
10 #[cfg(not(feature = "defmt"))]
11 ::core::assert!($($x)*);
12 #[cfg(feature = "defmt")]
13 ::defmt::assert!($($x)*);
14 }
15 };
16}
17
18macro_rules! assert_eq {
19 ($($x:tt)*) => {
20 {
21 #[cfg(not(feature = "defmt"))]
22 ::core::assert_eq!($($x)*);
23 #[cfg(feature = "defmt")]
24 ::defmt::assert_eq!($($x)*);
25 }
26 };
27}
28
29macro_rules! assert_ne {
30 ($($x:tt)*) => {
31 {
32 #[cfg(not(feature = "defmt"))]
33 ::core::assert_ne!($($x)*);
34 #[cfg(feature = "defmt")]
35 ::defmt::assert_ne!($($x)*);
36 }
37 };
38}
39
40macro_rules! debug_assert {
41 ($($x:tt)*) => {
42 {
43 #[cfg(not(feature = "defmt"))]
44 ::core::debug_assert!($($x)*);
45 #[cfg(feature = "defmt")]
46 ::defmt::debug_assert!($($x)*);
47 }
48 };
49}
50
51macro_rules! debug_assert_eq {
52 ($($x:tt)*) => {
53 {
54 #[cfg(not(feature = "defmt"))]
55 ::core::debug_assert_eq!($($x)*);
56 #[cfg(feature = "defmt")]
57 ::defmt::debug_assert_eq!($($x)*);
58 }
59 };
60}
61
62macro_rules! debug_assert_ne {
63 ($($x:tt)*) => {
64 {
65 #[cfg(not(feature = "defmt"))]
66 ::core::debug_assert_ne!($($x)*);
67 #[cfg(feature = "defmt")]
68 ::defmt::debug_assert_ne!($($x)*);
69 }
70 };
71}
72
73macro_rules! todo {
74 ($($x:tt)*) => {
75 {
76 #[cfg(not(feature = "defmt"))]
77 ::core::todo!($($x)*);
78 #[cfg(feature = "defmt")]
79 ::defmt::todo!($($x)*);
80 }
81 };
82}
83
84macro_rules! unreachable {
85 ($($x:tt)*) => {
86 {
87 #[cfg(not(feature = "defmt"))]
88 ::core::unreachable!($($x)*);
89 #[cfg(feature = "defmt")]
90 ::defmt::unreachable!($($x)*);
91 }
92 };
93}
94
95macro_rules! panic {
96 ($($x:tt)*) => {
97 {
98 #[cfg(not(feature = "defmt"))]
99 ::core::panic!($($x)*);
100 #[cfg(feature = "defmt")]
101 ::defmt::panic!($($x)*);
102 }
103 };
104}
105
106macro_rules! trace {
107 ($s:literal $(, $x:expr)* $(,)?) => {
108 {
109 #[cfg(feature = "log")]
110 ::log::trace!($s $(, $x)*);
111 #[cfg(feature = "defmt")]
112 ::defmt::trace!($s $(, $x)*);
113 #[cfg(not(any(feature = "log", feature="defmt")))]
114 let _ = ($( & $x ),*);
115 }
116 };
117}
118
119macro_rules! debug {
120 ($s:literal $(, $x:expr)* $(,)?) => {
121 {
122 #[cfg(feature = "log")]
123 ::log::debug!($s $(, $x)*);
124 #[cfg(feature = "defmt")]
125 ::defmt::debug!($s $(, $x)*);
126 #[cfg(not(any(feature = "log", feature="defmt")))]
127 let _ = ($( & $x ),*);
128 }
129 };
130}
131
132macro_rules! info {
133 ($s:literal $(, $x:expr)* $(,)?) => {
134 {
135 #[cfg(feature = "log")]
136 ::log::info!($s $(, $x)*);
137 #[cfg(feature = "defmt")]
138 ::defmt::info!($s $(, $x)*);
139 #[cfg(not(any(feature = "log", feature="defmt")))]
140 let _ = ($( & $x ),*);
141 }
142 };
143}
144
145macro_rules! warn {
146 ($s:literal $(, $x:expr)* $(,)?) => {
147 {
148 #[cfg(feature = "log")]
149 ::log::warn!($s $(, $x)*);
150 #[cfg(feature = "defmt")]
151 ::defmt::warn!($s $(, $x)*);
152 #[cfg(not(any(feature = "log", feature="defmt")))]
153 let _ = ($( & $x ),*);
154 }
155 };
156}
157
158macro_rules! error {
159 ($s:literal $(, $x:expr)* $(,)?) => {
160 {
161 #[cfg(feature = "log")]
162 ::log::error!($s $(, $x)*);
163 #[cfg(feature = "defmt")]
164 ::defmt::error!($s $(, $x)*);
165 #[cfg(not(any(feature = "log", feature="defmt")))]
166 let _ = ($( & $x ),*);
167 }
168 };
169}
170
171#[cfg(feature = "defmt")]
172macro_rules! unwrap {
173 ($($x:tt)*) => {
174 ::defmt::unwrap!($($x)*)
175 };
176}
177
178#[cfg(not(feature = "defmt"))]
179macro_rules! unwrap {
180 ($arg:expr) => {
181 match $crate::fmt::Try::into_result($arg) {
182 ::core::result::Result::Ok(t) => t,
183 ::core::result::Result::Err(e) => {
184 ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
185 }
186 }
187 };
188 ($arg:expr, $($msg:expr),+ $(,)? ) => {
189 match $crate::fmt::Try::into_result($arg) {
190 ::core::result::Result::Ok(t) => t,
191 ::core::result::Result::Err(e) => {
192 ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
193 }
194 }
195 }
196}
197
198#[derive(Debug, Copy, Clone, Eq, PartialEq)]
199pub struct NoneError;
200
201pub trait Try {
202 type Ok;
203 type Error;
204 fn into_result(self) -> Result<Self::Ok, Self::Error>;
205}
206
207impl<T> Try for Option<T> {
208 type Ok = T;
209 type Error = NoneError;
210
211 #[inline]
212 fn into_result(self) -> Result<T, NoneError> {
213 self.ok_or(NoneError)
214 }
215}
216
217impl<T, E> Try for Result<T, E> {
218 type Ok = T;
219 type Error = E;
220
221 #[inline]
222 fn into_result(self) -> Self {
223 self
224 }
225}
diff --git a/embassy-usb-hid/src/lib.rs b/embassy-usb-hid/src/lib.rs
new file mode 100644
index 000000000..c1f70c32a
--- /dev/null
+++ b/embassy-usb-hid/src/lib.rs
@@ -0,0 +1,529 @@
1#![no_std]
2#![feature(generic_associated_types)]
3#![feature(type_alias_impl_trait)]
4
5//! Implements HID functionality for a usb-device device.
6
7// This mod MUST go first, so that the others see its macros.
8pub(crate) mod fmt;
9
10use core::mem::MaybeUninit;
11
12use embassy::channel::signal::Signal;
13use embassy::time::Duration;
14use embassy_usb::driver::{EndpointOut, ReadError};
15use embassy_usb::{
16 control::{ControlHandler, InResponse, OutResponse, Request, RequestType},
17 driver::{Driver, Endpoint, EndpointIn, WriteError},
18 UsbDeviceBuilder,
19};
20use futures_util::future::{select, Either};
21use futures_util::pin_mut;
22#[cfg(feature = "usbd-hid")]
23use ssmarshal::serialize;
24#[cfg(feature = "usbd-hid")]
25use usbd_hid::descriptor::AsInputReport;
26
27const USB_CLASS_HID: u8 = 0x03;
28const USB_SUBCLASS_NONE: u8 = 0x00;
29const USB_PROTOCOL_NONE: u8 = 0x00;
30
31// HID
32const HID_DESC_DESCTYPE_HID: u8 = 0x21;
33const HID_DESC_DESCTYPE_HID_REPORT: u8 = 0x22;
34const HID_DESC_SPEC_1_10: [u8; 2] = [0x10, 0x01];
35const HID_DESC_COUNTRY_UNSPEC: u8 = 0x00;
36
37const HID_REQ_SET_IDLE: u8 = 0x0a;
38const HID_REQ_GET_IDLE: u8 = 0x02;
39const HID_REQ_GET_REPORT: u8 = 0x01;
40const HID_REQ_SET_REPORT: u8 = 0x09;
41const HID_REQ_GET_PROTOCOL: u8 = 0x03;
42const HID_REQ_SET_PROTOCOL: u8 = 0x0b;
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45#[cfg_attr(feature = "defmt", derive(defmt::Format))]
46pub enum ReportId {
47 In(u8),
48 Out(u8),
49 Feature(u8),
50}
51
52impl ReportId {
53 fn try_from(value: u16) -> Result<Self, ()> {
54 match value >> 8 {
55 1 => Ok(ReportId::In(value as u8)),
56 2 => Ok(ReportId::Out(value as u8)),
57 3 => Ok(ReportId::Feature(value as u8)),
58 _ => Err(()),
59 }
60 }
61}
62
63pub struct State<'a, const IN_N: usize, const OUT_N: usize, const FEATURE_N: usize> {
64 control: MaybeUninit<Control<'a, OUT_N, FEATURE_N>>,
65 out_signal: Signal<(usize, [u8; OUT_N])>,
66 feature_signal: Signal<(usize, [u8; FEATURE_N])>,
67}
68
69impl<'a, const IN_N: usize, const OUT_N: usize, const FEATURE_N: usize>
70 State<'a, IN_N, OUT_N, FEATURE_N>
71{
72 pub fn new() -> Self {
73 State {
74 control: MaybeUninit::uninit(),
75 out_signal: Signal::new(),
76 feature_signal: Signal::new(),
77 }
78 }
79}
80
81pub struct HidClass<
82 'd,
83 D: Driver<'d>,
84 const IN_N: usize,
85 const OUT_N: usize,
86 const FEATURE_N: usize,
87> {
88 input: ReportWriter<'d, D, IN_N>,
89 output: ReportReader<'d, D, OUT_N>,
90 feature: ReportReader<'d, D, FEATURE_N>,
91}
92
93impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize, const FEATURE_N: usize>
94 HidClass<'d, D, IN_N, OUT_N, FEATURE_N>
95{
96 /// Creates a new HidClass.
97 ///
98 /// poll_ms configures how frequently the host should poll for reading/writing
99 /// HID reports. A lower value means better throughput & latency, at the expense
100 /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for
101 /// high performance uses, and a value of 255 is good for best-effort usecases.
102 ///
103 /// This allocates two endpoints (IN and OUT).
104 /// See new_ep_in (IN endpoint only) and new_ep_out (OUT endpoint only) to only create a single
105 /// endpoint.
106 pub fn new(
107 builder: &mut UsbDeviceBuilder<'d, D>,
108 state: &'d mut State<'d, IN_N, OUT_N, FEATURE_N>,
109 report_descriptor: &'static [u8],
110 request_handler: Option<&'d dyn RequestHandler>,
111 poll_ms: u8,
112 ) -> Self {
113 let ep_out = Some(builder.alloc_interrupt_endpoint_out(64, poll_ms));
114 let ep_in = Some(builder.alloc_interrupt_endpoint_in(64, poll_ms));
115 Self::new_inner(
116 builder,
117 state,
118 report_descriptor,
119 request_handler,
120 ep_out,
121 ep_in,
122 )
123 }
124
125 /// Creates a new HidClass with the provided UsbBus & HID report descriptor.
126 /// See new() for more details.
127 pub fn new_ep_in(
128 builder: &mut UsbDeviceBuilder<'d, D>,
129 state: &'d mut State<'d, IN_N, OUT_N, FEATURE_N>,
130 report_descriptor: &'static [u8],
131 request_handler: Option<&'d dyn RequestHandler>,
132 poll_ms: u8,
133 ) -> Self {
134 let ep_out = None;
135 let ep_in = Some(builder.alloc_interrupt_endpoint_in(64, poll_ms));
136 Self::new_inner(
137 builder,
138 state,
139 report_descriptor,
140 request_handler,
141 ep_out,
142 ep_in,
143 )
144 }
145
146 /// Creates a new HidClass with the provided UsbBus & HID report descriptor.
147 /// See new() for more details.
148 pub fn new_ep_out(
149 builder: &mut UsbDeviceBuilder<'d, D>,
150 state: &'d mut State<'d, IN_N, OUT_N, FEATURE_N>,
151 report_descriptor: &'static [u8],
152 request_handler: Option<&'d dyn RequestHandler>,
153 poll_ms: u8,
154 ) -> Self {
155 let ep_out = Some(builder.alloc_interrupt_endpoint_out(64, poll_ms));
156 let ep_in = None;
157 Self::new_inner(
158 builder,
159 state,
160 report_descriptor,
161 request_handler,
162 ep_out,
163 ep_in,
164 )
165 }
166
167 fn new_inner(
168 builder: &mut UsbDeviceBuilder<'d, D>,
169 state: &'d mut State<'d, IN_N, OUT_N, FEATURE_N>,
170 report_descriptor: &'static [u8],
171 request_handler: Option<&'d dyn RequestHandler>,
172 ep_out: Option<D::EndpointOut>,
173 ep_in: Option<D::EndpointIn>,
174 ) -> Self {
175 let control = state.control.write(Control::new(
176 report_descriptor,
177 &state.out_signal,
178 &state.feature_signal,
179 request_handler,
180 ));
181
182 control.build(builder, ep_out.as_ref(), ep_in.as_ref());
183
184 Self {
185 input: ReportWriter { ep_in },
186 output: ReportReader {
187 ep_out,
188 receiver: &state.out_signal,
189 },
190 feature: ReportReader {
191 ep_out: None,
192 receiver: &state.feature_signal,
193 },
194 }
195 }
196
197 /// Gets the [`ReportWriter`] for input reports.
198 ///
199 /// **Note:** If the `HidClass` was created with [`new_ep_out()`](Self::new_ep_out)
200 /// this writer will be useless as no endpoint is availabe to send reports.
201 pub fn input(&mut self) -> &mut ReportWriter<'d, D, IN_N> {
202 &mut self.input
203 }
204
205 /// Gets the [`ReportReader`] for output reports.
206 pub fn output(&mut self) -> &mut ReportReader<'d, D, OUT_N> {
207 &mut self.output
208 }
209
210 /// Gets the [`ReportReader`] for feature reports.
211 pub fn feature(&mut self) -> &mut ReportReader<'d, D, FEATURE_N> {
212 &mut self.feature
213 }
214
215 /// Splits this `HidClass` into seperate readers/writers for each report type.
216 pub fn split(
217 self,
218 ) -> (
219 ReportWriter<'d, D, IN_N>,
220 ReportReader<'d, D, OUT_N>,
221 ReportReader<'d, D, FEATURE_N>,
222 ) {
223 (self.input, self.output, self.feature)
224 }
225}
226
227pub struct ReportWriter<'d, D: Driver<'d>, const N: usize> {
228 ep_in: Option<D::EndpointIn>,
229}
230
231pub struct ReportReader<'d, D: Driver<'d>, const N: usize> {
232 ep_out: Option<D::EndpointOut>,
233 receiver: &'d Signal<(usize, [u8; N])>,
234}
235
236impl<'d, D: Driver<'d>, const N: usize> ReportWriter<'d, D, N> {
237 /// Tries to write an input report by serializing the given report structure.
238 ///
239 /// Panics if no endpoint is available.
240 #[cfg(feature = "usbd-hid")]
241 pub async fn serialize<IR: AsInputReport>(&mut self, r: &IR) -> Result<(), WriteError> {
242 let mut buf: [u8; N] = [0; N];
243 let size = match serialize(&mut buf, r) {
244 Ok(size) => size,
245 Err(_) => return Err(WriteError::BufferOverflow),
246 };
247 self.write(&buf[0..size]).await
248 }
249
250 /// Writes `report` to its interrupt endpoint.
251 ///
252 /// Panics if no endpoint is available.
253 pub async fn write(&mut self, report: &[u8]) -> Result<(), WriteError> {
254 assert!(report.len() <= N);
255
256 let ep = self
257 .ep_in
258 .as_mut()
259 .expect("An IN endpoint must be allocated to write input reports.");
260
261 let max_packet_size = usize::from(ep.info().max_packet_size);
262 let zlp_needed = report.len() < N && (report.len() % max_packet_size == 0);
263 for chunk in report.chunks(max_packet_size) {
264 ep.write(chunk).await?;
265 }
266
267 if zlp_needed {
268 ep.write(&[]).await?;
269 }
270
271 Ok(())
272 }
273}
274
275impl<'d, D: Driver<'d>, const N: usize> ReportReader<'d, D, N> {
276 pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, ReadError> {
277 assert!(buf.len() >= N);
278 if let Some(ep) = &mut self.ep_out {
279 let max_packet_size = usize::from(ep.info().max_packet_size);
280
281 let mut chunks = buf.chunks_mut(max_packet_size);
282
283 // Wait until we've received a chunk from the endpoint or a report from a SET_REPORT control request
284 let (mut total, data) = {
285 let chunk = unwrap!(chunks.next());
286 let fut1 = ep.read(chunk);
287 pin_mut!(fut1);
288 match select(fut1, self.receiver.wait()).await {
289 Either::Left((Ok(size), _)) => (size, None),
290 Either::Left((Err(err), _)) => return Err(err),
291 Either::Right(((size, data), _)) => (size, Some(data)),
292 }
293 };
294
295 if let Some(data) = data {
296 buf[0..total].copy_from_slice(&data[0..total]);
297 Ok(total)
298 } else {
299 for chunk in chunks {
300 let size = ep.read(chunk).await?;
301 total += size;
302 if size < max_packet_size || total == N {
303 break;
304 }
305 }
306 Ok(total)
307 }
308 } else {
309 let (total, data) = self.receiver.wait().await;
310 buf[0..total].copy_from_slice(&data[0..total]);
311 Ok(total)
312 }
313 }
314}
315
316pub trait RequestHandler {
317 /// Read the value of report `id` into `buf` returning the size.
318 ///
319 /// Returns `None` if `id` is invalid or no data is available.
320 fn get_report(&self, id: ReportId, buf: &mut [u8]) -> Option<usize> {
321 let _ = (id, buf);
322 None
323 }
324
325 /// Set the idle rate for `id` to `dur`.
326 ///
327 /// If `id` is `None`, set the idle rate of all input reports to `dur`. If
328 /// an indefinite duration is requested, `dur` will be set to `Duration::MAX`.
329 fn set_idle(&self, id: Option<ReportId>, dur: Duration) {
330 let _ = (id, dur);
331 }
332
333 /// Get the idle rate for `id`.
334 ///
335 /// If `id` is `None`, get the idle rate for all reports. Returning `None`
336 /// will reject the control request. Any duration above 1.020 seconds or 0
337 /// will be returned as an indefinite idle rate.
338 fn get_idle(&self, id: Option<ReportId>) -> Option<Duration> {
339 let _ = id;
340 None
341 }
342}
343
344pub struct Control<'d, const OUT_N: usize, const FEATURE_N: usize> {
345 report_descriptor: &'static [u8],
346 out_signal: &'d Signal<(usize, [u8; OUT_N])>,
347 feature_signal: &'d Signal<(usize, [u8; FEATURE_N])>,
348 request_handler: Option<&'d dyn RequestHandler>,
349 hid_descriptor: [u8; 9],
350}
351
352impl<'a, const OUT_N: usize, const FEATURE_N: usize> Control<'a, OUT_N, FEATURE_N> {
353 fn new(
354 report_descriptor: &'static [u8],
355 out_signal: &'a Signal<(usize, [u8; OUT_N])>,
356 feature_signal: &'a Signal<(usize, [u8; FEATURE_N])>,
357 request_handler: Option<&'a dyn RequestHandler>,
358 ) -> Self {
359 Control {
360 report_descriptor,
361 out_signal,
362 feature_signal,
363 request_handler,
364 hid_descriptor: [
365 // Length of buf inclusive of size prefix
366 9,
367 // Descriptor type
368 HID_DESC_DESCTYPE_HID,
369 // HID Class spec version
370 HID_DESC_SPEC_1_10[0],
371 HID_DESC_SPEC_1_10[1],
372 // Country code not supported
373 HID_DESC_COUNTRY_UNSPEC,
374 // Number of following descriptors
375 1,
376 // We have a HID report descriptor the host should read
377 HID_DESC_DESCTYPE_HID_REPORT,
378 // HID report descriptor size,
379 (report_descriptor.len() & 0xFF) as u8,
380 (report_descriptor.len() >> 8 & 0xFF) as u8,
381 ],
382 }
383 }
384
385 fn build<'d, D: Driver<'d>>(
386 &'d mut self,
387 builder: &mut UsbDeviceBuilder<'d, D>,
388 ep_out: Option<&D::EndpointOut>,
389 ep_in: Option<&D::EndpointIn>,
390 ) {
391 let len = self.report_descriptor.len();
392 let if_num = builder.alloc_interface_with_handler(self);
393
394 builder.config_descriptor.interface(
395 if_num,
396 USB_CLASS_HID,
397 USB_SUBCLASS_NONE,
398 USB_PROTOCOL_NONE,
399 );
400
401 // HID descriptor
402 builder.config_descriptor.write(
403 HID_DESC_DESCTYPE_HID,
404 &[
405 // HID Class spec version
406 HID_DESC_SPEC_1_10[0],
407 HID_DESC_SPEC_1_10[1],
408 // Country code not supported
409 HID_DESC_COUNTRY_UNSPEC,
410 // Number of following descriptors
411 1,
412 // We have a HID report descriptor the host should read
413 HID_DESC_DESCTYPE_HID_REPORT,
414 // HID report descriptor size,
415 (len & 0xFF) as u8,
416 (len >> 8 & 0xFF) as u8,
417 ],
418 );
419
420 if let Some(ep) = ep_out {
421 builder.config_descriptor.endpoint(ep.info());
422 }
423 if let Some(ep) = ep_in {
424 builder.config_descriptor.endpoint(ep.info());
425 }
426 }
427}
428
429impl<'d, const OUT_N: usize, const FEATURE_N: usize> ControlHandler
430 for Control<'d, OUT_N, FEATURE_N>
431{
432 fn reset(&mut self) {}
433
434 fn control_out(&mut self, req: embassy_usb::control::Request, data: &[u8]) -> OutResponse {
435 trace!("HID control_out {:?} {=[u8]:x}", req, data);
436 if let RequestType::Class = req.request_type {
437 match req.request {
438 HID_REQ_SET_IDLE => {
439 if let Some(handler) = self.request_handler.as_ref() {
440 let id = req.value as u8;
441 let id = (id != 0).then(|| ReportId::In(id));
442 let dur = u64::from(req.value >> 8);
443 let dur = if dur == 0 {
444 Duration::MAX
445 } else {
446 Duration::from_millis(4 * dur)
447 };
448 handler.set_idle(id, dur);
449 }
450 OutResponse::Accepted
451 }
452 HID_REQ_SET_REPORT => match ReportId::try_from(req.value) {
453 Ok(ReportId::In(_)) => OutResponse::Rejected,
454 Ok(ReportId::Out(_id)) => {
455 let mut buf = [0; OUT_N];
456 buf[0..data.len()].copy_from_slice(data);
457 self.out_signal.signal((data.len(), buf));
458 OutResponse::Accepted
459 }
460 Ok(ReportId::Feature(_id)) => {
461 let mut buf = [0; FEATURE_N];
462 buf[0..data.len()].copy_from_slice(data);
463 self.feature_signal.signal((data.len(), buf));
464 OutResponse::Accepted
465 }
466 Err(_) => OutResponse::Rejected,
467 },
468 HID_REQ_SET_PROTOCOL => {
469 if req.value == 1 {
470 OutResponse::Accepted
471 } else {
472 warn!("HID Boot Protocol is unsupported.");
473 OutResponse::Rejected // UNSUPPORTED: Boot Protocol
474 }
475 }
476 _ => OutResponse::Rejected,
477 }
478 } else {
479 OutResponse::Rejected // UNSUPPORTED: SET_DESCRIPTOR
480 }
481 }
482
483 fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> {
484 trace!("HID control_in {:?}", req);
485 match (req.request_type, req.request) {
486 (RequestType::Standard, Request::GET_DESCRIPTOR) => match (req.value >> 8) as u8 {
487 HID_DESC_DESCTYPE_HID_REPORT => InResponse::Accepted(self.report_descriptor),
488 HID_DESC_DESCTYPE_HID => InResponse::Accepted(&self.hid_descriptor),
489 _ => InResponse::Rejected,
490 },
491 (RequestType::Class, HID_REQ_GET_REPORT) => {
492 let size = match ReportId::try_from(req.value) {
493 Ok(id) => self
494 .request_handler
495 .as_ref()
496 .and_then(|x| x.get_report(id, buf)),
497 Err(_) => None,
498 };
499
500 if let Some(size) = size {
501 InResponse::Accepted(&buf[0..size])
502 } else {
503 InResponse::Rejected
504 }
505 }
506 (RequestType::Class, HID_REQ_GET_IDLE) => {
507 if let Some(handler) = self.request_handler.as_ref() {
508 let id = req.value as u8;
509 let id = (id != 0).then(|| ReportId::In(id));
510 if let Some(dur) = handler.get_idle(id) {
511 let dur = u8::try_from(dur.as_millis() / 4).unwrap_or(0);
512 buf[0] = dur;
513 InResponse::Accepted(&buf[0..1])
514 } else {
515 InResponse::Rejected
516 }
517 } else {
518 InResponse::Rejected
519 }
520 }
521 (RequestType::Class, HID_REQ_GET_PROTOCOL) => {
522 // UNSUPPORTED: Boot Protocol
523 buf[0] = 1;
524 InResponse::Accepted(&buf[0..1])
525 }
526 _ => InResponse::Rejected,
527 }
528 }
529}
diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml
index aa30f3fa9..e944c171a 100644
--- a/examples/nrf/Cargo.toml
+++ b/examples/nrf/Cargo.toml
@@ -6,13 +6,14 @@ version = "0.1.0"
6 6
7[features] 7[features]
8default = ["nightly"] 8default = ["nightly"]
9nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embassy-usb-serial"] 9nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embassy-usb-serial", "embassy-usb-hid"]
10 10
11[dependencies] 11[dependencies]
12embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] } 12embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] }
13embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } 13embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] }
14embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true } 14embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true }
15embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"], optional = true } 15embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"], optional = true }
16embassy-usb-hid = { version = "0.1.0", path = "../../embassy-usb-hid", features = ["defmt"], optional = true }
16 17
17defmt = "0.3" 18defmt = "0.3"
18defmt-rtt = "0.3" 19defmt-rtt = "0.3"
@@ -23,4 +24,5 @@ panic-probe = { version = "0.3", features = ["print-defmt"] }
23futures = { version = "0.3.17", default-features = false, features = ["async-await"] } 24futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
24rand = { version = "0.8.4", default-features = false } 25rand = { version = "0.8.4", default-features = false }
25embedded-storage = "0.3.0" 26embedded-storage = "0.3.0"
26 27usbd-hid = "0.5.2"
28serde = { 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 000000000..1fd056d00
--- /dev/null
+++ b/examples/nrf/src/bin/usb_hid.rs
@@ -0,0 +1,131 @@
1#![no_std]
2#![no_main]
3#![feature(generic_associated_types)]
4#![feature(type_alias_impl_trait)]
5
6#[path = "../example_common.rs"]
7mod example_common;
8
9use core::mem;
10use defmt::*;
11use embassy::executor::Spawner;
12use embassy::time::{Duration, Timer};
13use embassy_nrf::interrupt;
14use embassy_nrf::pac;
15use embassy_nrf::usb::Driver;
16use embassy_nrf::Peripherals;
17use embassy_usb::{Config, UsbDeviceBuilder};
18use embassy_usb_hid::{HidClass, ReportId, RequestHandler, State};
19use futures::future::join;
20use usbd_hid::descriptor::{MouseReport, SerializedDescriptor};
21
22#[embassy::main]
23async fn main(_spawner: Spawner, p: Peripherals) {
24 let clock: pac::CLOCK = unsafe { mem::transmute(()) };
25 let power: pac::POWER = unsafe { mem::transmute(()) };
26
27 info!("Enabling ext hfosc...");
28 clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) });
29 while clock.events_hfclkstarted.read().bits() != 1 {}
30
31 info!("Waiting for vbus...");
32 while !power.usbregstatus.read().vbusdetect().is_vbus_present() {}
33 info!("vbus OK");
34
35 // Create the driver, from the HAL.
36 let irq = interrupt::take!(USBD);
37 let driver = Driver::new(p.USBD, irq);
38
39 // Create embassy-usb Config
40 let mut config = Config::new(0xc0de, 0xcafe);
41 config.manufacturer = Some("Tactile Engineering");
42 config.product = Some("Testy");
43 config.serial_number = Some("12345678");
44 config.max_power = 100;
45
46 // Create embassy-usb DeviceBuilder using the driver and config.
47 // It needs some buffers for building the descriptors.
48 let mut device_descriptor = [0; 256];
49 let mut config_descriptor = [0; 256];
50 let mut bos_descriptor = [0; 256];
51 let mut control_buf = [0; 16];
52 let request_handler = MyRequestHandler {};
53
54 let mut state = State::<5, 0, 0>::new();
55
56 let mut builder = UsbDeviceBuilder::new(
57 driver,
58 config,
59 &mut device_descriptor,
60 &mut config_descriptor,
61 &mut bos_descriptor,
62 &mut control_buf,
63 );
64
65 // Create classes on the builder.
66 // let mut class = CdcAcmClass::new(&mut builder, &mut state, 64);
67 let mut hid = HidClass::new(
68 &mut builder,
69 &mut state,
70 MouseReport::desc(),
71 Some(&request_handler),
72 60,
73 );
74
75 // Build the builder.
76 let mut usb = builder.build();
77
78 // Run the USB device.
79 let usb_fut = usb.run();
80
81 // Do stuff with the class!
82 let hid_fut = async {
83 loop {
84 Timer::after(Duration::from_millis(500)).await;
85 hid.input()
86 .serialize(&MouseReport {
87 buttons: 0,
88 x: 0,
89 y: 4,
90 wheel: 0,
91 pan: 0,
92 })
93 .await
94 .unwrap();
95
96 Timer::after(Duration::from_millis(500)).await;
97 hid.input()
98 .serialize(&MouseReport {
99 buttons: 0,
100 x: 0,
101 y: -4,
102 wheel: 0,
103 pan: 0,
104 })
105 .await
106 .unwrap();
107 }
108 };
109
110 // Run everything concurrently.
111 // If we had made everything `'static` above instead, we could do this using separate tasks instead.
112 join(usb_fut, hid_fut).await;
113}
114
115struct MyRequestHandler {}
116
117impl RequestHandler for MyRequestHandler {
118 fn get_report(&self, id: ReportId, _buf: &mut [u8]) -> Option<usize> {
119 info!("Get report for {:?}", id);
120 None
121 }
122
123 fn set_idle(&self, id: Option<ReportId>, dur: Duration) {
124 info!("Set idle rate for {:?} to {:?}", id, dur);
125 }
126
127 fn get_idle(&self, id: Option<ReportId>) -> Option<Duration> {
128 info!("Get idle rate for {:?}", id);
129 None
130 }
131}