stm32-usbd
stm32-usbd copied to clipboard
Support remote wakeup from suspend
This closes https://github.com/stm32-rs/stm32-usbd/issues/28.
This PR exposes remote_wakeup method that can be used to implement remote wakeup from USB suspend (e.g. for devices like keyboards/mice). I have tested it on STM32F072 device.
To perform remote wakeup, device must set CNTR.RESUME bit for 1-15 ms, so in order to properly implement it we must have means to measure time until clearing this bit. The UsbBus does not know current time, so it cannot do the countdown. While in general UsbBus could enable and use SOF interrupt (1 kHz) to count time for some needs, during suspend USB host doesn't send SOF (this is actually the indicator of USB suspend), so the whole countdown must be implemented in user application.
With the proposed API, user must first call remote_wakeup(true) and then must ensure to call remote_wakeup(false) during 1-15 ms time window. This can be implemented with a simple counter e.g. like this:
pub struct Usb {
pub dev: UsbDevice<'static, Bus>,
/// ...
wake_up_counter: u8,
}
/// Set current wake up state; must be called repeatedly at 1 ms intervals
pub fn wake_up_update(&mut self, wake_up: bool) {
if wake_up && self.wake_up_counter == 0 {
self.dev.bus().remote_wakeup(true);
self.wake_up_counter = 10;
} else {
self.wake_up_counter = self.wake_up_counter.saturating_sub(1);
self.dev.bus().remote_wakeup(self.wake_up_counter != 0);
}
}
This PR also includes a fix of setting LP_MODE bit when suspending. I noticed that setting both FSUSP and LP_MODE results in some weird behaviour where my application doesn't know that suspend happened. I am not sure what is causing this, but changing suspend() to first set FSUSP and then in separate memory access set LP_MODE fixes the issue. The reference manual describes the procedure:
- Set the FSUSP bit in the USB_CNTR register to 1. This action activates the suspend mode within the USB peripheral. As soon as the suspend mode is activated, the check on SOF reception is disabled to avoid any further SUSP interrupts being issued while the USB is suspended.
- Remove or reduce any static power consumption in blocks different from the USB peripheral.
- Set LP_MODE bit in USB_CNTR register to 1 to remove static power consumption in the analog USB transceivers but keeping them able to detect resume activity.
- Optionally turn off external oscillator and device PLL to stop any activity inside the device.
So another reason for not setting LP_MODE is that user application might want (and should) perform actions to reduce power consumption. For these reasons I added a separate method suspend_low_power_mode which can be used by the application to perform any additional steps.
Both methods make sure that we never set these bits when not in suspend state.