diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb.rs
index ed4d5cf35..6be4fec8c 100644
--- a/embassy-nrf/src/usb.rs
+++ b/embassy-nrf/src/usb.rs
@@ -391,11 +391,6 @@ impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> {
.await
}
- #[inline]
- fn set_address(&mut self, _addr: u8) {
- // Nothing to do, the peripheral handles this.
- }
-
fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) {
let regs = T::regs();
unsafe {
@@ -841,6 +836,11 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> {
let regs = T::regs();
regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().bit(true));
}
+
+ async fn accept_set_address(&mut self, _addr: u8) {
+ self.accept().await;
+ // Nothing to do, the peripheral handles this.
+ }
}
fn dma_start() {
diff --git a/embassy-rp/src/usb.rs b/embassy-rp/src/usb.rs
index 8361736a9..2710c4ad9 100644
--- a/embassy-rp/src/usb.rs
+++ b/embassy-rp/src/usb.rs
@@ -406,13 +406,6 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
.await
}
- #[inline]
- fn set_address(&mut self, addr: u8) {
- let regs = T::regs();
- trace!("setting addr: {}", addr);
- unsafe { regs.addr_endp().write(|w| w.set_address(addr)) }
- }
-
fn endpoint_set_stalled(&mut self, _ep_addr: EndpointAddress, _stalled: bool) {
todo!();
}
@@ -812,4 +805,12 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> {
T::dpram().ep_in_buffer_control(0).write(|w| w.set_stall(true));
}
}
+
+ async fn accept_set_address(&mut self, addr: u8) {
+ self.accept().await;
+
+ let regs = T::regs();
+ trace!("setting addr: {}", addr);
+ unsafe { regs.addr_endp().write(|w| w.set_address(addr)) }
+ }
}
diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs
index ddfa97b29..dbfc1370d 100644
--- a/embassy-stm32/build.rs
+++ b/embassy-stm32/build.rs
@@ -277,22 +277,20 @@ fn main() {
(("dcmi", "PIXCLK"), quote!(crate::dcmi::PixClkPin)),
(("usb", "DP"), quote!(crate::usb::DpPin)),
(("usb", "DM"), quote!(crate::usb::DmPin)),
- (("otgfs", "DP"), quote!(crate::usb_otg::DpPin)),
- (("otgfs", "DM"), quote!(crate::usb_otg::DmPin)),
- (("otghs", "DP"), quote!(crate::usb_otg::DpPin)),
- (("otghs", "DM"), quote!(crate::usb_otg::DmPin)),
- (("otghs", "ULPI_CK"), quote!(crate::usb_otg::UlpiClkPin)),
- (("otghs", "ULPI_DIR"), quote!(crate::usb_otg::UlpiDirPin)),
- (("otghs", "ULPI_NXT"), quote!(crate::usb_otg::UlpiNxtPin)),
- (("otghs", "ULPI_STP"), quote!(crate::usb_otg::UlpiStpPin)),
- (("otghs", "ULPI_D0"), quote!(crate::usb_otg::UlpiD0Pin)),
- (("otghs", "ULPI_D1"), quote!(crate::usb_otg::UlpiD1Pin)),
- (("otghs", "ULPI_D2"), quote!(crate::usb_otg::UlpiD2Pin)),
- (("otghs", "ULPI_D3"), quote!(crate::usb_otg::UlpiD3Pin)),
- (("otghs", "ULPI_D4"), quote!(crate::usb_otg::UlpiD4Pin)),
- (("otghs", "ULPI_D5"), quote!(crate::usb_otg::UlpiD5Pin)),
- (("otghs", "ULPI_D6"), quote!(crate::usb_otg::UlpiD6Pin)),
- (("otghs", "ULPI_D7"), quote!(crate::usb_otg::UlpiD7Pin)),
+ (("otg", "DP"), quote!(crate::usb_otg::DpPin)),
+ (("otg", "DM"), quote!(crate::usb_otg::DmPin)),
+ (("otg", "ULPI_CK"), quote!(crate::usb_otg::UlpiClkPin)),
+ (("otg", "ULPI_DIR"), quote!(crate::usb_otg::UlpiDirPin)),
+ (("otg", "ULPI_NXT"), quote!(crate::usb_otg::UlpiNxtPin)),
+ (("otg", "ULPI_STP"), quote!(crate::usb_otg::UlpiStpPin)),
+ (("otg", "ULPI_D0"), quote!(crate::usb_otg::UlpiD0Pin)),
+ (("otg", "ULPI_D1"), quote!(crate::usb_otg::UlpiD1Pin)),
+ (("otg", "ULPI_D2"), quote!(crate::usb_otg::UlpiD2Pin)),
+ (("otg", "ULPI_D3"), quote!(crate::usb_otg::UlpiD3Pin)),
+ (("otg", "ULPI_D4"), quote!(crate::usb_otg::UlpiD4Pin)),
+ (("otg", "ULPI_D5"), quote!(crate::usb_otg::UlpiD5Pin)),
+ (("otg", "ULPI_D6"), quote!(crate::usb_otg::UlpiD6Pin)),
+ (("otg", "ULPI_D7"), quote!(crate::usb_otg::UlpiD7Pin)),
(("can", "TX"), quote!(crate::can::TxPin)),
(("can", "RX"), quote!(crate::can::RxPin)),
(("eth", "REF_CLK"), quote!(crate::eth::RefClkPin)),
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs
index 16c46ca22..610c24888 100644
--- a/embassy-stm32/src/lib.rs
+++ b/embassy-stm32/src/lib.rs
@@ -58,7 +58,7 @@ pub mod spi;
pub mod usart;
#[cfg(all(usb, feature = "time"))]
pub mod usb;
-#[cfg(any(otgfs, otghs))]
+#[cfg(otg)]
pub mod usb_otg;
#[cfg(iwdg)]
diff --git a/embassy-stm32/src/rcc/u5.rs b/embassy-stm32/src/rcc/u5.rs
index 960c45322..2ba339ecf 100644
--- a/embassy-stm32/src/rcc/u5.rs
+++ b/embassy-stm32/src/rcc/u5.rs
@@ -295,6 +295,7 @@ pub struct Config {
pub apb1_pre: APBPrescaler,
pub apb2_pre: APBPrescaler,
pub apb3_pre: APBPrescaler,
+ pub hsi48: bool,
}
impl Default for Config {
@@ -305,6 +306,7 @@ impl Default for Config {
apb1_pre: Default::default(),
apb2_pre: Default::default(),
apb3_pre: Default::default(),
+ hsi48: false,
}
}
}
@@ -320,7 +322,6 @@ pub(crate) unsafe fn init(config: Config) {
RCC.cr().write(|w| {
w.set_msipllen(false);
w.set_msison(true);
- w.set_msison(true);
});
while !RCC.cr().read().msisrdy() {}
@@ -340,9 +341,20 @@ pub(crate) unsafe fn init(config: Config) {
}
ClockSrc::PLL1R(src, m, n, div) => {
let freq = match src {
- PllSrc::MSI(_) => MSIRange::default().into(),
- PllSrc::HSE(hertz) => hertz.0,
- PllSrc::HSI16 => HSI_FREQ.0,
+ PllSrc::MSI(_) => {
+ // TODO: enable MSI
+ MSIRange::default().into()
+ }
+ PllSrc::HSE(hertz) => {
+ // TODO: enable HSE
+ hertz.0
+ }
+ PllSrc::HSI16 => {
+ RCC.cr().write(|w| w.set_hsion(true));
+ while !RCC.cr().read().hsirdy() {}
+
+ HSI_FREQ.0
+ }
};
// disable
@@ -355,6 +367,7 @@ pub(crate) unsafe fn init(config: Config) {
RCC.pll1cfgr().write(|w| {
w.set_pllm(m.into());
w.set_pllsrc(src.into());
+ w.set_pllren(true);
});
RCC.pll1divr().modify(|w| {
@@ -365,15 +378,16 @@ pub(crate) unsafe fn init(config: Config) {
// Enable PLL
RCC.cr().modify(|w| w.set_pllon(0, true));
while !RCC.cr().read().pllrdy(0) {}
- RCC.pll1cfgr().modify(|w| w.set_pllren(true));
-
- RCC.cr().write(|w| w.set_pllon(0, true));
- while !RCC.cr().read().pllrdy(0) {}
pll_ck
}
};
+ if config.hsi48 {
+ RCC.cr().modify(|w| w.set_hsi48on(true));
+ while !RCC.cr().read().hsi48rdy() {}
+ }
+
// TODO make configurable
let power_vos = VoltageScale::Range4;
diff --git a/embassy-stm32/src/usb/usb.rs b/embassy-stm32/src/usb/usb.rs
index cba4915c1..03e792a21 100644
--- a/embassy-stm32/src/usb/usb.rs
+++ b/embassy-stm32/src/usb/usb.rs
@@ -154,6 +154,7 @@ impl<'d, T: Instance> Driver<'d, T> {
block_for(Duration::from_millis(100));
+ #[cfg(not(usb_v4))]
regs.btable().write(|w| w.set_btable(0));
dp.set_as_af(dp.af_num(), AFType::OutputPushPull);
@@ -489,18 +490,6 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
.await
}
- #[inline]
- fn set_address(&mut self, addr: u8) {
- let regs = T::regs();
- trace!("setting addr: {}", addr);
- unsafe {
- regs.daddr().write(|w| {
- w.set_ef(true);
- w.set_add(addr);
- })
- }
- }
-
fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) {
// This can race, so do a retry loop.
let reg = T::regs().epr(ep_addr.index() as _);
@@ -1017,4 +1006,17 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> {
});
}
}
+
+ async fn accept_set_address(&mut self, addr: u8) {
+ self.accept().await;
+
+ let regs = T::regs();
+ trace!("setting addr: {}", addr);
+ unsafe {
+ regs.daddr().write(|w| {
+ w.set_ef(true);
+ w.set_add(addr);
+ })
+ }
+ }
}
diff --git a/embassy-stm32/src/usb_otg.rs b/embassy-stm32/src/usb_otg.rs
deleted file mode 100644
index f7faf12a8..000000000
--- a/embassy-stm32/src/usb_otg.rs
+++ /dev/null
@@ -1,213 +0,0 @@
-use core::marker::PhantomData;
-
-use embassy_hal_common::into_ref;
-
-use crate::gpio::sealed::AFType;
-use crate::rcc::RccPeripheral;
-use crate::{peripherals, Peripheral};
-
-macro_rules! config_ulpi_pins {
- ($($pin:ident),*) => {
- into_ref!($($pin),*);
- // NOTE(unsafe) Exclusive access to the registers
- critical_section::with(|_| unsafe {
- $(
- $pin.set_as_af($pin.af_num(), AFType::OutputPushPull);
- #[cfg(gpio_v2)]
- $pin.set_speed(crate::gpio::Speed::VeryHigh);
- )*
- })
- };
-}
-
-/// USB PHY type
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-pub enum PhyType {
- /// Internal Full-Speed PHY
- ///
- /// Available on most High-Speed peripherals.
- InternalFullSpeed,
- /// Internal High-Speed PHY
- ///
- /// Available on a few STM32 chips.
- InternalHighSpeed,
- /// External ULPI High-Speed PHY
- ExternalHighSpeed,
-}
-
-pub struct UsbOtg<'d, T: Instance> {
- phantom: PhantomData<&'d mut T>,
- _phy_type: PhyType,
-}
-
-impl<'d, T: Instance> UsbOtg<'d, T> {
- /// Initializes USB OTG peripheral with internal Full-Speed PHY
- pub fn new_fs(
- _peri: impl Peripheral
+ 'd,
- dp: impl Peripheral
> + 'd,
- dm: impl Peripheral
> + 'd,
- ) -> Self {
- into_ref!(dp, dm);
-
- unsafe {
- dp.set_as_af(dp.af_num(), AFType::OutputPushPull);
- dm.set_as_af(dm.af_num(), AFType::OutputPushPull);
- }
-
- Self {
- phantom: PhantomData,
- _phy_type: PhyType::InternalFullSpeed,
- }
- }
-
- /// Initializes USB OTG peripheral with external High-Speed PHY
- pub fn new_hs_ulpi(
- _peri: impl Peripheral
+ 'd,
- ulpi_clk: impl Peripheral
> + 'd,
- ulpi_dir: impl Peripheral
> + 'd,
- ulpi_nxt: impl Peripheral
> + 'd,
- ulpi_stp: impl Peripheral
> + 'd,
- ulpi_d0: impl Peripheral
> + 'd,
- ulpi_d1: impl Peripheral
> + 'd,
- ulpi_d2: impl Peripheral
> + 'd,
- ulpi_d3: impl Peripheral
> + 'd,
- ulpi_d4: impl Peripheral
> + 'd,
- ulpi_d5: impl Peripheral
> + 'd,
- ulpi_d6: impl Peripheral
> + 'd,
- ulpi_d7: impl Peripheral
> + 'd,
- ) -> Self {
- config_ulpi_pins!(
- ulpi_clk, ulpi_dir, ulpi_nxt, ulpi_stp, ulpi_d0, ulpi_d1, ulpi_d2, ulpi_d3, ulpi_d4, ulpi_d5, ulpi_d6,
- ulpi_d7
- );
-
- Self {
- phantom: PhantomData,
- _phy_type: PhyType::ExternalHighSpeed,
- }
- }
-}
-
-impl<'d, T: Instance> Drop for UsbOtg<'d, T> {
- fn drop(&mut self) {
- T::reset();
- T::disable();
- }
-}
-
-pub(crate) mod sealed {
- pub trait Instance {
- const REGISTERS: *const ();
- const HIGH_SPEED: bool;
- const FIFO_DEPTH_WORDS: usize;
- const ENDPOINT_COUNT: usize;
- }
-}
-
-pub trait Instance: sealed::Instance + RccPeripheral {}
-
-// Internal PHY pins
-pin_trait!(DpPin, Instance);
-pin_trait!(DmPin, Instance);
-
-// External PHY pins
-pin_trait!(UlpiClkPin, Instance);
-pin_trait!(UlpiDirPin, Instance);
-pin_trait!(UlpiNxtPin, Instance);
-pin_trait!(UlpiStpPin, Instance);
-pin_trait!(UlpiD0Pin, Instance);
-pin_trait!(UlpiD1Pin, Instance);
-pin_trait!(UlpiD2Pin, Instance);
-pin_trait!(UlpiD3Pin, Instance);
-pin_trait!(UlpiD4Pin, Instance);
-pin_trait!(UlpiD5Pin, Instance);
-pin_trait!(UlpiD6Pin, Instance);
-pin_trait!(UlpiD7Pin, Instance);
-
-foreach_peripheral!(
- (otgfs, $inst:ident) => {
- impl sealed::Instance for peripherals::$inst {
- const REGISTERS: *const () = crate::pac::$inst.0 as *const ();
- const HIGH_SPEED: bool = false;
-
- cfg_if::cfg_if! {
- if #[cfg(stm32f1)] {
- const FIFO_DEPTH_WORDS: usize = 128;
- const ENDPOINT_COUNT: usize = 8;
- } else if #[cfg(any(
- stm32f2,
- stm32f401,
- stm32f405,
- stm32f407,
- stm32f411,
- stm32f415,
- stm32f417,
- stm32f427,
- stm32f429,
- stm32f437,
- stm32f439,
- ))] {
- const FIFO_DEPTH_WORDS: usize = 320;
- const ENDPOINT_COUNT: usize = 4;
- } else if #[cfg(any(
- stm32f412,
- stm32f413,
- stm32f423,
- stm32f446,
- stm32f469,
- stm32f479,
- stm32f7,
- stm32l4,
- stm32u5,
- ))] {
- const FIFO_DEPTH_WORDS: usize = 320;
- const ENDPOINT_COUNT: usize = 6;
- } else if #[cfg(stm32g0x1)] {
- const FIFO_DEPTH_WORDS: usize = 512;
- const ENDPOINT_COUNT: usize = 8;
- } else {
- compile_error!("USB_OTG_FS peripheral is not supported by this chip.");
- }
- }
- }
-
- impl Instance for peripherals::$inst {}
- };
-
- (otghs, $inst:ident) => {
- impl sealed::Instance for peripherals::$inst {
- const REGISTERS: *const () = crate::pac::$inst.0 as *const ();
- const HIGH_SPEED: bool = true;
-
- cfg_if::cfg_if! {
- if #[cfg(any(
- stm32f2,
- stm32f405,
- stm32f407,
- stm32f415,
- stm32f417,
- stm32f427,
- stm32f429,
- stm32f437,
- stm32f439,
- ))] {
- const FIFO_DEPTH_WORDS: usize = 1024;
- const ENDPOINT_COUNT: usize = 6;
- } else if #[cfg(any(
- stm32f446,
- stm32f469,
- stm32f479,
- stm32f7,
- stm32h7,
- ))] {
- const FIFO_DEPTH_WORDS: usize = 1024;
- const ENDPOINT_COUNT: usize = 9;
- } else {
- compile_error!("USB_OTG_HS peripheral is not supported by this chip.");
- }
- }
- }
-
- impl Instance for peripherals::$inst {}
- };
-);
diff --git a/embassy-stm32/src/usb_otg/mod.rs b/embassy-stm32/src/usb_otg/mod.rs
new file mode 100644
index 000000000..84fef78cb
--- /dev/null
+++ b/embassy-stm32/src/usb_otg/mod.rs
@@ -0,0 +1,161 @@
+use embassy_cortex_m::interrupt::Interrupt;
+
+use crate::peripherals;
+use crate::rcc::RccPeripheral;
+
+#[cfg(feature = "nightly")]
+mod usb;
+#[cfg(feature = "nightly")]
+pub use usb::*;
+
+// Using Instance::ENDPOINT_COUNT requires feature(const_generic_expr) so just define maximum eps
+#[cfg(feature = "nightly")]
+const MAX_EP_COUNT: usize = 9;
+
+pub(crate) mod sealed {
+ pub trait Instance {
+ const HIGH_SPEED: bool;
+ const FIFO_DEPTH_WORDS: u16;
+ const ENDPOINT_COUNT: usize;
+
+ fn regs() -> crate::pac::otg::Otg;
+ #[cfg(feature = "nightly")]
+ fn state() -> &'static super::State<{ super::MAX_EP_COUNT }>;
+ }
+}
+
+pub trait Instance: sealed::Instance + RccPeripheral {
+ type Interrupt: Interrupt;
+}
+
+// Internal PHY pins
+pin_trait!(DpPin, Instance);
+pin_trait!(DmPin, Instance);
+
+// External PHY pins
+pin_trait!(UlpiClkPin, Instance);
+pin_trait!(UlpiDirPin, Instance);
+pin_trait!(UlpiNxtPin, Instance);
+pin_trait!(UlpiStpPin, Instance);
+pin_trait!(UlpiD0Pin, Instance);
+pin_trait!(UlpiD1Pin, Instance);
+pin_trait!(UlpiD2Pin, Instance);
+pin_trait!(UlpiD3Pin, Instance);
+pin_trait!(UlpiD4Pin, Instance);
+pin_trait!(UlpiD5Pin, Instance);
+pin_trait!(UlpiD6Pin, Instance);
+pin_trait!(UlpiD7Pin, Instance);
+
+foreach_interrupt!(
+ (USB_OTG_FS, otg, $block:ident, GLOBAL, $irq:ident) => {
+ impl sealed::Instance for peripherals::USB_OTG_FS {
+ const HIGH_SPEED: bool = false;
+
+ cfg_if::cfg_if! {
+ if #[cfg(stm32f1)] {
+ const FIFO_DEPTH_WORDS: u16 = 128;
+ const ENDPOINT_COUNT: usize = 8;
+ } else if #[cfg(any(
+ stm32f2,
+ stm32f401,
+ stm32f405,
+ stm32f407,
+ stm32f411,
+ stm32f415,
+ stm32f417,
+ stm32f427,
+ stm32f429,
+ stm32f437,
+ stm32f439,
+ ))] {
+ const FIFO_DEPTH_WORDS: u16 = 320;
+ const ENDPOINT_COUNT: usize = 4;
+ } else if #[cfg(any(
+ stm32f412,
+ stm32f413,
+ stm32f423,
+ stm32f446,
+ stm32f469,
+ stm32f479,
+ stm32f7,
+ stm32l4,
+ stm32u5,
+ ))] {
+ const FIFO_DEPTH_WORDS: u16 = 320;
+ const ENDPOINT_COUNT: usize = 6;
+ } else if #[cfg(stm32g0x1)] {
+ const FIFO_DEPTH_WORDS: u16 = 512;
+ const ENDPOINT_COUNT: usize = 8;
+ } else if #[cfg(stm32h7)] {
+ const FIFO_DEPTH_WORDS: u16 = 1024;
+ const ENDPOINT_COUNT: usize = 9;
+ } else {
+ compile_error!("USB_OTG_FS peripheral is not supported by this chip.");
+ }
+ }
+
+ fn regs() -> crate::pac::otg::Otg {
+ crate::pac::USB_OTG_FS
+ }
+
+ #[cfg(feature = "nightly")]
+ fn state() -> &'static State {
+ static STATE: State = State::new();
+ &STATE
+ }
+ }
+
+ impl Instance for peripherals::USB_OTG_FS {
+ type Interrupt = crate::interrupt::$irq;
+ }
+ };
+
+ (USB_OTG_HS, otg, $block:ident, GLOBAL, $irq:ident) => {
+ impl sealed::Instance for peripherals::USB_OTG_HS {
+ const HIGH_SPEED: bool = true;
+
+ cfg_if::cfg_if! {
+ if #[cfg(any(
+ stm32f2,
+ stm32f405,
+ stm32f407,
+ stm32f415,
+ stm32f417,
+ stm32f427,
+ stm32f429,
+ stm32f437,
+ stm32f439,
+ ))] {
+ const FIFO_DEPTH_WORDS: u16 = 1024;
+ const ENDPOINT_COUNT: usize = 6;
+ } else if #[cfg(any(
+ stm32f446,
+ stm32f469,
+ stm32f479,
+ stm32f7,
+ stm32h7,
+ ))] {
+ const FIFO_DEPTH_WORDS: u16 = 1024;
+ const ENDPOINT_COUNT: usize = 9;
+ } else {
+ compile_error!("USB_OTG_HS peripheral is not supported by this chip.");
+ }
+ }
+
+ fn regs() -> crate::pac::otg::Otg {
+ // OTG HS registers are a superset of FS registers
+ crate::pac::otg::Otg(crate::pac::USB_OTG_HS.0)
+ }
+
+ #[cfg(feature = "nightly")]
+ fn state() -> &'static State {
+ static STATE: State = State::new();
+ &STATE
+ }
+ }
+
+ impl Instance for peripherals::USB_OTG_HS {
+ type Interrupt = crate::interrupt::$irq;
+ }
+ };
+);
diff --git a/embassy-stm32/src/usb_otg/usb.rs b/embassy-stm32/src/usb_otg/usb.rs
new file mode 100644
index 000000000..2d9b613ea
--- /dev/null
+++ b/embassy-stm32/src/usb_otg/usb.rs
@@ -0,0 +1,1346 @@
+use core::cell::UnsafeCell;
+use core::marker::PhantomData;
+use core::task::Poll;
+
+use atomic_polyfill::{AtomicBool, AtomicU16, Ordering};
+use embassy_cortex_m::interrupt::InterruptExt;
+use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
+use embassy_sync::waitqueue::AtomicWaker;
+use embassy_usb_driver::{
+ self, Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointIn, EndpointInfo, EndpointOut,
+ EndpointType, Event, Unsupported,
+};
+use futures::future::poll_fn;
+
+use super::*;
+use crate::gpio::sealed::AFType;
+use crate::pac::otg::{regs, vals};
+use crate::rcc::sealed::RccPeripheral;
+use crate::time::Hertz;
+
+macro_rules! config_ulpi_pins {
+ ($($pin:ident),*) => {
+ into_ref!($($pin),*);
+ // NOTE(unsafe) Exclusive access to the registers
+ critical_section::with(|_| unsafe {
+ $(
+ $pin.set_as_af($pin.af_num(), AFType::OutputPushPull);
+ #[cfg(gpio_v2)]
+ $pin.set_speed(crate::gpio::Speed::VeryHigh);
+ )*
+ })
+ };
+}
+
+// From `synopsys-usb-otg` crate:
+// This calculation doesn't correspond to one in a Reference Manual.
+// In fact, the required number of words is higher than indicated in RM.
+// The following numbers are pessimistic and were figured out empirically.
+const RX_FIFO_EXTRA_SIZE_WORDS: u16 = 30;
+
+/// USB PHY type
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum PhyType {
+ /// Internal Full-Speed PHY
+ ///
+ /// Available on most High-Speed peripherals.
+ InternalFullSpeed,
+ /// Internal High-Speed PHY
+ ///
+ /// Available on a few STM32 chips.
+ InternalHighSpeed,
+ /// External ULPI High-Speed PHY
+ ExternalHighSpeed,
+}
+
+impl PhyType {
+ pub fn internal(&self) -> bool {
+ match self {
+ PhyType::InternalFullSpeed | PhyType::InternalHighSpeed => true,
+ PhyType::ExternalHighSpeed => false,
+ }
+ }
+
+ pub fn high_speed(&self) -> bool {
+ match self {
+ PhyType::InternalFullSpeed => false,
+ PhyType::ExternalHighSpeed | PhyType::InternalHighSpeed => true,
+ }
+ }
+
+ pub fn to_dspd(&self) -> vals::Dspd {
+ match self {
+ PhyType::InternalFullSpeed => vals::Dspd::FULL_SPEED_INTERNAL,
+ PhyType::InternalHighSpeed => vals::Dspd::HIGH_SPEED,
+ PhyType::ExternalHighSpeed => vals::Dspd::HIGH_SPEED,
+ }
+ }
+}
+
+/// Indicates that [State::ep_out_buffers] is empty.
+const EP_OUT_BUFFER_EMPTY: u16 = u16::MAX;
+
+pub struct State {
+ /// Holds received SETUP packets. Available if [State::ep0_setup_ready] is true.
+ ep0_setup_data: UnsafeCell<[u8; 8]>,
+ ep0_setup_ready: AtomicBool,
+ ep_in_wakers: [AtomicWaker; EP_COUNT],
+ ep_out_wakers: [AtomicWaker; EP_COUNT],
+ /// RX FIFO is shared so extra buffers are needed to dequeue all data without waiting on each endpoint.
+ /// Buffers are ready when associated [State::ep_out_size] != [EP_OUT_BUFFER_EMPTY].
+ ep_out_buffers: [UnsafeCell<*mut u8>; EP_COUNT],
+ ep_out_size: [AtomicU16; EP_COUNT],
+ bus_waker: AtomicWaker,
+}
+
+unsafe impl Send for State {}
+unsafe impl Sync for State {}
+
+impl State {
+ pub const fn new() -> Self {
+ const NEW_AW: AtomicWaker = AtomicWaker::new();
+ const NEW_BUF: UnsafeCell<*mut u8> = UnsafeCell::new(0 as _);
+ const NEW_SIZE: AtomicU16 = AtomicU16::new(EP_OUT_BUFFER_EMPTY);
+
+ Self {
+ ep0_setup_data: UnsafeCell::new([0u8; 8]),
+ ep0_setup_ready: AtomicBool::new(false),
+ ep_in_wakers: [NEW_AW; EP_COUNT],
+ ep_out_wakers: [NEW_AW; EP_COUNT],
+ ep_out_buffers: [NEW_BUF; EP_COUNT],
+ ep_out_size: [NEW_SIZE; EP_COUNT],
+ bus_waker: NEW_AW,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+struct EndpointData {
+ ep_type: EndpointType,
+ max_packet_size: u16,
+ fifo_size_words: u16,
+}
+
+pub struct Driver<'d, T: Instance> {
+ phantom: PhantomData<&'d mut T>,
+ irq: PeripheralRef<'d, T::Interrupt>,
+ ep_in: [Option; MAX_EP_COUNT],
+ ep_out: [Option; MAX_EP_COUNT],
+ ep_out_buffer: &'d mut [u8],
+ ep_out_buffer_offset: usize,
+ phy_type: PhyType,
+}
+
+impl<'d, T: Instance> Driver<'d, T> {
+ /// Initializes USB OTG peripheral with internal Full-Speed PHY.
+ ///
+ /// # Arguments
+ ///
+ /// * `ep_out_buffer` - An internal buffer used to temporarily store recevied packets.
+ /// Must be large enough to fit all OUT endpoint max packet sizes.
+ /// Endpoint allocation will fail if it is too small.
+ pub fn new_fs(
+ _peri: impl Peripheral + 'd,
+ irq: impl Peripheral
+ 'd,
+ dp: impl Peripheral
> + 'd,
+ dm: impl Peripheral
> + 'd,
+ ep_out_buffer: &'d mut [u8],
+ ) -> Self {
+ into_ref!(dp, dm, irq);
+
+ unsafe {
+ dp.set_as_af(dp.af_num(), AFType::OutputPushPull);
+ dm.set_as_af(dm.af_num(), AFType::OutputPushPull);
+ }
+
+ Self {
+ phantom: PhantomData,
+ irq,
+ ep_in: [None; MAX_EP_COUNT],
+ ep_out: [None; MAX_EP_COUNT],
+ ep_out_buffer,
+ ep_out_buffer_offset: 0,
+ phy_type: PhyType::InternalFullSpeed,
+ }
+ }
+
+ /// Initializes USB OTG peripheral with external High-Speed PHY.
+ ///
+ /// # Arguments
+ ///
+ /// * `ep_out_buffer` - An internal buffer used to temporarily store recevied packets.
+ /// Must be large enough to fit all OUT endpoint max packet sizes.
+ /// Endpoint allocation will fail if it is too small.
+ pub fn new_hs_ulpi(
+ _peri: impl Peripheral
+ 'd,
+ irq: impl Peripheral
+ 'd,
+ ulpi_clk: impl Peripheral
> + 'd,
+ ulpi_dir: impl Peripheral
> + 'd,
+ ulpi_nxt: impl Peripheral
> + 'd,
+ ulpi_stp: impl Peripheral
> + 'd,
+ ulpi_d0: impl Peripheral
> + 'd,
+ ulpi_d1: impl Peripheral
> + 'd,
+ ulpi_d2: impl Peripheral
> + 'd,
+ ulpi_d3: impl Peripheral
> + 'd,
+ ulpi_d4: impl Peripheral
> + 'd,
+ ulpi_d5: impl Peripheral
> + 'd,
+ ulpi_d6: impl Peripheral
> + 'd,
+ ulpi_d7: impl Peripheral
> + 'd,
+ ep_out_buffer: &'d mut [u8],
+ ) -> Self {
+ assert!(T::HIGH_SPEED == true, "Peripheral is not capable of high-speed USB");
+
+ config_ulpi_pins!(
+ ulpi_clk, ulpi_dir, ulpi_nxt, ulpi_stp, ulpi_d0, ulpi_d1, ulpi_d2, ulpi_d3, ulpi_d4, ulpi_d5, ulpi_d6,
+ ulpi_d7
+ );
+
+ into_ref!(irq);
+
+ Self {
+ phantom: PhantomData,
+ irq,
+ ep_in: [None; MAX_EP_COUNT],
+ ep_out: [None; MAX_EP_COUNT],
+ ep_out_buffer,
+ ep_out_buffer_offset: 0,
+ phy_type: PhyType::ExternalHighSpeed,
+ }
+ }
+
+ // Returns total amount of words (u32) allocated in dedicated FIFO
+ fn allocated_fifo_words(&self) -> u16 {
+ RX_FIFO_EXTRA_SIZE_WORDS + ep_fifo_size(&self.ep_out) + ep_fifo_size(&self.ep_in)
+ }
+
+ fn alloc_endpoint(
+ &mut self,
+ ep_type: EndpointType,
+ max_packet_size: u16,
+ interval: u8,
+ ) -> Result, EndpointAllocError> {
+ trace!(
+ "allocating type={:?} mps={:?} interval={}, dir={:?}",
+ ep_type,
+ max_packet_size,
+ interval,
+ D::dir()
+ );
+
+ if D::dir() == Direction::Out {
+ if self.ep_out_buffer_offset + max_packet_size as usize >= self.ep_out_buffer.len() {
+ error!("Not enough endpoint out buffer capacity");
+ return Err(EndpointAllocError);
+ }
+ };
+
+ let fifo_size_words = match D::dir() {
+ Direction::Out => (max_packet_size + 3) / 4,
+ // INEPTXFD requires minimum size of 16 words
+ Direction::In => u16::max((max_packet_size + 3) / 4, 16),
+ };
+
+ if fifo_size_words + self.allocated_fifo_words() > T::FIFO_DEPTH_WORDS {
+ error!("Not enough FIFO capacity");
+ return Err(EndpointAllocError);
+ }
+
+ let eps = match D::dir() {
+ Direction::Out => &mut self.ep_out,
+ Direction::In => &mut self.ep_in,
+ };
+
+ // Find free endpoint slot
+ let slot = eps.iter_mut().enumerate().find(|(i, ep)| {
+ if *i == 0 && ep_type != EndpointType::Control {
+ // reserved for control pipe
+ false
+ } else {
+ ep.is_none()
+ }
+ });
+
+ let index = match slot {
+ Some((index, ep)) => {
+ *ep = Some(EndpointData {
+ ep_type,
+ max_packet_size,
+ fifo_size_words,
+ });
+ index
+ }
+ None => {
+ error!("No free endpoints available");
+ return Err(EndpointAllocError);
+ }
+ };
+
+ trace!(" index={}", index);
+
+ if D::dir() == Direction::Out {
+ // Buffer capacity check was done above, now allocation cannot fail
+ unsafe {
+ *T::state().ep_out_buffers[index].get() =
+ self.ep_out_buffer.as_mut_ptr().offset(self.ep_out_buffer_offset as _);
+ }
+ self.ep_out_buffer_offset += max_packet_size as usize;
+ }
+
+ Ok(Endpoint {
+ _phantom: PhantomData,
+ info: EndpointInfo {
+ addr: EndpointAddress::from_parts(index, D::dir()),
+ ep_type,
+ max_packet_size,
+ interval,
+ },
+ })
+ }
+}
+
+impl<'d, T: Instance> embassy_usb_driver::Driver<'d> for Driver<'d, T> {
+ type EndpointOut = Endpoint<'d, T, Out>;
+ type EndpointIn = Endpoint<'d, T, In>;
+ type ControlPipe = ControlPipe<'d, T>;
+ type Bus = Bus<'d, T>;
+
+ fn alloc_endpoint_in(
+ &mut self,
+ ep_type: EndpointType,
+ max_packet_size: u16,
+ interval: u8,
+ ) -> Result {
+ self.alloc_endpoint(ep_type, max_packet_size, interval)
+ }
+
+ fn alloc_endpoint_out(
+ &mut self,
+ ep_type: EndpointType,
+ max_packet_size: u16,
+ interval: u8,
+ ) -> Result {
+ self.alloc_endpoint(ep_type, max_packet_size, interval)
+ }
+
+ fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) {
+ let ep_out = self
+ .alloc_endpoint(EndpointType::Control, control_max_packet_size, 0)
+ .unwrap();
+ let ep_in = self
+ .alloc_endpoint(EndpointType::Control, control_max_packet_size, 0)
+ .unwrap();
+ assert_eq!(ep_out.info.addr.index(), 0);
+ assert_eq!(ep_in.info.addr.index(), 0);
+
+ trace!("start");
+
+ (
+ Bus {
+ phantom: PhantomData,
+ irq: self.irq,
+ ep_in: self.ep_in,
+ ep_out: self.ep_out,
+ phy_type: self.phy_type,
+ enabled: false,
+ },
+ ControlPipe {
+ _phantom: PhantomData,
+ max_packet_size: control_max_packet_size,
+ ep_out,
+ ep_in,
+ },
+ )
+ }
+}
+
+pub struct Bus<'d, T: Instance> {
+ phantom: PhantomData<&'d mut T>,
+ irq: PeripheralRef<'d, T::Interrupt>,
+ ep_in: [Option; MAX_EP_COUNT],
+ ep_out: [Option; MAX_EP_COUNT],
+ phy_type: PhyType,
+ enabled: bool,
+}
+
+impl<'d, T: Instance> Bus<'d, T> {
+ fn restore_irqs() {
+ // SAFETY: atomic write
+ unsafe {
+ T::regs().gintmsk().write(|w| {
+ w.set_usbrst(true);
+ w.set_enumdnem(true);
+ w.set_usbsuspm(true);
+ w.set_wuim(true);
+ w.set_iepint(true);
+ w.set_oepint(true);
+ w.set_rxflvlm(true);
+ });
+ }
+ }
+}
+
+impl<'d, T: Instance> Bus<'d, T> {
+ fn init_fifo(&mut self) {
+ trace!("init_fifo");
+
+ let r = T::regs();
+
+ // Configure RX fifo size. All endpoints share the same FIFO area.
+ let rx_fifo_size_words = RX_FIFO_EXTRA_SIZE_WORDS + ep_fifo_size(&self.ep_out);
+ trace!("configuring rx fifo size={}", rx_fifo_size_words);
+
+ // SAFETY: register is exclusive to `Bus` with `&mut self`
+ unsafe { r.grxfsiz().modify(|w| w.set_rxfd(rx_fifo_size_words)) };
+
+ // Configure TX (USB in direction) fifo size for each endpoint
+ let mut fifo_top = rx_fifo_size_words;
+ for i in 0..T::ENDPOINT_COUNT {
+ if let Some(ep) = self.ep_in[i] {
+ trace!(
+ "configuring tx fifo ep={}, offset={}, size={}",
+ i,
+ fifo_top,
+ ep.fifo_size_words
+ );
+
+ let dieptxf = if i == 0 { r.dieptxf0() } else { r.dieptxf(i - 1) };
+
+ // SAFETY: register is exclusive to `Bus` with `&mut self`
+ unsafe {
+ dieptxf.write(|w| {
+ w.set_fd(ep.fifo_size_words);
+ w.set_sa(fifo_top);
+ });
+ }
+
+ fifo_top += ep.fifo_size_words;
+ }
+ }
+
+ assert!(
+ fifo_top <= T::FIFO_DEPTH_WORDS,
+ "FIFO allocations exceeded maximum capacity"
+ );
+ }
+
+ fn configure_endpoints(&mut self) {
+ trace!("configure_endpoints");
+
+ let r = T::regs();
+
+ // Configure IN endpoints
+ for (index, ep) in self.ep_in.iter().enumerate() {
+ if let Some(ep) = ep {
+ // SAFETY: DIEPCTL is shared with `Endpoint` so critical section is needed for RMW
+ critical_section::with(|_| unsafe {
+ r.diepctl(index).write(|w| {
+ if index == 0 {
+ w.set_mpsiz(ep0_mpsiz(ep.max_packet_size));
+ } else {
+ w.set_mpsiz(ep.max_packet_size);
+ w.set_eptyp(to_eptyp(ep.ep_type));
+ w.set_sd0pid_sevnfrm(true);
+ }
+ });
+ });
+ }
+ }
+
+ // Configure OUT endpoints
+ for (index, ep) in self.ep_out.iter().enumerate() {
+ if let Some(ep) = ep {
+ // SAFETY: DOEPCTL/DOEPTSIZ is shared with `Endpoint` so critical section is needed for RMW
+ critical_section::with(|_| unsafe {
+ r.doepctl(index).write(|w| {
+ if index == 0 {
+ w.set_mpsiz(ep0_mpsiz(ep.max_packet_size));
+ } else {
+ w.set_mpsiz(ep.max_packet_size);
+ w.set_eptyp(to_eptyp(ep.ep_type));
+ w.set_sd0pid_sevnfrm(true);
+ }
+ });
+
+ r.doeptsiz(index).modify(|w| {
+ w.set_xfrsiz(ep.max_packet_size as _);
+ if index == 0 {
+ w.set_rxdpid_stupcnt(1);
+ } else {
+ w.set_pktcnt(1);
+ }
+ });
+ });
+ }
+ }
+
+ // Enable IRQs for allocated endpoints
+ // SAFETY: register is exclusive to `Bus` with `&mut self`
+ unsafe {
+ r.daintmsk().modify(|w| {
+ w.set_iepm(ep_irq_mask(&self.ep_in));
+ // OUT interrupts not used, handled in RXFLVL
+ // w.set_oepm(ep_irq_mask(&self.ep_out));
+ });
+ }
+ }
+
+ fn disable(&mut self) {
+ self.irq.disable();
+ self.irq.remove_handler();
+
+ ::disable();
+
+ #[cfg(stm32l4)]
+ unsafe {
+ crate::pac::PWR.cr2().modify(|w| w.set_usv(false));
+ // Cannot disable PWR, because other peripherals might be using it
+ }
+ }
+
+ fn on_interrupt(_: *mut ()) {
+ trace!("irq");
+ let r = T::regs();
+ let state = T::state();
+
+ // SAFETY: atomic read/write
+ let ints = unsafe { r.gintsts().read() };
+ if ints.wkupint() || ints.usbsusp() || ints.usbrst() || ints.enumdne() {
+ // Mask interrupts and notify `Bus` to process them
+ unsafe { r.gintmsk().write(|_| {}) };
+ T::state().bus_waker.wake();
+ }
+
+ // Handle RX
+ // SAFETY: atomic read with no side effects
+ while unsafe { r.gintsts().read().rxflvl() } {
+ // SAFETY: atomic "pop" register
+ let status = unsafe { r.grxstsp().read() };
+ let ep_num = status.epnum() as usize;
+ let len = status.bcnt() as usize;
+
+ assert!(ep_num < T::ENDPOINT_COUNT);
+
+ match status.pktstsd() {
+ vals::Pktstsd::SETUP_DATA_RX => {
+ trace!("SETUP_DATA_RX");
+ assert!(len == 8, "invalid SETUP packet length={}", len);
+ assert!(ep_num == 0, "invalid SETUP packet endpoint={}", ep_num);
+
+ if state.ep0_setup_ready.load(Ordering::Relaxed) == false {
+ // SAFETY: exclusive access ensured by atomic bool
+ let data = unsafe { &mut *state.ep0_setup_data.get() };
+ // SAFETY: FIFO reads are exclusive to this IRQ
+ unsafe {
+ data[0..4].copy_from_slice(&r.fifo(0).read().0.to_ne_bytes());
+ data[4..8].copy_from_slice(&r.fifo(0).read().0.to_ne_bytes());
+ }
+ state.ep0_setup_ready.store(true, Ordering::Release);
+ state.ep_out_wakers[0].wake();
+ } else {
+ error!("received SETUP before previous finished processing");
+ // discard FIFO
+ // SAFETY: FIFO reads are exclusive to IRQ
+ unsafe {
+ r.fifo(0).read();
+ r.fifo(0).read();
+ }
+ }
+ }
+ vals::Pktstsd::OUT_DATA_RX => {
+ trace!("OUT_DATA_RX ep={} len={}", ep_num, len);
+
+ if state.ep_out_size[ep_num].load(Ordering::Acquire) == EP_OUT_BUFFER_EMPTY {
+ // SAFETY: Buffer size is allocated to be equal to endpoint's maximum packet size
+ // We trust the peripheral to not exceed its configured MPSIZ
+ let buf = unsafe { core::slice::from_raw_parts_mut(*state.ep_out_buffers[ep_num].get(), len) };
+
+ for chunk in buf.chunks_mut(4) {
+ // RX FIFO is shared so always read from fifo(0)
+ // SAFETY: FIFO reads are exclusive to IRQ
+ let data = unsafe { r.fifo(0).read().0 };
+ chunk.copy_from_slice(&data.to_ne_bytes()[0..chunk.len()]);
+ }
+
+ state.ep_out_size[ep_num].store(len as u16, Ordering::Release);
+ state.ep_out_wakers[ep_num].wake();
+ } else {
+ error!("ep_out buffer overflow index={}", ep_num);
+
+ // discard FIFO data
+ let len_words = (len + 3) / 4;
+ for _ in 0..len_words {
+ // SAFETY: FIFO reads are exclusive to IRQ
+ unsafe { r.fifo(0).read().data() };
+ }
+ }
+ }
+ vals::Pktstsd::OUT_DATA_DONE => {
+ trace!("OUT_DATA_DONE ep={}", ep_num);
+ }
+ vals::Pktstsd::SETUP_DATA_DONE => {
+ trace!("SETUP_DATA_DONE ep={}", ep_num);
+ }
+ x => trace!("unknown PKTSTS: {}", x.0),
+ }
+ }
+
+ // IN endpoint interrupt
+ if ints.iepint() {
+ // SAFETY: atomic read with no side effects
+ let mut ep_mask = unsafe { r.daint().read().iepint() };
+ let mut ep_num = 0;
+
+ // Iterate over endpoints while there are non-zero bits in the mask
+ while ep_mask != 0 {
+ if ep_mask & 1 != 0 {
+ // SAFETY: atomic read with no side effects
+ let ep_ints = unsafe { r.diepint(ep_num).read() };
+
+ // clear all
+ // SAFETY: DIEPINT is exclusive to IRQ
+ unsafe { r.diepint(ep_num).write_value(ep_ints) };
+
+ // TXFE is cleared in DIEPEMPMSK
+ if ep_ints.txfe() {
+ // SAFETY: DIEPEMPMSK is shared with `Endpoint` so critical section is needed for RMW
+ critical_section::with(|_| unsafe {
+ r.diepempmsk().modify(|w| {
+ w.set_ineptxfem(w.ineptxfem() & !(1 << ep_num));
+ });
+ });
+ }
+
+ state.ep_in_wakers[ep_num].wake();
+ trace!("in ep={} irq val={:b}", ep_num, ep_ints.0);
+ }
+
+ ep_mask >>= 1;
+ ep_num += 1;
+ }
+ }
+
+ // not needed? reception handled in rxflvl
+ // OUT endpoint interrupt
+ // if ints.oepint() {
+ // let mut ep_mask = r.daint().read().oepint();
+ // let mut ep_num = 0;
+
+ // while ep_mask != 0 {
+ // if ep_mask & 1 != 0 {
+ // let ep_ints = r.doepint(ep_num).read();
+ // // clear all
+ // r.doepint(ep_num).write_value(ep_ints);
+ // state.ep_out_wakers[ep_num].wake();
+ // trace!("out ep={} irq val={=u32:b}", ep_num, ep_ints.0);
+ // }
+
+ // ep_mask >>= 1;
+ // ep_num += 1;
+ // }
+ // }
+ }
+}
+
+impl<'d, T: Instance> embassy_usb_driver::Bus for Bus<'d, T> {
+ async fn poll(&mut self) -> Event {
+ poll_fn(move |cx| {
+ // TODO: implement VBUS detection
+ if !self.enabled {
+ return Poll::Ready(Event::PowerDetected);
+ }
+
+ let r = T::regs();
+
+ T::state().bus_waker.register(cx.waker());
+
+ let ints = unsafe { r.gintsts().read() };
+ if ints.usbrst() {
+ trace!("reset");
+
+ self.init_fifo();
+ self.configure_endpoints();
+
+ // Reset address
+ // SAFETY: DCFG is shared with `ControlPipe` so critical section is needed for RMW
+ critical_section::with(|_| unsafe {
+ r.dcfg().modify(|w| {
+ w.set_dad(0);
+ });
+ });
+
+ // SAFETY: atomic clear on rc_w1 register
+ unsafe { r.gintsts().write(|w| w.set_usbrst(true)) }; // clear
+ Self::restore_irqs();
+ }
+
+ if ints.enumdne() {
+ trace!("enumdne");
+
+ // SAFETY: atomic read with no side effects
+ let speed = unsafe { r.dsts().read().enumspd() };
+ trace!(" speed={}", speed.0);
+
+ // SAFETY: register is only accessed by `Bus` under `&mut self`
+ unsafe {
+ r.gusbcfg().modify(|w| {
+ w.set_trdt(calculate_trdt(speed, T::frequency()));
+ })
+ };
+
+ // SAFETY: atomic clear on rc_w1 register
+ unsafe { r.gintsts().write(|w| w.set_enumdne(true)) }; // clear
+ Self::restore_irqs();
+
+ return Poll::Ready(Event::Reset);
+ }
+
+ if ints.usbsusp() {
+ trace!("suspend");
+ // SAFETY: atomic clear on rc_w1 register
+ unsafe { r.gintsts().write(|w| w.set_usbsusp(true)) }; // clear
+ Self::restore_irqs();
+ return Poll::Ready(Event::Suspend);
+ }
+
+ if ints.wkupint() {
+ trace!("resume");
+ // SAFETY: atomic clear on rc_w1 register
+ unsafe { r.gintsts().write(|w| w.set_wkupint(true)) }; // clear
+ Self::restore_irqs();
+ return Poll::Ready(Event::Resume);
+ }
+
+ Poll::Pending
+ })
+ .await
+ }
+
+ fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) {
+ trace!("endpoint_set_stalled ep={:?} en={}", ep_addr, stalled);
+
+ assert!(
+ ep_addr.index() < T::ENDPOINT_COUNT,
+ "endpoint_set_stalled index {} out of range",
+ ep_addr.index()
+ );
+
+ let regs = T::regs();
+ match ep_addr.direction() {
+ Direction::Out => {
+ // SAFETY: DOEPCTL is shared with `Endpoint` so critical section is needed for RMW
+ critical_section::with(|_| unsafe {
+ regs.doepctl(ep_addr.index()).modify(|w| {
+ w.set_stall(stalled);
+ });
+ });
+
+ T::state().ep_out_wakers[ep_addr.index()].wake();
+ }
+ Direction::In => {
+ // SAFETY: DIEPCTL is shared with `Endpoint` so critical section is needed for RMW
+ critical_section::with(|_| unsafe {
+ regs.diepctl(ep_addr.index()).modify(|w| {
+ w.set_stall(stalled);
+ });
+ });
+
+ T::state().ep_in_wakers[ep_addr.index()].wake();
+ }
+ }
+ }
+
+ fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool {
+ assert!(
+ ep_addr.index() < T::ENDPOINT_COUNT,
+ "endpoint_is_stalled index {} out of range",
+ ep_addr.index()
+ );
+
+ let regs = T::regs();
+
+ // SAFETY: atomic read with no side effects
+ match ep_addr.direction() {
+ Direction::Out => unsafe { regs.doepctl(ep_addr.index()).read().stall() },
+ Direction::In => unsafe { regs.diepctl(ep_addr.index()).read().stall() },
+ }
+ }
+
+ fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) {
+ trace!("endpoint_set_enabled ep={:?} en={}", ep_addr, enabled);
+
+ assert!(
+ ep_addr.index() < T::ENDPOINT_COUNT,
+ "endpoint_set_enabled index {} out of range",
+ ep_addr.index()
+ );
+
+ let r = T::regs();
+ match ep_addr.direction() {
+ Direction::Out => {
+ // SAFETY: DOEPCTL is shared with `Endpoint` so critical section is needed for RMW
+ critical_section::with(|_| unsafe {
+ // cancel transfer if active
+ if !enabled && r.doepctl(ep_addr.index()).read().epena() {
+ r.doepctl(ep_addr.index()).modify(|w| {
+ w.set_snak(true);
+ w.set_epdis(true);
+ })
+ }
+
+ r.doepctl(ep_addr.index()).modify(|w| {
+ w.set_usbaep(enabled);
+ })
+ });
+ }
+ Direction::In => {
+ // SAFETY: DIEPCTL is shared with `Endpoint` so critical section is needed for RMW
+ critical_section::with(|_| unsafe {
+ // cancel transfer if active
+ if !enabled && r.diepctl(ep_addr.index()).read().epena() {
+ r.diepctl(ep_addr.index()).modify(|w| {
+ w.set_snak(true);
+ w.set_epdis(true);
+ })
+ }
+
+ r.diepctl(ep_addr.index()).modify(|w| {
+ w.set_usbaep(enabled);
+ })
+ });
+ }
+ }
+ }
+
+ async fn enable(&mut self) {
+ trace!("enable");
+
+ // SAFETY: registers are only accessed by `Bus` under `&mut self`
+ unsafe {
+ #[cfg(stm32l4)]
+ {
+ crate::peripherals::PWR::enable();
+ critical_section::with(|_| crate::pac::PWR.cr2().modify(|w| w.set_usv(true)));
+ }
+
+ #[cfg(stm32h7)]
+ {
+ // If true, VDD33USB is generated by internal regulator from VDD50USB
+ // If false, VDD33USB and VDD50USB must be suplied directly with 3.3V (default on nucleo)
+ // TODO: unhardcode
+ let internal_regulator = false;
+
+ // Enable USB power
+ critical_section::with(|_| {
+ crate::pac::PWR.cr3().modify(|w| {
+ w.set_usb33den(true);
+ w.set_usbregen(internal_regulator);
+ })
+ });
+
+ // Wait for USB power to stabilize
+ while !crate::pac::PWR.cr3().read().usb33rdy() {}
+
+ // Use internal 48MHz HSI clock. Should be enabled in RCC by default.
+ critical_section::with(|_| {
+ crate::pac::RCC
+ .d2ccip2r()
+ .modify(|w| w.set_usbsel(crate::pac::rcc::vals::Usbsel::HSI48))
+ });
+
+ // Enable ULPI clock if external PHY is used
+ let ulpien = !self.phy_type.internal();
+ critical_section::with(|_| {
+ crate::pac::RCC.ahb1enr().modify(|w| {
+ if T::HIGH_SPEED {
+ w.set_usb_otg_hs_ulpien(ulpien);
+ } else {
+ w.set_usb_otg_fs_ulpien(ulpien);
+ }
+ });
+ crate::pac::RCC.ahb1lpenr().modify(|w| {
+ if T::HIGH_SPEED {
+ w.set_usb_otg_hs_ulpilpen(ulpien);
+ } else {
+ w.set_usb_otg_fs_ulpilpen(ulpien);
+ }
+ });
+ });
+ }
+
+ #[cfg(stm32u5)]
+ {
+ // Enable USB power
+ critical_section::with(|_| {
+ crate::pac::RCC.ahb3enr().modify(|w| {
+ w.set_pwren(true);
+ });
+ cortex_m::asm::delay(2);
+
+ crate::pac::PWR.svmcr().modify(|w| {
+ w.set_usv(true);
+ w.set_uvmen(true);
+ });
+ });
+
+ // Wait for USB power to stabilize
+ while !crate::pac::PWR.svmsr().read().vddusbrdy() {}
+
+ // Select HSI48 as USB clock source.
+ critical_section::with(|_| {
+ crate::pac::RCC.ccipr1().modify(|w| {
+ w.set_iclksel(crate::pac::rcc::vals::Iclksel::HSI48);
+ })
+ });
+ }
+
+ ::enable();
+ ::reset();
+
+ self.irq.set_handler(Self::on_interrupt);
+ self.irq.unpend();
+ self.irq.enable();
+
+ let r = T::regs();
+ let core_id = r.cid().read().0;
+ info!("Core id {:08x}", core_id);
+
+ // Wait for AHB ready.
+ while !r.grstctl().read().ahbidl() {}
+
+ // Configure as device.
+ r.gusbcfg().write(|w| {
+ // Force device mode
+ w.set_fdmod(true);
+ // Enable internal full-speed PHY
+ w.set_physel(self.phy_type.internal() && !self.phy_type.high_speed());
+ });
+
+ // Configuring Vbus sense and SOF output
+ match core_id {
+ 0x0000_1200 | 0x0000_1100 => {
+ assert!(self.phy_type != PhyType::InternalHighSpeed);
+
+ r.gccfg_v1().modify(|w| {
+ // Enable internal full-speed PHY, logic is inverted
+ w.set_pwrdwn(self.phy_type.internal());
+ });
+
+ // F429-like chips have the GCCFG.NOVBUSSENS bit
+ r.gccfg_v1().modify(|w| {
+ w.set_novbussens(true);
+ w.set_vbusasen(false);
+ w.set_vbusbsen(false);
+ w.set_sofouten(false);
+ });
+ }
+ 0x0000_2000 | 0x0000_2100 | 0x0000_2300 | 0x0000_3000 | 0x0000_3100 => {
+ // F446-like chips have the GCCFG.VBDEN bit with the opposite meaning
+ r.gccfg_v2().modify(|w| {
+ // Enable internal full-speed PHY, logic is inverted
+ w.set_pwrdwn(self.phy_type.internal() && !self.phy_type.high_speed());
+ w.set_phyhsen(self.phy_type.internal() && self.phy_type.high_speed());
+ });
+
+ r.gccfg_v2().modify(|w| {
+ w.set_vbden(false);
+ });
+
+ // Force B-peripheral session
+ r.gotgctl().modify(|w| {
+ w.set_bvaloen(true);
+ w.set_bvaloval(true);
+ });
+ }
+ _ => unimplemented!("Unknown USB core id {:X}", core_id),
+ }
+
+ // Soft disconnect.
+ r.dctl().write(|w| w.set_sdis(true));
+
+ // Set speed.
+ r.dcfg().write(|w| {
+ w.set_pfivl(vals::Pfivl::FRAME_INTERVAL_80);
+ w.set_dspd(self.phy_type.to_dspd());
+ });
+
+ // Unmask transfer complete EP interrupt
+ r.diepmsk().write(|w| {
+ w.set_xfrcm(true);
+ });
+
+ // Unmask and clear core interrupts
+ Bus::::restore_irqs();
+ r.gintsts().write_value(regs::Gintsts(0xFFFF_FFFF));
+
+ // Unmask global interrupt
+ r.gahbcfg().write(|w| {
+ w.set_gint(true); // unmask global interrupt
+ });
+
+ // Connect
+ r.dctl().write(|w| w.set_sdis(false));
+ }
+
+ self.enabled = true;
+ }
+
+ async fn disable(&mut self) {
+ trace!("disable");
+
+ Bus::disable(self);
+
+ self.enabled = false;
+ }
+
+ async fn remote_wakeup(&mut self) -> Result<(), Unsupported> {
+ Err(Unsupported)
+ }
+}
+
+impl<'d, T: Instance> Drop for Bus<'d, T> {
+ fn drop(&mut self) {
+ Bus::disable(self);
+ }
+}
+
+trait Dir {
+ fn dir() -> Direction;
+}
+
+pub enum In {}
+impl Dir for In {
+ fn dir() -> Direction {
+ Direction::In
+ }
+}
+
+pub enum Out {}
+impl Dir for Out {
+ fn dir() -> Direction {
+ Direction::Out
+ }
+}
+
+pub struct Endpoint<'d, T: Instance, D> {
+ _phantom: PhantomData<(&'d mut T, D)>,
+ info: EndpointInfo,
+}
+
+impl<'d, T: Instance> embassy_usb_driver::Endpoint for Endpoint<'d, T, In> {
+ fn info(&self) -> &EndpointInfo {
+ &self.info
+ }
+
+ async fn wait_enabled(&mut self) {}
+}
+
+impl<'d, T: Instance> embassy_usb_driver::Endpoint for Endpoint<'d, T, Out> {
+ fn info(&self) -> &EndpointInfo {
+ &self.info
+ }
+
+ async fn wait_enabled(&mut self) {}
+}
+
+impl<'d, T: Instance> embassy_usb_driver::EndpointOut for Endpoint<'d, T, Out> {
+ async fn read(&mut self, buf: &mut [u8]) -> Result {
+ trace!("read start len={}", buf.len());
+
+ poll_fn(|cx| {
+ let index = self.info.addr.index();
+ let state = T::state();
+
+ state.ep_out_wakers[index].register(cx.waker());
+
+ let len = state.ep_out_size[index].load(Ordering::Relaxed);
+ if len != EP_OUT_BUFFER_EMPTY {
+ trace!("read done len={}", len);
+
+ if len as usize > buf.len() {
+ return Poll::Ready(Err(EndpointError::BufferOverflow));
+ }
+
+ // SAFETY: exclusive access ensured by `ep_out_size` atomic variable
+ let data = unsafe { core::slice::from_raw_parts(*state.ep_out_buffers[index].get(), len as usize) };
+ buf[..len as usize].copy_from_slice(data);
+
+ // Release buffer
+ state.ep_out_size[index].store(EP_OUT_BUFFER_EMPTY, Ordering::Release);
+
+ // SAFETY: DOEPCTL/DOEPTSIZ is shared with `Bus` so a critical section is needed for RMW
+ critical_section::with(|_| unsafe {
+ // Receive 1 packet
+ T::regs().doeptsiz(index).modify(|w| {
+ w.set_xfrsiz(self.info.max_packet_size as _);
+ w.set_pktcnt(1);
+ });
+
+ // Clear NAK to indicate we are ready to receive more data
+ T::regs().doepctl(index).modify(|w| {
+ w.set_cnak(true);
+ });
+ });
+
+ Poll::Ready(Ok(len as usize))
+ } else {
+ Poll::Pending
+ }
+ })
+ .await
+ }
+}
+
+impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> {
+ async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> {
+ if buf.len() > self.info.max_packet_size as usize {
+ return Err(EndpointError::BufferOverflow);
+ }
+
+ let r = T::regs();
+ let index = self.info.addr.index();
+ let state = T::state();
+
+ // Wait for previous transfer to complete
+ poll_fn(|cx| {
+ state.ep_in_wakers[index].register(cx.waker());
+
+ // SAFETY: atomic read with no side effects
+ if unsafe { r.diepctl(index).read().epena() } {
+ Poll::Pending
+ } else {
+ Poll::Ready(())
+ }
+ })
+ .await;
+
+ if buf.len() > 0 {
+ poll_fn(|cx| {
+ state.ep_in_wakers[index].register(cx.waker());
+
+ let size_words = (buf.len() + 3) / 4;
+
+ // SAFETY: atomic read with no side effects
+ let fifo_space = unsafe { r.dtxfsts(index).read().ineptfsav() as usize };
+ if size_words > fifo_space {
+ // Not enough space in fifo, enable tx fifo empty interrupt
+ // SAFETY: DIEPEMPMSK is shared with IRQ so critical section is needed for RMW
+ critical_section::with(|_| unsafe {
+ r.diepempmsk().modify(|w| {
+ w.set_ineptxfem(w.ineptxfem() | (1 << index));
+ });
+ });
+
+ trace!("tx fifo for ep={} full, waiting for txfe", index);
+
+ Poll::Pending
+ } else {
+ Poll::Ready(())
+ }
+ })
+ .await
+ }
+
+ // SAFETY: DIEPTSIZ is exclusive to this endpoint under `&mut self`
+ unsafe {
+ // Setup transfer size
+ r.dieptsiz(index).write(|w| {
+ w.set_mcnt(1);
+ w.set_pktcnt(1);
+ w.set_xfrsiz(buf.len() as _);
+ });
+ }
+
+ // SAFETY: DIEPCTL is shared with `Bus` so a critical section is needed for RMW
+ critical_section::with(|_| unsafe {
+ // Enable endpoint
+ r.diepctl(index).modify(|w| {
+ w.set_cnak(true);
+ w.set_epena(true);
+ });
+ });
+
+ // Write data to FIFO
+ for chunk in buf.chunks(4) {
+ let mut tmp = [0u8; 4];
+ tmp[0..chunk.len()].copy_from_slice(chunk);
+ // SAFETY: FIFO is exclusive to this endpoint under `&mut self`
+ unsafe { r.fifo(index).write_value(regs::Fifo(u32::from_ne_bytes(tmp))) };
+ }
+
+ trace!("WRITE OK");
+
+ Ok(())
+ }
+}
+
+pub struct ControlPipe<'d, T: Instance> {
+ _phantom: PhantomData<&'d mut T>,
+ max_packet_size: u16,
+ ep_in: Endpoint<'d, T, In>,
+ ep_out: Endpoint<'d, T, Out>,
+}
+
+impl<'d, T: Instance> embassy_usb_driver::ControlPipe for ControlPipe<'d, T> {
+ fn max_packet_size(&self) -> usize {
+ usize::from(self.max_packet_size)
+ }
+
+ async fn setup(&mut self) -> [u8; 8] {
+ poll_fn(|cx| {
+ let state = T::state();
+
+ state.ep_out_wakers[0].register(cx.waker());
+
+ if state.ep0_setup_ready.load(Ordering::Relaxed) {
+ let data = unsafe { *state.ep0_setup_data.get() };
+ state.ep0_setup_ready.store(false, Ordering::Release);
+
+ // EP0 should not be controlled by `Bus` so this RMW does not need a critical section
+ unsafe {
+ // Receive 1 SETUP packet
+ T::regs().doeptsiz(self.ep_out.info.addr.index()).modify(|w| {
+ w.set_rxdpid_stupcnt(1);
+ });
+
+ // Clear NAK to indicate we are ready to receive more data
+ T::regs().doepctl(self.ep_out.info.addr.index()).modify(|w| {
+ w.set_cnak(true);
+ })
+ };
+
+ trace!("SETUP received: {:?}", data);
+ Poll::Ready(data)
+ } else {
+ trace!("SETUP waiting");
+ Poll::Pending
+ }
+ })
+ .await
+ }
+
+ async fn data_out(&mut self, buf: &mut [u8], _first: bool, _last: bool) -> Result {
+ trace!("control: data_out");
+ let len = self.ep_out.read(buf).await?;
+ trace!("control: data_out read: {:?}", &buf[..len]);
+ Ok(len)
+ }
+
+ async fn data_in(&mut self, data: &[u8], _first: bool, last: bool) -> Result<(), EndpointError> {
+ trace!("control: data_in write: {:?}", data);
+ self.ep_in.write(data).await?;
+
+ // wait for status response from host after sending the last packet
+ if last {
+ trace!("control: data_in waiting for status");
+ self.ep_out.read(&mut []).await?;
+ trace!("control: complete");
+ }
+
+ Ok(())
+ }
+
+ async fn accept(&mut self) {
+ trace!("control: accept");
+
+ self.ep_in.write(&[]).await.ok();
+
+ trace!("control: accept OK");
+ }
+
+ async fn reject(&mut self) {
+ trace!("control: reject");
+
+ // EP0 should not be controlled by `Bus` so this RMW does not need a critical section
+ unsafe {
+ let regs = T::regs();
+ regs.diepctl(self.ep_in.info.addr.index()).modify(|w| {
+ w.set_stall(true);
+ });
+ regs.doepctl(self.ep_out.info.addr.index()).modify(|w| {
+ w.set_stall(true);
+ });
+ }
+ }
+
+ async fn accept_set_address(&mut self, addr: u8) {
+ trace!("setting addr: {}", addr);
+ critical_section::with(|_| unsafe {
+ T::regs().dcfg().modify(|w| {
+ w.set_dad(addr);
+ });
+ });
+
+ // synopsys driver requires accept to be sent after changing address
+ self.accept().await
+ }
+}
+
+/// Translates HAL [EndpointType] into PAC [vals::Eptyp]
+fn to_eptyp(ep_type: EndpointType) -> vals::Eptyp {
+ match ep_type {
+ EndpointType::Control => vals::Eptyp::CONTROL,
+ EndpointType::Isochronous => vals::Eptyp::ISOCHRONOUS,
+ EndpointType::Bulk => vals::Eptyp::BULK,
+ EndpointType::Interrupt => vals::Eptyp::INTERRUPT,
+ }
+}
+
+/// Calculates total allocated FIFO size in words
+fn ep_fifo_size(eps: &[Option]) -> u16 {
+ eps.iter().map(|ep| ep.map(|ep| ep.fifo_size_words).unwrap_or(0)).sum()
+}
+
+/// Generates IRQ mask for enabled endpoints
+fn ep_irq_mask(eps: &[Option]) -> u16 {
+ eps.iter().enumerate().fold(
+ 0,
+ |mask, (index, ep)| {
+ if ep.is_some() {
+ mask | (1 << index)
+ } else {
+ mask
+ }
+ },
+ )
+}
+
+/// Calculates MPSIZ value for EP0, which uses special values.
+fn ep0_mpsiz(max_packet_size: u16) -> u16 {
+ match max_packet_size {
+ 8 => 0b11,
+ 16 => 0b10,
+ 32 => 0b01,
+ 64 => 0b00,
+ other => panic!("Unsupported EP0 size: {}", other),
+ }
+}
+
+fn calculate_trdt(speed: vals::Dspd, ahb_freq: Hertz) -> u8 {
+ match speed {
+ vals::Dspd::HIGH_SPEED => {
+ // From RM0431 (F72xx), RM0090 (F429), RM0390 (F446)
+ if ahb_freq.0 >= 30_000_000 {
+ 0x9
+ } else {
+ panic!("AHB frequency is too low")
+ }
+ }
+ vals::Dspd::FULL_SPEED_EXTERNAL | vals::Dspd::FULL_SPEED_INTERNAL => {
+ // From RM0431 (F72xx), RM0090 (F429)
+ match ahb_freq.0 {
+ 0..=14_199_999 => panic!("AHB frequency is too low"),
+ 14_200_000..=14_999_999 => 0xF,
+ 15_000_000..=15_999_999 => 0xE,
+ 16_000_000..=17_199_999 => 0xD,
+ 17_200_000..=18_499_999 => 0xC,
+ 18_500_000..=19_999_999 => 0xB,
+ 20_000_000..=21_799_999 => 0xA,
+ 21_800_000..=23_999_999 => 0x9,
+ 24_000_000..=27_499_999 => 0x8,
+ 27_500_000..=31_999_999 => 0x7, // 27.7..32 in code from CubeIDE
+ 32_000_000..=u32::MAX => 0x6,
+ }
+ }
+ _ => unimplemented!(),
+ }
+}
diff --git a/embassy-usb-driver/src/lib.rs b/embassy-usb-driver/src/lib.rs
index d7238dc7d..64d647351 100644
--- a/embassy-usb-driver/src/lib.rs
+++ b/embassy-usb-driver/src/lib.rs
@@ -164,9 +164,6 @@ pub trait Bus {
async fn poll(&mut self) -> Event;
- /// Sets the device USB address to `addr`.
- fn set_address(&mut self, addr: u8);
-
/// Enables or disables an endpoint.
fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool);
@@ -306,6 +303,12 @@ pub trait ControlPipe {
///
/// Sets a STALL condition on the pipe to indicate an error.
async fn reject(&mut self);
+
+ /// Accept SET_ADDRESS control and change bus address.
+ ///
+ /// For most drivers this function should firstly call `accept()` and then change the bus address.
+ /// However, there are peripherals (Synopsys USB OTG) that have reverse order.
+ async fn accept_set_address(&mut self, addr: u8);
}
pub trait EndpointIn: Endpoint {
diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs
index 661b84119..096e8b07a 100644
--- a/embassy-usb/src/lib.rs
+++ b/embassy-usb/src/lib.rs
@@ -122,10 +122,9 @@ struct Inner<'d, D: Driver<'d>> {
/// Our device address, or 0 if none.
address: u8,
- /// When receiving a set addr control request, we have to apply it AFTER we've
- /// finished handling the control request, as the status stage still has to be
- /// handled with addr 0.
- /// If true, do a set_addr after finishing the current control req.
+ /// SET_ADDRESS requests have special handling depending on the driver.
+ /// This flag indicates that requests must be handled by `ControlPipe::accept_set_address()`
+ /// instead of regular `accept()`.
set_address_pending: bool,
interfaces: Vec, MAX_INTERFACE_COUNT>,
@@ -254,11 +253,6 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
Direction::In => self.handle_control_in(req).await,
Direction::Out => self.handle_control_out(req).await,
}
-
- if self.inner.set_address_pending {
- self.inner.bus.set_address(self.inner.address);
- self.inner.set_address_pending = false;
- }
}
async fn handle_control_in(&mut self, req: Request) {
@@ -328,7 +322,14 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> {
trace!(" control out data: {:02x?}", data);
match self.inner.handle_control_out(req, data) {
- OutResponse::Accepted => self.control.accept().await,
+ OutResponse::Accepted => {
+ if self.inner.set_address_pending {
+ self.control.accept_set_address(self.inner.address).await;
+ self.inner.set_address_pending = false;
+ } else {
+ self.control.accept().await
+ }
+ }
OutResponse::Rejected => self.control.reject().await,
}
}
@@ -655,7 +656,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
buf[1] = descriptor_type::STRING;
let mut pos = 2;
for c in s.encode_utf16() {
- if pos >= buf.len() {
+ if pos + 2 >= buf.len() {
panic!("control buffer too small");
}
diff --git a/examples/stm32f4/Cargo.toml b/examples/stm32f4/Cargo.toml
index 62d3f08df..252d60855 100644
--- a/examples/stm32f4/Cargo.toml
+++ b/examples/stm32f4/Cargo.toml
@@ -10,6 +10,7 @@ embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["de
embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] }
embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] }
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti"] }
+embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
defmt = "0.3"
defmt-rtt = "0.4"
@@ -26,5 +27,5 @@ embedded-storage = "0.3.0"
micromath = "2.0.0"
static_cell = "1.0"
-usb-device = "0.2"
-usbd-serial = "0.1.1"
+[profile.release]
+debug = 2
diff --git a/examples/stm32f4/src/bin/usb_serial.rs b/examples/stm32f4/src/bin/usb_serial.rs
new file mode 100644
index 000000000..014647762
--- /dev/null
+++ b/examples/stm32f4/src/bin/usb_serial.rs
@@ -0,0 +1,106 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::{panic, *};
+use embassy_executor::Spawner;
+use embassy_stm32::time::mhz;
+use embassy_stm32::usb_otg::{Driver, Instance};
+use embassy_stm32::{interrupt, Config};
+use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
+use embassy_usb::driver::EndpointError;
+use embassy_usb::Builder;
+use futures::future::join;
+use {defmt_rtt as _, panic_probe as _};
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+ info!("Hello World!");
+
+ let mut config = Config::default();
+ config.rcc.pll48 = true;
+ config.rcc.sys_ck = Some(mhz(48));
+
+ let p = embassy_stm32::init(config);
+
+ // Create the driver, from the HAL.
+ let irq = interrupt::take!(OTG_FS);
+ let mut ep_out_buffer = [0u8; 256];
+ let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer);
+
+ // Create embassy-usb Config
+ let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
+ config.manufacturer = Some("Embassy");
+ config.product = Some("USB-serial example");
+ config.serial_number = Some("12345678");
+
+ // Required for windows compatiblity.
+ // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
+ config.device_class = 0xEF;
+ config.device_sub_class = 0x02;
+ config.device_protocol = 0x01;
+ config.composite_with_iads = true;
+
+ // Create embassy-usb DeviceBuilder using the driver and config.
+ // It needs some buffers for building the descriptors.
+ let mut device_descriptor = [0; 256];
+ let mut config_descriptor = [0; 256];
+ let mut bos_descriptor = [0; 256];
+ let mut control_buf = [0; 64];
+
+ let mut state = State::new();
+
+ let mut builder = Builder::new(
+ driver,
+ config,
+ &mut device_descriptor,
+ &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();
+
+ // Run the USB device.
+ let usb_fut = usb.run();
+
+ // Do stuff with the class!
+ let echo_fut = async {
+ loop {
+ class.wait_connection().await;
+ info!("Connected");
+ let _ = echo(&mut class).await;
+ info!("Disconnected");
+ }
+ };
+
+ // Run everything concurrently.
+ // If we had made everything `'static` above instead, we could do this using separate tasks instead.
+ join(usb_fut, echo_fut).await;
+}
+
+struct Disconnected {}
+
+impl From for Disconnected {
+ fn from(val: EndpointError) -> Self {
+ match val {
+ EndpointError::BufferOverflow => panic!("Buffer overflow"),
+ EndpointError::Disabled => Disconnected {},
+ }
+ }
+}
+
+async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> {
+ let mut buf = [0; 64];
+ loop {
+ let n = class.read_packet(&mut buf).await?;
+ let data = &buf[..n];
+ info!("data: {:x}", data);
+ class.write_packet(data).await?;
+ }
+}
diff --git a/examples/stm32f7/Cargo.toml b/examples/stm32f7/Cargo.toml
index b80dbbf9c..ea4cbd808 100644
--- a/examples/stm32f7/Cargo.toml
+++ b/examples/stm32f7/Cargo.toml
@@ -11,6 +11,7 @@ embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["de
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f767zi", "unstable-pac", "time-driver-any", "exti"] }
embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet"] }
embedded-io = { version = "0.4.0", features = ["async"] }
+embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
defmt = "0.3"
defmt-rtt = "0.4"
diff --git a/examples/stm32f7/src/bin/usb_serial.rs b/examples/stm32f7/src/bin/usb_serial.rs
new file mode 100644
index 000000000..688bd0dab
--- /dev/null
+++ b/examples/stm32f7/src/bin/usb_serial.rs
@@ -0,0 +1,107 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::{panic, *};
+use embassy_executor::Spawner;
+use embassy_stm32::time::mhz;
+use embassy_stm32::usb_otg::{Driver, Instance};
+use embassy_stm32::{interrupt, Config};
+use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
+use embassy_usb::driver::EndpointError;
+use embassy_usb::Builder;
+use futures::future::join;
+use {defmt_rtt as _, panic_probe as _};
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+ info!("Hello World!");
+
+ let mut config = Config::default();
+ config.rcc.hse = Some(mhz(8));
+ config.rcc.pll48 = true;
+ config.rcc.sys_ck = Some(mhz(200));
+
+ let p = embassy_stm32::init(config);
+
+ // Create the driver, from the HAL.
+ let irq = interrupt::take!(OTG_FS);
+ let mut ep_out_buffer = [0u8; 256];
+ let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer);
+
+ // Create embassy-usb Config
+ let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
+ config.manufacturer = Some("Embassy");
+ config.product = Some("USB-serial example");
+ config.serial_number = Some("12345678");
+
+ // Required for windows compatiblity.
+ // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
+ config.device_class = 0xEF;
+ config.device_sub_class = 0x02;
+ config.device_protocol = 0x01;
+ config.composite_with_iads = true;
+
+ // Create embassy-usb DeviceBuilder using the driver and config.
+ // It needs some buffers for building the descriptors.
+ let mut device_descriptor = [0; 256];
+ let mut config_descriptor = [0; 256];
+ let mut bos_descriptor = [0; 256];
+ let mut control_buf = [0; 64];
+
+ let mut state = State::new();
+
+ let mut builder = Builder::new(
+ driver,
+ config,
+ &mut device_descriptor,
+ &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();
+
+ // Run the USB device.
+ let usb_fut = usb.run();
+
+ // Do stuff with the class!
+ let echo_fut = async {
+ loop {
+ class.wait_connection().await;
+ info!("Connected");
+ let _ = echo(&mut class).await;
+ info!("Disconnected");
+ }
+ };
+
+ // Run everything concurrently.
+ // If we had made everything `'static` above instead, we could do this using separate tasks instead.
+ join(usb_fut, echo_fut).await;
+}
+
+struct Disconnected {}
+
+impl From for Disconnected {
+ fn from(val: EndpointError) -> Self {
+ match val {
+ EndpointError::BufferOverflow => panic!("Buffer overflow"),
+ EndpointError::Disabled => Disconnected {},
+ }
+ }
+}
+
+async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> {
+ let mut buf = [0; 64];
+ loop {
+ let n = class.read_packet(&mut buf).await?;
+ let data = &buf[..n];
+ info!("data: {:x}", data);
+ class.write_packet(data).await?;
+ }
+}
diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml
index d30c42b1f..ff38440a7 100644
--- a/examples/stm32h7/Cargo.toml
+++ b/examples/stm32h7/Cargo.toml
@@ -11,6 +11,7 @@ embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["de
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32h743bi", "time-driver-any", "exti", "unstable-pac", "unstable-traits"] }
embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "unstable-traits"] }
embedded-io = { version = "0.4.0", features = ["async"] }
+embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
defmt = "0.3"
defmt-rtt = "0.4"
diff --git a/examples/stm32h7/src/bin/usb_serial.rs b/examples/stm32h7/src/bin/usb_serial.rs
new file mode 100644
index 000000000..b319d12c3
--- /dev/null
+++ b/examples/stm32h7/src/bin/usb_serial.rs
@@ -0,0 +1,106 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::{panic, *};
+use embassy_executor::Spawner;
+use embassy_stm32::time::mhz;
+use embassy_stm32::usb_otg::{Driver, Instance};
+use embassy_stm32::{interrupt, Config};
+use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
+use embassy_usb::driver::EndpointError;
+use embassy_usb::Builder;
+use futures::future::join;
+use {defmt_rtt as _, panic_probe as _};
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+ info!("Hello World!");
+
+ let mut config = Config::default();
+ config.rcc.sys_ck = Some(mhz(400));
+ config.rcc.hclk = Some(mhz(200));
+ config.rcc.pll1.q_ck = Some(mhz(100));
+ let p = embassy_stm32::init(config);
+
+ // Create the driver, from the HAL.
+ let irq = interrupt::take!(OTG_FS);
+ let mut ep_out_buffer = [0u8; 256];
+ let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer);
+
+ // Create embassy-usb Config
+ let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
+ config.manufacturer = Some("Embassy");
+ config.product = Some("USB-serial example");
+ config.serial_number = Some("12345678");
+
+ // Required for windows compatiblity.
+ // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
+ config.device_class = 0xEF;
+ config.device_sub_class = 0x02;
+ config.device_protocol = 0x01;
+ config.composite_with_iads = true;
+
+ // Create embassy-usb DeviceBuilder using the driver and config.
+ // It needs some buffers for building the descriptors.
+ let mut device_descriptor = [0; 256];
+ let mut config_descriptor = [0; 256];
+ let mut bos_descriptor = [0; 256];
+ let mut control_buf = [0; 64];
+
+ let mut state = State::new();
+
+ let mut builder = Builder::new(
+ driver,
+ config,
+ &mut device_descriptor,
+ &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();
+
+ // Run the USB device.
+ let usb_fut = usb.run();
+
+ // Do stuff with the class!
+ let echo_fut = async {
+ loop {
+ class.wait_connection().await;
+ info!("Connected");
+ let _ = echo(&mut class).await;
+ info!("Disconnected");
+ }
+ };
+
+ // Run everything concurrently.
+ // If we had made everything `'static` above instead, we could do this using separate tasks instead.
+ join(usb_fut, echo_fut).await;
+}
+
+struct Disconnected {}
+
+impl From for Disconnected {
+ fn from(val: EndpointError) -> Self {
+ match val {
+ EndpointError::BufferOverflow => panic!("Buffer overflow"),
+ EndpointError::Disabled => Disconnected {},
+ }
+ }
+}
+
+async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> {
+ let mut buf = [0; 64];
+ loop {
+ let n = class.read_packet(&mut buf).await?;
+ let data = &buf[..n];
+ info!("data: {:x}", data);
+ class.write_packet(data).await?;
+ }
+}
diff --git a/examples/stm32l4/Cargo.toml b/examples/stm32l4/Cargo.toml
index 45d3dd366..5627760ef 100644
--- a/examples/stm32l4/Cargo.toml
+++ b/examples/stm32l4/Cargo.toml
@@ -12,6 +12,7 @@ embassy-executor = { version = "0.1.0", path = "../../embassy-executor", feature
embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" }
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l4s5vi", "time-driver-any", "exti", "unstable-traits"] }
+embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
defmt = "0.3"
defmt-rtt = "0.4"
@@ -26,6 +27,3 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa
heapless = { version = "0.7.5", default-features = false }
micromath = "2.0.0"
-usb-device = "0.2"
-usbd-serial = "0.1.1"
-
diff --git a/examples/stm32l4/src/bin/usb_serial.rs b/examples/stm32l4/src/bin/usb_serial.rs
new file mode 100644
index 000000000..3e38b10a3
--- /dev/null
+++ b/examples/stm32l4/src/bin/usb_serial.rs
@@ -0,0 +1,108 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::{panic, *};
+use defmt_rtt as _; // global logger
+use embassy_executor::Spawner;
+use embassy_stm32::rcc::*;
+use embassy_stm32::usb_otg::{Driver, Instance};
+use embassy_stm32::{interrupt, Config};
+use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
+use embassy_usb::driver::EndpointError;
+use embassy_usb::Builder;
+use futures::future::join;
+use panic_probe as _;
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+ info!("Hello World!");
+
+ let mut config = Config::default();
+ config.rcc.mux = ClockSrc::PLL(PLLSource::HSI16, PLLClkDiv::Div2, PLLSrcDiv::Div1, PLLMul::Mul10, None);
+ config.rcc.hsi48 = true;
+
+ let p = embassy_stm32::init(config);
+
+ // Create the driver, from the HAL.
+ let irq = interrupt::take!(OTG_FS);
+ let mut ep_out_buffer = [0u8; 256];
+ let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer);
+
+ // Create embassy-usb Config
+ let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
+ config.max_packet_size_0 = 64;
+ config.manufacturer = Some("Embassy");
+ config.product = Some("USB-serial example");
+ config.serial_number = Some("12345678");
+
+ // Required for windows compatiblity.
+ // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
+ config.device_class = 0xEF;
+ config.device_sub_class = 0x02;
+ config.device_protocol = 0x01;
+ config.composite_with_iads = true;
+
+ // Create embassy-usb DeviceBuilder using the driver and config.
+ // It needs some buffers for building the descriptors.
+ let mut device_descriptor = [0; 256];
+ let mut config_descriptor = [0; 256];
+ let mut bos_descriptor = [0; 256];
+ let mut control_buf = [0; 64];
+
+ let mut state = State::new();
+
+ let mut builder = Builder::new(
+ driver,
+ config,
+ &mut device_descriptor,
+ &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();
+
+ // Run the USB device.
+ let usb_fut = usb.run();
+
+ // Do stuff with the class!
+ let echo_fut = async {
+ loop {
+ class.wait_connection().await;
+ info!("Connected");
+ let _ = echo(&mut class).await;
+ info!("Disconnected");
+ }
+ };
+
+ // Run everything concurrently.
+ // If we had made everything `'static` above instead, we could do this using separate tasks instead.
+ join(usb_fut, echo_fut).await;
+}
+
+struct Disconnected {}
+
+impl From for Disconnected {
+ fn from(val: EndpointError) -> Self {
+ match val {
+ EndpointError::BufferOverflow => panic!("Buffer overflow"),
+ EndpointError::Disabled => Disconnected {},
+ }
+ }
+}
+
+async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> {
+ let mut buf = [0; 64];
+ loop {
+ let n = class.read_packet(&mut buf).await?;
+ let data = &buf[..n];
+ info!("data: {:x}", data);
+ class.write_packet(data).await?;
+ }
+}
diff --git a/examples/stm32u5/Cargo.toml b/examples/stm32u5/Cargo.toml
index d88fdda50..2b02eda92 100644
--- a/examples/stm32u5/Cargo.toml
+++ b/examples/stm32u5/Cargo.toml
@@ -9,6 +9,7 @@ embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["de
embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] }
embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32u585ai", "time-driver-any", "memory-x" ] }
+embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
defmt = "0.3"
defmt-rtt = "0.4"
diff --git a/examples/stm32u5/src/bin/usb_serial.rs b/examples/stm32u5/src/bin/usb_serial.rs
new file mode 100644
index 000000000..c846836b0
--- /dev/null
+++ b/examples/stm32u5/src/bin/usb_serial.rs
@@ -0,0 +1,108 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::{panic, *};
+use defmt_rtt as _; // global logger
+use embassy_executor::Spawner;
+use embassy_stm32::rcc::*;
+use embassy_stm32::usb_otg::{Driver, Instance};
+use embassy_stm32::{interrupt, Config};
+use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
+use embassy_usb::driver::EndpointError;
+use embassy_usb::Builder;
+use futures::future::join;
+use panic_probe as _;
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+ info!("Hello World!");
+
+ let mut config = Config::default();
+ config.rcc.mux = ClockSrc::PLL1R(PllSrc::HSI16, PllM::Div2, PllN::Mul10, PllClkDiv::NotDivided);
+ //config.rcc.mux = ClockSrc::MSI(MSIRange::Range48mhz);
+ config.rcc.hsi48 = true;
+
+ let p = embassy_stm32::init(config);
+
+ // Create the driver, from the HAL.
+ let irq = interrupt::take!(OTG_FS);
+ let mut ep_out_buffer = [0u8; 256];
+ let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer);
+
+ // Create embassy-usb Config
+ let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
+ config.manufacturer = Some("Embassy");
+ config.product = Some("USB-serial example");
+ config.serial_number = Some("12345678");
+
+ // Required for windows compatiblity.
+ // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
+ config.device_class = 0xEF;
+ config.device_sub_class = 0x02;
+ config.device_protocol = 0x01;
+ config.composite_with_iads = true;
+
+ // Create embassy-usb DeviceBuilder using the driver and config.
+ // It needs some buffers for building the descriptors.
+ let mut device_descriptor = [0; 256];
+ let mut config_descriptor = [0; 256];
+ let mut bos_descriptor = [0; 256];
+ let mut control_buf = [0; 64];
+
+ let mut state = State::new();
+
+ let mut builder = Builder::new(
+ driver,
+ config,
+ &mut device_descriptor,
+ &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();
+
+ // Run the USB device.
+ let usb_fut = usb.run();
+
+ // Do stuff with the class!
+ let echo_fut = async {
+ loop {
+ class.wait_connection().await;
+ info!("Connected");
+ let _ = echo(&mut class).await;
+ info!("Disconnected");
+ }
+ };
+
+ // Run everything concurrently.
+ // If we had made everything `'static` above instead, we could do this using separate tasks instead.
+ join(usb_fut, echo_fut).await;
+}
+
+struct Disconnected {}
+
+impl From for Disconnected {
+ fn from(val: EndpointError) -> Self {
+ match val {
+ EndpointError::BufferOverflow => panic!("Buffer overflow"),
+ EndpointError::Disabled => Disconnected {},
+ }
+ }
+}
+
+async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> {
+ let mut buf = [0; 64];
+ loop {
+ let n = class.read_packet(&mut buf).await?;
+ let data = &buf[..n];
+ info!("data: {:x}", data);
+ class.write_packet(data).await?;
+ }
+}
diff --git a/stm32-data b/stm32-data
index 14a448c31..844793fc3 160000
--- a/stm32-data
+++ b/stm32-data
@@ -1 +1 @@
-Subproject commit 14a448c318192fe9da1c95a4de1beb4ec4892f1c
+Subproject commit 844793fc3da2ba3f12ab6a69b78cd8e6fb5497b4