diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index f2577d4f..eba46b89 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -11,6 +11,7 @@ pub mod descriptor; mod descriptor_reader; pub mod driver; pub mod types; +pub mod util; use embassy::util::{select, Either}; use heapless::Vec; diff --git a/embassy-usb/src/util.rs b/embassy-usb/src/util.rs new file mode 100644 index 00000000..ac56691b --- /dev/null +++ b/embassy-usb/src/util.rs @@ -0,0 +1,68 @@ +use embassy::channel::signal::Signal; +use embassy::util::{select, Either}; + +use crate::driver::Driver; +use crate::UsbDevice; + +/// Am enabled usb device is a device that further receives external notifications +/// regarding whether it is enabled or not. A common example of where this is +/// required is when receiving notifications from the POWER peripheral that +/// USB has been connected to or removed. The device here wraps an existing +/// USB device, keeping it publically available so that device-oriented operations +/// may still be performed. A signal is also provided that enables/disables the +/// USB device, taking care of suspension and resumption. In the case of the POWER +/// peripheral, this signal can be used from within a POWER_CLOCK interrupt +/// handler. Alternatively, for softdevice usage where the POWER peripheral is not +/// available, similar USB power events can be leveraged. +pub struct EnabledUsbDevice<'d, D: Driver<'d>> { + pub underlying: UsbDevice<'d, D>, + enable_usb_signal: &'d Signal, +} + +impl<'d, D: Driver<'d>> EnabledUsbDevice<'d, D> { + /// Wrap an existing UsbDevice and take a signal that will be used + /// to enable/disable it, perhaps from an external POWER_CLOCK + /// interrupt, or the equivalent when dealing with softdevices. + pub fn new(underlying: UsbDevice<'d, D>, enable_usb_signal: &'d Signal) -> Self { + Self { + underlying, + enable_usb_signal, + } + } + + /// Runs the underlying `UsbDevice` taking care of reacting to USB becoming + /// enabled/disabled. + /// + /// 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) -> ! { + while !self.enable_usb_signal.wait().await {} + loop { + match select( + self.underlying.run_until_suspend(), + self.enable_usb_signal.wait(), + ) + .await + { + Either::First(_) => {} + Either::Second(enable) => { + if !enable { + self.underlying.disable().await; + while !self.enable_usb_signal.wait().await {} + } + } + } + match select(self.underlying.wait_resume(), self.enable_usb_signal.wait()).await { + Either::First(_) => {} + Either::Second(enable) => { + if !enable { + self.underlying.disable().await; + while !self.enable_usb_signal.wait().await {} + } + } + } + } + } +} diff --git a/examples/nrf/src/bin/usb_serial.rs b/examples/nrf/src/bin/usb_serial.rs index f108db46..377ae8c3 100644 --- a/examples/nrf/src/bin/usb_serial.rs +++ b/examples/nrf/src/bin/usb_serial.rs @@ -6,28 +6,52 @@ use core::mem; use defmt::{info, panic}; +use embassy::channel::signal::Signal; use embassy::executor::Spawner; +use embassy::interrupt::InterruptExt; use embassy_nrf::usb::{Driver, Instance}; -use embassy_nrf::{interrupt, pac, Peripherals}; +use embassy_nrf::{interrupt, interrupt, pac, pac, Peripherals}; use embassy_usb::driver::EndpointError; +use embassy_usb::util::EnabledUsbDevice; use embassy_usb::{Builder, Config}; use embassy_usb_serial::{CdcAcmClass, State}; use futures::future::join; -use {defmt_rtt as _, panic_probe as _}; +use {defmt_rtt as _, panic_probe as _}; // global logger + +static ENABLE_USB: Signal = Signal::new(); + +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(()) }; let power: pac::POWER = unsafe { mem::transmute(()) }; + 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()); + 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); @@ -70,7 +94,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); // Build the builder. - let mut usb = builder.build(); + let mut usb = EnabledUsbDevice::new(builder.build(), &ENABLE_USB); // Run the USB device. let usb_fut = usb.run();