diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs index 5e2f585f..b67201e6 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb.rs @@ -10,7 +10,7 @@ use embassy::util::Unborrow; use embassy::waitqueue::AtomicWaker; use embassy_hal_common::unborrow; use embassy_usb::control::Request; -use embassy_usb::driver::{self, EndpointError, Event}; +use embassy_usb::driver::{self, EndpointError, Event, Unsupported}; use embassy_usb::types::{EndpointAddress, EndpointInfo, EndpointType, UsbDirection}; use futures::future::poll_fn; use futures::Future; @@ -140,7 +140,6 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { type EndpointIn = Endpoint<'d, T, In>; type ControlPipe = ControlPipe<'d, T>; type Bus = Bus<'d, T>; - type EnableFuture = impl Future + 'd; fn alloc_endpoint_in( &mut self, @@ -192,7 +191,28 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { }) } - fn enable(self) -> Self::EnableFuture { + fn into_bus(self) -> Self::Bus { + Bus { + phantom: PhantomData, + alloc_in: self.alloc_in, + alloc_out: self.alloc_out, + } + } +} + +pub struct Bus<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + alloc_in: Allocator, + alloc_out: Allocator, +} + +impl<'d, T: Instance> driver::Bus for Bus<'d, T> { + type EnableFuture<'a> = impl Future + 'a where Self: 'a; + type DisableFuture<'a> = impl Future + 'a where Self: 'a; + type PollFuture<'a> = impl Future + 'a where Self: 'a; + type RemoteWakeupFuture<'a> = impl Future> + 'a where Self: 'a; + + fn enable(&mut self) -> Self::EnableFuture<'_> { async move { let regs = T::regs(); @@ -226,33 +246,25 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { // Enable the USB pullup, allowing enumeration. regs.usbpullup.write(|w| w.connect().enabled()); trace!("enabled"); - - Bus { - phantom: PhantomData, - alloc_in: self.alloc_in, - alloc_out: self.alloc_out, - } } } -} -pub struct Bus<'d, T: Instance> { - phantom: PhantomData<&'d mut T>, - alloc_in: Allocator, - alloc_out: Allocator, -} - -impl<'d, T: Instance> driver::Bus for Bus<'d, T> { - type PollFuture<'a> = impl Future + 'a where Self: 'a; + fn disable(&mut self) -> Self::DisableFuture<'_> { + async move { + let regs = T::regs(); + regs.enable.write(|x| x.enable().disabled()); + } + } fn poll<'a>(&'a mut self) -> Self::PollFuture<'a> { - poll_fn(|cx| { + poll_fn(move |cx| { BUS_WAKER.register(cx.waker()); let regs = T::regs(); if regs.events_usbreset.read().bits() != 0 { regs.events_usbreset.reset(); regs.intenset.write(|w| w.usbreset().set()); + self.set_configured(false); return Poll::Ready(Event::Reset); } @@ -268,11 +280,12 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { } if r.suspend().bit() { regs.eventcause.write(|w| w.suspend().set_bit()); - trace!("USB event: suspend"); + regs.lowpower.write(|w| w.lowpower().low_power()); + return Poll::Ready(Event::Suspend); } if r.resume().bit() { regs.eventcause.write(|w| w.resume().set_bit()); - trace!("USB event: resume"); + return Poll::Ready(Event::Resume); } if r.ready().bit() { regs.eventcause.write(|w| w.ready().set_bit()); @@ -283,11 +296,6 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { }) } - #[inline] - fn reset(&mut self) { - self.set_configured(false); - } - #[inline] fn set_configured(&mut self, configured: bool) { let regs = T::regs(); @@ -343,18 +351,43 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { } #[inline] - fn suspend(&mut self) { - let regs = T::regs(); - regs.lowpower.write(|w| w.lowpower().low_power()); - } + fn remote_wakeup(&mut self) -> Self::RemoteWakeupFuture<'_> { + async move { + let regs = T::regs(); - #[inline] - fn resume(&mut self) { - let regs = T::regs(); + if regs.lowpower.read().lowpower().is_low_power() { + errata::pre_wakeup(); - errata::pre_wakeup(); + regs.lowpower.write(|w| w.lowpower().force_normal()); - regs.lowpower.write(|w| w.lowpower().force_normal()); + poll_fn(|cx| { + BUS_WAKER.register(cx.waker()); + let regs = T::regs(); + let r = regs.eventcause.read(); + + if regs.events_usbreset.read().bits() != 0 { + Poll::Ready(()) + } else if r.resume().bit() { + Poll::Ready(()) + } else if r.usbwuallowed().bit() { + regs.eventcause.write(|w| w.usbwuallowed().set_bit()); + + regs.dpdmvalue.write(|w| w.state().resume()); + regs.tasks_dpdmdrive + .write(|w| w.tasks_dpdmdrive().set_bit()); + + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + errata::post_wakeup(); + } + + Ok(()) + } } } @@ -834,17 +867,20 @@ macro_rules! impl_usb { mod errata { /// Writes `val` to `addr`. Used to apply Errata workarounds. + #[cfg(any(feature = "nrf52840", feature = "nrf52833", feature = "nrf52820"))] unsafe fn poke(addr: u32, val: u32) { (addr as *mut u32).write_volatile(val); } /// Reads 32 bits from `addr`. + #[cfg(feature = "nrf52840")] unsafe fn peek(addr: u32) -> u32 { (addr as *mut u32).read_volatile() } pub fn pre_enable() { // Works around Erratum 187 on chip revisions 1 and 2. + #[cfg(any(feature = "nrf52840", feature = "nrf52833", feature = "nrf52820"))] unsafe { poke(0x4006EC00, 0x00009375); poke(0x4006ED14, 0x00000003); @@ -858,6 +894,7 @@ mod errata { post_wakeup(); // Works around Erratum 187 on chip revisions 1 and 2. + #[cfg(any(feature = "nrf52840", feature = "nrf52833", feature = "nrf52820"))] unsafe { poke(0x4006EC00, 0x00009375); poke(0x4006ED14, 0x00000000); @@ -868,6 +905,7 @@ mod errata { pub fn pre_wakeup() { // Works around Erratum 171 on chip revisions 1 and 2. + #[cfg(feature = "nrf52840")] unsafe { if peek(0x4006EC00) == 0x00000000 { poke(0x4006EC00, 0x00009375); @@ -881,6 +919,7 @@ mod errata { pub fn post_wakeup() { // Works around Erratum 171 on chip revisions 1 and 2. + #[cfg(feature = "nrf52840")] unsafe { if peek(0x4006EC00) == 0x00000000 { poke(0x4006EC00, 0x00009375); diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index 4bbcd3e5..48667205 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -4,6 +4,7 @@ use super::control::ControlHandler; use super::descriptor::{BosWriter, DescriptorWriter}; use super::driver::{Driver, EndpointAllocError}; use super::types::*; +use super::DeviceStateHandler; use super::UsbDevice; use super::MAX_INTERFACE_COUNT; @@ -119,6 +120,7 @@ impl<'a> Config<'a> { /// Used to build new [`UsbDevice`]s. pub struct UsbDeviceBuilder<'d, D: Driver<'d>> { config: Config<'d>, + handler: Option<&'d dyn DeviceStateHandler>, interfaces: Vec<(u8, &'d mut dyn ControlHandler), MAX_INTERFACE_COUNT>, control_buf: &'d mut [u8], @@ -145,6 +147,7 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { config_descriptor_buf: &'d mut [u8], bos_descriptor_buf: &'d mut [u8], control_buf: &'d mut [u8], + handler: Option<&'d dyn DeviceStateHandler>, ) -> Self { // Magic values specified in USB-IF ECN on IADs. if config.composite_with_iads @@ -174,6 +177,7 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { UsbDeviceBuilder { driver, + handler, config, interfaces: Vec::new(), control_buf, @@ -187,20 +191,20 @@ impl<'d, D: Driver<'d>> UsbDeviceBuilder<'d, D> { } /// Creates the [`UsbDevice`] instance with the configuration in this builder. - pub async fn build(mut self) -> UsbDevice<'d, D> { + pub fn build(mut self) -> UsbDevice<'d, D> { self.config_descriptor.end_configuration(); self.bos_descriptor.end_bos(); UsbDevice::build( self.driver, self.config, + self.handler, self.device_descriptor.into_buf(), self.config_descriptor.into_buf(), self.bos_descriptor.writer.into_buf(), self.interfaces, self.control_buf, ) - .await } /// Allocates a new interface number. diff --git a/embassy-usb/src/driver.rs b/embassy-usb/src/driver.rs index 875ceafc..cedd349f 100644 --- a/embassy-usb/src/driver.rs +++ b/embassy-usb/src/driver.rs @@ -11,7 +11,6 @@ pub trait Driver<'a> { type EndpointIn: EndpointIn + 'a; type ControlPipe: ControlPipe + 'a; type Bus: Bus + 'a; - type EnableFuture: Future + 'a; /// Allocates an endpoint and specified endpoint parameters. This method is called by the device /// and class implementations to allocate endpoints, and can only be called before @@ -47,7 +46,7 @@ pub trait Driver<'a> { /// Enables and initializes the USB peripheral. Soon after enabling the device will be reset, so /// there is no need to perform a USB reset in this method. - fn enable(self) -> Self::EnableFuture; + fn into_bus(self) -> Self::Bus; /// Indicates that `set_device_address` must be called before accepting the corresponding /// control transfer, not after. @@ -57,19 +56,28 @@ pub trait Driver<'a> { } pub trait Bus { + type EnableFuture<'a>: Future + 'a + where + Self: 'a; + type DisableFuture<'a>: Future + 'a + where + Self: 'a; type PollFuture<'a>: Future + 'a where Self: 'a; + type RemoteWakeupFuture<'a>: Future> + 'a + where + Self: 'a; + + /// Enables the USB peripheral. Soon after enabling the device will be reset, so + /// there is no need to perform a USB reset in this method. + fn enable(&mut self) -> Self::EnableFuture<'_>; + + /// Disables and powers down the USB peripheral. + fn disable(&mut self) -> Self::DisableFuture<'_>; fn poll<'a>(&'a mut self) -> Self::PollFuture<'a>; - /// Called when the host resets the device. This will be soon called after - /// [`poll`](crate::device::UsbDevice::poll) returns [`PollResult::Reset`]. This method should - /// reset the state of all endpoints and peripheral flags back to a state suitable for - /// enumeration, as well as ensure that all endpoints previously allocated with alloc_ep are - /// initialized as specified. - fn reset(&mut self); - /// Sets the device USB address to `addr`. fn set_device_address(&mut self, addr: u8); @@ -83,17 +91,6 @@ pub trait Bus { /// Gets whether the STALL condition is set for an endpoint. Only used during control transfers. fn is_stalled(&mut self, ep_addr: EndpointAddress) -> bool; - /// Causes the USB peripheral to enter USB suspend mode, lowering power consumption and - /// preparing to detect a USB wakeup event. This will be called after - /// [`poll`](crate::device::UsbDevice::poll) returns [`PollResult::Suspend`]. The device will - /// continue be polled, and it shall return a value other than `Suspend` from `poll` when it no - /// longer detects the suspend condition. - fn suspend(&mut self); - - /// Resumes from suspend mode. This may only be called after the peripheral has been previously - /// suspended. - fn resume(&mut self); - /// Simulates a disconnect from the USB bus, causing the host to reset and re-enumerate the /// device. /// @@ -106,6 +103,14 @@ pub trait Bus { fn force_reset(&mut self) -> Result<(), Unsupported> { Err(Unsupported) } + + /// Initiates a remote wakeup of the host by the device. + /// + /// # Errors + /// + /// * [`Unsupported`](crate::UsbError::Unsupported) - This UsbBus implementation doesn't support + /// remote wakeup or it has not been enabled at creation time. + fn remote_wakeup(&mut self) -> Self::RemoteWakeupFuture<'_>; } pub trait Endpoint { diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index e98cbdee..baeca099 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -10,15 +10,14 @@ pub mod control; pub mod descriptor; pub mod driver; pub mod types; -mod util; +use embassy::util::{select, Either}; use heapless::Vec; use self::control::*; use self::descriptor::*; use self::driver::{Bus, Driver, Event}; use self::types::*; -use self::util::*; pub use self::builder::Config; pub use self::builder::UsbDeviceBuilder; @@ -28,8 +27,12 @@ pub use self::builder::UsbDeviceBuilder; /// In general class traffic is only possible in the `Configured` state. #[repr(u8)] #[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum UsbDeviceState { - /// The USB device has just been created or reset. + /// The USB device is disabled. + Disabled, + + /// The USB device has just been enabled or reset. Default, /// The USB device has received an address from the host. @@ -37,9 +40,19 @@ pub enum UsbDeviceState { /// The USB device has been configured and is fully functional. Configured, +} - /// The USB device has been suspended by the host or it has been unplugged from the USB bus. - Suspend, +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RemoteWakeupError { + InvalidState, + Unsupported, +} + +impl From for RemoteWakeupError { + fn from(_: driver::Unsupported) -> Self { + RemoteWakeupError::Unsupported + } } /// The bConfiguration value for the not configured state. @@ -53,8 +66,30 @@ pub const DEFAULT_ALTERNATE_SETTING: u8 = 0; pub const MAX_INTERFACE_COUNT: usize = 4; +/// A handler trait for changes in the device state of the [UsbDevice]. +pub trait DeviceStateHandler { + /// Called when the USB device has been enabled or disabled. + fn enabled(&self, _enabled: bool) {} + + /// Called when the host resets the device. + fn reset(&self) {} + + /// Called when the host has set the address of the device to `addr`. + fn addressed(&self, _addr: u8) {} + + /// Called when the host has enabled or disabled the configuration of the device. + fn configured(&self, _configured: bool) {} + + /// Called when the bus has entered or exited the suspend state. + fn suspended(&self, _suspended: bool) {} + + /// Called when remote wakeup feature is enabled or disabled. + fn remote_wakeup_enabled(&self, _enabled: bool) {} +} + pub struct UsbDevice<'d, D: Driver<'d>> { bus: D::Bus, + handler: Option<&'d dyn DeviceStateHandler>, control: ControlPipe, config: Config<'d>, @@ -64,6 +99,7 @@ pub struct UsbDevice<'d, D: Driver<'d>> { control_buf: &'d mut [u8], device_state: UsbDeviceState, + suspended: bool, remote_wakeup_enabled: bool, self_powered: bool, pending_address: u8, @@ -72,9 +108,10 @@ pub struct UsbDevice<'d, D: Driver<'d>> { } impl<'d, D: Driver<'d>> UsbDevice<'d, D> { - pub(crate) async fn build( + pub(crate) fn build( mut driver: D, config: Config<'d>, + handler: Option<&'d dyn DeviceStateHandler>, device_descriptor: &'d [u8], config_descriptor: &'d [u8], bos_descriptor: &'d [u8], @@ -87,17 +124,19 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { // Enable the USB bus. // This prevent further allocation by consuming the driver. - let bus = driver.enable().await; + let bus = driver.into_bus(); Self { bus, config, + handler, control: ControlPipe::new(control), device_descriptor, config_descriptor, bos_descriptor, control_buf, - device_state: UsbDeviceState::Default, + device_state: UsbDeviceState::Disabled, + suspended: false, remote_wakeup_enabled: false, self_powered: false, pending_address: 0, @@ -105,34 +144,46 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } } - pub async fn run(&mut self) { + /// Runs the `UsbDevice` forever. + /// + /// This future may leave the bus in an invalid state if it is dropped. + /// After dropping the future, [`UsbDevice::disable()`] should be called + /// before calling any other `UsbDevice` methods to fully reset the + /// peripheral. + pub async fn run(&mut self) -> ! { + loop { + self.run_until_suspend().await; + self.wait_resume().await; + } + } + + /// Runs the `UsbDevice` until the bus is suspended. + /// + /// This future may leave the bus in an invalid state if it is dropped. + /// After dropping the future, [`UsbDevice::disable()`] should be called + /// before calling any other `UsbDevice` methods to fully reset the + /// peripheral. + pub async fn run_until_suspend(&mut self) -> () { + if self.device_state == UsbDeviceState::Disabled { + self.bus.enable().await; + self.device_state = UsbDeviceState::Default; + + if let Some(h) = &self.handler { + h.enabled(true); + } + } + loop { let control_fut = self.control.setup(); let bus_fut = self.bus.poll(); match select(bus_fut, control_fut).await { - Either::Left(evt) => match evt { - Event::Reset => { - trace!("usb: reset"); - self.bus.reset(); - - self.device_state = UsbDeviceState::Default; - self.remote_wakeup_enabled = false; - self.pending_address = 0; - - for (_, h) in self.interfaces.iter_mut() { - h.reset(); - } + Either::First(evt) => { + self.handle_bus_event(evt); + if self.suspended { + return; } - Event::Resume => { - trace!("usb: resume"); - } - Event::Suspend => { - trace!("usb: suspend"); - self.bus.suspend(); - self.device_state = UsbDeviceState::Suspend; - } - }, - Either::Right(req) => match req { + } + Either::Second(req) => match req { Setup::DataIn(req, stage) => self.handle_control_in(req, stage).await, Setup::DataOut(req, stage) => self.handle_control_out(req, stage).await, }, @@ -140,6 +191,87 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { } } + /// Disables the USB peripheral. + pub async fn disable(&mut self) { + if self.device_state != UsbDeviceState::Disabled { + self.bus.disable().await; + self.device_state = UsbDeviceState::Disabled; + self.suspended = false; + self.remote_wakeup_enabled = false; + + if let Some(h) = &self.handler { + h.enabled(false); + } + } + } + + /// Waits for a resume condition on the USB bus. + /// + /// This future is cancel-safe. + pub async fn wait_resume(&mut self) { + while self.suspended { + let evt = self.bus.poll().await; + self.handle_bus_event(evt); + } + } + + /// Initiates a device remote wakeup on the USB bus. + /// + /// If the bus is not suspended or remote wakeup is not enabled, an error + /// will be returned. + /// + /// This future may leave the bus in an inconsistent state if dropped. + /// After dropping the future, [`UsbDevice::disable()`] should be called + /// before calling any other `UsbDevice` methods to fully reset the peripheral. + pub async fn remote_wakeup(&mut self) -> Result<(), RemoteWakeupError> { + if self.suspended && self.remote_wakeup_enabled { + self.bus.remote_wakeup().await?; + self.suspended = false; + + if let Some(h) = &self.handler { + h.suspended(false); + } + + Ok(()) + } else { + Err(RemoteWakeupError::InvalidState) + } + } + + fn handle_bus_event(&mut self, evt: Event) { + match evt { + Event::Reset => { + trace!("usb: reset"); + self.device_state = UsbDeviceState::Default; + self.suspended = false; + self.remote_wakeup_enabled = false; + self.pending_address = 0; + + for (_, h) in self.interfaces.iter_mut() { + h.reset(); + } + + if let Some(h) = &self.handler { + h.reset(); + } + } + Event::Resume => { + trace!("usb: resume"); + self.suspended = false; + if let Some(h) = &self.handler { + h.suspended(false); + } + } + Event::Suspend => { + trace!("usb: suspend"); + self.suspended = true; + if let Some(h) = &self.handler { + h.suspended(true); + } + } + } + } + async fn handle_control_out(&mut self, req: Request, stage: DataOutStage) { const CONFIGURATION_NONE_U16: u16 = CONFIGURATION_NONE as u16; const CONFIGURATION_VALUE_U16: u16 = CONFIGURATION_VALUE as u16; @@ -156,20 +288,33 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { (RequestType::Standard, Recipient::Device) => match (req.request, req.value) { (Request::CLEAR_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => { self.remote_wakeup_enabled = false; + if let Some(h) = &self.handler { + h.remote_wakeup_enabled(false); + } self.control.accept(stage) } (Request::SET_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => { self.remote_wakeup_enabled = true; + if let Some(h) = &self.handler { + h.remote_wakeup_enabled(true); + } self.control.accept(stage) } (Request::SET_ADDRESS, addr @ 1..=127) => { self.pending_address = addr as u8; self.bus.set_device_address(self.pending_address); + self.device_state = UsbDeviceState::Addressed; + if let Some(h) = &self.handler { + h.addressed(self.pending_address); + } self.control.accept(stage) } (Request::SET_CONFIGURATION, CONFIGURATION_VALUE_U16) => { self.device_state = UsbDeviceState::Configured; self.bus.set_configured(true); + if let Some(h) = &self.handler { + h.configured(true); + } self.control.accept(stage) } (Request::SET_CONFIGURATION, CONFIGURATION_NONE_U16) => match self.device_state { @@ -177,6 +322,9 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { _ => { self.device_state = UsbDeviceState::Addressed; self.bus.set_configured(false); + if let Some(h) = &self.handler { + h.configured(false); + } self.control.accept(stage) } }, diff --git a/embassy-usb/src/util.rs b/embassy-usb/src/util.rs deleted file mode 100644 index 18cc875c..00000000 --- a/embassy-usb/src/util.rs +++ /dev/null @@ -1,45 +0,0 @@ -use core::future::Future; -use core::pin::Pin; -use core::task::{Context, Poll}; - -#[derive(Debug, Clone)] -pub enum Either { - Left(A), - Right(B), -} - -pub fn select(a: A, b: B) -> Select -where - A: Future, - B: Future, -{ - Select { a, b } -} - -pub struct Select { - a: A, - b: B, -} - -impl Unpin for Select {} - -impl Future for Select -where - A: Future, - B: Future, -{ - type Output = Either; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = unsafe { self.get_unchecked_mut() }; - let a = unsafe { Pin::new_unchecked(&mut this.a) }; - let b = unsafe { Pin::new_unchecked(&mut this.b) }; - match a.poll(cx) { - Poll::Ready(x) => Poll::Ready(Either::Left(x)), - Poll::Pending => match b.poll(cx) { - Poll::Ready(x) => Poll::Ready(Either::Right(x)), - Poll::Pending => Poll::Pending, - }, - } - } -} diff --git a/examples/nrf/src/bin/usb_hid_keyboard.rs b/examples/nrf/src/bin/usb_hid_keyboard.rs index 0812697e..5f03f512 100644 --- a/examples/nrf/src/bin/usb_hid_keyboard.rs +++ b/examples/nrf/src/bin/usb_hid_keyboard.rs @@ -4,16 +4,20 @@ #![feature(type_alias_impl_trait)] use core::mem; +use core::sync::atomic::{AtomicBool, Ordering}; use defmt::*; +use embassy::channel::Signal; use embassy::executor::Spawner; +use embassy::interrupt::InterruptExt; use embassy::time::Duration; +use embassy::util::{select, select3, Either, Either3}; use embassy_nrf::gpio::{Input, Pin, Pull}; use embassy_nrf::interrupt; use embassy_nrf::pac; use embassy_nrf::usb::Driver; use embassy_nrf::Peripherals; use embassy_usb::control::OutResponse; -use embassy_usb::{Config, UsbDeviceBuilder}; +use embassy_usb::{Config, DeviceStateHandler, UsbDeviceBuilder}; use embassy_usb_hid::{HidClass, ReportId, RequestHandler, State}; use futures::future::join; use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; @@ -21,6 +25,25 @@ use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; use defmt_rtt as _; // global logger use panic_probe as _; +static ENABLE_USB: Signal = Signal::new(); +static SUSPENDED: AtomicBool = AtomicBool::new(false); + +fn on_power_interrupt(_: *mut ()) { + let regs = unsafe { &*pac::POWER::ptr() }; + + if regs.events_usbdetected.read().bits() != 0 { + regs.events_usbdetected.reset(); + info!("Vbus detected, enabling USB..."); + ENABLE_USB.signal(true); + } + + if regs.events_usbremoved.read().bits() != 0 { + regs.events_usbremoved.reset(); + info!("Vbus removed, disabling USB..."); + ENABLE_USB.signal(false); + } +} + #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { let clock: pac::CLOCK = unsafe { mem::transmute(()) }; @@ -30,10 +53,6 @@ async fn main(_spawner: Spawner, p: Peripherals) { 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); @@ -45,6 +64,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { config.serial_number = Some("12345678"); config.max_power = 100; config.max_packet_size_0 = 64; + config.supports_remote_wakeup = true; // Create embassy-usb DeviceBuilder using the driver and config. // It needs some buffers for building the descriptors. @@ -53,6 +73,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut bos_descriptor = [0; 256]; let mut control_buf = [0; 16]; let request_handler = MyRequestHandler {}; + let device_state_handler = MyDeviceStateHandler::new(); let mut state = State::<8, 1>::new(); @@ -63,6 +84,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, + Some(&device_state_handler), ); // Create classes on the builder. @@ -76,10 +98,40 @@ async fn main(_spawner: Spawner, p: Peripherals) { ); // Build the builder. - let mut usb = builder.build().await; + let mut usb = builder.build(); + + let remote_wakeup = Signal::new(); // Run the USB device. - let usb_fut = usb.run(); + let usb_fut = async { + enable_command().await; + loop { + match select(usb.run_until_suspend(), ENABLE_USB.wait()).await { + Either::First(_) => {} + Either::Second(enable) => { + if enable { + warn!("Enable when already enabled!"); + } else { + usb.disable().await; + enable_command().await; + } + } + } + + match select3(usb.wait_resume(), ENABLE_USB.wait(), remote_wakeup.wait()).await { + Either3::First(_) => (), + Either3::Second(enable) => { + if enable { + warn!("Enable when already enabled!"); + } else { + usb.disable().await; + enable_command().await; + } + } + Either3::Third(_) => unwrap!(usb.remote_wakeup().await), + } + } + }; let mut button = Input::new(p.P0_11.degrade(), Pull::Up); @@ -90,16 +142,22 @@ async fn main(_spawner: Spawner, p: Peripherals) { loop { button.wait_for_low().await; info!("PRESSED"); - let report = KeyboardReport { - keycodes: [4, 0, 0, 0, 0, 0], - leds: 0, - modifier: 0, - reserved: 0, - }; - match hid_in.serialize(&report).await { - Ok(()) => {} - Err(e) => warn!("Failed to send report: {:?}", e), - }; + + if SUSPENDED.load(Ordering::Acquire) { + info!("Triggering remote wakeup"); + remote_wakeup.signal(()); + } else { + let report = KeyboardReport { + keycodes: [4, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + match hid_in.serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + } button.wait_for_high().await; info!("RELEASED"); @@ -119,11 +177,31 @@ async fn main(_spawner: Spawner, p: Peripherals) { let out_fut = async { hid_out.run(false, &request_handler).await; }; + + let power_irq = interrupt::take!(POWER_CLOCK); + power_irq.set_handler(on_power_interrupt); + power_irq.unpend(); + power_irq.enable(); + + power + .intenset + .write(|w| w.usbdetected().set().usbremoved().set()); + // Run everything concurrently. // If we had made everything `'static` above instead, we could do this using separate tasks instead. join(usb_fut, join(in_fut, out_fut)).await; } +async fn enable_command() { + loop { + if ENABLE_USB.wait().await { + break; + } else { + warn!("Received disable signal when already disabled!"); + } + } +} + struct MyRequestHandler {} impl RequestHandler for MyRequestHandler { @@ -146,3 +224,64 @@ impl RequestHandler for MyRequestHandler { None } } + +struct MyDeviceStateHandler { + configured: AtomicBool, +} + +impl MyDeviceStateHandler { + fn new() -> Self { + MyDeviceStateHandler { + configured: AtomicBool::new(false), + } + } +} + +impl DeviceStateHandler for MyDeviceStateHandler { + fn enabled(&self, enabled: bool) { + self.configured.store(false, Ordering::Relaxed); + SUSPENDED.store(false, Ordering::Release); + if enabled { + info!("Device enabled"); + } else { + info!("Device disabled"); + } + } + + fn reset(&self) { + self.configured.store(false, Ordering::Relaxed); + info!("Bus reset, the Vbus current limit is 100mA"); + } + + fn addressed(&self, addr: u8) { + self.configured.store(false, Ordering::Relaxed); + info!("USB address set to: {}", addr); + } + + fn configured(&self, configured: bool) { + self.configured.store(configured, Ordering::Relaxed); + if configured { + info!( + "Device configured, it may now draw up to the configured current limit from Vbus." + ) + } else { + info!("Device is no longer configured, the Vbus current limit is 100mA."); + } + } + + fn suspended(&self, suspended: bool) { + if suspended { + info!("Device suspended, the Vbus current limit is 500µA (or 2.5mA for high-power devices with remote wakeup enabled)."); + SUSPENDED.store(true, Ordering::Release); + } else { + SUSPENDED.store(false, Ordering::Release); + if self.configured.load(Ordering::Relaxed) { + info!( + "Device resumed, it may now draw up to the configured current limit from Vbus" + ); + } else { + info!("Device resumed, the Vbus current limit is 100mA"); + } + } + } +} diff --git a/examples/nrf/src/bin/usb_hid_mouse.rs b/examples/nrf/src/bin/usb_hid_mouse.rs index ca938382..fe27e76f 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) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, + None, ); // Create classes on the builder. @@ -74,7 +75,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { ); // Build the builder. - let mut usb = builder.build().await; + let mut usb = builder.build(); // Run the USB device. let usb_fut = usb.run(); diff --git a/examples/nrf/src/bin/usb_serial.rs b/examples/nrf/src/bin/usb_serial.rs index 68432283..987cc413 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) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, + None, ); // Create classes on the builder. let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); // Build the builder. - let mut usb = builder.build().await; + let mut usb = builder.build(); // Run the USB device. 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 bfb09014..5fcb0e05 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) { &mut res.config_descriptor, &mut res.bos_descriptor, &mut res.control_buf, + None, ); // Create classes on the builder. let class = CdcAcmClass::new(&mut builder, &mut res.serial_state, 64); // Build the builder. - let usb = builder.build().await; + let usb = builder.build(); unwrap!(spawner.spawn(usb_task(usb))); unwrap!(spawner.spawn(echo_task(class)));