summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--embassy-stm32/Cargo.toml5
-rw-r--r--embassy-stm32/build.rs2
-rw-r--r--embassy-stm32/src/lib.rs2
-rw-r--r--embassy-stm32/src/usb/mod.rs36
-rw-r--r--embassy-stm32/src/usb/usb.rs1064
-rw-r--r--examples/stm32f1/Cargo.toml2
-rw-r--r--examples/stm32f1/src/bin/usb_serial.rs117
-rw-r--r--examples/stm32f3/.cargo/config.toml2
-rw-r--r--examples/stm32f3/Cargo.toml5
-rw-r--r--examples/stm32f3/src/bin/usb_serial.rs116
-rw-r--r--examples/stm32l5/.cargo/config.toml6
-rw-r--r--examples/stm32l5/Cargo.toml30
-rw-r--r--examples/stm32l5/build.rs5
-rw-r--r--examples/stm32l5/src/bin/button_exti.rs28
-rw-r--r--examples/stm32l5/src/bin/rng.rs34
-rw-r--r--examples/stm32l5/src/bin/usb_ethernet.rs290
-rw-r--r--examples/stm32l5/src/bin/usb_hid_mouse.rs136
-rw-r--r--examples/stm32l5/src/bin/usb_serial.rs112
m---------stm32-data0
19 files changed, 1988 insertions, 4 deletions
diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml
index 32311cc7..01a96a5f 100644
--- a/embassy-stm32/Cargo.toml
+++ b/embassy-stm32/Cargo.toml
@@ -37,6 +37,7 @@ embassy = { version = "0.1.0", path = "../embassy" }
embassy-macros = { version = "0.1.0", path = "../embassy-macros", features = ["stm32"] }
embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common" }
embassy-net = { version = "0.1.0", path = "../embassy-net", optional = true }
+embassy-usb = {version = "0.1.0", path = "../embassy-usb", optional = true }
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] }
embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8", optional = true}
@@ -71,7 +72,7 @@ quote = "1.0.15"
stm32-metapac = { version = "0.1.0", path = "../stm32-metapac", default-features = false, features = ["metadata"]}
[features]
-defmt = ["dep:defmt", "embassy/defmt", "embedded-io?/defmt" ]
+defmt = ["dep:defmt", "embassy/defmt", "embedded-io?/defmt", "embassy-usb?/defmt"]
sdmmc-rs = ["embedded-sdmmc"]
net = ["embassy-net" ]
memory-x = ["stm32-metapac/memory-x"]
@@ -90,7 +91,7 @@ time-driver-tim12 = ["_time-driver"]
time-driver-tim15 = ["_time-driver"]
# Enable nightly-only features
-nightly = ["embassy/nightly", "embedded-hal-1", "embedded-hal-async", "embedded-storage-async", "dep:embedded-io"]
+nightly = ["embassy/nightly", "embedded-hal-1", "embedded-hal-async", "embedded-storage-async", "dep:embedded-io", "dep:embassy-usb"]
# Reexport stm32-metapac at `embassy_stm32::pac`.
# This is unstable because semver-minor (non-breaking) releases of embassy-stm32 may major-bump (breaking) the stm32-metapac version.
diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs
index 490f2d8f..7b1376f0 100644
--- a/embassy-stm32/build.rs
+++ b/embassy-stm32/build.rs
@@ -279,6 +279,8 @@ fn main() {
(("dcmi", "HSYNC"), quote!(crate::dcmi::HSyncPin)),
(("dcmi", "VSYNC"), quote!(crate::dcmi::VSyncPin)),
(("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)),
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs
index fbd2008c..bb70faab 100644
--- a/embassy-stm32/src/lib.rs
+++ b/embassy-stm32/src/lib.rs
@@ -63,6 +63,8 @@ pub mod sdmmc;
pub mod spi;
#[cfg(usart)]
pub mod usart;
+#[cfg(usb)]
+pub mod usb;
#[cfg(any(otgfs, otghs))]
pub mod usb_otg;
diff --git a/embassy-stm32/src/usb/mod.rs b/embassy-stm32/src/usb/mod.rs
new file mode 100644
index 00000000..71b407cb
--- /dev/null
+++ b/embassy-stm32/src/usb/mod.rs
@@ -0,0 +1,36 @@
+use embassy::interrupt::Interrupt;
+
+use crate::rcc::RccPeripheral;
+
+#[cfg(feature = "nightly")]
+mod usb;
+#[cfg(feature = "nightly")]
+pub use usb::*;
+
+pub(crate) mod sealed {
+ pub trait Instance {
+ fn regs() -> crate::pac::usb::Usb;
+ }
+}
+
+pub trait Instance: sealed::Instance + RccPeripheral + 'static {
+ type Interrupt: Interrupt;
+}
+
+// Internal PHY pins
+pin_trait!(DpPin, Instance);
+pin_trait!(DmPin, Instance);
+
+foreach_interrupt!(
+ ($inst:ident, usb, $block:ident, LP, $irq:ident) => {
+ impl sealed::Instance for crate::peripherals::$inst {
+ fn regs() -> crate::pac::usb::Usb {
+ crate::pac::$inst
+ }
+ }
+
+ impl Instance for crate::peripherals::$inst {
+ type Interrupt = crate::interrupt::$irq;
+ }
+ };
+);
diff --git a/embassy-stm32/src/usb/usb.rs b/embassy-stm32/src/usb/usb.rs
new file mode 100644
index 00000000..113b2026
--- /dev/null
+++ b/embassy-stm32/src/usb/usb.rs
@@ -0,0 +1,1064 @@
+#![macro_use]
+
+use atomic_polyfill::{AtomicBool, AtomicU8};
+use core::marker::PhantomData;
+use core::sync::atomic::Ordering;
+use core::task::Poll;
+use embassy::interrupt::InterruptExt;
+use embassy::time::{block_for, Duration};
+use embassy::util::Unborrow;
+use embassy::waitqueue::AtomicWaker;
+use embassy_hal_common::unborrow;
+use embassy_usb::driver::{self, EndpointAllocError, EndpointError, Event, Unsupported};
+use embassy_usb::types::{EndpointAddress, EndpointInfo, EndpointType, UsbDirection};
+use futures::future::poll_fn;
+use futures::Future;
+use pac::common::{Reg, RW};
+use pac::usb::vals::{EpType, Stat};
+
+use crate::gpio::sealed::AFType;
+use crate::pac;
+use crate::pac::usb::regs;
+use crate::rcc::sealed::RccPeripheral;
+
+use super::{DmPin, DpPin, Instance};
+
+const EP_COUNT: usize = 8;
+
+#[cfg(any(usb_v1_x1, usb_v1_x2))]
+const EP_MEMORY_SIZE: usize = 512;
+#[cfg(not(any(usb_v1_x1, usb_v1_x2)))]
+const EP_MEMORY_SIZE: usize = 1024;
+
+const NEW_AW: AtomicWaker = AtomicWaker::new();
+static BUS_WAKER: AtomicWaker = NEW_AW;
+static EP0_SETUP: AtomicBool = AtomicBool::new(false);
+static EP_IN_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT];
+static EP_OUT_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT];
+static IRQ_FLAGS: AtomicU8 = AtomicU8::new(0);
+const IRQ_FLAG_RESET: u8 = 0x01;
+const IRQ_FLAG_SUSPEND: u8 = 0x02;
+const IRQ_FLAG_RESUME: u8 = 0x04;
+
+fn convert_type(t: EndpointType) -> EpType {
+ match t {
+ EndpointType::Bulk => EpType::BULK,
+ EndpointType::Control => EpType::CONTROL,
+ EndpointType::Interrupt => EpType::INTERRUPT,
+ EndpointType::Isochronous => EpType::ISO,
+ }
+}
+
+fn invariant(mut r: regs::Epr) -> regs::Epr {
+ r.set_ctr_rx(true); // don't clear
+ r.set_ctr_tx(true); // don't clear
+ r.set_dtog_rx(false); // don't toggle
+ r.set_dtog_tx(false); // don't toggle
+ r.set_stat_rx(Stat(0));
+ r.set_stat_tx(Stat(0));
+ r
+}
+
+// Returns (actual_len, len_bits)
+fn calc_out_len(len: u16) -> (u16, u16) {
+ match len {
+ 2..=62 => ((len + 1) / 2 * 2, ((len + 1) / 2) << 10),
+ 63..=480 => ((len + 31) / 32 * 32, (((len + 31) / 32 - 1) << 10) | 0x8000),
+ _ => panic!("invalid OUT length {}", len),
+ }
+}
+fn ep_in_addr<T: Instance>(index: usize) -> Reg<u16, RW> {
+ T::regs().ep_mem(index * 4 + 0)
+}
+fn ep_in_len<T: Instance>(index: usize) -> Reg<u16, RW> {
+ T::regs().ep_mem(index * 4 + 1)
+}
+fn ep_out_addr<T: Instance>(index: usize) -> Reg<u16, RW> {
+ T::regs().ep_mem(index * 4 + 2)
+}
+fn ep_out_len<T: Instance>(index: usize) -> Reg<u16, RW> {
+ T::regs().ep_mem(index * 4 + 3)
+}
+
+struct EndpointBuffer<T: Instance> {
+ addr: u16,
+ len: u16,
+ _phantom: PhantomData<T>,
+}
+
+impl<T: Instance> EndpointBuffer<T> {
+ fn read(&mut self, buf: &mut [u8]) {
+ assert!(buf.len() <= self.len as usize);
+ for i in 0..((buf.len() + 1) / 2) {
+ let val = unsafe { T::regs().ep_mem(self.addr as usize / 2 + i).read() };
+ buf[i * 2] = val as u8;
+ if i * 2 + 1 < buf.len() {
+ buf[i * 2 + 1] = (val >> 8) as u8;
+ }
+ }
+ }
+
+ fn write(&mut self, buf: &[u8]) {
+ assert!(buf.len() <= self.len as usize);
+ for i in 0..((buf.len() + 1) / 2) {
+ let mut val = buf[i * 2] as u16;
+ if i * 2 + 1 < buf.len() {
+ val |= (buf[i * 2 + 1] as u16) << 8;
+ }
+ unsafe {
+ T::regs()
+ .ep_mem(self.addr as usize / 2 + i)
+ .write_value(val)
+ };
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+struct EndpointData {
+ ep_type: EndpointType, // only valid if used_in || used_out
+ used_in: bool,
+ used_out: bool,
+}
+
+pub struct Driver<'d, T: Instance> {
+ phantom: PhantomData<&'d mut T>,
+ alloc: [EndpointData; EP_COUNT],
+ ep_mem_free: u16, // first free address in EP mem, in bytes.
+}
+
+impl<'d, T: Instance> Driver<'d, T> {
+ pub fn new(
+ _usb: impl Unborrow<Target = T> + 'd,
+ irq: impl Unborrow<Target = T::Interrupt> + 'd,
+ dp: impl Unborrow<Target = impl DpPin<T>> + 'd,
+ dm: impl Unborrow<Target = impl DmPin<T>> + 'd,
+ ) -> Self {
+ unborrow!(irq, dp, dm);
+ irq.set_handler(Self::on_interrupt);
+ irq.unpend();
+ irq.enable();
+
+ let regs = T::regs();
+
+ #[cfg(stm32l5)]
+ unsafe {
+ crate::peripherals::PWR::enable();
+
+ pac::PWR
+ .cr2()
+ .modify(|w| w.set_usv(pac::pwr::vals::Usv::VALID));
+ }
+
+ unsafe {
+ <T as RccPeripheral>::enable();
+ <T as RccPeripheral>::reset();
+
+ regs.cntr().write(|w| {
+ w.set_pdwn(false);
+ w.set_fres(true);
+ });
+
+ block_for(Duration::from_millis(100));
+
+ regs.btable().write(|w| w.set_btable(0));
+
+ dp.set_as_af(dp.af_num(), AFType::OutputPushPull);
+ dm.set_as_af(dm.af_num(), AFType::OutputPushPull);
+ }
+
+ Self {
+ phantom: PhantomData,
+ alloc: [EndpointData {
+ ep_type: EndpointType::Bulk,
+ used_in: false,
+ used_out: false,
+ }; EP_COUNT],
+ ep_mem_free: EP_COUNT as u16 * 8, // for each EP, 4 regs, so 8 bytes
+ }
+ }
+
+ fn on_interrupt(_: *mut ()) {
+ unsafe {
+ let regs = T::regs();
+ //let x = regs.istr().read().0;
+ //trace!("USB IRQ: {:08x}", x);
+
+ let istr = regs.istr().read();
+
+ let mut flags: u8 = 0;
+
+ if istr.susp() {
+ //trace!("USB IRQ: susp");
+ flags |= IRQ_FLAG_SUSPEND;
+ regs.cntr().modify(|w| {
+ w.set_fsusp(true);
+ w.set_lpmode(true);
+ })
+ }
+
+ if istr.wkup() {
+ //trace!("USB IRQ: wkup");
+ flags |= IRQ_FLAG_RESUME;
+ regs.cntr().modify(|w| {
+ w.set_fsusp(false);
+ w.set_lpmode(false);
+ })
+ }
+
+ if istr.reset() {
+ //trace!("USB IRQ: reset");
+ flags |= IRQ_FLAG_RESET;
+
+ // Write 0 to clear.
+ let mut clear = regs::Istr(!0);
+ clear.set_reset(false);
+ regs.istr().write_value(clear);
+ }
+
+ if flags != 0 {
+ // Send irqs to main thread.
+ IRQ_FLAGS.fetch_or(flags, Ordering::AcqRel);
+ BUS_WAKER.wake();
+
+ // Clear them
+ let mut mask = regs::Istr(0);
+ mask.set_wkup(true);
+ mask.set_susp(true);
+ mask.set_reset(true);
+ regs.istr().write_value(regs::Istr(!(istr.0 & mask.0)));
+ }
+
+ if istr.ctr() {
+ let index = istr.ep_id() as usize;
+ let mut epr = regs.epr(index).read();
+ if epr.ctr_rx() {
+ if index == 0 && epr.setup() {
+ EP0_SETUP.store(true, Ordering::Relaxed);
+ }
+ //trace!("EP {} RX, setup={}", index, epr.setup());
+ EP_OUT_WAKERS[index].wake();
+ }
+ if epr.ctr_tx() {
+ //trace!("EP {} TX", index);
+ EP_IN_WAKERS[index].wake();
+ }
+ epr.set_dtog_rx(false);
+ epr.set_dtog_tx(false);
+ epr.set_stat_rx(Stat(0));
+ epr.set_stat_tx(Stat(0));
+ epr.set_ctr_rx(!epr.ctr_rx());
+ epr.set_ctr_tx(!epr.ctr_tx());
+ regs.epr(index).write_value(epr);
+ }
+ }
+ }
+
+ fn alloc_ep_mem(&mut self, len: u16) -> u16 {
+ let addr = self.ep_mem_free;
+ if addr + len > EP_MEMORY_SIZE as _ {
+ panic!("Endpoint memory full");
+ }
+ self.ep_mem_free += len;
+ addr
+ }
+
+ fn alloc_endpoint<D: Dir>(
+ &mut self,
+ ep_type: EndpointType,
+ max_packet_size: u16,
+ interval: u8,
+ ) -> Result<Endpoint<'d, T, D>, driver::EndpointAllocError> {
+ trace!(
+ "allocating type={:?} mps={:?} interval={}, dir={:?}",
+ ep_type,
+ max_packet_size,
+ interval,
+ D::dir()
+ );
+
+ let index = self.alloc.iter_mut().enumerate().find(|(i, ep)| {
+ if *i == 0 && ep_type != EndpointType::Control {
+ return false; // reserved for control pipe
+ }
+ let used = ep.used_out || ep.used_in;
+ let used_dir = match D::dir() {
+ UsbDirection::Out => ep.used_out,
+ UsbDirection::In => ep.used_in,
+ };
+ !used || (ep.ep_type == ep_type && !used_dir)
+ });
+
+ let (index, ep) = match index {
+ Some(x) => x,
+ None => return Err(EndpointAllocError),
+ };
+
+ ep.ep_type = ep_type;
+
+ let buf = match D::dir() {
+ UsbDirection::Out => {
+ assert!(!ep.used_out);
+ ep.used_out = true;
+
+ let (len, len_bits) = calc_out_len(max_packet_size);
+ let addr = self.alloc_ep_mem(len);
+
+ trace!(" len_bits = {:04x}", len_bits);
+ unsafe {
+ ep_out_addr::<T>(index).write_value(addr);
+ ep_out_len::<T>(index).write_value(len_bits);
+ }
+
+ EndpointBuffer {
+ addr,
+ len,
+ _phantom: PhantomData,
+ }
+ }
+ UsbDirection::In => {
+ assert!(!ep.used_in);
+ ep.used_in = true;
+
+ let len = (max_packet_size + 1) / 2 * 2;
+ let addr = self.alloc_ep_mem(len);
+
+ unsafe {
+ ep_in_addr::<T>(index).write_value(addr);
+ // ep_in_len is written when actually TXing packets.
+ }
+
+ EndpointBuffer {
+ addr,
+ len,
+ _phantom: PhantomData,
+ }
+ }
+ };
+
+ trace!(" index={} addr={} len={}", index, buf.addr, buf.len);
+
+ Ok(Endpoint {
+ _phantom: PhantomData,
+ info: EndpointInfo {
+ addr: EndpointAddress::from_parts(index, D::dir()),
+ ep_type,
+ max_packet_size,
+ interval,
+ },
+ buf,
+ })
+ }
+}
+
+impl<'d, T: Instance> 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::EndpointIn, driver::EndpointAllocError> {
+ 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::EndpointOut, driver::EndpointAllocError> {
+ 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);
+
+ let regs = T::regs();
+
+ unsafe {
+ regs.cntr().write(|w| {
+ w.set_pdwn(false);
+ w.set_fres(false);
+ w.set_resetm(true);
+ w.set_suspm(true);
+ w.set_wkupm(true);
+ w.set_ctrm(true);
+ });
+
+ #[cfg(usb_v3)]
+ regs.bcdr().write(|w| w.set_dppu(true))
+ }
+
+ trace!("enabled");
+
+ let mut ep_types = [EpType::BULK; EP_COUNT - 1];
+ for i in 1..EP_COUNT {
+ ep_types[i - 1] = convert_type(self.alloc[i].ep_type);
+ }
+
+ (
+ Bus {
+ phantom: PhantomData,
+ ep_types,
+ },
+ 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>,
+ ep_types: [EpType; EP_COUNT - 1],
+}
+
+impl<'d, T: Instance> driver::Bus for Bus<'d, T> {
+ type PollFuture<'a> = impl Future<Output = Event> + 'a where Self: 'a;
+
+ fn poll<'a>(&'a mut self) -> Self::PollFuture<'a> {
+ poll_fn(move |cx| unsafe {
+ BUS_WAKER.register(cx.waker());
+ let regs = T::regs();
+
+ let flags = IRQ_FLAGS.load(Ordering::Acquire);
+
+ if flags & IRQ_FLAG_RESUME != 0 {
+ IRQ_FLAGS.fetch_and(!IRQ_FLAG_RESUME, Ordering::AcqRel);
+ return Poll::Ready(Event::Resume);
+ }
+
+ if flags & IRQ_FLAG_RESET != 0 {
+ IRQ_FLAGS.fetch_and(!IRQ_FLAG_RESET, Ordering::AcqRel);
+
+ trace!("RESET REGS WRITINGINGING");
+ regs.daddr().write(|w| {
+ w.set_ef(true);
+ w.set_add(0);
+ });
+
+ regs.epr(0).write(|w| {
+ w.set_ep_type(EpType::CONTROL);
+ w.set_stat_rx(Stat::NAK);
+ w.set_stat_tx(Stat::NAK);
+ });
+
+ for i in 1..EP_COUNT {
+ regs.epr(i).write(|w| {
+ w.set_ea(i as _);
+ w.set_ep_type(self.ep_types[i - 1]);
+ })
+ }
+
+ for w in &EP_IN_WAKERS {
+ w.wake()
+ }
+ for w in &EP_OUT_WAKERS {
+ w.wake()
+ }
+
+ return Poll::Ready(Event::Reset);
+ }
+
+ if flags & IRQ_FLAG_SUSPEND != 0 {
+ IRQ_FLAGS.fetch_and(!IRQ_FLAG_SUSPEND, Ordering::AcqRel);
+ return Poll::Ready(Event::Suspend);
+ }
+
+ Poll::Pending
+ })
+ }
+
+ #[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 _);
+ match ep_addr.direction() {
+ UsbDirection::In => {
+ loop {
+ let r = unsafe { reg.read() };
+ match r.stat_tx() {
+ Stat::DISABLED => break, // if disabled, stall does nothing.
+ Stat::STALL => break, // done!
+ _ => {
+ let want_stat = match stalled {
+ false => Stat::NAK,
+ true => Stat::STALL,
+ };
+ let mut w = invariant(r);
+ w.set_stat_tx(Stat(r.stat_tx().0 ^ want_stat.0));
+ unsafe { reg.write_value(w) };
+ }
+ }
+ }
+ EP_IN_WAKERS[ep_addr.index()].wake();
+ }
+ UsbDirection::Out => {
+ loop {
+ let r = unsafe { reg.read() };
+ match r.stat_rx() {
+ Stat::DISABLED => break, // if disabled, stall does nothing.
+ Stat::STALL => break, // done!
+ _ => {
+ let want_stat = match stalled {
+ false => Stat::VALID,
+ true => Stat::STALL,
+ };
+ let mut w = invariant(r);
+ w.set_stat_rx(Stat(r.stat_rx().0 ^ want_stat.0));
+ unsafe { reg.write_value(w) };
+ }
+ }
+ }
+ EP_OUT_WAKERS[ep_addr.index()].wake();
+ }
+ }
+ }
+
+ fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool {
+ let regs = T::regs();
+ let epr = unsafe { regs.epr(ep_addr.index() as _).read() };
+ match ep_addr.direction() {
+ UsbDirection::In => epr.stat_tx() == Stat::STALL,
+ UsbDirection::Out => epr.stat_rx() == Stat::STALL,
+ }
+ }
+
+ fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) {
+ trace!("set_enabled {:x} {}", ep_addr, enabled);
+ // This can race, so do a retry loop.
+ let reg = T::regs().epr(ep_addr.index() as _);
+ trace!("EPR before: {:04x}", unsafe { reg.read() }.0);
+ match ep_addr.direction() {
+ UsbDirection::In => {
+ loop {
+ let want_stat = match enabled {
+ false => Stat::DISABLED,
+ true => Stat::NAK,
+ };
+ let r = unsafe { reg.read() };
+ if r.stat_tx() == want_stat {
+ break;
+ }
+ let mut w = invariant(r);
+ w.set_stat_tx(Stat(r.stat_tx().0 ^ want_stat.0));
+ unsafe { reg.write_value(w) };
+ }
+ EP_IN_WAKERS[ep_addr.index()].wake();
+ }
+ UsbDirection::Out => {
+ loop {
+ let want_stat = match enabled {
+ false => Stat::DISABLED,
+ true => Stat::VALID,
+ };
+ let r = unsafe { reg.read() };
+ if r.stat_rx() == want_stat {
+ break;
+ }
+ let mut w = invariant(r);
+ w.set_stat_rx(Stat(r.stat_rx().0 ^ want_stat.0));
+ unsafe { reg.write_value(w) };
+ }
+ EP_OUT_WAKERS[ep_addr.index()].wake();
+ }
+ }
+ trace!("EPR after: {:04x}", unsafe { reg.read() }.0);
+ }
+
+ type EnableFuture<'a> = impl Future<Output = ()> + 'a where Self: 'a;
+
+ fn enable(&mut self) -> Self::EnableFuture<'_> {
+ async move {}
+ }
+
+ type DisableFuture<'a> = impl Future<Output = ()> + 'a where Self: 'a;
+
+ fn disable(&mut self) -> Self::DisableFuture<'_> {
+ async move {}
+ }
+
+ type RemoteWakeupFuture<'a> = impl Future<Output = Result<(), Unsupported>> + 'a where Self: 'a;
+
+ fn remote_wakeup(&mut self) -> Self::RemoteWakeupFuture<'_> {
+ async move { Err(Unsupported) }
+ }
+}
+
+trait Dir {
+ fn dir() -> UsbDirection;
+ fn waker(i: usize) -> &'static AtomicWaker;
+}
+
+pub enum In {}
+impl Dir for In {
+ fn dir() -> UsbDirection {
+ UsbDirection::In
+ }
+
+ #[inline]
+ fn waker(i: usize) -> &'static AtomicWaker {
+ &EP_IN_WAKERS[i]
+ }
+}
+
+pub enum Out {}
+impl Dir for Out {
+ fn dir() -> UsbDirection {
+ UsbDirection::Out
+ }
+
+ #[inline]
+ fn waker(i: usize) -> &'static AtomicWaker {
+ &EP_OUT_WAKERS[i]
+ }
+}
+
+pub struct Endpoint<'d, T: Instance, D> {
+ _phantom: PhantomData<(&'d mut T, D)>,
+ info: EndpointInfo,
+ buf: EndpointBuffer<T>,
+}
+
+impl<'d, T: Instance, D> Endpoint<'d, T, D> {
+ fn write_data(&mut self, buf: &[u8]) {
+ let index = self.info.addr.index();
+ self.buf.write(buf);
+ unsafe { ep_in_len::<T>(index).write_value(buf.len() as _) };
+ }
+
+ fn read_data(&mut self, buf: &mut [u8]) -> Result<usize, EndpointError> {
+ let index = self.info.addr.index();
+ let rx_len = unsafe { ep_out_len::<T>(index).read() as usize } & 0x3FF;
+ trace!("READ DONE, rx_len = {}", rx_len);
+ if rx_len > buf.len() {
+ return Err(EndpointError::BufferOverflow);
+ }
+ self.buf.read(&mut buf[..rx_len]);
+ Ok(rx_len)
+ }
+}
+
+impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, In> {
+ fn info(&self) -> &EndpointInfo {
+ &self.info
+ }
+
+ type WaitEnabledFuture<'a> = impl Future<Output = ()> + 'a where Self: 'a;
+
+ fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_> {
+ async move {
+ trace!("wait_enabled OUT WAITING");
+ let index = self.info.addr.index();
+ poll_fn(|cx| {
+ EP_OUT_WAKERS[index].register(cx.waker());
+ let regs = T::regs();
+ if unsafe { regs.epr(index).read() }.stat_tx() == Stat::DISABLED {
+ Poll::Pending
+ } else {
+ Poll::Ready(())
+ }
+ })
+ .await;
+ trace!("wait_enabled OUT OK");
+ }
+ }
+}
+
+impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, Out> {
+ fn info(&self) -> &EndpointInfo {
+ &self.info
+ }
+
+ type WaitEnabledFuture<'a> = impl Future<Output = ()> + 'a where Self: 'a;
+
+ fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_> {
+ async move {
+ trace!("wait_enabled OUT WAITING");
+ let index = self.info.addr.index();
+ poll_fn(|cx| {
+ EP_OUT_WAKERS[index].register(cx.waker());
+ let regs = T::regs();
+ if unsafe { regs.epr(index).read() }.stat_rx() == Stat::DISABLED {
+ Poll::Pending
+ } else {
+ Poll::Ready(())
+ }
+ })
+ .await;
+ trace!("wait_enabled OUT OK");
+ }
+ }
+}
+
+impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> {
+ type ReadFuture<'a> = impl Future<Output = Result<usize, EndpointError>> + 'a where Self: 'a;
+
+ fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
+ async move {
+ trace!("READ WAITING, buf.len() = {}", buf.len());
+ let index = self.info.addr.index();
+ let stat = poll_fn(|cx| {
+ EP_OUT_WAKERS[index].register(cx.waker());
+ let regs = T::regs();
+ let stat = unsafe { regs.epr(index).read() }.stat_rx();
+ if matches!(stat, Stat::NAK | Stat::DISABLED) {
+ Poll::Ready(stat)
+ } else {
+ Poll::Pending
+ }
+ })
+ .await;
+
+ if stat == Stat::DISABLED {
+ return Err(EndpointError::Disabled);
+ }
+
+ let rx_len = self.read_data(buf)?;
+
+ let regs = T::regs();
+ unsafe {
+ regs.epr(index).write(|w| {
+ w.set_ep_type(convert_type(self.info.ep_type));
+ w.set_ea(self.info.addr.index() as _);
+ w.set_stat_rx(Stat(Stat::NAK.0 ^ Stat::VALID.0));
+ w.set_stat_tx(Stat(0));
+ w.set_ctr_rx(true); // don't clear
+ w.set_ctr_tx(true); // don't clear
+ })
+ };
+ trace!("READ OK, rx_len = {}", rx_len);
+
+ Ok(rx_len)
+ }
+ }
+}
+
+impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> {
+ type WriteFuture<'a> = impl Future<Output = Result<(), EndpointError>> + 'a where Self: 'a;
+
+ fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> {
+ async move {
+ if buf.len() > self.info.max_packet_size as usize {
+ return Err(EndpointError::BufferOverflow);
+ }
+
+ let index = self.info.addr.index();
+
+ trace!("WRITE WAITING");
+ let stat = poll_fn(|cx| {
+ EP_IN_WAKERS[index].register(cx.waker());
+ let regs = T::regs();
+ let stat = unsafe { regs.epr(index).read() }.stat_tx();
+ if matches!(stat, Stat::NAK | Stat::DISABLED) {
+ Poll::Ready(stat)
+ } else {
+ Poll::Pending
+ }
+ })
+ .await;
+
+ if stat == Stat::DISABLED {
+ return Err(EndpointError::Disabled);
+ }
+
+ self.write_data(buf);
+
+ let regs = T::regs();
+ unsafe {
+ regs.epr(index).write(|w| {
+ w.set_ep_type(convert_type(self.info.ep_type));
+ w.set_ea(self.info.addr.index() as _);
+ w.set_stat_tx(Stat(Stat::NAK.0 ^ Stat::VALID.0));
+ w.set_stat_rx(Stat(0));
+ w.set_ctr_rx(true); // don't clear
+ w.set_ctr_tx(true); // don't clear
+ })
+ };
+
+ 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> driver::ControlPipe for ControlPipe<'d, T> {
+ type SetupFuture<'a> = impl Future<Output = [u8;8]> + 'a where Self: 'a;
+ type DataOutFuture<'a> = impl Future<Output = Result<usize, EndpointError>> + 'a where Self: 'a;
+ type DataInFuture<'a> = impl Future<Output = Result<(), EndpointError>> + 'a where Self: 'a;
+ type AcceptFuture<'a> = impl Future<Output = ()> + 'a where Self: 'a;
+ type RejectFuture<'a> = impl Future<Output = ()> + 'a where Self: 'a;
+
+ fn max_packet_size(&self) -> usize {
+ usize::from(self.max_packet_size)
+ }
+
+ fn setup<'a>(&'a mut self) -> Self::SetupFuture<'a> {
+ async move {
+ loop {
+ trace!("SETUP read waiting");
+ poll_fn(|cx| {
+ EP_OUT_WAKERS[0].register(cx.waker());
+ if EP0_SETUP.load(Ordering::Relaxed) {
+ Poll::Ready(())
+ } else {
+ Poll::Pending
+ }
+ })
+ .await;
+
+ let mut buf = [0; 8];
+ let rx_len = self.ep_out.read_data(&mut buf);
+ if rx_len != Ok(8) {
+ trace!("SETUP read failed: {:?}", rx_len);
+ continue;
+ }
+
+ EP0_SETUP.store(false, Ordering::Relaxed);
+
+ trace!("SETUP read ok");
+ return buf;
+ }
+ }
+ }
+
+ fn data_out<'a>(
+ &'a mut self,
+ buf: &'a mut [u8],
+ first: bool,
+ last: bool,
+ ) -> Self::DataOutFuture<'a> {
+ async move {
+ let regs = T::regs();
+
+ // When a SETUP is received, Stat/Stat is set to NAK.
+ // On first transfer, we must set Stat=VALID, to get the OUT data stage.
+ // We want Stat=STALL so that the host gets a STALL if it switches to the status
+ // stage too soon, except in the last transfer we set Stat=NAK so that it waits
+ // for the status stage, which we will ACK or STALL later.
+ if first || last {
+ let mut stat_rx = 0;
+ let mut stat_tx = 0;
+ if first {
+ // change NAK -> VALID
+ stat_rx ^= Stat::NAK.0 ^ Stat::VALID.0;
+ stat_tx ^= Stat::NAK.0 ^ Stat::STALL.0;
+ }
+ if last {
+ // change STALL -> VALID
+ stat_tx ^= Stat::STALL.0 ^ Stat::NAK.0;
+ }
+ // Note: if this is the first AND last transfer, the above effectively
+ // changes stat_tx like NAK -> NAK, so noop.
+ unsafe {
+ regs.epr(0).write(|w| {
+ w.set_ep_type(EpType::CONTROL);
+ w.set_stat_rx(Stat(stat_rx));
+ w.set_stat_tx(Stat(stat_tx));
+ w.set_ctr_rx(true); // don't clear
+ w.set_ctr_tx(true); // don't clear
+ })
+ }
+ }
+
+ trace!("data_out WAITING, buf.len() = {}", buf.len());
+ poll_fn(|cx| {
+ EP_OUT_WAKERS[0].register(cx.waker());
+ let regs = T::regs();
+ if unsafe { regs.epr(0).read() }.stat_rx() == Stat::NAK {
+ Poll::Ready(())
+ } else {
+ Poll::Pending
+ }
+ })
+ .await;
+
+ if EP0_SETUP.load(Ordering::Relaxed) {
+ trace!("received another SETUP, aborting data_out.");
+ return Err(EndpointError::Disabled);
+ }
+
+ let rx_len = self.ep_out.read_data(buf)?;
+
+ unsafe {
+ regs.epr(0).write(|w| {
+ w.set_ep_type(EpType::CONTROL);
+ w.set_stat_rx(Stat(match last {
+ // If last, set STAT_RX=STALL.
+ true => Stat::NAK.0 ^ Stat::STALL.0,
+ // Otherwise, set STAT_RX=VALID, to allow the host to send the next packet.
+ false => Stat::NAK.0 ^ Stat::VALID.0,
+ }));
+ w.set_ctr_rx(true); // don't clear
+ w.set_ctr_tx(true); // don't clear
+ })
+ };
+
+ Ok(rx_len)
+ }
+ }
+
+ fn data_in<'a>(&'a mut self, buf: &'a [u8], first: bool, last: bool) -> Self::DataInFuture<'a> {
+ async move {
+ trace!("control: data_in");
+
+ if buf.len() > self.ep_in.info.max_packet_size as usize {
+ return Err(EndpointError::BufferOverflow);
+ }
+
+ let regs = T::regs();
+
+ // When a SETUP is received, Stat is set to NAK.
+ // We want it to be STALL in non-last transfers.
+ // We want it to be VALID in last transfer, so the HW does the status stage.
+ if first || last {
+ let mut stat_rx = 0;
+ if first {
+ // change NAK -> STALL
+ stat_rx ^= Stat::NAK.0 ^ Stat::STALL.0;
+ }
+ if last {
+ // change STALL -> VALID
+ stat_rx ^= Stat::STALL.0 ^ Stat::VALID.0;
+ }
+ // Note: if this is the first AND last transfer, the above effectively
+ // does a change of NAK -> VALID.
+ unsafe {
+ regs.epr(0).write(|w| {
+ w.set_ep_type(EpType::CONTROL);
+ w.set_stat_rx(Stat(stat_rx));
+ w.set_ep_kind(last); // set OUT_STATUS if last.
+ w.set_ctr_rx(true); // don't clear
+ w.set_ctr_tx(true); // don't clear
+ })
+ }
+ }
+
+ trace!("WRITE WAITING");
+ poll_fn(|cx| {
+ EP_IN_WAKERS[0].register(cx.waker());
+ EP_OUT_WAKERS[0].register(cx.waker());
+ let regs = T::regs();
+ if unsafe { regs.epr(0).read() }.stat_tx() == Stat::NAK {
+ Poll::Ready(())
+ } else {
+ Poll::Pending
+ }
+ })
+ .await;
+
+ if EP0_SETUP.load(Ordering::Relaxed) {
+ trace!("received another SETUP, aborting data_in.");
+ return Err(EndpointError::Disabled);
+ }
+
+ self.ep_in.write_data(buf);
+
+ let regs = T::regs();
+ unsafe {
+ regs.epr(0).write(|w| {
+ w.set_ep_type(EpType::CONTROL);
+ w.set_stat_tx(Stat(Stat::NAK.0 ^ Stat::VALID.0));
+ w.set_ep_kind(last); // set OUT_STATUS if last.
+ w.set_ctr_rx(true); // don't clear
+ w.set_ctr_tx(true); // don't clear
+ })
+ };
+
+ trace!("WRITE OK");
+
+ Ok(())
+ }
+ }
+
+ fn accept<'a>(&'a mut self) -> Self::AcceptFuture<'a> {
+ async move {
+ let regs = T::regs();
+ trace!("control: accept");
+
+ self.ep_in.write_data(&[]);
+
+ // Set OUT=stall, IN=accept
+ unsafe {
+ let epr = regs.epr(0).read();
+ regs.epr(0).write(|w| {
+ w.set_ep_type(EpType::CONTROL);
+ w.set_stat_rx(Stat(epr.stat_rx().0 ^ Stat::STALL.0));
+ w.set_stat_tx(Stat(epr.stat_tx().0 ^ Stat::VALID.0));
+ w.set_ctr_rx(true); // don't clear
+ w.set_ctr_tx(true); // don't clear
+ });
+ }
+ trace!("control: accept WAITING");
+
+ // Wait is needed, so that we don't set the address too soon, breaking the status stage.
+ // (embassy-usb sets the address after accept() returns)
+ poll_fn(|cx| {
+ EP_IN_WAKERS[0].register(cx.waker());
+ let regs = T::regs();
+ if unsafe { regs.epr(0).read() }.stat_tx() == Stat::NAK {
+ Poll::Ready(())
+ } else {
+ Poll::Pending
+ }
+ })
+ .await;
+
+ trace!("control: accept OK");
+ }
+ }
+
+ fn reject<'a>(&'a mut self) -> Self::RejectFuture<'a> {
+ async move {
+ let regs = T::regs();
+ trace!("control: reject");
+
+ // Set IN+OUT to stall
+ unsafe {
+ let epr = regs.epr(0).read();
+ regs.epr(0).write(|w| {
+ w.set_ep_type(EpType::CONTROL);
+ w.set_stat_rx(Stat(epr.stat_rx().0 ^ Stat::STALL.0));
+ w.set_stat_tx(Stat(epr.stat_tx().0 ^ Stat::STALL.0));
+ w.set_ctr_rx(true); // don't clear
+ w.set_ctr_tx(true); // don't clear
+ });
+ }
+ }
+ }
+}
diff --git a/examples/stm32f1/Cargo.toml b/examples/stm32f1/Cargo.toml
index e09e17fc..8de736d6 100644
--- a/examples/stm32f1/Cargo.toml
+++ b/examples/stm32f1/Cargo.toml
@@ -8,6 +8,8 @@ resolver = "2"
[dependencies]
embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime", "time-tick-32768hz"] }
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f103c8", "unstable-pac", "memory-x", "time-driver-any"] }
+embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
+embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] }
defmt = "0.3"
defmt-rtt = "0.3"
diff --git a/examples/stm32f1/src/bin/usb_serial.rs b/examples/stm32f1/src/bin/usb_serial.rs
new file mode 100644
index 00000000..fe4aa4cc
--- /dev/null
+++ b/examples/stm32f1/src/bin/usb_serial.rs
@@ -0,0 +1,117 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::panic;
+use defmt::*;
+use defmt_rtt as _; // global logger
+use embassy::executor::Spawner;
+use embassy::time::Duration;
+use embassy::time::Timer;
+use embassy_stm32::gpio::Level;
+use embassy_stm32::gpio::Output;
+use embassy_stm32::gpio::Speed;
+use embassy_stm32::interrupt;
+use embassy_stm32::time::Hertz;
+use embassy_stm32::usb::{Driver, Instance};
+use embassy_stm32::{Config, Peripherals};
+use embassy_usb::driver::EndpointError;
+use embassy_usb::Builder;
+use embassy_usb_serial::{CdcAcmClass, State};
+use futures::future::join;
+use panic_probe as _;
+
+fn config() -> Config {
+ let mut config = Config::default();
+ config.rcc.hse = Some(Hertz(8_000_000));
+ config.rcc.sys_ck = Some(Hertz(48_000_000));
+ config.rcc.pclk1 = Some(Hertz(24_000_000));
+ config
+}
+
+#[embassy::main(config = "config()")]
+async fn main(_spawner: Spawner, mut p: Peripherals) {
+ info!("Hello World!");
+
+ {
+ // BluePill board has a pull-up resistor on the D+ line.
+ // Pull the D+ pin down to send a RESET condition to the USB bus.
+ // This forced reset is needed only for development, without it host
+ // will not reset your device when you upload new firmware.
+ let _dp = Output::new(&mut p.PA12, Level::Low, Speed::Low);
+ Timer::after(Duration::from_millis(10)).await;
+ }
+
+ // Create the driver, from the HAL.
+ let irq = interrupt::take!(USB_LP_CAN1_RX0);
+ let driver = Driver::new(p.USB, irq, p.PA12, p.PA11);
+
+ // Create embassy-usb Config
+ let config = embassy_usb::Config::new(0xc0de, 0xcafe);
+ //config.max_packet_size_0 = 64;
+
+ // 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; 7];
+
+ 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<EndpointError> 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/stm32f3/.cargo/config.toml b/examples/stm32f3/.cargo/config.toml
index eb8a8b33..84b4b2f1 100644
--- a/examples/stm32f3/.cargo/config.toml
+++ b/examples/stm32f3/.cargo/config.toml
@@ -1,6 +1,6 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# replace STM32F429ZITx with your chip as listed in `probe-run --list-chips`
-runner = "probe-run --chip STM32F303VCTx"
+runner = "probe-run --chip STM32F303ZETx"
[build]
target = "thumbv7em-none-eabihf"
diff --git a/examples/stm32f3/Cargo.toml b/examples/stm32f3/Cargo.toml
index de81f100..15128ecc 100644
--- a/examples/stm32f3/Cargo.toml
+++ b/examples/stm32f3/Cargo.toml
@@ -7,7 +7,10 @@ resolver = "2"
[dependencies]
embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime", "time-tick-32768hz"] }
-embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f303vc", "unstable-pac", "memory-x", "time-driver-any", "exti"] }
+embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f303ze", "unstable-pac", "memory-x", "time-driver-any", "exti"] }
+embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
+embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] }
+embassy-usb-hid = { version = "0.1.0", path = "../../embassy-usb-hid", features = ["defmt"] }
defmt = "0.3"
defmt-rtt = "0.3"
diff --git a/examples/stm32f3/src/bin/usb_serial.rs b/examples/stm32f3/src/bin/usb_serial.rs
new file mode 100644
index 00000000..fc33d0bc
--- /dev/null
+++ b/examples/stm32f3/src/bin/usb_serial.rs
@@ -0,0 +1,116 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::panic;
+use defmt::*;
+use defmt_rtt as _; // global logger
+use embassy::executor::Spawner;
+use embassy::time::Duration;
+use embassy::time::Timer;
+use embassy_stm32::gpio::Level;
+use embassy_stm32::gpio::Output;
+use embassy_stm32::gpio::Speed;
+use embassy_stm32::interrupt;
+use embassy_stm32::time::U32Ext;
+use embassy_stm32::usb::{Driver, Instance};
+use embassy_stm32::{Config, Peripherals};
+use embassy_usb::driver::EndpointError;
+use embassy_usb::Builder;
+use embassy_usb_serial::{CdcAcmClass, State};
+use futures::future::join;
+use panic_probe as _;
+
+fn config() -> Config {
+ let mut config = Config::default();
+
+ config.rcc.hse = Some(8.mhz().into());
+ config.rcc.sysclk = Some(48.mhz().into());
+ config.rcc.pclk1 = Some(24.mhz().into());
+ config.rcc.pclk2 = Some(24.mhz().into());
+ config.rcc.pll48 = true;
+
+ config
+}
+
+#[embassy::main(config = "config()")]
+async fn main(_spawner: Spawner, p: Peripherals) {
+ info!("Hello World!");
+
+ // Needed for nucleo-stm32f303ze
+ let mut dp_pullup = Output::new(p.PG6, Level::Low, Speed::Medium);
+ Timer::after(Duration::from_millis(10)).await;
+ dp_pullup.set_high();
+
+ // Create the driver, from the HAL.
+ let irq = interrupt::take!(USB_LP_CAN_RX0);
+ let driver = Driver::new(p.USB, irq, p.PA12, p.PA11);
+
+ // Create embassy-usb Config
+ let config = embassy_usb::Config::new(0xc0de, 0xcafe);
+
+ // 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; 7];
+
+ 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<EndpointError> 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/stm32l5/.cargo/config.toml b/examples/stm32l5/.cargo/config.toml
new file mode 100644
index 00000000..e63fe37e
--- /dev/null
+++ b/examples/stm32l5/.cargo/config.toml
@@ -0,0 +1,6 @@
+[target.'cfg(all(target_arch = "arm", target_os = "none"))']
+# replace STM32F429ZITx with your chip as listed in `probe-run --list-chips`
+runner = "probe-run --chip STM32L552ZETxQ"
+
+[build]
+target = "thumbv8m.main-none-eabihf"
diff --git a/examples/stm32l5/Cargo.toml b/examples/stm32l5/Cargo.toml
new file mode 100644
index 00000000..7f60e26d
--- /dev/null
+++ b/examples/stm32l5/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+authors = ["Dario Nieuwenhuis <dirbaio@dirbaio.net>"]
+edition = "2018"
+name = "embassy-stm32l5-examples"
+version = "0.1.0"
+resolver = "2"
+
+[features]
+
+[dependencies]
+embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime", "time-tick-32768hz"] }
+embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l552ze", "time-driver-any", "exti", "unstable-traits", "memory-x"] }
+embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
+embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] }
+embassy-usb-hid = { version = "0.1.0", path = "../../embassy-usb-hid", features = ["defmt"] }
+embassy-usb-ncm = { version = "0.1.0", path = "../../embassy-usb-ncm", features = ["defmt"] }
+embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] }
+usbd-hid = "0.5.2"
+
+defmt = "0.3"
+defmt-rtt = "0.3"
+panic-probe = { version = "0.3", features = ["print-defmt"] }
+
+cortex-m = "0.7.3"
+cortex-m-rt = "0.7.0"
+embedded-hal = "0.2.6"
+futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
+heapless = { version = "0.7.5", default-features = false }
+rand_core = { version = "0.6.3", default-features = false }
+embedded-io = { version = "0.3.0", features = ["async"] }
diff --git a/examples/stm32l5/build.rs b/examples/stm32l5/build.rs
new file mode 100644
index 00000000..8cd32d7e
--- /dev/null
+++ b/examples/stm32l5/build.rs
@@ -0,0 +1,5 @@
+fn main() {
+ println!("cargo:rustc-link-arg-bins=--nmagic");
+ println!("cargo:rustc-link-arg-bins=-Tlink.x");
+ println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
+}
diff --git a/examples/stm32l5/src/bin/button_exti.rs b/examples/stm32l5/src/bin/button_exti.rs
new file mode 100644
index 00000000..304ce0a8
--- /dev/null
+++ b/examples/stm32l5/src/bin/button_exti.rs
@@ -0,0 +1,28 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::*;
+use defmt_rtt as _; // global logger
+use embassy::executor::Spawner;
+use embassy_stm32::exti::ExtiInput;
+use embassy_stm32::gpio::{Input, Pull};
+use embassy_stm32::Peripherals;
+use panic_probe as _;
+
+#[embassy::main]
+async fn main(_spawner: Spawner, p: Peripherals) {
+ info!("Hello World!");
+
+ let button = Input::new(p.PC13, Pull::Down);
+ let mut button = ExtiInput::new(button, p.EXTI13);
+
+ info!("Press the USER button...");
+
+ loop {
+ button.wait_for_falling_edge().await;
+ info!("Pressed!");
+ button.wait_for_rising_edge().await;
+ info!("Released!");
+ }
+}
diff --git a/examples/stm32l5/src/bin/rng.rs b/examples/stm32l5/src/bin/rng.rs
new file mode 100644
index 00000000..5f75c1ff
--- /dev/null
+++ b/examples/stm32l5/src/bin/rng.rs
@@ -0,0 +1,34 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::*;
+use defmt_rtt as _; // global logger
+use embassy::executor::Spawner;
+use embassy_stm32::rcc::{ClockSrc, PLLClkDiv, PLLMul, PLLSource, PLLSrcDiv};
+use embassy_stm32::rng::Rng;
+use embassy_stm32::{Config, Peripherals};
+use panic_probe as _;
+
+fn config() -> Config {
+ let mut config = Config::default();
+ config.rcc.mux = ClockSrc::PLL(
+ PLLSource::HSI16,
+ PLLClkDiv::Div2,
+ PLLSrcDiv::Div1,
+ PLLMul::Mul8,
+ Some(PLLClkDiv::Div2),
+ );
+ config
+}
+
+#[embassy::main(config = "config()")]
+async fn main(_spawner: Spawner, p: Peripherals) {
+ info!("Hello World!");
+
+ let mut rng = Rng::new(p.RNG);
+
+ let mut buf = [0u8; 16];
+ unwrap!(rng.async_fill_bytes(&mut buf).await);
+ info!("random bytes: {:02x}", buf);
+}
diff --git a/examples/stm32l5/src/bin/usb_ethernet.rs b/examples/stm32l5/src/bin/usb_ethernet.rs
new file mode 100644
index 00000000..fa445eec
--- /dev/null
+++ b/examples/stm32l5/src/bin/usb_ethernet.rs
@@ -0,0 +1,290 @@
+#![no_std]
+#![no_main]
+#![feature(generic_associated_types)]
+#![feature(type_alias_impl_trait)]
+
+use core::sync::atomic::{AtomicBool, Ordering};
+use core::task::Waker;
+use defmt::*;
+use defmt_rtt as _; // global logger
+use embassy::blocking_mutex::raw::ThreadModeRawMutex;
+use embassy::channel::Channel;
+use embassy::executor::Spawner;
+use embassy::util::Forever;
+use embassy_net::tcp::TcpSocket;
+use embassy_net::{PacketBox, PacketBoxExt, PacketBuf, Stack, StackResources};
+use embassy_stm32::interrupt;
+use embassy_stm32::rcc::*;
+use embassy_stm32::rng::Rng;
+use embassy_stm32::time::Hertz;
+use embassy_stm32::usb::Driver;
+use embassy_stm32::{Config, Peripherals};
+use embassy_usb::{Builder, UsbDevice};
+use embassy_usb_ncm::{CdcNcmClass, Receiver, Sender, State};
+use panic_probe as _;
+
+use defmt_rtt as _;
+use embedded_io::asynch::{Read, Write};
+// global logger
+use panic_probe as _;
+use rand_core::RngCore;
+
+type MyDriver = Driver<'static, embassy_stm32::peripherals::USB>;
+
+macro_rules! forever {
+ ($val:expr) => {{
+ type T = impl Sized;
+ static FOREVER: Forever<T> = Forever::new();
+ FOREVER.put_with(move || $val)
+ }};
+}
+
+#[embassy::task]
+async fn usb_task(mut device: UsbDevice<'static, MyDriver>) -> ! {
+ device.run().await
+}
+
+#[embassy::task]
+async fn usb_ncm_rx_task(mut class: Receiver<'static, MyDriver>) {
+ loop {
+ warn!("WAITING for connection");
+ LINK_UP.store(false, Ordering::Relaxed);
+
+ class.wait_connection().await.unwrap();
+
+ warn!("Connected");
+ LINK_UP.store(true, Ordering::Relaxed);
+
+ loop {
+ let mut p = unwrap!(PacketBox::new(embassy_net::Packet::new()));
+ let n = match class.read_packet(&mut p[..]).await {
+ Ok(n) => n,
+ Err(e) => {
+ warn!("error reading packet: {:?}", e);
+ break;
+ }
+ };
+
+ let buf = p.slice(0..n);
+ if RX_CHANNEL.try_send(buf).is_err() {
+ warn!("Failed pushing rx'd packet to channel.");
+ }
+ }
+ }
+}
+
+#[embassy::task]
+async fn usb_ncm_tx_task(mut class: Sender<'static, MyDriver>) {
+ loop {
+ let pkt = TX_CHANNEL.recv().await;
+ if let Err(e) = class.write_packet(&pkt[..]).await {
+ warn!("Failed to TX packet: {:?}", e);
+ }
+ }
+}
+
+#[embassy::task]
+async fn net_task(stack: &'static Stack<Device>) -> ! {
+ stack.run().await
+}
+
+fn config() -> Config {
+ let mut config = Config::default();
+ config.rcc.mux = ClockSrc::HSE(Hertz(16_000_000));
+
+ config.rcc.mux = ClockSrc::PLL(
+ PLLSource::HSI16,
+ PLLClkDiv::Div2,
+ PLLSrcDiv::Div1,
+ PLLMul::Mul10,
+ None,
+ );
+ config.rcc.hsi48 = true;
+
+ config
+}
+
+#[embassy::main(config = "config()")]
+async fn main(spawner: Spawner, p: Peripherals) {
+ // Create the driver, from the HAL.
+ let irq = interrupt::take!(USB_FS);
+ let driver = Driver::new(p.USB, irq, p.PA12, p.PA11);
+
+ // Create embassy-usb Config
+ let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
+ config.manufacturer = Some("Embassy");
+ config.product = Some("USB-Ethernet example");
+ config.serial_number = Some("12345678");
+ config.max_power = 100;
+ config.max_packet_size_0 = 64;
+
+ // Required for Windows support.
+ config.composite_with_iads = true;
+ config.device_class = 0xEF;
+ config.device_sub_class = 0x02;
+ config.device_protocol = 0x01;
+
+ struct Resources {
+ device_descriptor: [u8; 256],
+ config_descriptor: [u8; 256],
+ bos_descriptor: [u8; 256],
+ control_buf: [u8; 128],
+ serial_state: State<'static>,
+ }
+ let res: &mut Resources = forever!(Resources {
+ device_descriptor: [0; 256],
+ config_descriptor: [0; 256],
+ bos_descriptor: [0; 256],
+ control_buf: [0; 128],
+ serial_state: State::new(),
+ });
+
+ // Create embassy-usb DeviceBuilder using the driver and config.
+ let mut builder = Builder::new(
+ driver,
+ config,
+ &mut res.device_descriptor,
+ &mut res.config_descriptor,
+ &mut res.bos_descriptor,
+ &mut res.control_buf,
+ None,
+ );
+
+ // WARNINGS for Android ethernet tethering:
+ // - On Pixel 4a, it refused to work on Android 11, worked on Android 12.
+ // - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte),
+ // it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled.
+ // This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417
+ // and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757
+
+ // Our MAC addr.
+ let our_mac_addr = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC];
+ // Host's MAC addr. This is the MAC the host "thinks" its USB-to-ethernet adapter has.
+ let host_mac_addr = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88];
+
+ // Create classes on the builder.
+ let class = CdcNcmClass::new(&mut builder, &mut res.serial_state, host_mac_addr, 64);
+
+ // Build the builder.
+ let usb = builder.build();
+
+ unwrap!(spawner.spawn(usb_task(usb)));
+
+ let (tx, rx) = class.split();
+ unwrap!(spawner.spawn(usb_ncm_rx_task(rx)));
+ unwrap!(spawner.spawn(usb_ncm_tx_task(tx)));
+
+ let config = embassy_net::ConfigStrategy::Dhcp;
+ //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config {
+ // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24),
+ // dns_servers: Vec::new(),
+ // gateway: Some(Ipv4Address::new(10, 42, 0, 1)),
+ //});
+
+ // Generate random seed
+ let mut rng = Rng::new(p.RNG);
+ let seed = rng.next_u64();
+
+ // Init network stack
+ let device = Device {
+ mac_addr: our_mac_addr,
+ };
+ let stack = &*forever!(Stack::new(
+ device,
+ config,
+ forever!(StackResources::<1, 2, 8>::new()),
+ seed
+ ));
+
+ unwrap!(spawner.spawn(net_task(stack)));
+
+ // And now we can use it!
+
+ let mut rx_buffer = [0; 4096];
+ let mut tx_buffer = [0; 4096];
+ let mut buf = [0; 4096];
+
+ loop {
+ let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
+ socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10)));
+
+ info!("Listening on TCP:1234...");
+ if let Err(e) = socket.accept(1234).await {
+ warn!("accept error: {:?}", e);
+ continue;
+ }
+
+ info!("Received connection from {:?}", socket.remote_endpoint());
+
+ loop {
+ let n = match socket.read(&mut buf).await {
+ Ok(0) => {
+ warn!("read EOF");
+ break;
+ }
+ Ok(n) => n,
+ Err(e) => {
+ warn!("read error: {:?}", e);
+ break;
+ }
+ };
+
+ info!("rxd {:02x}", &buf[..n]);
+
+ match socket.write_all(&buf[..n]).await {
+ Ok(()) => {}
+ Err(e) => {
+ warn!("write error: {:?}", e);
+ break;
+ }
+ };
+ }
+ }
+}
+
+static TX_CHANNEL: Channel<ThreadModeRawMutex, PacketBuf, 8> = Channel::new();
+static RX_CHANNEL: Channel<ThreadModeRawMutex, PacketBuf, 8> = Channel::new();
+static LINK_UP: AtomicBool = AtomicBool::new(false);
+
+struct Device {
+ mac_addr: [u8; 6],
+}
+
+impl embassy_net::Device for Device {
+ fn register_waker(&mut self, waker: &Waker) {
+ // loopy loopy wakey wakey
+ waker.wake_by_ref()
+ }
+
+ fn link_state(&mut self) -> embassy_net::LinkState {
+ match LINK_UP.load(Ordering::Relaxed) {
+ true => embassy_net::LinkState::Up,
+ false => embassy_net::LinkState::Down,
+ }
+ }
+
+ fn capabilities(&self) -> embassy_net::DeviceCapabilities {
+ let mut caps = embassy_net::DeviceCapabilities::default();
+ caps.max_transmission_unit = 1514; // 1500 IP + 14 ethernet header
+ caps.medium = embassy_net::Medium::Ethernet;
+ caps
+ }
+
+ fn is_transmit_ready(&mut self) -> bool {
+ true
+ }
+
+ fn transmit(&mut self, pkt: PacketBuf) {
+ if TX_CHANNEL.try_send(pkt).is_err() {
+ warn!("TX failed")
+ }
+ }
+
+ fn receive<'a>(&mut self) -> Option<PacketBuf> {
+ RX_CHANNEL.try_recv().ok()
+ }
+
+ fn ethernet_address(&self) -> [u8; 6] {
+ self.mac_addr
+ }
+}
diff --git a/examples/stm32l5/src/bin/usb_hid_mouse.rs b/examples/stm32l5/src/bin/usb_hid_mouse.rs
new file mode 100644
index 00000000..d275aba3
--- /dev/null
+++ b/examples/stm32l5/src/bin/usb_hid_mouse.rs
@@ -0,0 +1,136 @@
+#![no_std]
+#![no_main]
+#![feature(generic_associated_types)]
+#![feature(type_alias_impl_trait)]
+
+use defmt::*;
+use embassy::executor::Spawner;
+use embassy::time::{Duration, Timer};
+use embassy_stm32::interrupt;
+use embassy_stm32::rcc::*;
+use embassy_stm32::time::Hertz;
+use embassy_stm32::usb::Driver;
+use embassy_stm32::{Config, Peripherals};
+use embassy_usb::control::OutResponse;
+use embassy_usb::Builder;
+use embassy_usb_hid::{HidWriter, ReportId, RequestHandler, State};
+use futures::future::join;
+use usbd_hid::descriptor::{MouseReport, SerializedDescriptor};
+
+use defmt_rtt as _; // global logger
+use panic_probe as _;
+
+fn config() -> Config {
+ let mut config = Config::default();
+ config.rcc.mux = ClockSrc::HSE(Hertz(16_000_000));
+
+ config.rcc.mux = ClockSrc::PLL(
+ PLLSource::HSI16,
+ PLLClkDiv::Div2,
+ PLLSrcDiv::Div1,
+ PLLMul::Mul10,
+ None,
+ );
+ config.rcc.hsi48 = true;
+
+ config
+}
+
+#[embassy::main(config = "config()")]
+async fn main(_spawner: Spawner, p: Peripherals) {
+ // Create the driver, from the HAL.
+ let irq = interrupt::take!(USB_FS);
+ let driver = Driver::new(p.USB, irq, p.PA12, p.PA11);
+
+ // Create embassy-usb Config
+ let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
+ config.manufacturer = Some("Embassy");
+ config.product = Some("HID mouse example");
+ config.serial_number = Some("12345678");
+ config.max_power = 100;
+ config.max_packet_size_0 = 64;
+
+ // 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 request_handler = MyRequestHandler {};
+
+ 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 config = embassy_usb_hid::Config {
+ report_descriptor: MouseReport::desc(),
+ request_handler: Some(&request_handler),
+ poll_ms: 60,
+ max_packet_size: 8,
+ };
+
+ let mut writer = HidWriter::<_, 5>::new(&mut builder, &mut state, config);
+
+ // Build the builder.
+ let mut usb = builder.build();
+
+ // Run the USB device.
+ let usb_fut = usb.run();
+
+ // Do stuff with the class!
+ let hid_fut = async {
+ let mut y: i8 = 5;
+ loop {
+ Timer::after(Duration::from_millis(500)).await;
+
+ y = -y;
+ let report = MouseReport {
+ buttons: 0,
+ x: 0,
+ y,
+ wheel: 0,
+ pan: 0,
+ };
+ match writer.write_serialize(&report).await {
+ Ok(()) => {}
+ Err(e) => warn!("Failed to send report: {:?}", e),
+ }
+ }
+ };
+
+ // Run everything concurrently.
+ // If we had made everything `'static` above instead, we could do this using separate tasks instead.
+ join(usb_fut, hid_fut).await;
+}
+
+struct MyRequestHandler {}
+
+impl RequestHandler for MyRequestHandler {
+ fn get_report(&self, id: ReportId, _buf: &mut [u8]) -> Option<usize> {
+ info!("Get report for {:?}", id);
+ None
+ }
+
+ fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse {
+ info!("Set report for {:?}: {=[u8]}", id, data);
+ OutResponse::Accepted
+ }
+
+ fn set_idle(&self, id: Option<ReportId>, dur: Duration) {
+ info!("Set idle rate for {:?} to {:?}", id, dur);
+ }
+
+ fn get_idle(&self, id: Option<ReportId>) -> Option<Duration> {
+ info!("Get idle rate for {:?}", id);
+ None
+ }
+}
diff --git a/examples/stm32l5/src/bin/usb_serial.rs b/examples/stm32l5/src/bin/usb_serial.rs
new file mode 100644
index 00000000..987f1b69
--- /dev/null
+++ b/examples/stm32l5/src/bin/usb_serial.rs
@@ -0,0 +1,112 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::panic;
+use defmt::*;
+use defmt_rtt as _; // global logger
+use embassy::executor::Spawner;
+use embassy_stm32::interrupt;
+use embassy_stm32::rcc::*;
+use embassy_stm32::time::Hertz;
+use embassy_stm32::usb::{Driver, Instance};
+use embassy_stm32::{Config, Peripherals};
+use embassy_usb::driver::EndpointError;
+use embassy_usb::Builder;
+use embassy_usb_serial::{CdcAcmClass, State};
+use futures::future::join;
+use panic_probe as _;
+
+fn config() -> Config {
+ let mut config = Config::default();
+ config.rcc.mux = ClockSrc::HSE(Hertz(16_000_000));
+
+ config.rcc.mux = ClockSrc::PLL(
+ PLLSource::HSI16,
+ PLLClkDiv::Div2,
+ PLLSrcDiv::Div1,
+ PLLMul::Mul10,
+ None,
+ );
+ config.rcc.hsi48 = true;
+
+ config
+}
+
+#[embassy::main(config = "config()")]
+async fn main(_spawner: Spawner, p: Peripherals) {
+ info!("Hello World!");
+
+ // Create the driver, from the HAL.
+ let irq = interrupt::take!(USB_FS);
+ let driver = Driver::new(p.USB, irq, p.PA12, p.PA11);
+
+ // Create embassy-usb Config
+ let config = embassy_usb::Config::new(0xc0de, 0xcafe);
+ //config.max_packet_size_0 = 64;
+
+ // 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; 7];
+
+ 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<EndpointError> 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
-Subproject aa2e63996c0fe35d680c9c48917c07b042905e4
+Subproject fa294eae79c0f33f4cde1e73b4e69db59f7429e