diff options
| author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2022-04-14 15:16:55 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2022-04-14 15:16:55 +0000 |
| commit | 3a90a8eb4a5ef61aef034025ac882255c94260dc (patch) | |
| tree | a40a8f9bb7b847d168a5b4c725d678d59d4d7823 /examples | |
| parent | 391fdc097ec1691cfab6a3db5c3992f6f2e6da8e (diff) | |
| parent | b0725c14d36cb9c508cb9fcec5b92ca9e0148583 (diff) | |
Merge #711
711: Add DeviceStateHandler, DeviceCommand channel, and remote wakeup support r=Dirbaio a=alexmoon
Apologies for the size of this PR. Once I started getting into the Vbus power management side of my device I found a couple of areas of functionality missing from embassy-usb. Specifically, I need the application to be able to respond to changes in the USB device state in order to properly control the amount of power I'm drawing from Vbus. I also wanted to enable remote wakeup support for my device.
In order to enable device state monitoring, I've created a `DeviceStateHandler` trait and made it possible to pass in an optional reference a handler implementing that trait when creating the `UsbDeviceBuilder`.
Remote wakeup required a way to send commands to the bus which is exclusively owned by the `UsbDevice::run` method. This is the same problem we were discussing for enabling/disabling the device on Vbus power events. My solution is to allow an optional `Channel` to be provided to the `UsbDeviceBuilder` (via `UsbDeviceBuilder::new_with_channel`), allowing the application to send commands into the `run` method. Right now it supports enable, disable and remote wakeup commands.
Since there's now a way to dynamically enable and disable the device, I also added `Config::start_enabled` to control whether or not the `UsbDevice` should start in the enabled state. That also allowed me to make `UsbDeviceBuilder::build` sync again and move enabling the bus into `UsbDevice::run`.
This led to a few driver changes:
1. `Driver::enable` became `Driver::into_bus`
2. `Bus::enable`, `Bus::disable`, and `Bus::remote_wakeup` were added
3. I removed `Bus::reset`, `Bus::suspend`, and `Bus::resume` because they were only ever called based on the result of `Bus::poll`. It made more sense to me to have `Bus::poll` handle the driver-specific state management itself.
I've updated the `usb_hid_keyboard` example to take advantage of all these additional features.
Let me know what you think.
Thanks!
Co-authored-by: alexmoon <[email protected]>
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/nrf/src/bin/usb_hid_keyboard.rs | 173 | ||||
| -rw-r--r-- | examples/nrf/src/bin/usb_hid_mouse.rs | 3 | ||||
| -rw-r--r-- | examples/nrf/src/bin/usb_serial.rs | 3 | ||||
| -rw-r--r-- | examples/nrf/src/bin/usb_serial_multitask.rs | 3 |
4 files changed, 162 insertions, 20 deletions
diff --git a/examples/nrf/src/bin/usb_hid_keyboard.rs b/examples/nrf/src/bin/usb_hid_keyboard.rs index 0812697e4..5f03f5126 100644 --- a/examples/nrf/src/bin/usb_hid_keyboard.rs +++ b/examples/nrf/src/bin/usb_hid_keyboard.rs | |||
| @@ -4,16 +4,20 @@ | |||
| 4 | #![feature(type_alias_impl_trait)] | 4 | #![feature(type_alias_impl_trait)] |
| 5 | 5 | ||
| 6 | use core::mem; | 6 | use core::mem; |
| 7 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 7 | use defmt::*; | 8 | use defmt::*; |
| 9 | use embassy::channel::Signal; | ||
| 8 | use embassy::executor::Spawner; | 10 | use embassy::executor::Spawner; |
| 11 | use embassy::interrupt::InterruptExt; | ||
| 9 | use embassy::time::Duration; | 12 | use embassy::time::Duration; |
| 13 | use embassy::util::{select, select3, Either, Either3}; | ||
| 10 | use embassy_nrf::gpio::{Input, Pin, Pull}; | 14 | use embassy_nrf::gpio::{Input, Pin, Pull}; |
| 11 | use embassy_nrf::interrupt; | 15 | use embassy_nrf::interrupt; |
| 12 | use embassy_nrf::pac; | 16 | use embassy_nrf::pac; |
| 13 | use embassy_nrf::usb::Driver; | 17 | use embassy_nrf::usb::Driver; |
| 14 | use embassy_nrf::Peripherals; | 18 | use embassy_nrf::Peripherals; |
| 15 | use embassy_usb::control::OutResponse; | 19 | use embassy_usb::control::OutResponse; |
| 16 | use embassy_usb::{Config, UsbDeviceBuilder}; | 20 | use embassy_usb::{Config, DeviceStateHandler, UsbDeviceBuilder}; |
| 17 | use embassy_usb_hid::{HidClass, ReportId, RequestHandler, State}; | 21 | use embassy_usb_hid::{HidClass, ReportId, RequestHandler, State}; |
| 18 | use futures::future::join; | 22 | use futures::future::join; |
| 19 | use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; | 23 | use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; |
| @@ -21,6 +25,25 @@ use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; | |||
| 21 | use defmt_rtt as _; // global logger | 25 | use defmt_rtt as _; // global logger |
| 22 | use panic_probe as _; | 26 | use panic_probe as _; |
| 23 | 27 | ||
| 28 | static ENABLE_USB: Signal<bool> = Signal::new(); | ||
| 29 | static SUSPENDED: AtomicBool = AtomicBool::new(false); | ||
| 30 | |||
| 31 | fn on_power_interrupt(_: *mut ()) { | ||
| 32 | let regs = unsafe { &*pac::POWER::ptr() }; | ||
| 33 | |||
| 34 | if regs.events_usbdetected.read().bits() != 0 { | ||
| 35 | regs.events_usbdetected.reset(); | ||
| 36 | info!("Vbus detected, enabling USB..."); | ||
| 37 | ENABLE_USB.signal(true); | ||
| 38 | } | ||
| 39 | |||
| 40 | if regs.events_usbremoved.read().bits() != 0 { | ||
| 41 | regs.events_usbremoved.reset(); | ||
| 42 | info!("Vbus removed, disabling USB..."); | ||
| 43 | ENABLE_USB.signal(false); | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 24 | #[embassy::main] | 47 | #[embassy::main] |
| 25 | async fn main(_spawner: Spawner, p: Peripherals) { | 48 | async fn main(_spawner: Spawner, p: Peripherals) { |
| 26 | let clock: pac::CLOCK = unsafe { mem::transmute(()) }; | 49 | let clock: pac::CLOCK = unsafe { mem::transmute(()) }; |
| @@ -30,10 +53,6 @@ async fn main(_spawner: Spawner, p: Peripherals) { | |||
| 30 | clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); | 53 | clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); |
| 31 | while clock.events_hfclkstarted.read().bits() != 1 {} | 54 | while clock.events_hfclkstarted.read().bits() != 1 {} |
| 32 | 55 | ||
| 33 | info!("Waiting for vbus..."); | ||
| 34 | while !power.usbregstatus.read().vbusdetect().is_vbus_present() {} | ||
| 35 | info!("vbus OK"); | ||
| 36 | |||
| 37 | // Create the driver, from the HAL. | 56 | // Create the driver, from the HAL. |
| 38 | let irq = interrupt::take!(USBD); | 57 | let irq = interrupt::take!(USBD); |
| 39 | let driver = Driver::new(p.USBD, irq); | 58 | let driver = Driver::new(p.USBD, irq); |
| @@ -45,6 +64,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { | |||
| 45 | config.serial_number = Some("12345678"); | 64 | config.serial_number = Some("12345678"); |
| 46 | config.max_power = 100; | 65 | config.max_power = 100; |
| 47 | config.max_packet_size_0 = 64; | 66 | config.max_packet_size_0 = 64; |
| 67 | config.supports_remote_wakeup = true; | ||
| 48 | 68 | ||
| 49 | // Create embassy-usb DeviceBuilder using the driver and config. | 69 | // Create embassy-usb DeviceBuilder using the driver and config. |
| 50 | // It needs some buffers for building the descriptors. | 70 | // It needs some buffers for building the descriptors. |
| @@ -53,6 +73,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { | |||
| 53 | let mut bos_descriptor = [0; 256]; | 73 | let mut bos_descriptor = [0; 256]; |
| 54 | let mut control_buf = [0; 16]; | 74 | let mut control_buf = [0; 16]; |
| 55 | let request_handler = MyRequestHandler {}; | 75 | let request_handler = MyRequestHandler {}; |
| 76 | let device_state_handler = MyDeviceStateHandler::new(); | ||
| 56 | 77 | ||
| 57 | let mut state = State::<8, 1>::new(); | 78 | let mut state = State::<8, 1>::new(); |
| 58 | 79 | ||
| @@ -63,6 +84,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { | |||
| 63 | &mut config_descriptor, | 84 | &mut config_descriptor, |
| 64 | &mut bos_descriptor, | 85 | &mut bos_descriptor, |
| 65 | &mut control_buf, | 86 | &mut control_buf, |
| 87 | Some(&device_state_handler), | ||
| 66 | ); | 88 | ); |
| 67 | 89 | ||
| 68 | // Create classes on the builder. | 90 | // Create classes on the builder. |
| @@ -76,10 +98,40 @@ async fn main(_spawner: Spawner, p: Peripherals) { | |||
| 76 | ); | 98 | ); |
| 77 | 99 | ||
| 78 | // Build the builder. | 100 | // Build the builder. |
| 79 | let mut usb = builder.build().await; | 101 | let mut usb = builder.build(); |
| 102 | |||
| 103 | let remote_wakeup = Signal::new(); | ||
| 80 | 104 | ||
| 81 | // Run the USB device. | 105 | // Run the USB device. |
| 82 | let usb_fut = usb.run(); | 106 | let usb_fut = async { |
| 107 | enable_command().await; | ||
| 108 | loop { | ||
| 109 | match select(usb.run_until_suspend(), ENABLE_USB.wait()).await { | ||
| 110 | Either::First(_) => {} | ||
| 111 | Either::Second(enable) => { | ||
| 112 | if enable { | ||
| 113 | warn!("Enable when already enabled!"); | ||
| 114 | } else { | ||
| 115 | usb.disable().await; | ||
| 116 | enable_command().await; | ||
| 117 | } | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | match select3(usb.wait_resume(), ENABLE_USB.wait(), remote_wakeup.wait()).await { | ||
| 122 | Either3::First(_) => (), | ||
| 123 | Either3::Second(enable) => { | ||
| 124 | if enable { | ||
| 125 | warn!("Enable when already enabled!"); | ||
| 126 | } else { | ||
| 127 | usb.disable().await; | ||
| 128 | enable_command().await; | ||
| 129 | } | ||
| 130 | } | ||
| 131 | Either3::Third(_) => unwrap!(usb.remote_wakeup().await), | ||
| 132 | } | ||
| 133 | } | ||
| 134 | }; | ||
| 83 | 135 | ||
| 84 | let mut button = Input::new(p.P0_11.degrade(), Pull::Up); | 136 | let mut button = Input::new(p.P0_11.degrade(), Pull::Up); |
| 85 | 137 | ||
| @@ -90,16 +142,22 @@ async fn main(_spawner: Spawner, p: Peripherals) { | |||
| 90 | loop { | 142 | loop { |
| 91 | button.wait_for_low().await; | 143 | button.wait_for_low().await; |
| 92 | info!("PRESSED"); | 144 | info!("PRESSED"); |
| 93 | let report = KeyboardReport { | 145 | |
| 94 | keycodes: [4, 0, 0, 0, 0, 0], | 146 | if SUSPENDED.load(Ordering::Acquire) { |
| 95 | leds: 0, | 147 | info!("Triggering remote wakeup"); |
| 96 | modifier: 0, | 148 | remote_wakeup.signal(()); |
| 97 | reserved: 0, | 149 | } else { |
| 98 | }; | 150 | let report = KeyboardReport { |
| 99 | match hid_in.serialize(&report).await { | 151 | keycodes: [4, 0, 0, 0, 0, 0], |
| 100 | Ok(()) => {} | 152 | leds: 0, |
| 101 | Err(e) => warn!("Failed to send report: {:?}", e), | 153 | modifier: 0, |
| 102 | }; | 154 | reserved: 0, |
| 155 | }; | ||
| 156 | match hid_in.serialize(&report).await { | ||
| 157 | Ok(()) => {} | ||
| 158 | Err(e) => warn!("Failed to send report: {:?}", e), | ||
| 159 | }; | ||
| 160 | } | ||
| 103 | 161 | ||
| 104 | button.wait_for_high().await; | 162 | button.wait_for_high().await; |
| 105 | info!("RELEASED"); | 163 | info!("RELEASED"); |
| @@ -119,11 +177,31 @@ async fn main(_spawner: Spawner, p: Peripherals) { | |||
| 119 | let out_fut = async { | 177 | let out_fut = async { |
| 120 | hid_out.run(false, &request_handler).await; | 178 | hid_out.run(false, &request_handler).await; |
| 121 | }; | 179 | }; |
| 180 | |||
| 181 | let power_irq = interrupt::take!(POWER_CLOCK); | ||
| 182 | power_irq.set_handler(on_power_interrupt); | ||
| 183 | power_irq.unpend(); | ||
| 184 | power_irq.enable(); | ||
| 185 | |||
| 186 | power | ||
| 187 | .intenset | ||
| 188 | .write(|w| w.usbdetected().set().usbremoved().set()); | ||
| 189 | |||
| 122 | // Run everything concurrently. | 190 | // Run everything concurrently. |
| 123 | // If we had made everything `'static` above instead, we could do this using separate tasks instead. | 191 | // If we had made everything `'static` above instead, we could do this using separate tasks instead. |
| 124 | join(usb_fut, join(in_fut, out_fut)).await; | 192 | join(usb_fut, join(in_fut, out_fut)).await; |
| 125 | } | 193 | } |
| 126 | 194 | ||
| 195 | async fn enable_command() { | ||
| 196 | loop { | ||
| 197 | if ENABLE_USB.wait().await { | ||
| 198 | break; | ||
| 199 | } else { | ||
| 200 | warn!("Received disable signal when already disabled!"); | ||
| 201 | } | ||
| 202 | } | ||
| 203 | } | ||
| 204 | |||
| 127 | struct MyRequestHandler {} | 205 | struct MyRequestHandler {} |
| 128 | 206 | ||
| 129 | impl RequestHandler for MyRequestHandler { | 207 | impl RequestHandler for MyRequestHandler { |
| @@ -146,3 +224,64 @@ impl RequestHandler for MyRequestHandler { | |||
| 146 | None | 224 | None |
| 147 | } | 225 | } |
| 148 | } | 226 | } |
| 227 | |||
| 228 | struct MyDeviceStateHandler { | ||
| 229 | configured: AtomicBool, | ||
| 230 | } | ||
| 231 | |||
| 232 | impl MyDeviceStateHandler { | ||
| 233 | fn new() -> Self { | ||
| 234 | MyDeviceStateHandler { | ||
| 235 | configured: AtomicBool::new(false), | ||
| 236 | } | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | impl DeviceStateHandler for MyDeviceStateHandler { | ||
| 241 | fn enabled(&self, enabled: bool) { | ||
| 242 | self.configured.store(false, Ordering::Relaxed); | ||
| 243 | SUSPENDED.store(false, Ordering::Release); | ||
| 244 | if enabled { | ||
| 245 | info!("Device enabled"); | ||
| 246 | } else { | ||
| 247 | info!("Device disabled"); | ||
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | fn reset(&self) { | ||
| 252 | self.configured.store(false, Ordering::Relaxed); | ||
| 253 | info!("Bus reset, the Vbus current limit is 100mA"); | ||
| 254 | } | ||
| 255 | |||
| 256 | fn addressed(&self, addr: u8) { | ||
| 257 | self.configured.store(false, Ordering::Relaxed); | ||
| 258 | info!("USB address set to: {}", addr); | ||
| 259 | } | ||
| 260 | |||
| 261 | fn configured(&self, configured: bool) { | ||
| 262 | self.configured.store(configured, Ordering::Relaxed); | ||
| 263 | if configured { | ||
| 264 | info!( | ||
| 265 | "Device configured, it may now draw up to the configured current limit from Vbus." | ||
| 266 | ) | ||
| 267 | } else { | ||
| 268 | info!("Device is no longer configured, the Vbus current limit is 100mA."); | ||
| 269 | } | ||
| 270 | } | ||
| 271 | |||
| 272 | fn suspended(&self, suspended: bool) { | ||
| 273 | if suspended { | ||
| 274 | info!("Device suspended, the Vbus current limit is 500µA (or 2.5mA for high-power devices with remote wakeup enabled)."); | ||
| 275 | SUSPENDED.store(true, Ordering::Release); | ||
| 276 | } else { | ||
| 277 | SUSPENDED.store(false, Ordering::Release); | ||
| 278 | if self.configured.load(Ordering::Relaxed) { | ||
| 279 | info!( | ||
| 280 | "Device resumed, it may now draw up to the configured current limit from Vbus" | ||
| 281 | ); | ||
| 282 | } else { | ||
| 283 | info!("Device resumed, the Vbus current limit is 100mA"); | ||
| 284 | } | ||
| 285 | } | ||
| 286 | } | ||
| 287 | } | ||
diff --git a/examples/nrf/src/bin/usb_hid_mouse.rs b/examples/nrf/src/bin/usb_hid_mouse.rs index ca9383827..fe27e76fb 100644 --- a/examples/nrf/src/bin/usb_hid_mouse.rs +++ b/examples/nrf/src/bin/usb_hid_mouse.rs | |||
| @@ -61,6 +61,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { | |||
| 61 | &mut config_descriptor, | 61 | &mut config_descriptor, |
| 62 | &mut bos_descriptor, | 62 | &mut bos_descriptor, |
| 63 | &mut control_buf, | 63 | &mut control_buf, |
| 64 | None, | ||
| 64 | ); | 65 | ); |
| 65 | 66 | ||
| 66 | // Create classes on the builder. | 67 | // Create classes on the builder. |
| @@ -74,7 +75,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { | |||
| 74 | ); | 75 | ); |
| 75 | 76 | ||
| 76 | // Build the builder. | 77 | // Build the builder. |
| 77 | let mut usb = builder.build().await; | 78 | let mut usb = builder.build(); |
| 78 | 79 | ||
| 79 | // Run the USB device. | 80 | // Run the USB device. |
| 80 | let usb_fut = usb.run(); | 81 | let usb_fut = usb.run(); |
diff --git a/examples/nrf/src/bin/usb_serial.rs b/examples/nrf/src/bin/usb_serial.rs index 684322837..987cc4139 100644 --- a/examples/nrf/src/bin/usb_serial.rs +++ b/examples/nrf/src/bin/usb_serial.rs | |||
| @@ -54,13 +54,14 @@ async fn main(_spawner: Spawner, p: Peripherals) { | |||
| 54 | &mut config_descriptor, | 54 | &mut config_descriptor, |
| 55 | &mut bos_descriptor, | 55 | &mut bos_descriptor, |
| 56 | &mut control_buf, | 56 | &mut control_buf, |
| 57 | None, | ||
| 57 | ); | 58 | ); |
| 58 | 59 | ||
| 59 | // Create classes on the builder. | 60 | // Create classes on the builder. |
| 60 | let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); | 61 | let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); |
| 61 | 62 | ||
| 62 | // Build the builder. | 63 | // Build the builder. |
| 63 | let mut usb = builder.build().await; | 64 | let mut usb = builder.build(); |
| 64 | 65 | ||
| 65 | // Run the USB device. | 66 | // Run the USB device. |
| 66 | let usb_fut = usb.run(); | 67 | let usb_fut = usb.run(); |
diff --git a/examples/nrf/src/bin/usb_serial_multitask.rs b/examples/nrf/src/bin/usb_serial_multitask.rs index bfb09014c..5fcb0e052 100644 --- a/examples/nrf/src/bin/usb_serial_multitask.rs +++ b/examples/nrf/src/bin/usb_serial_multitask.rs | |||
| @@ -79,13 +79,14 @@ async fn main(spawner: Spawner, p: Peripherals) { | |||
| 79 | &mut res.config_descriptor, | 79 | &mut res.config_descriptor, |
| 80 | &mut res.bos_descriptor, | 80 | &mut res.bos_descriptor, |
| 81 | &mut res.control_buf, | 81 | &mut res.control_buf, |
| 82 | None, | ||
| 82 | ); | 83 | ); |
| 83 | 84 | ||
| 84 | // Create classes on the builder. | 85 | // Create classes on the builder. |
| 85 | let class = CdcAcmClass::new(&mut builder, &mut res.serial_state, 64); | 86 | let class = CdcAcmClass::new(&mut builder, &mut res.serial_state, 64); |
| 86 | 87 | ||
| 87 | // Build the builder. | 88 | // Build the builder. |
| 88 | let usb = builder.build().await; | 89 | let usb = builder.build(); |
| 89 | 90 | ||
| 90 | unwrap!(spawner.spawn(usb_task(usb))); | 91 | unwrap!(spawner.spawn(usb_task(usb))); |
| 91 | unwrap!(spawner.spawn(echo_task(class))); | 92 | unwrap!(spawner.spawn(echo_task(class))); |
