diff options
| author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2023-01-11 17:05:59 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-01-11 17:05:59 +0000 |
| commit | 88fd521b016be18230b5b3d4b3bf96c29f10e202 (patch) | |
| tree | d416bfda40860e61898e5599809c503c7149285a | |
| parent | 96b97c4711af0851f7d3a6785be13b0202b6e902 (diff) | |
| parent | 1af102a1aaa11d03bfa37831a3284546b605efd8 (diff) | |
Merge #1145
1145: STM32 USB OTG #2 r=Dirbaio a=chemicstry
This is a continuation of #799
The usb serial example is already working!
TODO:
- [x] Add critical sections to registers shared with IRQ
- [x] Fix `disable()`
- [x] ~Implement cable disconnect detection~ - postponed
- [x] Fix `endpoint_set_enabled()`
- [x] ~Endpoint `wait_enabled`~ USB OTG does not have enable delay (?)
- [x] HS internal and HS ULPI PHY - untested
Co-authored-by: chemicstry <[email protected]>
Co-authored-by: Dario Nieuwenhuis <[email protected]>
| -rw-r--r-- | embassy-nrf/src/usb.rs | 10 | ||||
| -rw-r--r-- | embassy-rp/src/usb.rs | 15 | ||||
| -rw-r--r-- | embassy-stm32/build.rs | 30 | ||||
| -rw-r--r-- | embassy-stm32/src/lib.rs | 2 | ||||
| -rw-r--r-- | embassy-stm32/src/rcc/u5.rs | 30 | ||||
| -rw-r--r-- | embassy-stm32/src/usb/usb.rs | 26 | ||||
| -rw-r--r-- | embassy-stm32/src/usb_otg.rs | 213 | ||||
| -rw-r--r-- | embassy-stm32/src/usb_otg/mod.rs | 161 | ||||
| -rw-r--r-- | embassy-stm32/src/usb_otg/usb.rs | 1346 | ||||
| -rw-r--r-- | embassy-usb-driver/src/lib.rs | 9 | ||||
| -rw-r--r-- | embassy-usb/src/lib.rs | 23 | ||||
| -rw-r--r-- | examples/stm32f4/Cargo.toml | 5 | ||||
| -rw-r--r-- | examples/stm32f4/src/bin/usb_serial.rs | 106 | ||||
| -rw-r--r-- | examples/stm32f7/Cargo.toml | 1 | ||||
| -rw-r--r-- | examples/stm32f7/src/bin/usb_serial.rs | 107 | ||||
| -rw-r--r-- | examples/stm32h7/Cargo.toml | 1 | ||||
| -rw-r--r-- | examples/stm32h7/src/bin/usb_serial.rs | 106 | ||||
| -rw-r--r-- | examples/stm32l4/Cargo.toml | 4 | ||||
| -rw-r--r-- | examples/stm32l4/src/bin/usb_serial.rs | 108 | ||||
| -rw-r--r-- | examples/stm32u5/Cargo.toml | 1 | ||||
| -rw-r--r-- | examples/stm32u5/src/bin/usb_serial.rs | 108 | ||||
| m--------- | stm32-data | 0 |
22 files changed, 2131 insertions, 281 deletions
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> { | |||
| 391 | .await | 391 | .await |
| 392 | } | 392 | } |
| 393 | 393 | ||
| 394 | #[inline] | ||
| 395 | fn set_address(&mut self, _addr: u8) { | ||
| 396 | // Nothing to do, the peripheral handles this. | ||
| 397 | } | ||
| 398 | |||
| 399 | fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { | 394 | fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { |
| 400 | let regs = T::regs(); | 395 | let regs = T::regs(); |
| 401 | unsafe { | 396 | unsafe { |
| @@ -841,6 +836,11 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { | |||
| 841 | let regs = T::regs(); | 836 | let regs = T::regs(); |
| 842 | regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().bit(true)); | 837 | regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().bit(true)); |
| 843 | } | 838 | } |
| 839 | |||
| 840 | async fn accept_set_address(&mut self, _addr: u8) { | ||
| 841 | self.accept().await; | ||
| 842 | // Nothing to do, the peripheral handles this. | ||
| 843 | } | ||
| 844 | } | 844 | } |
| 845 | 845 | ||
| 846 | fn dma_start() { | 846 | 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> { | |||
| 406 | .await | 406 | .await |
| 407 | } | 407 | } |
| 408 | 408 | ||
| 409 | #[inline] | ||
| 410 | fn set_address(&mut self, addr: u8) { | ||
| 411 | let regs = T::regs(); | ||
| 412 | trace!("setting addr: {}", addr); | ||
| 413 | unsafe { regs.addr_endp().write(|w| w.set_address(addr)) } | ||
| 414 | } | ||
| 415 | |||
| 416 | fn endpoint_set_stalled(&mut self, _ep_addr: EndpointAddress, _stalled: bool) { | 409 | fn endpoint_set_stalled(&mut self, _ep_addr: EndpointAddress, _stalled: bool) { |
| 417 | todo!(); | 410 | todo!(); |
| 418 | } | 411 | } |
| @@ -812,4 +805,12 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { | |||
| 812 | T::dpram().ep_in_buffer_control(0).write(|w| w.set_stall(true)); | 805 | T::dpram().ep_in_buffer_control(0).write(|w| w.set_stall(true)); |
| 813 | } | 806 | } |
| 814 | } | 807 | } |
| 808 | |||
| 809 | async fn accept_set_address(&mut self, addr: u8) { | ||
| 810 | self.accept().await; | ||
| 811 | |||
| 812 | let regs = T::regs(); | ||
| 813 | trace!("setting addr: {}", addr); | ||
| 814 | unsafe { regs.addr_endp().write(|w| w.set_address(addr)) } | ||
| 815 | } | ||
| 815 | } | 816 | } |
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() { | |||
| 277 | (("dcmi", "PIXCLK"), quote!(crate::dcmi::PixClkPin)), | 277 | (("dcmi", "PIXCLK"), quote!(crate::dcmi::PixClkPin)), |
| 278 | (("usb", "DP"), quote!(crate::usb::DpPin)), | 278 | (("usb", "DP"), quote!(crate::usb::DpPin)), |
| 279 | (("usb", "DM"), quote!(crate::usb::DmPin)), | 279 | (("usb", "DM"), quote!(crate::usb::DmPin)), |
| 280 | (("otgfs", "DP"), quote!(crate::usb_otg::DpPin)), | 280 | (("otg", "DP"), quote!(crate::usb_otg::DpPin)), |
| 281 | (("otgfs", "DM"), quote!(crate::usb_otg::DmPin)), | 281 | (("otg", "DM"), quote!(crate::usb_otg::DmPin)), |
| 282 | (("otghs", "DP"), quote!(crate::usb_otg::DpPin)), | 282 | (("otg", "ULPI_CK"), quote!(crate::usb_otg::UlpiClkPin)), |
| 283 | (("otghs", "DM"), quote!(crate::usb_otg::DmPin)), | 283 | (("otg", "ULPI_DIR"), quote!(crate::usb_otg::UlpiDirPin)), |
| 284 | (("otghs", "ULPI_CK"), quote!(crate::usb_otg::UlpiClkPin)), | 284 | (("otg", "ULPI_NXT"), quote!(crate::usb_otg::UlpiNxtPin)), |
| 285 | (("otghs", "ULPI_DIR"), quote!(crate::usb_otg::UlpiDirPin)), | 285 | (("otg", "ULPI_STP"), quote!(crate::usb_otg::UlpiStpPin)), |
| 286 | (("otghs", "ULPI_NXT"), quote!(crate::usb_otg::UlpiNxtPin)), | 286 | (("otg", "ULPI_D0"), quote!(crate::usb_otg::UlpiD0Pin)), |
| 287 | (("otghs", "ULPI_STP"), quote!(crate::usb_otg::UlpiStpPin)), | 287 | (("otg", "ULPI_D1"), quote!(crate::usb_otg::UlpiD1Pin)), |
| 288 | (("otghs", "ULPI_D0"), quote!(crate::usb_otg::UlpiD0Pin)), | 288 | (("otg", "ULPI_D2"), quote!(crate::usb_otg::UlpiD2Pin)), |
| 289 | (("otghs", "ULPI_D1"), quote!(crate::usb_otg::UlpiD1Pin)), | 289 | (("otg", "ULPI_D3"), quote!(crate::usb_otg::UlpiD3Pin)), |
| 290 | (("otghs", "ULPI_D2"), quote!(crate::usb_otg::UlpiD2Pin)), | 290 | (("otg", "ULPI_D4"), quote!(crate::usb_otg::UlpiD4Pin)), |
| 291 | (("otghs", "ULPI_D3"), quote!(crate::usb_otg::UlpiD3Pin)), | 291 | (("otg", "ULPI_D5"), quote!(crate::usb_otg::UlpiD5Pin)), |
| 292 | (("otghs", "ULPI_D4"), quote!(crate::usb_otg::UlpiD4Pin)), | 292 | (("otg", "ULPI_D6"), quote!(crate::usb_otg::UlpiD6Pin)), |
| 293 | (("otghs", "ULPI_D5"), quote!(crate::usb_otg::UlpiD5Pin)), | 293 | (("otg", "ULPI_D7"), quote!(crate::usb_otg::UlpiD7Pin)), |
| 294 | (("otghs", "ULPI_D6"), quote!(crate::usb_otg::UlpiD6Pin)), | ||
| 295 | (("otghs", "ULPI_D7"), quote!(crate::usb_otg::UlpiD7Pin)), | ||
| 296 | (("can", "TX"), quote!(crate::can::TxPin)), | 294 | (("can", "TX"), quote!(crate::can::TxPin)), |
| 297 | (("can", "RX"), quote!(crate::can::RxPin)), | 295 | (("can", "RX"), quote!(crate::can::RxPin)), |
| 298 | (("eth", "REF_CLK"), quote!(crate::eth::RefClkPin)), | 296 | (("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; | |||
| 58 | pub mod usart; | 58 | pub mod usart; |
| 59 | #[cfg(all(usb, feature = "time"))] | 59 | #[cfg(all(usb, feature = "time"))] |
| 60 | pub mod usb; | 60 | pub mod usb; |
| 61 | #[cfg(any(otgfs, otghs))] | 61 | #[cfg(otg)] |
| 62 | pub mod usb_otg; | 62 | pub mod usb_otg; |
| 63 | 63 | ||
| 64 | #[cfg(iwdg)] | 64 | #[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 { | |||
| 295 | pub apb1_pre: APBPrescaler, | 295 | pub apb1_pre: APBPrescaler, |
| 296 | pub apb2_pre: APBPrescaler, | 296 | pub apb2_pre: APBPrescaler, |
| 297 | pub apb3_pre: APBPrescaler, | 297 | pub apb3_pre: APBPrescaler, |
| 298 | pub hsi48: bool, | ||
| 298 | } | 299 | } |
| 299 | 300 | ||
| 300 | impl Default for Config { | 301 | impl Default for Config { |
| @@ -305,6 +306,7 @@ impl Default for Config { | |||
| 305 | apb1_pre: Default::default(), | 306 | apb1_pre: Default::default(), |
| 306 | apb2_pre: Default::default(), | 307 | apb2_pre: Default::default(), |
| 307 | apb3_pre: Default::default(), | 308 | apb3_pre: Default::default(), |
| 309 | hsi48: false, | ||
| 308 | } | 310 | } |
| 309 | } | 311 | } |
| 310 | } | 312 | } |
| @@ -320,7 +322,6 @@ pub(crate) unsafe fn init(config: Config) { | |||
| 320 | RCC.cr().write(|w| { | 322 | RCC.cr().write(|w| { |
| 321 | w.set_msipllen(false); | 323 | w.set_msipllen(false); |
| 322 | w.set_msison(true); | 324 | w.set_msison(true); |
| 323 | w.set_msison(true); | ||
| 324 | }); | 325 | }); |
| 325 | while !RCC.cr().read().msisrdy() {} | 326 | while !RCC.cr().read().msisrdy() {} |
| 326 | 327 | ||
| @@ -340,9 +341,20 @@ pub(crate) unsafe fn init(config: Config) { | |||
| 340 | } | 341 | } |
| 341 | ClockSrc::PLL1R(src, m, n, div) => { | 342 | ClockSrc::PLL1R(src, m, n, div) => { |
| 342 | let freq = match src { | 343 | let freq = match src { |
| 343 | PllSrc::MSI(_) => MSIRange::default().into(), | 344 | PllSrc::MSI(_) => { |
| 344 | PllSrc::HSE(hertz) => hertz.0, | 345 | // TODO: enable MSI |
| 345 | PllSrc::HSI16 => HSI_FREQ.0, | 346 | MSIRange::default().into() |
| 347 | } | ||
| 348 | PllSrc::HSE(hertz) => { | ||
| 349 | // TODO: enable HSE | ||
| 350 | hertz.0 | ||
| 351 | } | ||
| 352 | PllSrc::HSI16 => { | ||
| 353 | RCC.cr().write(|w| w.set_hsion(true)); | ||
| 354 | while !RCC.cr().read().hsirdy() {} | ||
| 355 | |||
| 356 | HSI_FREQ.0 | ||
| 357 | } | ||
| 346 | }; | 358 | }; |
| 347 | 359 | ||
| 348 | // disable | 360 | // disable |
| @@ -355,6 +367,7 @@ pub(crate) unsafe fn init(config: Config) { | |||
| 355 | RCC.pll1cfgr().write(|w| { | 367 | RCC.pll1cfgr().write(|w| { |
| 356 | w.set_pllm(m.into()); | 368 | w.set_pllm(m.into()); |
| 357 | w.set_pllsrc(src.into()); | 369 | w.set_pllsrc(src.into()); |
| 370 | w.set_pllren(true); | ||
| 358 | }); | 371 | }); |
| 359 | 372 | ||
| 360 | RCC.pll1divr().modify(|w| { | 373 | RCC.pll1divr().modify(|w| { |
| @@ -365,15 +378,16 @@ pub(crate) unsafe fn init(config: Config) { | |||
| 365 | // Enable PLL | 378 | // Enable PLL |
| 366 | RCC.cr().modify(|w| w.set_pllon(0, true)); | 379 | RCC.cr().modify(|w| w.set_pllon(0, true)); |
| 367 | while !RCC.cr().read().pllrdy(0) {} | 380 | while !RCC.cr().read().pllrdy(0) {} |
| 368 | RCC.pll1cfgr().modify(|w| w.set_pllren(true)); | ||
| 369 | |||
| 370 | RCC.cr().write(|w| w.set_pllon(0, true)); | ||
| 371 | while !RCC.cr().read().pllrdy(0) {} | ||
| 372 | 381 | ||
| 373 | pll_ck | 382 | pll_ck |
| 374 | } | 383 | } |
| 375 | }; | 384 | }; |
| 376 | 385 | ||
| 386 | if config.hsi48 { | ||
| 387 | RCC.cr().modify(|w| w.set_hsi48on(true)); | ||
| 388 | while !RCC.cr().read().hsi48rdy() {} | ||
| 389 | } | ||
| 390 | |||
| 377 | // TODO make configurable | 391 | // TODO make configurable |
| 378 | let power_vos = VoltageScale::Range4; | 392 | let power_vos = VoltageScale::Range4; |
| 379 | 393 | ||
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> { | |||
| 154 | 154 | ||
| 155 | block_for(Duration::from_millis(100)); | 155 | block_for(Duration::from_millis(100)); |
| 156 | 156 | ||
| 157 | #[cfg(not(usb_v4))] | ||
| 157 | regs.btable().write(|w| w.set_btable(0)); | 158 | regs.btable().write(|w| w.set_btable(0)); |
| 158 | 159 | ||
| 159 | dp.set_as_af(dp.af_num(), AFType::OutputPushPull); | 160 | dp.set_as_af(dp.af_num(), AFType::OutputPushPull); |
| @@ -489,18 +490,6 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { | |||
| 489 | .await | 490 | .await |
| 490 | } | 491 | } |
| 491 | 492 | ||
| 492 | #[inline] | ||
| 493 | fn set_address(&mut self, addr: u8) { | ||
| 494 | let regs = T::regs(); | ||
| 495 | trace!("setting addr: {}", addr); | ||
| 496 | unsafe { | ||
| 497 | regs.daddr().write(|w| { | ||
| 498 | w.set_ef(true); | ||
| 499 | w.set_add(addr); | ||
| 500 | }) | ||
| 501 | } | ||
| 502 | } | ||
| 503 | |||
| 504 | fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { | 493 | fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { |
| 505 | // This can race, so do a retry loop. | 494 | // This can race, so do a retry loop. |
| 506 | let reg = T::regs().epr(ep_addr.index() as _); | 495 | let reg = T::regs().epr(ep_addr.index() as _); |
| @@ -1017,4 +1006,17 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { | |||
| 1017 | }); | 1006 | }); |
| 1018 | } | 1007 | } |
| 1019 | } | 1008 | } |
| 1009 | |||
| 1010 | async fn accept_set_address(&mut self, addr: u8) { | ||
| 1011 | self.accept().await; | ||
| 1012 | |||
| 1013 | let regs = T::regs(); | ||
| 1014 | trace!("setting addr: {}", addr); | ||
| 1015 | unsafe { | ||
| 1016 | regs.daddr().write(|w| { | ||
| 1017 | w.set_ef(true); | ||
| 1018 | w.set_add(addr); | ||
| 1019 | }) | ||
| 1020 | } | ||
| 1021 | } | ||
| 1020 | } | 1022 | } |
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 @@ | |||
| 1 | use core::marker::PhantomData; | ||
| 2 | |||
| 3 | use embassy_hal_common::into_ref; | ||
| 4 | |||
| 5 | use crate::gpio::sealed::AFType; | ||
| 6 | use crate::rcc::RccPeripheral; | ||
| 7 | use crate::{peripherals, Peripheral}; | ||
| 8 | |||
| 9 | macro_rules! config_ulpi_pins { | ||
| 10 | ($($pin:ident),*) => { | ||
| 11 | into_ref!($($pin),*); | ||
| 12 | // NOTE(unsafe) Exclusive access to the registers | ||
| 13 | critical_section::with(|_| unsafe { | ||
| 14 | $( | ||
| 15 | $pin.set_as_af($pin.af_num(), AFType::OutputPushPull); | ||
| 16 | #[cfg(gpio_v2)] | ||
| 17 | $pin.set_speed(crate::gpio::Speed::VeryHigh); | ||
| 18 | )* | ||
| 19 | }) | ||
| 20 | }; | ||
| 21 | } | ||
| 22 | |||
| 23 | /// USB PHY type | ||
| 24 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] | ||
| 25 | pub enum PhyType { | ||
| 26 | /// Internal Full-Speed PHY | ||
| 27 | /// | ||
| 28 | /// Available on most High-Speed peripherals. | ||
| 29 | InternalFullSpeed, | ||
| 30 | /// Internal High-Speed PHY | ||
| 31 | /// | ||
| 32 | /// Available on a few STM32 chips. | ||
| 33 | InternalHighSpeed, | ||
| 34 | /// External ULPI High-Speed PHY | ||
| 35 | ExternalHighSpeed, | ||
| 36 | } | ||
| 37 | |||
| 38 | pub struct UsbOtg<'d, T: Instance> { | ||
| 39 | phantom: PhantomData<&'d mut T>, | ||
| 40 | _phy_type: PhyType, | ||
| 41 | } | ||
| 42 | |||
| 43 | impl<'d, T: Instance> UsbOtg<'d, T> { | ||
| 44 | /// Initializes USB OTG peripheral with internal Full-Speed PHY | ||
| 45 | pub fn new_fs( | ||
| 46 | _peri: impl Peripheral<P = T> + 'd, | ||
| 47 | dp: impl Peripheral<P = impl DpPin<T>> + 'd, | ||
| 48 | dm: impl Peripheral<P = impl DmPin<T>> + 'd, | ||
| 49 | ) -> Self { | ||
| 50 | into_ref!(dp, dm); | ||
| 51 | |||
| 52 | unsafe { | ||
| 53 | dp.set_as_af(dp.af_num(), AFType::OutputPushPull); | ||
| 54 | dm.set_as_af(dm.af_num(), AFType::OutputPushPull); | ||
| 55 | } | ||
| 56 | |||
| 57 | Self { | ||
| 58 | phantom: PhantomData, | ||
| 59 | _phy_type: PhyType::InternalFullSpeed, | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | /// Initializes USB OTG peripheral with external High-Speed PHY | ||
| 64 | pub fn new_hs_ulpi( | ||
| 65 | _peri: impl Peripheral<P = T> + 'd, | ||
| 66 | ulpi_clk: impl Peripheral<P = impl UlpiClkPin<T>> + 'd, | ||
| 67 | ulpi_dir: impl Peripheral<P = impl UlpiDirPin<T>> + 'd, | ||
| 68 | ulpi_nxt: impl Peripheral<P = impl UlpiNxtPin<T>> + 'd, | ||
| 69 | ulpi_stp: impl Peripheral<P = impl UlpiStpPin<T>> + 'd, | ||
| 70 | ulpi_d0: impl Peripheral<P = impl UlpiD0Pin<T>> + 'd, | ||
| 71 | ulpi_d1: impl Peripheral<P = impl UlpiD1Pin<T>> + 'd, | ||
| 72 | ulpi_d2: impl Peripheral<P = impl UlpiD2Pin<T>> + 'd, | ||
| 73 | ulpi_d3: impl Peripheral<P = impl UlpiD3Pin<T>> + 'd, | ||
| 74 | ulpi_d4: impl Peripheral<P = impl UlpiD4Pin<T>> + 'd, | ||
| 75 | ulpi_d5: impl Peripheral<P = impl UlpiD5Pin<T>> + 'd, | ||
| 76 | ulpi_d6: impl Peripheral<P = impl UlpiD6Pin<T>> + 'd, | ||
| 77 | ulpi_d7: impl Peripheral<P = impl UlpiD7Pin<T>> + 'd, | ||
| 78 | ) -> Self { | ||
| 79 | config_ulpi_pins!( | ||
| 80 | ulpi_clk, ulpi_dir, ulpi_nxt, ulpi_stp, ulpi_d0, ulpi_d1, ulpi_d2, ulpi_d3, ulpi_d4, ulpi_d5, ulpi_d6, | ||
| 81 | ulpi_d7 | ||
| 82 | ); | ||
| 83 | |||
| 84 | Self { | ||
| 85 | phantom: PhantomData, | ||
| 86 | _phy_type: PhyType::ExternalHighSpeed, | ||
| 87 | } | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | impl<'d, T: Instance> Drop for UsbOtg<'d, T> { | ||
| 92 | fn drop(&mut self) { | ||
| 93 | T::reset(); | ||
| 94 | T::disable(); | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | pub(crate) mod sealed { | ||
| 99 | pub trait Instance { | ||
| 100 | const REGISTERS: *const (); | ||
| 101 | const HIGH_SPEED: bool; | ||
| 102 | const FIFO_DEPTH_WORDS: usize; | ||
| 103 | const ENDPOINT_COUNT: usize; | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | pub trait Instance: sealed::Instance + RccPeripheral {} | ||
| 108 | |||
| 109 | // Internal PHY pins | ||
| 110 | pin_trait!(DpPin, Instance); | ||
| 111 | pin_trait!(DmPin, Instance); | ||
| 112 | |||
| 113 | // External PHY pins | ||
| 114 | pin_trait!(UlpiClkPin, Instance); | ||
| 115 | pin_trait!(UlpiDirPin, Instance); | ||
| 116 | pin_trait!(UlpiNxtPin, Instance); | ||
| 117 | pin_trait!(UlpiStpPin, Instance); | ||
| 118 | pin_trait!(UlpiD0Pin, Instance); | ||
| 119 | pin_trait!(UlpiD1Pin, Instance); | ||
| 120 | pin_trait!(UlpiD2Pin, Instance); | ||
| 121 | pin_trait!(UlpiD3Pin, Instance); | ||
| 122 | pin_trait!(UlpiD4Pin, Instance); | ||
| 123 | pin_trait!(UlpiD5Pin, Instance); | ||
| 124 | pin_trait!(UlpiD6Pin, Instance); | ||
| 125 | pin_trait!(UlpiD7Pin, Instance); | ||
| 126 | |||
| 127 | foreach_peripheral!( | ||
| 128 | (otgfs, $inst:ident) => { | ||
| 129 | impl sealed::Instance for peripherals::$inst { | ||
| 130 | const REGISTERS: *const () = crate::pac::$inst.0 as *const (); | ||
| 131 | const HIGH_SPEED: bool = false; | ||
| 132 | |||
| 133 | cfg_if::cfg_if! { | ||
| 134 | if #[cfg(stm32f1)] { | ||
| 135 | const FIFO_DEPTH_WORDS: usize = 128; | ||
| 136 | const ENDPOINT_COUNT: usize = 8; | ||
| 137 | } else if #[cfg(any( | ||
| 138 | stm32f2, | ||
| 139 | stm32f401, | ||
| 140 | stm32f405, | ||
| 141 | stm32f407, | ||
| 142 | stm32f411, | ||
| 143 | stm32f415, | ||
| 144 | stm32f417, | ||
| 145 | stm32f427, | ||
| 146 | stm32f429, | ||
| 147 | stm32f437, | ||
| 148 | stm32f439, | ||
| 149 | ))] { | ||
| 150 | const FIFO_DEPTH_WORDS: usize = 320; | ||
| 151 | const ENDPOINT_COUNT: usize = 4; | ||
| 152 | } else if #[cfg(any( | ||
| 153 | stm32f412, | ||
| 154 | stm32f413, | ||
| 155 | stm32f423, | ||
| 156 | stm32f446, | ||
| 157 | stm32f469, | ||
| 158 | stm32f479, | ||
| 159 | stm32f7, | ||
| 160 | stm32l4, | ||
| 161 | stm32u5, | ||
| 162 | ))] { | ||
| 163 | const FIFO_DEPTH_WORDS: usize = 320; | ||
| 164 | const ENDPOINT_COUNT: usize = 6; | ||
| 165 | } else if #[cfg(stm32g0x1)] { | ||
| 166 | const FIFO_DEPTH_WORDS: usize = 512; | ||
| 167 | const ENDPOINT_COUNT: usize = 8; | ||
| 168 | } else { | ||
| 169 | compile_error!("USB_OTG_FS peripheral is not supported by this chip."); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | impl Instance for peripherals::$inst {} | ||
| 175 | }; | ||
| 176 | |||
| 177 | (otghs, $inst:ident) => { | ||
| 178 | impl sealed::Instance for peripherals::$inst { | ||
| 179 | const REGISTERS: *const () = crate::pac::$inst.0 as *const (); | ||
| 180 | const HIGH_SPEED: bool = true; | ||
| 181 | |||
| 182 | cfg_if::cfg_if! { | ||
| 183 | if #[cfg(any( | ||
| 184 | stm32f2, | ||
| 185 | stm32f405, | ||
| 186 | stm32f407, | ||
| 187 | stm32f415, | ||
| 188 | stm32f417, | ||
| 189 | stm32f427, | ||
| 190 | stm32f429, | ||
| 191 | stm32f437, | ||
| 192 | stm32f439, | ||
| 193 | ))] { | ||
| 194 | const FIFO_DEPTH_WORDS: usize = 1024; | ||
| 195 | const ENDPOINT_COUNT: usize = 6; | ||
| 196 | } else if #[cfg(any( | ||
| 197 | stm32f446, | ||
| 198 | stm32f469, | ||
| 199 | stm32f479, | ||
| 200 | stm32f7, | ||
| 201 | stm32h7, | ||
| 202 | ))] { | ||
| 203 | const FIFO_DEPTH_WORDS: usize = 1024; | ||
| 204 | const ENDPOINT_COUNT: usize = 9; | ||
| 205 | } else { | ||
| 206 | compile_error!("USB_OTG_HS peripheral is not supported by this chip."); | ||
| 207 | } | ||
| 208 | } | ||
| 209 | } | ||
| 210 | |||
| 211 | impl Instance for peripherals::$inst {} | ||
| 212 | }; | ||
| 213 | ); | ||
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 @@ | |||
| 1 | use embassy_cortex_m::interrupt::Interrupt; | ||
| 2 | |||
| 3 | use crate::peripherals; | ||
| 4 | use crate::rcc::RccPeripheral; | ||
| 5 | |||
| 6 | #[cfg(feature = "nightly")] | ||
| 7 | mod usb; | ||
| 8 | #[cfg(feature = "nightly")] | ||
| 9 | pub use usb::*; | ||
| 10 | |||
| 11 | // Using Instance::ENDPOINT_COUNT requires feature(const_generic_expr) so just define maximum eps | ||
| 12 | #[cfg(feature = "nightly")] | ||
| 13 | const MAX_EP_COUNT: usize = 9; | ||
| 14 | |||
| 15 | pub(crate) mod sealed { | ||
| 16 | pub trait Instance { | ||
| 17 | const HIGH_SPEED: bool; | ||
| 18 | const FIFO_DEPTH_WORDS: u16; | ||
| 19 | const ENDPOINT_COUNT: usize; | ||
| 20 | |||
| 21 | fn regs() -> crate::pac::otg::Otg; | ||
| 22 | #[cfg(feature = "nightly")] | ||
| 23 | fn state() -> &'static super::State<{ super::MAX_EP_COUNT }>; | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | pub trait Instance: sealed::Instance + RccPeripheral { | ||
| 28 | type Interrupt: Interrupt; | ||
| 29 | } | ||
| 30 | |||
| 31 | // Internal PHY pins | ||
| 32 | pin_trait!(DpPin, Instance); | ||
| 33 | pin_trait!(DmPin, Instance); | ||
| 34 | |||
| 35 | // External PHY pins | ||
| 36 | pin_trait!(UlpiClkPin, Instance); | ||
| 37 | pin_trait!(UlpiDirPin, Instance); | ||
| 38 | pin_trait!(UlpiNxtPin, Instance); | ||
| 39 | pin_trait!(UlpiStpPin, Instance); | ||
| 40 | pin_trait!(UlpiD0Pin, Instance); | ||
| 41 | pin_trait!(UlpiD1Pin, Instance); | ||
| 42 | pin_trait!(UlpiD2Pin, Instance); | ||
| 43 | pin_trait!(UlpiD3Pin, Instance); | ||
| 44 | pin_trait!(UlpiD4Pin, Instance); | ||
| 45 | pin_trait!(UlpiD5Pin, Instance); | ||
| 46 | pin_trait!(UlpiD6Pin, Instance); | ||
| 47 | pin_trait!(UlpiD7Pin, Instance); | ||
| 48 | |||
| 49 | foreach_interrupt!( | ||
| 50 | (USB_OTG_FS, otg, $block:ident, GLOBAL, $irq:ident) => { | ||
| 51 | impl sealed::Instance for peripherals::USB_OTG_FS { | ||
| 52 | const HIGH_SPEED: bool = false; | ||
| 53 | |||
| 54 | cfg_if::cfg_if! { | ||
| 55 | if #[cfg(stm32f1)] { | ||
| 56 | const FIFO_DEPTH_WORDS: u16 = 128; | ||
| 57 | const ENDPOINT_COUNT: usize = 8; | ||
| 58 | } else if #[cfg(any( | ||
| 59 | stm32f2, | ||
| 60 | stm32f401, | ||
| 61 | stm32f405, | ||
| 62 | stm32f407, | ||
| 63 | stm32f411, | ||
| 64 | stm32f415, | ||
| 65 | stm32f417, | ||
| 66 | stm32f427, | ||
| 67 | stm32f429, | ||
| 68 | stm32f437, | ||
| 69 | stm32f439, | ||
| 70 | ))] { | ||
| 71 | const FIFO_DEPTH_WORDS: u16 = 320; | ||
| 72 | const ENDPOINT_COUNT: usize = 4; | ||
| 73 | } else if #[cfg(any( | ||
| 74 | stm32f412, | ||
| 75 | stm32f413, | ||
| 76 | stm32f423, | ||
| 77 | stm32f446, | ||
| 78 | stm32f469, | ||
| 79 | stm32f479, | ||
| 80 | stm32f7, | ||
| 81 | stm32l4, | ||
| 82 | stm32u5, | ||
| 83 | ))] { | ||
| 84 | const FIFO_DEPTH_WORDS: u16 = 320; | ||
| 85 | const ENDPOINT_COUNT: usize = 6; | ||
| 86 | } else if #[cfg(stm32g0x1)] { | ||
| 87 | const FIFO_DEPTH_WORDS: u16 = 512; | ||
| 88 | const ENDPOINT_COUNT: usize = 8; | ||
| 89 | } else if #[cfg(stm32h7)] { | ||
| 90 | const FIFO_DEPTH_WORDS: u16 = 1024; | ||
| 91 | const ENDPOINT_COUNT: usize = 9; | ||
| 92 | } else { | ||
| 93 | compile_error!("USB_OTG_FS peripheral is not supported by this chip."); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | fn regs() -> crate::pac::otg::Otg { | ||
| 98 | crate::pac::USB_OTG_FS | ||
| 99 | } | ||
| 100 | |||
| 101 | #[cfg(feature = "nightly")] | ||
| 102 | fn state() -> &'static State<MAX_EP_COUNT> { | ||
| 103 | static STATE: State<MAX_EP_COUNT> = State::new(); | ||
| 104 | &STATE | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | impl Instance for peripherals::USB_OTG_FS { | ||
| 109 | type Interrupt = crate::interrupt::$irq; | ||
| 110 | } | ||
| 111 | }; | ||
| 112 | |||
| 113 | (USB_OTG_HS, otg, $block:ident, GLOBAL, $irq:ident) => { | ||
| 114 | impl sealed::Instance for peripherals::USB_OTG_HS { | ||
| 115 | const HIGH_SPEED: bool = true; | ||
| 116 | |||
| 117 | cfg_if::cfg_if! { | ||
| 118 | if #[cfg(any( | ||
| 119 | stm32f2, | ||
| 120 | stm32f405, | ||
| 121 | stm32f407, | ||
| 122 | stm32f415, | ||
| 123 | stm32f417, | ||
| 124 | stm32f427, | ||
| 125 | stm32f429, | ||
| 126 | stm32f437, | ||
| 127 | stm32f439, | ||
| 128 | ))] { | ||
| 129 | const FIFO_DEPTH_WORDS: u16 = 1024; | ||
| 130 | const ENDPOINT_COUNT: usize = 6; | ||
| 131 | } else if #[cfg(any( | ||
| 132 | stm32f446, | ||
| 133 | stm32f469, | ||
| 134 | stm32f479, | ||
| 135 | stm32f7, | ||
| 136 | stm32h7, | ||
| 137 | ))] { | ||
| 138 | const FIFO_DEPTH_WORDS: u16 = 1024; | ||
| 139 | const ENDPOINT_COUNT: usize = 9; | ||
| 140 | } else { | ||
| 141 | compile_error!("USB_OTG_HS peripheral is not supported by this chip."); | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | fn regs() -> crate::pac::otg::Otg { | ||
| 146 | // OTG HS registers are a superset of FS registers | ||
| 147 | crate::pac::otg::Otg(crate::pac::USB_OTG_HS.0) | ||
| 148 | } | ||
| 149 | |||
| 150 | #[cfg(feature = "nightly")] | ||
| 151 | fn state() -> &'static State<MAX_EP_COUNT> { | ||
| 152 | static STATE: State<MAX_EP_COUNT> = State::new(); | ||
| 153 | &STATE | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | impl Instance for peripherals::USB_OTG_HS { | ||
| 158 | type Interrupt = crate::interrupt::$irq; | ||
| 159 | } | ||
| 160 | }; | ||
| 161 | ); | ||
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 @@ | |||
| 1 | use core::cell::UnsafeCell; | ||
| 2 | use core::marker::PhantomData; | ||
| 3 | use core::task::Poll; | ||
| 4 | |||
| 5 | use atomic_polyfill::{AtomicBool, AtomicU16, Ordering}; | ||
| 6 | use embassy_cortex_m::interrupt::InterruptExt; | ||
| 7 | use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; | ||
| 8 | use embassy_sync::waitqueue::AtomicWaker; | ||
| 9 | use embassy_usb_driver::{ | ||
| 10 | self, Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointIn, EndpointInfo, EndpointOut, | ||
| 11 | EndpointType, Event, Unsupported, | ||
| 12 | }; | ||
| 13 | use futures::future::poll_fn; | ||
| 14 | |||
| 15 | use super::*; | ||
| 16 | use crate::gpio::sealed::AFType; | ||
| 17 | use crate::pac::otg::{regs, vals}; | ||
| 18 | use crate::rcc::sealed::RccPeripheral; | ||
| 19 | use crate::time::Hertz; | ||
| 20 | |||
| 21 | macro_rules! config_ulpi_pins { | ||
| 22 | ($($pin:ident),*) => { | ||
| 23 | into_ref!($($pin),*); | ||
| 24 | // NOTE(unsafe) Exclusive access to the registers | ||
| 25 | critical_section::with(|_| unsafe { | ||
| 26 | $( | ||
| 27 | $pin.set_as_af($pin.af_num(), AFType::OutputPushPull); | ||
| 28 | #[cfg(gpio_v2)] | ||
| 29 | $pin.set_speed(crate::gpio::Speed::VeryHigh); | ||
| 30 | )* | ||
| 31 | }) | ||
| 32 | }; | ||
| 33 | } | ||
| 34 | |||
| 35 | // From `synopsys-usb-otg` crate: | ||
| 36 | // This calculation doesn't correspond to one in a Reference Manual. | ||
| 37 | // In fact, the required number of words is higher than indicated in RM. | ||
| 38 | // The following numbers are pessimistic and were figured out empirically. | ||
| 39 | const RX_FIFO_EXTRA_SIZE_WORDS: u16 = 30; | ||
| 40 | |||
| 41 | /// USB PHY type | ||
| 42 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] | ||
| 43 | pub enum PhyType { | ||
| 44 | /// Internal Full-Speed PHY | ||
| 45 | /// | ||
| 46 | /// Available on most High-Speed peripherals. | ||
| 47 | InternalFullSpeed, | ||
| 48 | /// Internal High-Speed PHY | ||
| 49 | /// | ||
| 50 | /// Available on a few STM32 chips. | ||
| 51 | InternalHighSpeed, | ||
| 52 | /// External ULPI High-Speed PHY | ||
| 53 | ExternalHighSpeed, | ||
| 54 | } | ||
| 55 | |||
| 56 | impl PhyType { | ||
| 57 | pub fn internal(&self) -> bool { | ||
| 58 | match self { | ||
| 59 | PhyType::InternalFullSpeed | PhyType::InternalHighSpeed => true, | ||
| 60 | PhyType::ExternalHighSpeed => false, | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | pub fn high_speed(&self) -> bool { | ||
| 65 | match self { | ||
| 66 | PhyType::InternalFullSpeed => false, | ||
| 67 | PhyType::ExternalHighSpeed | PhyType::InternalHighSpeed => true, | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | pub fn to_dspd(&self) -> vals::Dspd { | ||
| 72 | match self { | ||
| 73 | PhyType::InternalFullSpeed => vals::Dspd::FULL_SPEED_INTERNAL, | ||
| 74 | PhyType::InternalHighSpeed => vals::Dspd::HIGH_SPEED, | ||
| 75 | PhyType::ExternalHighSpeed => vals::Dspd::HIGH_SPEED, | ||
| 76 | } | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | /// Indicates that [State::ep_out_buffers] is empty. | ||
| 81 | const EP_OUT_BUFFER_EMPTY: u16 = u16::MAX; | ||
| 82 | |||
| 83 | pub struct State<const EP_COUNT: usize> { | ||
| 84 | /// Holds received SETUP packets. Available if [State::ep0_setup_ready] is true. | ||
| 85 | ep0_setup_data: UnsafeCell<[u8; 8]>, | ||
| 86 | ep0_setup_ready: AtomicBool, | ||
| 87 | ep_in_wakers: [AtomicWaker; EP_COUNT], | ||
| 88 | ep_out_wakers: [AtomicWaker; EP_COUNT], | ||
| 89 | /// RX FIFO is shared so extra buffers are needed to dequeue all data without waiting on each endpoint. | ||
| 90 | /// Buffers are ready when associated [State::ep_out_size] != [EP_OUT_BUFFER_EMPTY]. | ||
| 91 | ep_out_buffers: [UnsafeCell<*mut u8>; EP_COUNT], | ||
| 92 | ep_out_size: [AtomicU16; EP_COUNT], | ||
| 93 | bus_waker: AtomicWaker, | ||
| 94 | } | ||
| 95 | |||
| 96 | unsafe impl<const EP_COUNT: usize> Send for State<EP_COUNT> {} | ||
| 97 | unsafe impl<const EP_COUNT: usize> Sync for State<EP_COUNT> {} | ||
| 98 | |||
| 99 | impl<const EP_COUNT: usize> State<EP_COUNT> { | ||
| 100 | pub const fn new() -> Self { | ||
| 101 | const NEW_AW: AtomicWaker = AtomicWaker::new(); | ||
| 102 | const NEW_BUF: UnsafeCell<*mut u8> = UnsafeCell::new(0 as _); | ||
| 103 | const NEW_SIZE: AtomicU16 = AtomicU16::new(EP_OUT_BUFFER_EMPTY); | ||
| 104 | |||
| 105 | Self { | ||
| 106 | ep0_setup_data: UnsafeCell::new([0u8; 8]), | ||
| 107 | ep0_setup_ready: AtomicBool::new(false), | ||
| 108 | ep_in_wakers: [NEW_AW; EP_COUNT], | ||
| 109 | ep_out_wakers: [NEW_AW; EP_COUNT], | ||
| 110 | ep_out_buffers: [NEW_BUF; EP_COUNT], | ||
| 111 | ep_out_size: [NEW_SIZE; EP_COUNT], | ||
| 112 | bus_waker: NEW_AW, | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | #[derive(Debug, Clone, Copy)] | ||
| 118 | struct EndpointData { | ||
| 119 | ep_type: EndpointType, | ||
| 120 | max_packet_size: u16, | ||
| 121 | fifo_size_words: u16, | ||
| 122 | } | ||
| 123 | |||
| 124 | pub struct Driver<'d, T: Instance> { | ||
| 125 | phantom: PhantomData<&'d mut T>, | ||
| 126 | irq: PeripheralRef<'d, T::Interrupt>, | ||
| 127 | ep_in: [Option<EndpointData>; MAX_EP_COUNT], | ||
| 128 | ep_out: [Option<EndpointData>; MAX_EP_COUNT], | ||
| 129 | ep_out_buffer: &'d mut [u8], | ||
| 130 | ep_out_buffer_offset: usize, | ||
| 131 | phy_type: PhyType, | ||
| 132 | } | ||
| 133 | |||
| 134 | impl<'d, T: Instance> Driver<'d, T> { | ||
| 135 | /// Initializes USB OTG peripheral with internal Full-Speed PHY. | ||
| 136 | /// | ||
| 137 | /// # Arguments | ||
| 138 | /// | ||
| 139 | /// * `ep_out_buffer` - An internal buffer used to temporarily store recevied packets. | ||
| 140 | /// Must be large enough to fit all OUT endpoint max packet sizes. | ||
| 141 | /// Endpoint allocation will fail if it is too small. | ||
| 142 | pub fn new_fs( | ||
| 143 | _peri: impl Peripheral<P = T> + 'd, | ||
| 144 | irq: impl Peripheral<P = T::Interrupt> + 'd, | ||
| 145 | dp: impl Peripheral<P = impl DpPin<T>> + 'd, | ||
| 146 | dm: impl Peripheral<P = impl DmPin<T>> + 'd, | ||
| 147 | ep_out_buffer: &'d mut [u8], | ||
| 148 | ) -> Self { | ||
| 149 | into_ref!(dp, dm, irq); | ||
| 150 | |||
| 151 | unsafe { | ||
| 152 | dp.set_as_af(dp.af_num(), AFType::OutputPushPull); | ||
| 153 | dm.set_as_af(dm.af_num(), AFType::OutputPushPull); | ||
| 154 | } | ||
| 155 | |||
| 156 | Self { | ||
| 157 | phantom: PhantomData, | ||
| 158 | irq, | ||
| 159 | ep_in: [None; MAX_EP_COUNT], | ||
| 160 | ep_out: [None; MAX_EP_COUNT], | ||
| 161 | ep_out_buffer, | ||
| 162 | ep_out_buffer_offset: 0, | ||
| 163 | phy_type: PhyType::InternalFullSpeed, | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | /// Initializes USB OTG peripheral with external High-Speed PHY. | ||
| 168 | /// | ||
| 169 | /// # Arguments | ||
| 170 | /// | ||
| 171 | /// * `ep_out_buffer` - An internal buffer used to temporarily store recevied packets. | ||
| 172 | /// Must be large enough to fit all OUT endpoint max packet sizes. | ||
| 173 | /// Endpoint allocation will fail if it is too small. | ||
| 174 | pub fn new_hs_ulpi( | ||
| 175 | _peri: impl Peripheral<P = T> + 'd, | ||
| 176 | irq: impl Peripheral<P = T::Interrupt> + 'd, | ||
| 177 | ulpi_clk: impl Peripheral<P = impl UlpiClkPin<T>> + 'd, | ||
| 178 | ulpi_dir: impl Peripheral<P = impl UlpiDirPin<T>> + 'd, | ||
| 179 | ulpi_nxt: impl Peripheral<P = impl UlpiNxtPin<T>> + 'd, | ||
| 180 | ulpi_stp: impl Peripheral<P = impl UlpiStpPin<T>> + 'd, | ||
| 181 | ulpi_d0: impl Peripheral<P = impl UlpiD0Pin<T>> + 'd, | ||
| 182 | ulpi_d1: impl Peripheral<P = impl UlpiD1Pin<T>> + 'd, | ||
| 183 | ulpi_d2: impl Peripheral<P = impl UlpiD2Pin<T>> + 'd, | ||
| 184 | ulpi_d3: impl Peripheral<P = impl UlpiD3Pin<T>> + 'd, | ||
| 185 | ulpi_d4: impl Peripheral<P = impl UlpiD4Pin<T>> + 'd, | ||
| 186 | ulpi_d5: impl Peripheral<P = impl UlpiD5Pin<T>> + 'd, | ||
| 187 | ulpi_d6: impl Peripheral<P = impl UlpiD6Pin<T>> + 'd, | ||
| 188 | ulpi_d7: impl Peripheral<P = impl UlpiD7Pin<T>> + 'd, | ||
| 189 | ep_out_buffer: &'d mut [u8], | ||
| 190 | ) -> Self { | ||
| 191 | assert!(T::HIGH_SPEED == true, "Peripheral is not capable of high-speed USB"); | ||
| 192 | |||
| 193 | config_ulpi_pins!( | ||
| 194 | ulpi_clk, ulpi_dir, ulpi_nxt, ulpi_stp, ulpi_d0, ulpi_d1, ulpi_d2, ulpi_d3, ulpi_d4, ulpi_d5, ulpi_d6, | ||
| 195 | ulpi_d7 | ||
| 196 | ); | ||
| 197 | |||
| 198 | into_ref!(irq); | ||
| 199 | |||
| 200 | Self { | ||
| 201 | phantom: PhantomData, | ||
| 202 | irq, | ||
| 203 | ep_in: [None; MAX_EP_COUNT], | ||
| 204 | ep_out: [None; MAX_EP_COUNT], | ||
| 205 | ep_out_buffer, | ||
| 206 | ep_out_buffer_offset: 0, | ||
| 207 | phy_type: PhyType::ExternalHighSpeed, | ||
| 208 | } | ||
| 209 | } | ||
| 210 | |||
| 211 | // Returns total amount of words (u32) allocated in dedicated FIFO | ||
| 212 | fn allocated_fifo_words(&self) -> u16 { | ||
| 213 | RX_FIFO_EXTRA_SIZE_WORDS + ep_fifo_size(&self.ep_out) + ep_fifo_size(&self.ep_in) | ||
| 214 | } | ||
| 215 | |||
| 216 | fn alloc_endpoint<D: Dir>( | ||
| 217 | &mut self, | ||
| 218 | ep_type: EndpointType, | ||
| 219 | max_packet_size: u16, | ||
| 220 | interval: u8, | ||
| 221 | ) -> Result<Endpoint<'d, T, D>, EndpointAllocError> { | ||
| 222 | trace!( | ||
| 223 | "allocating type={:?} mps={:?} interval={}, dir={:?}", | ||
| 224 | ep_type, | ||
| 225 | max_packet_size, | ||
| 226 | interval, | ||
| 227 | D::dir() | ||
| 228 | ); | ||
| 229 | |||
| 230 | if D::dir() == Direction::Out { | ||
| 231 | if self.ep_out_buffer_offset + max_packet_size as usize >= self.ep_out_buffer.len() { | ||
| 232 | error!("Not enough endpoint out buffer capacity"); | ||
| 233 | return Err(EndpointAllocError); | ||
| 234 | } | ||
| 235 | }; | ||
| 236 | |||
| 237 | let fifo_size_words = match D::dir() { | ||
| 238 | Direction::Out => (max_packet_size + 3) / 4, | ||
| 239 | // INEPTXFD requires minimum size of 16 words | ||
| 240 | Direction::In => u16::max((max_packet_size + 3) / 4, 16), | ||
| 241 | }; | ||
| 242 | |||
| 243 | if fifo_size_words + self.allocated_fifo_words() > T::FIFO_DEPTH_WORDS { | ||
| 244 | error!("Not enough FIFO capacity"); | ||
| 245 | return Err(EndpointAllocError); | ||
| 246 | } | ||
| 247 | |||
| 248 | let eps = match D::dir() { | ||
| 249 | Direction::Out => &mut self.ep_out, | ||
| 250 | Direction::In => &mut self.ep_in, | ||
| 251 | }; | ||
| 252 | |||
| 253 | // Find free endpoint slot | ||
| 254 | let slot = eps.iter_mut().enumerate().find(|(i, ep)| { | ||
| 255 | if *i == 0 && ep_type != EndpointType::Control { | ||
| 256 | // reserved for control pipe | ||
| 257 | false | ||
| 258 | } else { | ||
| 259 | ep.is_none() | ||
| 260 | } | ||
| 261 | }); | ||
| 262 | |||
| 263 | let index = match slot { | ||
| 264 | Some((index, ep)) => { | ||
| 265 | *ep = Some(EndpointData { | ||
| 266 | ep_type, | ||
| 267 | max_packet_size, | ||
| 268 | fifo_size_words, | ||
| 269 | }); | ||
| 270 | index | ||
| 271 | } | ||
| 272 | None => { | ||
| 273 | error!("No free endpoints available"); | ||
| 274 | return Err(EndpointAllocError); | ||
| 275 | } | ||
| 276 | }; | ||
| 277 | |||
| 278 | trace!(" index={}", index); | ||
| 279 | |||
| 280 | if D::dir() == Direction::Out { | ||
| 281 | // Buffer capacity check was done above, now allocation cannot fail | ||
| 282 | unsafe { | ||
| 283 | *T::state().ep_out_buffers[index].get() = | ||
| 284 | self.ep_out_buffer.as_mut_ptr().offset(self.ep_out_buffer_offset as _); | ||
| 285 | } | ||
| 286 | self.ep_out_buffer_offset += max_packet_size as usize; | ||
| 287 | } | ||
| 288 | |||
| 289 | Ok(Endpoint { | ||
| 290 | _phantom: PhantomData, | ||
| 291 | info: EndpointInfo { | ||
| 292 | addr: EndpointAddress::from_parts(index, D::dir()), | ||
| 293 | ep_type, | ||
| 294 | max_packet_size, | ||
| 295 | interval, | ||
| 296 | }, | ||
| 297 | }) | ||
| 298 | } | ||
| 299 | } | ||
| 300 | |||
| 301 | impl<'d, T: Instance> embassy_usb_driver::Driver<'d> for Driver<'d, T> { | ||
| 302 | type EndpointOut = Endpoint<'d, T, Out>; | ||
| 303 | type EndpointIn = Endpoint<'d, T, In>; | ||
| 304 | type ControlPipe = ControlPipe<'d, T>; | ||
| 305 | type Bus = Bus<'d, T>; | ||
| 306 | |||
| 307 | fn alloc_endpoint_in( | ||
| 308 | &mut self, | ||
| 309 | ep_type: EndpointType, | ||
| 310 | max_packet_size: u16, | ||
| 311 | interval: u8, | ||
| 312 | ) -> Result<Self::EndpointIn, EndpointAllocError> { | ||
| 313 | self.alloc_endpoint(ep_type, max_packet_size, interval) | ||
| 314 | } | ||
| 315 | |||
| 316 | fn alloc_endpoint_out( | ||
| 317 | &mut self, | ||
| 318 | ep_type: EndpointType, | ||
| 319 | max_packet_size: u16, | ||
| 320 | interval: u8, | ||
| 321 | ) -> Result<Self::EndpointOut, EndpointAllocError> { | ||
| 322 | self.alloc_endpoint(ep_type, max_packet_size, interval) | ||
| 323 | } | ||
| 324 | |||
| 325 | fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { | ||
| 326 | let ep_out = self | ||
| 327 | .alloc_endpoint(EndpointType::Control, control_max_packet_size, 0) | ||
| 328 | .unwrap(); | ||
| 329 | let ep_in = self | ||
| 330 | .alloc_endpoint(EndpointType::Control, control_max_packet_size, 0) | ||
| 331 | .unwrap(); | ||
| 332 | assert_eq!(ep_out.info.addr.index(), 0); | ||
| 333 | assert_eq!(ep_in.info.addr.index(), 0); | ||
| 334 | |||
| 335 | trace!("start"); | ||
| 336 | |||
| 337 | ( | ||
| 338 | Bus { | ||
| 339 | phantom: PhantomData, | ||
| 340 | irq: self.irq, | ||
| 341 | ep_in: self.ep_in, | ||
| 342 | ep_out: self.ep_out, | ||
| 343 | phy_type: self.phy_type, | ||
| 344 | enabled: false, | ||
| 345 | }, | ||
| 346 | ControlPipe { | ||
| 347 | _phantom: PhantomData, | ||
| 348 | max_packet_size: control_max_packet_size, | ||
| 349 | ep_out, | ||
| 350 | ep_in, | ||
| 351 | }, | ||
| 352 | ) | ||
| 353 | } | ||
| 354 | } | ||
| 355 | |||
| 356 | pub struct Bus<'d, T: Instance> { | ||
| 357 | phantom: PhantomData<&'d mut T>, | ||
| 358 | irq: PeripheralRef<'d, T::Interrupt>, | ||
| 359 | ep_in: [Option<EndpointData>; MAX_EP_COUNT], | ||
| 360 | ep_out: [Option<EndpointData>; MAX_EP_COUNT], | ||
| 361 | phy_type: PhyType, | ||
| 362 | enabled: bool, | ||
| 363 | } | ||
| 364 | |||
| 365 | impl<'d, T: Instance> Bus<'d, T> { | ||
| 366 | fn restore_irqs() { | ||
| 367 | // SAFETY: atomic write | ||
| 368 | unsafe { | ||
| 369 | T::regs().gintmsk().write(|w| { | ||
| 370 | w.set_usbrst(true); | ||
| 371 | w.set_enumdnem(true); | ||
| 372 | w.set_usbsuspm(true); | ||
| 373 | w.set_wuim(true); | ||
| 374 | w.set_iepint(true); | ||
| 375 | w.set_oepint(true); | ||
| 376 | w.set_rxflvlm(true); | ||
| 377 | }); | ||
| 378 | } | ||
| 379 | } | ||
| 380 | } | ||
| 381 | |||
| 382 | impl<'d, T: Instance> Bus<'d, T> { | ||
| 383 | fn init_fifo(&mut self) { | ||
| 384 | trace!("init_fifo"); | ||
| 385 | |||
| 386 | let r = T::regs(); | ||
| 387 | |||
| 388 | // Configure RX fifo size. All endpoints share the same FIFO area. | ||
| 389 | let rx_fifo_size_words = RX_FIFO_EXTRA_SIZE_WORDS + ep_fifo_size(&self.ep_out); | ||
| 390 | trace!("configuring rx fifo size={}", rx_fifo_size_words); | ||
| 391 | |||
| 392 | // SAFETY: register is exclusive to `Bus` with `&mut self` | ||
| 393 | unsafe { r.grxfsiz().modify(|w| w.set_rxfd(rx_fifo_size_words)) }; | ||
| 394 | |||
| 395 | // Configure TX (USB in direction) fifo size for each endpoint | ||
| 396 | let mut fifo_top = rx_fifo_size_words; | ||
| 397 | for i in 0..T::ENDPOINT_COUNT { | ||
| 398 | if let Some(ep) = self.ep_in[i] { | ||
| 399 | trace!( | ||
| 400 | "configuring tx fifo ep={}, offset={}, size={}", | ||
| 401 | i, | ||
| 402 | fifo_top, | ||
| 403 | ep.fifo_size_words | ||
| 404 | ); | ||
| 405 | |||
| 406 | let dieptxf = if i == 0 { r.dieptxf0() } else { r.dieptxf(i - 1) }; | ||
| 407 | |||
| 408 | // SAFETY: register is exclusive to `Bus` with `&mut self` | ||
| 409 | unsafe { | ||
| 410 | dieptxf.write(|w| { | ||
| 411 | w.set_fd(ep.fifo_size_words); | ||
| 412 | w.set_sa(fifo_top); | ||
| 413 | }); | ||
| 414 | } | ||
| 415 | |||
| 416 | fifo_top += ep.fifo_size_words; | ||
| 417 | } | ||
| 418 | } | ||
| 419 | |||
| 420 | assert!( | ||
| 421 | fifo_top <= T::FIFO_DEPTH_WORDS, | ||
| 422 | "FIFO allocations exceeded maximum capacity" | ||
| 423 | ); | ||
| 424 | } | ||
| 425 | |||
| 426 | fn configure_endpoints(&mut self) { | ||
| 427 | trace!("configure_endpoints"); | ||
| 428 | |||
| 429 | let r = T::regs(); | ||
| 430 | |||
| 431 | // Configure IN endpoints | ||
| 432 | for (index, ep) in self.ep_in.iter().enumerate() { | ||
| 433 | if let Some(ep) = ep { | ||
| 434 | // SAFETY: DIEPCTL is shared with `Endpoint` so critical section is needed for RMW | ||
| 435 | critical_section::with(|_| unsafe { | ||
| 436 | r.diepctl(index).write(|w| { | ||
| 437 | if index == 0 { | ||
| 438 | w.set_mpsiz(ep0_mpsiz(ep.max_packet_size)); | ||
| 439 | } else { | ||
| 440 | w.set_mpsiz(ep.max_packet_size); | ||
| 441 | w.set_eptyp(to_eptyp(ep.ep_type)); | ||
| 442 | w.set_sd0pid_sevnfrm(true); | ||
| 443 | } | ||
| 444 | }); | ||
| 445 | }); | ||
| 446 | } | ||
| 447 | } | ||
| 448 | |||
| 449 | // Configure OUT endpoints | ||
| 450 | for (index, ep) in self.ep_out.iter().enumerate() { | ||
| 451 | if let Some(ep) = ep { | ||
| 452 | // SAFETY: DOEPCTL/DOEPTSIZ is shared with `Endpoint` so critical section is needed for RMW | ||
| 453 | critical_section::with(|_| unsafe { | ||
| 454 | r.doepctl(index).write(|w| { | ||
| 455 | if index == 0 { | ||
| 456 | w.set_mpsiz(ep0_mpsiz(ep.max_packet_size)); | ||
| 457 | } else { | ||
| 458 | w.set_mpsiz(ep.max_packet_size); | ||
| 459 | w.set_eptyp(to_eptyp(ep.ep_type)); | ||
| 460 | w.set_sd0pid_sevnfrm(true); | ||
| 461 | } | ||
| 462 | }); | ||
| 463 | |||
| 464 | r.doeptsiz(index).modify(|w| { | ||
| 465 | w.set_xfrsiz(ep.max_packet_size as _); | ||
| 466 | if index == 0 { | ||
| 467 | w.set_rxdpid_stupcnt(1); | ||
| 468 | } else { | ||
| 469 | w.set_pktcnt(1); | ||
| 470 | } | ||
| 471 | }); | ||
| 472 | }); | ||
| 473 | } | ||
| 474 | } | ||
| 475 | |||
| 476 | // Enable IRQs for allocated endpoints | ||
| 477 | // SAFETY: register is exclusive to `Bus` with `&mut self` | ||
| 478 | unsafe { | ||
| 479 | r.daintmsk().modify(|w| { | ||
| 480 | w.set_iepm(ep_irq_mask(&self.ep_in)); | ||
| 481 | // OUT interrupts not used, handled in RXFLVL | ||
| 482 | // w.set_oepm(ep_irq_mask(&self.ep_out)); | ||
| 483 | }); | ||
| 484 | } | ||
| 485 | } | ||
| 486 | |||
| 487 | fn disable(&mut self) { | ||
| 488 | self.irq.disable(); | ||
| 489 | self.irq.remove_handler(); | ||
| 490 | |||
| 491 | <T as RccPeripheral>::disable(); | ||
| 492 | |||
| 493 | #[cfg(stm32l4)] | ||
| 494 | unsafe { | ||
| 495 | crate::pac::PWR.cr2().modify(|w| w.set_usv(false)); | ||
| 496 | // Cannot disable PWR, because other peripherals might be using it | ||
| 497 | } | ||
| 498 | } | ||
| 499 | |||
| 500 | fn on_interrupt(_: *mut ()) { | ||
| 501 | trace!("irq"); | ||
| 502 | let r = T::regs(); | ||
| 503 | let state = T::state(); | ||
| 504 | |||
| 505 | // SAFETY: atomic read/write | ||
| 506 | let ints = unsafe { r.gintsts().read() }; | ||
| 507 | if ints.wkupint() || ints.usbsusp() || ints.usbrst() || ints.enumdne() { | ||
| 508 | // Mask interrupts and notify `Bus` to process them | ||
| 509 | unsafe { r.gintmsk().write(|_| {}) }; | ||
| 510 | T::state().bus_waker.wake(); | ||
| 511 | } | ||
| 512 | |||
| 513 | // Handle RX | ||
| 514 | // SAFETY: atomic read with no side effects | ||
| 515 | while unsafe { r.gintsts().read().rxflvl() } { | ||
| 516 | // SAFETY: atomic "pop" register | ||
| 517 | let status = unsafe { r.grxstsp().read() }; | ||
| 518 | let ep_num = status.epnum() as usize; | ||
| 519 | let len = status.bcnt() as usize; | ||
| 520 | |||
| 521 | assert!(ep_num < T::ENDPOINT_COUNT); | ||
| 522 | |||
| 523 | match status.pktstsd() { | ||
| 524 | vals::Pktstsd::SETUP_DATA_RX => { | ||
| 525 | trace!("SETUP_DATA_RX"); | ||
| 526 | assert!(len == 8, "invalid SETUP packet length={}", len); | ||
| 527 | assert!(ep_num == 0, "invalid SETUP packet endpoint={}", ep_num); | ||
| 528 | |||
| 529 | if state.ep0_setup_ready.load(Ordering::Relaxed) == false { | ||
| 530 | // SAFETY: exclusive access ensured by atomic bool | ||
| 531 | let data = unsafe { &mut *state.ep0_setup_data.get() }; | ||
| 532 | // SAFETY: FIFO reads are exclusive to this IRQ | ||
| 533 | unsafe { | ||
| 534 | data[0..4].copy_from_slice(&r.fifo(0).read().0.to_ne_bytes()); | ||
| 535 | data[4..8].copy_from_slice(&r.fifo(0).read().0.to_ne_bytes()); | ||
| 536 | } | ||
| 537 | state.ep0_setup_ready.store(true, Ordering::Release); | ||
| 538 | state.ep_out_wakers[0].wake(); | ||
| 539 | } else { | ||
| 540 | error!("received SETUP before previous finished processing"); | ||
| 541 | // discard FIFO | ||
| 542 | // SAFETY: FIFO reads are exclusive to IRQ | ||
| 543 | unsafe { | ||
| 544 | r.fifo(0).read(); | ||
| 545 | r.fifo(0).read(); | ||
| 546 | } | ||
| 547 | } | ||
| 548 | } | ||
| 549 | vals::Pktstsd::OUT_DATA_RX => { | ||
| 550 | trace!("OUT_DATA_RX ep={} len={}", ep_num, len); | ||
| 551 | |||
| 552 | if state.ep_out_size[ep_num].load(Ordering::Acquire) == EP_OUT_BUFFER_EMPTY { | ||
| 553 | // SAFETY: Buffer size is allocated to be equal to endpoint's maximum packet size | ||
| 554 | // We trust the peripheral to not exceed its configured MPSIZ | ||
| 555 | let buf = unsafe { core::slice::from_raw_parts_mut(*state.ep_out_buffers[ep_num].get(), len) }; | ||
| 556 | |||
| 557 | for chunk in buf.chunks_mut(4) { | ||
| 558 | // RX FIFO is shared so always read from fifo(0) | ||
| 559 | // SAFETY: FIFO reads are exclusive to IRQ | ||
| 560 | let data = unsafe { r.fifo(0).read().0 }; | ||
| 561 | chunk.copy_from_slice(&data.to_ne_bytes()[0..chunk.len()]); | ||
| 562 | } | ||
| 563 | |||
| 564 | state.ep_out_size[ep_num].store(len as u16, Ordering::Release); | ||
| 565 | state.ep_out_wakers[ep_num].wake(); | ||
| 566 | } else { | ||
| 567 | error!("ep_out buffer overflow index={}", ep_num); | ||
| 568 | |||
| 569 | // discard FIFO data | ||
| 570 | let len_words = (len + 3) / 4; | ||
| 571 | for _ in 0..len_words { | ||
| 572 | // SAFETY: FIFO reads are exclusive to IRQ | ||
| 573 | unsafe { r.fifo(0).read().data() }; | ||
| 574 | } | ||
| 575 | } | ||
| 576 | } | ||
| 577 | vals::Pktstsd::OUT_DATA_DONE => { | ||
| 578 | trace!("OUT_DATA_DONE ep={}", ep_num); | ||
| 579 | } | ||
| 580 | vals::Pktstsd::SETUP_DATA_DONE => { | ||
| 581 | trace!("SETUP_DATA_DONE ep={}", ep_num); | ||
| 582 | } | ||
| 583 | x => trace!("unknown PKTSTS: {}", x.0), | ||
| 584 | } | ||
| 585 | } | ||
| 586 | |||
| 587 | // IN endpoint interrupt | ||
| 588 | if ints.iepint() { | ||
| 589 | // SAFETY: atomic read with no side effects | ||
| 590 | let mut ep_mask = unsafe { r.daint().read().iepint() }; | ||
| 591 | let mut ep_num = 0; | ||
| 592 | |||
| 593 | // Iterate over endpoints while there are non-zero bits in the mask | ||
| 594 | while ep_mask != 0 { | ||
| 595 | if ep_mask & 1 != 0 { | ||
| 596 | // SAFETY: atomic read with no side effects | ||
| 597 | let ep_ints = unsafe { r.diepint(ep_num).read() }; | ||
| 598 | |||
| 599 | // clear all | ||
| 600 | // SAFETY: DIEPINT is exclusive to IRQ | ||
| 601 | unsafe { r.diepint(ep_num).write_value(ep_ints) }; | ||
| 602 | |||
| 603 | // TXFE is cleared in DIEPEMPMSK | ||
| 604 | if ep_ints.txfe() { | ||
| 605 | // SAFETY: DIEPEMPMSK is shared with `Endpoint` so critical section is needed for RMW | ||
| 606 | critical_section::with(|_| unsafe { | ||
| 607 | r.diepempmsk().modify(|w| { | ||
| 608 | w.set_ineptxfem(w.ineptxfem() & !(1 << ep_num)); | ||
| 609 | }); | ||
| 610 | }); | ||
| 611 | } | ||
| 612 | |||
| 613 | state.ep_in_wakers[ep_num].wake(); | ||
| 614 | trace!("in ep={} irq val={:b}", ep_num, ep_ints.0); | ||
| 615 | } | ||
| 616 | |||
| 617 | ep_mask >>= 1; | ||
| 618 | ep_num += 1; | ||
| 619 | } | ||
| 620 | } | ||
| 621 | |||
| 622 | // not needed? reception handled in rxflvl | ||
| 623 | // OUT endpoint interrupt | ||
| 624 | // if ints.oepint() { | ||
| 625 | // let mut ep_mask = r.daint().read().oepint(); | ||
| 626 | // let mut ep_num = 0; | ||
| 627 | |||
| 628 | // while ep_mask != 0 { | ||
| 629 | // if ep_mask & 1 != 0 { | ||
| 630 | // let ep_ints = r.doepint(ep_num).read(); | ||
| 631 | // // clear all | ||
| 632 | // r.doepint(ep_num).write_value(ep_ints); | ||
| 633 | // state.ep_out_wakers[ep_num].wake(); | ||
| 634 | // trace!("out ep={} irq val={=u32:b}", ep_num, ep_ints.0); | ||
| 635 | // } | ||
| 636 | |||
| 637 | // ep_mask >>= 1; | ||
| 638 | // ep_num += 1; | ||
| 639 | // } | ||
| 640 | // } | ||
| 641 | } | ||
| 642 | } | ||
| 643 | |||
| 644 | impl<'d, T: Instance> embassy_usb_driver::Bus for Bus<'d, T> { | ||
| 645 | async fn poll(&mut self) -> Event { | ||
| 646 | poll_fn(move |cx| { | ||
| 647 | // TODO: implement VBUS detection | ||
| 648 | if !self.enabled { | ||
| 649 | return Poll::Ready(Event::PowerDetected); | ||
| 650 | } | ||
| 651 | |||
| 652 | let r = T::regs(); | ||
| 653 | |||
| 654 | T::state().bus_waker.register(cx.waker()); | ||
| 655 | |||
| 656 | let ints = unsafe { r.gintsts().read() }; | ||
| 657 | if ints.usbrst() { | ||
| 658 | trace!("reset"); | ||
| 659 | |||
| 660 | self.init_fifo(); | ||
| 661 | self.configure_endpoints(); | ||
| 662 | |||
| 663 | // Reset address | ||
| 664 | // SAFETY: DCFG is shared with `ControlPipe` so critical section is needed for RMW | ||
| 665 | critical_section::with(|_| unsafe { | ||
| 666 | r.dcfg().modify(|w| { | ||
| 667 | w.set_dad(0); | ||
| 668 | }); | ||
| 669 | }); | ||
| 670 | |||
| 671 | // SAFETY: atomic clear on rc_w1 register | ||
| 672 | unsafe { r.gintsts().write(|w| w.set_usbrst(true)) }; // clear | ||
| 673 | Self::restore_irqs(); | ||
| 674 | } | ||
| 675 | |||
| 676 | if ints.enumdne() { | ||
| 677 | trace!("enumdne"); | ||
| 678 | |||
| 679 | // SAFETY: atomic read with no side effects | ||
| 680 | let speed = unsafe { r.dsts().read().enumspd() }; | ||
| 681 | trace!(" speed={}", speed.0); | ||
| 682 | |||
| 683 | // SAFETY: register is only accessed by `Bus` under `&mut self` | ||
| 684 | unsafe { | ||
| 685 | r.gusbcfg().modify(|w| { | ||
| 686 | w.set_trdt(calculate_trdt(speed, T::frequency())); | ||
| 687 | }) | ||
| 688 | }; | ||
| 689 | |||
| 690 | // SAFETY: atomic clear on rc_w1 register | ||
| 691 | unsafe { r.gintsts().write(|w| w.set_enumdne(true)) }; // clear | ||
| 692 | Self::restore_irqs(); | ||
| 693 | |||
| 694 | return Poll::Ready(Event::Reset); | ||
| 695 | } | ||
| 696 | |||
| 697 | if ints.usbsusp() { | ||
| 698 | trace!("suspend"); | ||
| 699 | // SAFETY: atomic clear on rc_w1 register | ||
| 700 | unsafe { r.gintsts().write(|w| w.set_usbsusp(true)) }; // clear | ||
| 701 | Self::restore_irqs(); | ||
| 702 | return Poll::Ready(Event::Suspend); | ||
| 703 | } | ||
| 704 | |||
| 705 | if ints.wkupint() { | ||
| 706 | trace!("resume"); | ||
| 707 | // SAFETY: atomic clear on rc_w1 register | ||
| 708 | unsafe { r.gintsts().write(|w| w.set_wkupint(true)) }; // clear | ||
| 709 | Self::restore_irqs(); | ||
| 710 | return Poll::Ready(Event::Resume); | ||
| 711 | } | ||
| 712 | |||
| 713 | Poll::Pending | ||
| 714 | }) | ||
| 715 | .await | ||
| 716 | } | ||
| 717 | |||
| 718 | fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { | ||
| 719 | trace!("endpoint_set_stalled ep={:?} en={}", ep_addr, stalled); | ||
| 720 | |||
| 721 | assert!( | ||
| 722 | ep_addr.index() < T::ENDPOINT_COUNT, | ||
| 723 | "endpoint_set_stalled index {} out of range", | ||
| 724 | ep_addr.index() | ||
| 725 | ); | ||
| 726 | |||
| 727 | let regs = T::regs(); | ||
| 728 | match ep_addr.direction() { | ||
| 729 | Direction::Out => { | ||
| 730 | // SAFETY: DOEPCTL is shared with `Endpoint` so critical section is needed for RMW | ||
| 731 | critical_section::with(|_| unsafe { | ||
| 732 | regs.doepctl(ep_addr.index()).modify(|w| { | ||
| 733 | w.set_stall(stalled); | ||
| 734 | }); | ||
| 735 | }); | ||
| 736 | |||
| 737 | T::state().ep_out_wakers[ep_addr.index()].wake(); | ||
| 738 | } | ||
| 739 | Direction::In => { | ||
| 740 | // SAFETY: DIEPCTL is shared with `Endpoint` so critical section is needed for RMW | ||
| 741 | critical_section::with(|_| unsafe { | ||
| 742 | regs.diepctl(ep_addr.index()).modify(|w| { | ||
| 743 | w.set_stall(stalled); | ||
| 744 | }); | ||
| 745 | }); | ||
| 746 | |||
| 747 | T::state().ep_in_wakers[ep_addr.index()].wake(); | ||
| 748 | } | ||
| 749 | } | ||
| 750 | } | ||
| 751 | |||
| 752 | fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool { | ||
| 753 | assert!( | ||
| 754 | ep_addr.index() < T::ENDPOINT_COUNT, | ||
| 755 | "endpoint_is_stalled index {} out of range", | ||
| 756 | ep_addr.index() | ||
| 757 | ); | ||
| 758 | |||
| 759 | let regs = T::regs(); | ||
| 760 | |||
| 761 | // SAFETY: atomic read with no side effects | ||
| 762 | match ep_addr.direction() { | ||
| 763 | Direction::Out => unsafe { regs.doepctl(ep_addr.index()).read().stall() }, | ||
| 764 | Direction::In => unsafe { regs.diepctl(ep_addr.index()).read().stall() }, | ||
| 765 | } | ||
| 766 | } | ||
| 767 | |||
| 768 | fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) { | ||
| 769 | trace!("endpoint_set_enabled ep={:?} en={}", ep_addr, enabled); | ||
| 770 | |||
| 771 | assert!( | ||
| 772 | ep_addr.index() < T::ENDPOINT_COUNT, | ||
| 773 | "endpoint_set_enabled index {} out of range", | ||
| 774 | ep_addr.index() | ||
| 775 | ); | ||
| 776 | |||
| 777 | let r = T::regs(); | ||
| 778 | match ep_addr.direction() { | ||
| 779 | Direction::Out => { | ||
| 780 | // SAFETY: DOEPCTL is shared with `Endpoint` so critical section is needed for RMW | ||
| 781 | critical_section::with(|_| unsafe { | ||
| 782 | // cancel transfer if active | ||
| 783 | if !enabled && r.doepctl(ep_addr.index()).read().epena() { | ||
| 784 | r.doepctl(ep_addr.index()).modify(|w| { | ||
| 785 | w.set_snak(true); | ||
| 786 | w.set_epdis(true); | ||
| 787 | }) | ||
| 788 | } | ||
| 789 | |||
| 790 | r.doepctl(ep_addr.index()).modify(|w| { | ||
| 791 | w.set_usbaep(enabled); | ||
| 792 | }) | ||
| 793 | }); | ||
| 794 | } | ||
| 795 | Direction::In => { | ||
| 796 | // SAFETY: DIEPCTL is shared with `Endpoint` so critical section is needed for RMW | ||
| 797 | critical_section::with(|_| unsafe { | ||
| 798 | // cancel transfer if active | ||
| 799 | if !enabled && r.diepctl(ep_addr.index()).read().epena() { | ||
| 800 | r.diepctl(ep_addr.index()).modify(|w| { | ||
| 801 | w.set_snak(true); | ||
| 802 | w.set_epdis(true); | ||
| 803 | }) | ||
| 804 | } | ||
| 805 | |||
| 806 | r.diepctl(ep_addr.index()).modify(|w| { | ||
| 807 | w.set_usbaep(enabled); | ||
| 808 | }) | ||
| 809 | }); | ||
| 810 | } | ||
| 811 | } | ||
| 812 | } | ||
| 813 | |||
| 814 | async fn enable(&mut self) { | ||
| 815 | trace!("enable"); | ||
| 816 | |||
| 817 | // SAFETY: registers are only accessed by `Bus` under `&mut self` | ||
| 818 | unsafe { | ||
| 819 | #[cfg(stm32l4)] | ||
| 820 | { | ||
| 821 | crate::peripherals::PWR::enable(); | ||
| 822 | critical_section::with(|_| crate::pac::PWR.cr2().modify(|w| w.set_usv(true))); | ||
| 823 | } | ||
| 824 | |||
| 825 | #[cfg(stm32h7)] | ||
| 826 | { | ||
| 827 | // If true, VDD33USB is generated by internal regulator from VDD50USB | ||
| 828 | // If false, VDD33USB and VDD50USB must be suplied directly with 3.3V (default on nucleo) | ||
| 829 | // TODO: unhardcode | ||
| 830 | let internal_regulator = false; | ||
| 831 | |||
| 832 | // Enable USB power | ||
| 833 | critical_section::with(|_| { | ||
| 834 | crate::pac::PWR.cr3().modify(|w| { | ||
| 835 | w.set_usb33den(true); | ||
| 836 | w.set_usbregen(internal_regulator); | ||
| 837 | }) | ||
| 838 | }); | ||
| 839 | |||
| 840 | // Wait for USB power to stabilize | ||
| 841 | while !crate::pac::PWR.cr3().read().usb33rdy() {} | ||
| 842 | |||
| 843 | // Use internal 48MHz HSI clock. Should be enabled in RCC by default. | ||
| 844 | critical_section::with(|_| { | ||
| 845 | crate::pac::RCC | ||
| 846 | .d2ccip2r() | ||
| 847 | .modify(|w| w.set_usbsel(crate::pac::rcc::vals::Usbsel::HSI48)) | ||
| 848 | }); | ||
| 849 | |||
| 850 | // Enable ULPI clock if external PHY is used | ||
| 851 | let ulpien = !self.phy_type.internal(); | ||
| 852 | critical_section::with(|_| { | ||
| 853 | crate::pac::RCC.ahb1enr().modify(|w| { | ||
| 854 | if T::HIGH_SPEED { | ||
| 855 | w.set_usb_otg_hs_ulpien(ulpien); | ||
| 856 | } else { | ||
| 857 | w.set_usb_otg_fs_ulpien(ulpien); | ||
| 858 | } | ||
| 859 | }); | ||
| 860 | crate::pac::RCC.ahb1lpenr().modify(|w| { | ||
| 861 | if T::HIGH_SPEED { | ||
| 862 | w.set_usb_otg_hs_ulpilpen(ulpien); | ||
| 863 | } else { | ||
| 864 | w.set_usb_otg_fs_ulpilpen(ulpien); | ||
| 865 | } | ||
| 866 | }); | ||
| 867 | }); | ||
| 868 | } | ||
| 869 | |||
| 870 | #[cfg(stm32u5)] | ||
| 871 | { | ||
| 872 | // Enable USB power | ||
| 873 | critical_section::with(|_| { | ||
| 874 | crate::pac::RCC.ahb3enr().modify(|w| { | ||
| 875 | w.set_pwren(true); | ||
| 876 | }); | ||
| 877 | cortex_m::asm::delay(2); | ||
| 878 | |||
| 879 | crate::pac::PWR.svmcr().modify(|w| { | ||
| 880 | w.set_usv(true); | ||
| 881 | w.set_uvmen(true); | ||
| 882 | }); | ||
| 883 | }); | ||
| 884 | |||
| 885 | // Wait for USB power to stabilize | ||
| 886 | while !crate::pac::PWR.svmsr().read().vddusbrdy() {} | ||
| 887 | |||
| 888 | // Select HSI48 as USB clock source. | ||
| 889 | critical_section::with(|_| { | ||
| 890 | crate::pac::RCC.ccipr1().modify(|w| { | ||
| 891 | w.set_iclksel(crate::pac::rcc::vals::Iclksel::HSI48); | ||
| 892 | }) | ||
| 893 | }); | ||
| 894 | } | ||
| 895 | |||
| 896 | <T as RccPeripheral>::enable(); | ||
| 897 | <T as RccPeripheral>::reset(); | ||
| 898 | |||
| 899 | self.irq.set_handler(Self::on_interrupt); | ||
| 900 | self.irq.unpend(); | ||
| 901 | self.irq.enable(); | ||
| 902 | |||
| 903 | let r = T::regs(); | ||
| 904 | let core_id = r.cid().read().0; | ||
| 905 | info!("Core id {:08x}", core_id); | ||
| 906 | |||
| 907 | // Wait for AHB ready. | ||
| 908 | while !r.grstctl().read().ahbidl() {} | ||
| 909 | |||
| 910 | // Configure as device. | ||
| 911 | r.gusbcfg().write(|w| { | ||
| 912 | // Force device mode | ||
| 913 | w.set_fdmod(true); | ||
| 914 | // Enable internal full-speed PHY | ||
| 915 | w.set_physel(self.phy_type.internal() && !self.phy_type.high_speed()); | ||
| 916 | }); | ||
| 917 | |||
| 918 | // Configuring Vbus sense and SOF output | ||
| 919 | match core_id { | ||
| 920 | 0x0000_1200 | 0x0000_1100 => { | ||
| 921 | assert!(self.phy_type != PhyType::InternalHighSpeed); | ||
| 922 | |||
| 923 | r.gccfg_v1().modify(|w| { | ||
| 924 | // Enable internal full-speed PHY, logic is inverted | ||
| 925 | w.set_pwrdwn(self.phy_type.internal()); | ||
| 926 | }); | ||
| 927 | |||
| 928 | // F429-like chips have the GCCFG.NOVBUSSENS bit | ||
| 929 | r.gccfg_v1().modify(|w| { | ||
| 930 | w.set_novbussens(true); | ||
| 931 | w.set_vbusasen(false); | ||
| 932 | w.set_vbusbsen(false); | ||
| 933 | w.set_sofouten(false); | ||
| 934 | }); | ||
| 935 | } | ||
| 936 | 0x0000_2000 | 0x0000_2100 | 0x0000_2300 | 0x0000_3000 | 0x0000_3100 => { | ||
| 937 | // F446-like chips have the GCCFG.VBDEN bit with the opposite meaning | ||
| 938 | r.gccfg_v2().modify(|w| { | ||
| 939 | // Enable internal full-speed PHY, logic is inverted | ||
| 940 | w.set_pwrdwn(self.phy_type.internal() && !self.phy_type.high_speed()); | ||
| 941 | w.set_phyhsen(self.phy_type.internal() && self.phy_type.high_speed()); | ||
| 942 | }); | ||
| 943 | |||
| 944 | r.gccfg_v2().modify(|w| { | ||
| 945 | w.set_vbden(false); | ||
| 946 | }); | ||
| 947 | |||
| 948 | // Force B-peripheral session | ||
| 949 | r.gotgctl().modify(|w| { | ||
| 950 | w.set_bvaloen(true); | ||
| 951 | w.set_bvaloval(true); | ||
| 952 | }); | ||
| 953 | } | ||
| 954 | _ => unimplemented!("Unknown USB core id {:X}", core_id), | ||
| 955 | } | ||
| 956 | |||
| 957 | // Soft disconnect. | ||
| 958 | r.dctl().write(|w| w.set_sdis(true)); | ||
| 959 | |||
| 960 | // Set speed. | ||
| 961 | r.dcfg().write(|w| { | ||
| 962 | w.set_pfivl(vals::Pfivl::FRAME_INTERVAL_80); | ||
| 963 | w.set_dspd(self.phy_type.to_dspd()); | ||
| 964 | }); | ||
| 965 | |||
| 966 | // Unmask transfer complete EP interrupt | ||
| 967 | r.diepmsk().write(|w| { | ||
| 968 | w.set_xfrcm(true); | ||
| 969 | }); | ||
| 970 | |||
| 971 | // Unmask and clear core interrupts | ||
| 972 | Bus::<T>::restore_irqs(); | ||
| 973 | r.gintsts().write_value(regs::Gintsts(0xFFFF_FFFF)); | ||
| 974 | |||
| 975 | // Unmask global interrupt | ||
| 976 | r.gahbcfg().write(|w| { | ||
| 977 | w.set_gint(true); // unmask global interrupt | ||
| 978 | }); | ||
| 979 | |||
| 980 | // Connect | ||
| 981 | r.dctl().write(|w| w.set_sdis(false)); | ||
| 982 | } | ||
| 983 | |||
| 984 | self.enabled = true; | ||
| 985 | } | ||
| 986 | |||
| 987 | async fn disable(&mut self) { | ||
| 988 | trace!("disable"); | ||
| 989 | |||
| 990 | Bus::disable(self); | ||
| 991 | |||
| 992 | self.enabled = false; | ||
| 993 | } | ||
| 994 | |||
| 995 | async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { | ||
| 996 | Err(Unsupported) | ||
| 997 | } | ||
| 998 | } | ||
| 999 | |||
| 1000 | impl<'d, T: Instance> Drop for Bus<'d, T> { | ||
| 1001 | fn drop(&mut self) { | ||
| 1002 | Bus::disable(self); | ||
| 1003 | } | ||
| 1004 | } | ||
| 1005 | |||
| 1006 | trait Dir { | ||
| 1007 | fn dir() -> Direction; | ||
| 1008 | } | ||
| 1009 | |||
| 1010 | pub enum In {} | ||
| 1011 | impl Dir for In { | ||
| 1012 | fn dir() -> Direction { | ||
| 1013 | Direction::In | ||
| 1014 | } | ||
| 1015 | } | ||
| 1016 | |||
| 1017 | pub enum Out {} | ||
| 1018 | impl Dir for Out { | ||
| 1019 | fn dir() -> Direction { | ||
| 1020 | Direction::Out | ||
| 1021 | } | ||
| 1022 | } | ||
| 1023 | |||
| 1024 | pub struct Endpoint<'d, T: Instance, D> { | ||
| 1025 | _phantom: PhantomData<(&'d mut T, D)>, | ||
| 1026 | info: EndpointInfo, | ||
| 1027 | } | ||
| 1028 | |||
| 1029 | impl<'d, T: Instance> embassy_usb_driver::Endpoint for Endpoint<'d, T, In> { | ||
| 1030 | fn info(&self) -> &EndpointInfo { | ||
| 1031 | &self.info | ||
| 1032 | } | ||
| 1033 | |||
| 1034 | async fn wait_enabled(&mut self) {} | ||
| 1035 | } | ||
| 1036 | |||
| 1037 | impl<'d, T: Instance> embassy_usb_driver::Endpoint for Endpoint<'d, T, Out> { | ||
| 1038 | fn info(&self) -> &EndpointInfo { | ||
| 1039 | &self.info | ||
| 1040 | } | ||
| 1041 | |||
| 1042 | async fn wait_enabled(&mut self) {} | ||
| 1043 | } | ||
| 1044 | |||
| 1045 | impl<'d, T: Instance> embassy_usb_driver::EndpointOut for Endpoint<'d, T, Out> { | ||
| 1046 | async fn read(&mut self, buf: &mut [u8]) -> Result<usize, EndpointError> { | ||
| 1047 | trace!("read start len={}", buf.len()); | ||
| 1048 | |||
| 1049 | poll_fn(|cx| { | ||
| 1050 | let index = self.info.addr.index(); | ||
| 1051 | let state = T::state(); | ||
| 1052 | |||
| 1053 | state.ep_out_wakers[index].register(cx.waker()); | ||
| 1054 | |||
| 1055 | let len = state.ep_out_size[index].load(Ordering::Relaxed); | ||
| 1056 | if len != EP_OUT_BUFFER_EMPTY { | ||
| 1057 | trace!("read done len={}", len); | ||
| 1058 | |||
| 1059 | if len as usize > buf.len() { | ||
| 1060 | return Poll::Ready(Err(EndpointError::BufferOverflow)); | ||
| 1061 | } | ||
| 1062 | |||
| 1063 | // SAFETY: exclusive access ensured by `ep_out_size` atomic variable | ||
| 1064 | let data = unsafe { core::slice::from_raw_parts(*state.ep_out_buffers[index].get(), len as usize) }; | ||
| 1065 | buf[..len as usize].copy_from_slice(data); | ||
| 1066 | |||
| 1067 | // Release buffer | ||
| 1068 | state.ep_out_size[index].store(EP_OUT_BUFFER_EMPTY, Ordering::Release); | ||
| 1069 | |||
| 1070 | // SAFETY: DOEPCTL/DOEPTSIZ is shared with `Bus` so a critical section is needed for RMW | ||
| 1071 | critical_section::with(|_| unsafe { | ||
| 1072 | // Receive 1 packet | ||
| 1073 | T::regs().doeptsiz(index).modify(|w| { | ||
| 1074 | w.set_xfrsiz(self.info.max_packet_size as _); | ||
| 1075 | w.set_pktcnt(1); | ||
| 1076 | }); | ||
| 1077 | |||
| 1078 | // Clear NAK to indicate we are ready to receive more data | ||
| 1079 | T::regs().doepctl(index).modify(|w| { | ||
| 1080 | w.set_cnak(true); | ||
| 1081 | }); | ||
| 1082 | }); | ||
| 1083 | |||
| 1084 | Poll::Ready(Ok(len as usize)) | ||
| 1085 | } else { | ||
| 1086 | Poll::Pending | ||
| 1087 | } | ||
| 1088 | }) | ||
| 1089 | .await | ||
| 1090 | } | ||
| 1091 | } | ||
| 1092 | |||
| 1093 | impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> { | ||
| 1094 | async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { | ||
| 1095 | if buf.len() > self.info.max_packet_size as usize { | ||
| 1096 | return Err(EndpointError::BufferOverflow); | ||
| 1097 | } | ||
| 1098 | |||
| 1099 | let r = T::regs(); | ||
| 1100 | let index = self.info.addr.index(); | ||
| 1101 | let state = T::state(); | ||
| 1102 | |||
| 1103 | // Wait for previous transfer to complete | ||
| 1104 | poll_fn(|cx| { | ||
| 1105 | state.ep_in_wakers[index].register(cx.waker()); | ||
| 1106 | |||
| 1107 | // SAFETY: atomic read with no side effects | ||
| 1108 | if unsafe { r.diepctl(index).read().epena() } { | ||
| 1109 | Poll::Pending | ||
| 1110 | } else { | ||
| 1111 | Poll::Ready(()) | ||
| 1112 | } | ||
| 1113 | }) | ||
| 1114 | .await; | ||
| 1115 | |||
| 1116 | if buf.len() > 0 { | ||
| 1117 | poll_fn(|cx| { | ||
| 1118 | state.ep_in_wakers[index].register(cx.waker()); | ||
| 1119 | |||
| 1120 | let size_words = (buf.len() + 3) / 4; | ||
| 1121 | |||
| 1122 | // SAFETY: atomic read with no side effects | ||
| 1123 | let fifo_space = unsafe { r.dtxfsts(index).read().ineptfsav() as usize }; | ||
| 1124 | if size_words > fifo_space { | ||
| 1125 | // Not enough space in fifo, enable tx fifo empty interrupt | ||
| 1126 | // SAFETY: DIEPEMPMSK is shared with IRQ so critical section is needed for RMW | ||
| 1127 | critical_section::with(|_| unsafe { | ||
| 1128 | r.diepempmsk().modify(|w| { | ||
| 1129 | w.set_ineptxfem(w.ineptxfem() | (1 << index)); | ||
| 1130 | }); | ||
| 1131 | }); | ||
| 1132 | |||
| 1133 | trace!("tx fifo for ep={} full, waiting for txfe", index); | ||
| 1134 | |||
| 1135 | Poll::Pending | ||
| 1136 | } else { | ||
| 1137 | Poll::Ready(()) | ||
| 1138 | } | ||
| 1139 | }) | ||
| 1140 | .await | ||
| 1141 | } | ||
| 1142 | |||
| 1143 | // SAFETY: DIEPTSIZ is exclusive to this endpoint under `&mut self` | ||
| 1144 | unsafe { | ||
| 1145 | // Setup transfer size | ||
| 1146 | r.dieptsiz(index).write(|w| { | ||
| 1147 | w.set_mcnt(1); | ||
| 1148 | w.set_pktcnt(1); | ||
| 1149 | w.set_xfrsiz(buf.len() as _); | ||
| 1150 | }); | ||
| 1151 | } | ||
| 1152 | |||
| 1153 | // SAFETY: DIEPCTL is shared with `Bus` so a critical section is needed for RMW | ||
| 1154 | critical_section::with(|_| unsafe { | ||
| 1155 | // Enable endpoint | ||
| 1156 | r.diepctl(index).modify(|w| { | ||
| 1157 | w.set_cnak(true); | ||
| 1158 | w.set_epena(true); | ||
| 1159 | }); | ||
| 1160 | }); | ||
| 1161 | |||
| 1162 | // Write data to FIFO | ||
| 1163 | for chunk in buf.chunks(4) { | ||
| 1164 | let mut tmp = [0u8; 4]; | ||
| 1165 | tmp[0..chunk.len()].copy_from_slice(chunk); | ||
| 1166 | // SAFETY: FIFO is exclusive to this endpoint under `&mut self` | ||
| 1167 | unsafe { r.fifo(index).write_value(regs::Fifo(u32::from_ne_bytes(tmp))) }; | ||
| 1168 | } | ||
| 1169 | |||
| 1170 | trace!("WRITE OK"); | ||
| 1171 | |||
| 1172 | Ok(()) | ||
| 1173 | } | ||
| 1174 | } | ||
| 1175 | |||
| 1176 | pub struct ControlPipe<'d, T: Instance> { | ||
| 1177 | _phantom: PhantomData<&'d mut T>, | ||
| 1178 | max_packet_size: u16, | ||
| 1179 | ep_in: Endpoint<'d, T, In>, | ||
| 1180 | ep_out: Endpoint<'d, T, Out>, | ||
| 1181 | } | ||
| 1182 | |||
| 1183 | impl<'d, T: Instance> embassy_usb_driver::ControlPipe for ControlPipe<'d, T> { | ||
| 1184 | fn max_packet_size(&self) -> usize { | ||
| 1185 | usize::from(self.max_packet_size) | ||
| 1186 | } | ||
| 1187 | |||
| 1188 | async fn setup(&mut self) -> [u8; 8] { | ||
| 1189 | poll_fn(|cx| { | ||
| 1190 | let state = T::state(); | ||
| 1191 | |||
| 1192 | state.ep_out_wakers[0].register(cx.waker()); | ||
| 1193 | |||
| 1194 | if state.ep0_setup_ready.load(Ordering::Relaxed) { | ||
| 1195 | let data = unsafe { *state.ep0_setup_data.get() }; | ||
| 1196 | state.ep0_setup_ready.store(false, Ordering::Release); | ||
| 1197 | |||
| 1198 | // EP0 should not be controlled by `Bus` so this RMW does not need a critical section | ||
| 1199 | unsafe { | ||
| 1200 | // Receive 1 SETUP packet | ||
| 1201 | T::regs().doeptsiz(self.ep_out.info.addr.index()).modify(|w| { | ||
| 1202 | w.set_rxdpid_stupcnt(1); | ||
| 1203 | }); | ||
| 1204 | |||
| 1205 | // Clear NAK to indicate we are ready to receive more data | ||
| 1206 | T::regs().doepctl(self.ep_out.info.addr.index()).modify(|w| { | ||
| 1207 | w.set_cnak(true); | ||
| 1208 | }) | ||
| 1209 | }; | ||
| 1210 | |||
| 1211 | trace!("SETUP received: {:?}", data); | ||
| 1212 | Poll::Ready(data) | ||
| 1213 | } else { | ||
| 1214 | trace!("SETUP waiting"); | ||
| 1215 | Poll::Pending | ||
| 1216 | } | ||
| 1217 | }) | ||
| 1218 | .await | ||
| 1219 | } | ||
| 1220 | |||
| 1221 | async fn data_out(&mut self, buf: &mut [u8], _first: bool, _last: bool) -> Result<usize, EndpointError> { | ||
| 1222 | trace!("control: data_out"); | ||
| 1223 | let len = self.ep_out.read(buf).await?; | ||
| 1224 | trace!("control: data_out read: {:?}", &buf[..len]); | ||
| 1225 | Ok(len) | ||
| 1226 | } | ||
| 1227 | |||
| 1228 | async fn data_in(&mut self, data: &[u8], _first: bool, last: bool) -> Result<(), EndpointError> { | ||
| 1229 | trace!("control: data_in write: {:?}", data); | ||
| 1230 | self.ep_in.write(data).await?; | ||
| 1231 | |||
| 1232 | // wait for status response from host after sending the last packet | ||
| 1233 | if last { | ||
| 1234 | trace!("control: data_in waiting for status"); | ||
| 1235 | self.ep_out.read(&mut []).await?; | ||
| 1236 | trace!("control: complete"); | ||
| 1237 | } | ||
| 1238 | |||
| 1239 | Ok(()) | ||
| 1240 | } | ||
| 1241 | |||
| 1242 | async fn accept(&mut self) { | ||
| 1243 | trace!("control: accept"); | ||
| 1244 | |||
| 1245 | self.ep_in.write(&[]).await.ok(); | ||
| 1246 | |||
| 1247 | trace!("control: accept OK"); | ||
| 1248 | } | ||
| 1249 | |||
| 1250 | async fn reject(&mut self) { | ||
| 1251 | trace!("control: reject"); | ||
| 1252 | |||
| 1253 | // EP0 should not be controlled by `Bus` so this RMW does not need a critical section | ||
| 1254 | unsafe { | ||
| 1255 | let regs = T::regs(); | ||
| 1256 | regs.diepctl(self.ep_in.info.addr.index()).modify(|w| { | ||
| 1257 | w.set_stall(true); | ||
| 1258 | }); | ||
| 1259 | regs.doepctl(self.ep_out.info.addr.index()).modify(|w| { | ||
| 1260 | w.set_stall(true); | ||
| 1261 | }); | ||
| 1262 | } | ||
| 1263 | } | ||
| 1264 | |||
| 1265 | async fn accept_set_address(&mut self, addr: u8) { | ||
| 1266 | trace!("setting addr: {}", addr); | ||
| 1267 | critical_section::with(|_| unsafe { | ||
| 1268 | T::regs().dcfg().modify(|w| { | ||
| 1269 | w.set_dad(addr); | ||
| 1270 | }); | ||
| 1271 | }); | ||
| 1272 | |||
| 1273 | // synopsys driver requires accept to be sent after changing address | ||
| 1274 | self.accept().await | ||
| 1275 | } | ||
| 1276 | } | ||
| 1277 | |||
| 1278 | /// Translates HAL [EndpointType] into PAC [vals::Eptyp] | ||
| 1279 | fn to_eptyp(ep_type: EndpointType) -> vals::Eptyp { | ||
| 1280 | match ep_type { | ||
| 1281 | EndpointType::Control => vals::Eptyp::CONTROL, | ||
| 1282 | EndpointType::Isochronous => vals::Eptyp::ISOCHRONOUS, | ||
| 1283 | EndpointType::Bulk => vals::Eptyp::BULK, | ||
| 1284 | EndpointType::Interrupt => vals::Eptyp::INTERRUPT, | ||
| 1285 | } | ||
| 1286 | } | ||
| 1287 | |||
| 1288 | /// Calculates total allocated FIFO size in words | ||
| 1289 | fn ep_fifo_size(eps: &[Option<EndpointData>]) -> u16 { | ||
| 1290 | eps.iter().map(|ep| ep.map(|ep| ep.fifo_size_words).unwrap_or(0)).sum() | ||
| 1291 | } | ||
| 1292 | |||
| 1293 | /// Generates IRQ mask for enabled endpoints | ||
| 1294 | fn ep_irq_mask(eps: &[Option<EndpointData>]) -> u16 { | ||
| 1295 | eps.iter().enumerate().fold( | ||
| 1296 | 0, | ||
| 1297 | |mask, (index, ep)| { | ||
| 1298 | if ep.is_some() { | ||
| 1299 | mask | (1 << index) | ||
| 1300 | } else { | ||
| 1301 | mask | ||
| 1302 | } | ||
| 1303 | }, | ||
| 1304 | ) | ||
| 1305 | } | ||
| 1306 | |||
| 1307 | /// Calculates MPSIZ value for EP0, which uses special values. | ||
| 1308 | fn ep0_mpsiz(max_packet_size: u16) -> u16 { | ||
| 1309 | match max_packet_size { | ||
| 1310 | 8 => 0b11, | ||
| 1311 | 16 => 0b10, | ||
| 1312 | 32 => 0b01, | ||
| 1313 | 64 => 0b00, | ||
| 1314 | other => panic!("Unsupported EP0 size: {}", other), | ||
| 1315 | } | ||
| 1316 | } | ||
| 1317 | |||
| 1318 | fn calculate_trdt(speed: vals::Dspd, ahb_freq: Hertz) -> u8 { | ||
| 1319 | match speed { | ||
| 1320 | vals::Dspd::HIGH_SPEED => { | ||
| 1321 | // From RM0431 (F72xx), RM0090 (F429), RM0390 (F446) | ||
| 1322 | if ahb_freq.0 >= 30_000_000 { | ||
| 1323 | 0x9 | ||
| 1324 | } else { | ||
| 1325 | panic!("AHB frequency is too low") | ||
| 1326 | } | ||
| 1327 | } | ||
| 1328 | vals::Dspd::FULL_SPEED_EXTERNAL | vals::Dspd::FULL_SPEED_INTERNAL => { | ||
| 1329 | // From RM0431 (F72xx), RM0090 (F429) | ||
| 1330 | match ahb_freq.0 { | ||
| 1331 | 0..=14_199_999 => panic!("AHB frequency is too low"), | ||
| 1332 | 14_200_000..=14_999_999 => 0xF, | ||
| 1333 | 15_000_000..=15_999_999 => 0xE, | ||
| 1334 | 16_000_000..=17_199_999 => 0xD, | ||
| 1335 | 17_200_000..=18_499_999 => 0xC, | ||
| 1336 | 18_500_000..=19_999_999 => 0xB, | ||
| 1337 | 20_000_000..=21_799_999 => 0xA, | ||
| 1338 | 21_800_000..=23_999_999 => 0x9, | ||
| 1339 | 24_000_000..=27_499_999 => 0x8, | ||
| 1340 | 27_500_000..=31_999_999 => 0x7, // 27.7..32 in code from CubeIDE | ||
| 1341 | 32_000_000..=u32::MAX => 0x6, | ||
| 1342 | } | ||
| 1343 | } | ||
| 1344 | _ => unimplemented!(), | ||
| 1345 | } | ||
| 1346 | } | ||
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 { | |||
| 164 | 164 | ||
| 165 | async fn poll(&mut self) -> Event; | 165 | async fn poll(&mut self) -> Event; |
| 166 | 166 | ||
| 167 | /// Sets the device USB address to `addr`. | ||
| 168 | fn set_address(&mut self, addr: u8); | ||
| 169 | |||
| 170 | /// Enables or disables an endpoint. | 167 | /// Enables or disables an endpoint. |
| 171 | fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool); | 168 | fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool); |
| 172 | 169 | ||
| @@ -306,6 +303,12 @@ pub trait ControlPipe { | |||
| 306 | /// | 303 | /// |
| 307 | /// Sets a STALL condition on the pipe to indicate an error. | 304 | /// Sets a STALL condition on the pipe to indicate an error. |
| 308 | async fn reject(&mut self); | 305 | async fn reject(&mut self); |
| 306 | |||
| 307 | /// Accept SET_ADDRESS control and change bus address. | ||
| 308 | /// | ||
| 309 | /// For most drivers this function should firstly call `accept()` and then change the bus address. | ||
| 310 | /// However, there are peripherals (Synopsys USB OTG) that have reverse order. | ||
| 311 | async fn accept_set_address(&mut self, addr: u8); | ||
| 309 | } | 312 | } |
| 310 | 313 | ||
| 311 | pub trait EndpointIn: Endpoint { | 314 | 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>> { | |||
| 122 | 122 | ||
| 123 | /// Our device address, or 0 if none. | 123 | /// Our device address, or 0 if none. |
| 124 | address: u8, | 124 | address: u8, |
| 125 | /// When receiving a set addr control request, we have to apply it AFTER we've | 125 | /// SET_ADDRESS requests have special handling depending on the driver. |
| 126 | /// finished handling the control request, as the status stage still has to be | 126 | /// This flag indicates that requests must be handled by `ControlPipe::accept_set_address()` |
| 127 | /// handled with addr 0. | 127 | /// instead of regular `accept()`. |
| 128 | /// If true, do a set_addr after finishing the current control req. | ||
| 129 | set_address_pending: bool, | 128 | set_address_pending: bool, |
| 130 | 129 | ||
| 131 | interfaces: Vec<Interface<'d>, MAX_INTERFACE_COUNT>, | 130 | interfaces: Vec<Interface<'d>, MAX_INTERFACE_COUNT>, |
| @@ -254,11 +253,6 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { | |||
| 254 | Direction::In => self.handle_control_in(req).await, | 253 | Direction::In => self.handle_control_in(req).await, |
| 255 | Direction::Out => self.handle_control_out(req).await, | 254 | Direction::Out => self.handle_control_out(req).await, |
| 256 | } | 255 | } |
| 257 | |||
| 258 | if self.inner.set_address_pending { | ||
| 259 | self.inner.bus.set_address(self.inner.address); | ||
| 260 | self.inner.set_address_pending = false; | ||
| 261 | } | ||
| 262 | } | 256 | } |
| 263 | 257 | ||
| 264 | async fn handle_control_in(&mut self, req: Request) { | 258 | async fn handle_control_in(&mut self, req: Request) { |
| @@ -328,7 +322,14 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { | |||
| 328 | trace!(" control out data: {:02x?}", data); | 322 | trace!(" control out data: {:02x?}", data); |
| 329 | 323 | ||
| 330 | match self.inner.handle_control_out(req, data) { | 324 | match self.inner.handle_control_out(req, data) { |
| 331 | OutResponse::Accepted => self.control.accept().await, | 325 | OutResponse::Accepted => { |
| 326 | if self.inner.set_address_pending { | ||
| 327 | self.control.accept_set_address(self.inner.address).await; | ||
| 328 | self.inner.set_address_pending = false; | ||
| 329 | } else { | ||
| 330 | self.control.accept().await | ||
| 331 | } | ||
| 332 | } | ||
| 332 | OutResponse::Rejected => self.control.reject().await, | 333 | OutResponse::Rejected => self.control.reject().await, |
| 333 | } | 334 | } |
| 334 | } | 335 | } |
| @@ -655,7 +656,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { | |||
| 655 | buf[1] = descriptor_type::STRING; | 656 | buf[1] = descriptor_type::STRING; |
| 656 | let mut pos = 2; | 657 | let mut pos = 2; |
| 657 | for c in s.encode_utf16() { | 658 | for c in s.encode_utf16() { |
| 658 | if pos >= buf.len() { | 659 | if pos + 2 >= buf.len() { |
| 659 | panic!("control buffer too small"); | 660 | panic!("control buffer too small"); |
| 660 | } | 661 | } |
| 661 | 662 | ||
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 | |||
| 10 | embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } | 10 | embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } |
| 11 | embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } | 11 | embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } |
| 12 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti"] } | 12 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti"] } |
| 13 | embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } | ||
| 13 | 14 | ||
| 14 | defmt = "0.3" | 15 | defmt = "0.3" |
| 15 | defmt-rtt = "0.4" | 16 | defmt-rtt = "0.4" |
| @@ -26,5 +27,5 @@ embedded-storage = "0.3.0" | |||
| 26 | micromath = "2.0.0" | 27 | micromath = "2.0.0" |
| 27 | static_cell = "1.0" | 28 | static_cell = "1.0" |
| 28 | 29 | ||
| 29 | usb-device = "0.2" | 30 | [profile.release] |
| 30 | usbd-serial = "0.1.1" | 31 | 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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use defmt::{panic, *}; | ||
| 6 | use embassy_executor::Spawner; | ||
| 7 | use embassy_stm32::time::mhz; | ||
| 8 | use embassy_stm32::usb_otg::{Driver, Instance}; | ||
| 9 | use embassy_stm32::{interrupt, Config}; | ||
| 10 | use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; | ||
| 11 | use embassy_usb::driver::EndpointError; | ||
| 12 | use embassy_usb::Builder; | ||
| 13 | use futures::future::join; | ||
| 14 | use {defmt_rtt as _, panic_probe as _}; | ||
| 15 | |||
| 16 | #[embassy_executor::main] | ||
| 17 | async fn main(_spawner: Spawner) { | ||
| 18 | info!("Hello World!"); | ||
| 19 | |||
| 20 | let mut config = Config::default(); | ||
| 21 | config.rcc.pll48 = true; | ||
| 22 | config.rcc.sys_ck = Some(mhz(48)); | ||
| 23 | |||
| 24 | let p = embassy_stm32::init(config); | ||
| 25 | |||
| 26 | // Create the driver, from the HAL. | ||
| 27 | let irq = interrupt::take!(OTG_FS); | ||
| 28 | let mut ep_out_buffer = [0u8; 256]; | ||
| 29 | let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer); | ||
| 30 | |||
| 31 | // Create embassy-usb Config | ||
| 32 | let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); | ||
| 33 | config.manufacturer = Some("Embassy"); | ||
| 34 | config.product = Some("USB-serial example"); | ||
| 35 | config.serial_number = Some("12345678"); | ||
| 36 | |||
| 37 | // Required for windows compatiblity. | ||
| 38 | // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help | ||
| 39 | config.device_class = 0xEF; | ||
| 40 | config.device_sub_class = 0x02; | ||
| 41 | config.device_protocol = 0x01; | ||
| 42 | config.composite_with_iads = true; | ||
| 43 | |||
| 44 | // Create embassy-usb DeviceBuilder using the driver and config. | ||
| 45 | // It needs some buffers for building the descriptors. | ||
| 46 | let mut device_descriptor = [0; 256]; | ||
| 47 | let mut config_descriptor = [0; 256]; | ||
| 48 | let mut bos_descriptor = [0; 256]; | ||
| 49 | let mut control_buf = [0; 64]; | ||
| 50 | |||
| 51 | let mut state = State::new(); | ||
| 52 | |||
| 53 | let mut builder = Builder::new( | ||
| 54 | driver, | ||
| 55 | config, | ||
| 56 | &mut device_descriptor, | ||
| 57 | &mut config_descriptor, | ||
| 58 | &mut bos_descriptor, | ||
| 59 | &mut control_buf, | ||
| 60 | None, | ||
| 61 | ); | ||
| 62 | |||
| 63 | // Create classes on the builder. | ||
| 64 | let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); | ||
| 65 | |||
| 66 | // Build the builder. | ||
| 67 | let mut usb = builder.build(); | ||
| 68 | |||
| 69 | // Run the USB device. | ||
| 70 | let usb_fut = usb.run(); | ||
| 71 | |||
| 72 | // Do stuff with the class! | ||
| 73 | let echo_fut = async { | ||
| 74 | loop { | ||
| 75 | class.wait_connection().await; | ||
| 76 | info!("Connected"); | ||
| 77 | let _ = echo(&mut class).await; | ||
| 78 | info!("Disconnected"); | ||
| 79 | } | ||
| 80 | }; | ||
| 81 | |||
| 82 | // Run everything concurrently. | ||
| 83 | // If we had made everything `'static` above instead, we could do this using separate tasks instead. | ||
| 84 | join(usb_fut, echo_fut).await; | ||
| 85 | } | ||
| 86 | |||
| 87 | struct Disconnected {} | ||
| 88 | |||
| 89 | impl From<EndpointError> for Disconnected { | ||
| 90 | fn from(val: EndpointError) -> Self { | ||
| 91 | match val { | ||
| 92 | EndpointError::BufferOverflow => panic!("Buffer overflow"), | ||
| 93 | EndpointError::Disabled => Disconnected {}, | ||
| 94 | } | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { | ||
| 99 | let mut buf = [0; 64]; | ||
| 100 | loop { | ||
| 101 | let n = class.read_packet(&mut buf).await?; | ||
| 102 | let data = &buf[..n]; | ||
| 103 | info!("data: {:x}", data); | ||
| 104 | class.write_packet(data).await?; | ||
| 105 | } | ||
| 106 | } | ||
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 | |||
| 11 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f767zi", "unstable-pac", "time-driver-any", "exti"] } | 11 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f767zi", "unstable-pac", "time-driver-any", "exti"] } |
| 12 | embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet"] } | 12 | embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet"] } |
| 13 | embedded-io = { version = "0.4.0", features = ["async"] } | 13 | embedded-io = { version = "0.4.0", features = ["async"] } |
| 14 | embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } | ||
| 14 | 15 | ||
| 15 | defmt = "0.3" | 16 | defmt = "0.3" |
| 16 | defmt-rtt = "0.4" | 17 | 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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use defmt::{panic, *}; | ||
| 6 | use embassy_executor::Spawner; | ||
| 7 | use embassy_stm32::time::mhz; | ||
| 8 | use embassy_stm32::usb_otg::{Driver, Instance}; | ||
| 9 | use embassy_stm32::{interrupt, Config}; | ||
| 10 | use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; | ||
| 11 | use embassy_usb::driver::EndpointError; | ||
| 12 | use embassy_usb::Builder; | ||
| 13 | use futures::future::join; | ||
| 14 | use {defmt_rtt as _, panic_probe as _}; | ||
| 15 | |||
| 16 | #[embassy_executor::main] | ||
| 17 | async fn main(_spawner: Spawner) { | ||
| 18 | info!("Hello World!"); | ||
| 19 | |||
| 20 | let mut config = Config::default(); | ||
| 21 | config.rcc.hse = Some(mhz(8)); | ||
| 22 | config.rcc.pll48 = true; | ||
| 23 | config.rcc.sys_ck = Some(mhz(200)); | ||
| 24 | |||
| 25 | let p = embassy_stm32::init(config); | ||
| 26 | |||
| 27 | // Create the driver, from the HAL. | ||
| 28 | let irq = interrupt::take!(OTG_FS); | ||
| 29 | let mut ep_out_buffer = [0u8; 256]; | ||
| 30 | let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer); | ||
| 31 | |||
| 32 | // Create embassy-usb Config | ||
| 33 | let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); | ||
| 34 | config.manufacturer = Some("Embassy"); | ||
| 35 | config.product = Some("USB-serial example"); | ||
| 36 | config.serial_number = Some("12345678"); | ||
| 37 | |||
| 38 | // Required for windows compatiblity. | ||
| 39 | // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help | ||
| 40 | config.device_class = 0xEF; | ||
| 41 | config.device_sub_class = 0x02; | ||
| 42 | config.device_protocol = 0x01; | ||
| 43 | config.composite_with_iads = true; | ||
| 44 | |||
| 45 | // Create embassy-usb DeviceBuilder using the driver and config. | ||
| 46 | // It needs some buffers for building the descriptors. | ||
| 47 | let mut device_descriptor = [0; 256]; | ||
| 48 | let mut config_descriptor = [0; 256]; | ||
| 49 | let mut bos_descriptor = [0; 256]; | ||
| 50 | let mut control_buf = [0; 64]; | ||
| 51 | |||
| 52 | let mut state = State::new(); | ||
| 53 | |||
| 54 | let mut builder = Builder::new( | ||
| 55 | driver, | ||
| 56 | config, | ||
| 57 | &mut device_descriptor, | ||
| 58 | &mut config_descriptor, | ||
| 59 | &mut bos_descriptor, | ||
| 60 | &mut control_buf, | ||
| 61 | None, | ||
| 62 | ); | ||
| 63 | |||
| 64 | // Create classes on the builder. | ||
| 65 | let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); | ||
| 66 | |||
| 67 | // Build the builder. | ||
| 68 | let mut usb = builder.build(); | ||
| 69 | |||
| 70 | // Run the USB device. | ||
| 71 | let usb_fut = usb.run(); | ||
| 72 | |||
| 73 | // Do stuff with the class! | ||
| 74 | let echo_fut = async { | ||
| 75 | loop { | ||
| 76 | class.wait_connection().await; | ||
| 77 | info!("Connected"); | ||
| 78 | let _ = echo(&mut class).await; | ||
| 79 | info!("Disconnected"); | ||
| 80 | } | ||
| 81 | }; | ||
| 82 | |||
| 83 | // Run everything concurrently. | ||
| 84 | // If we had made everything `'static` above instead, we could do this using separate tasks instead. | ||
| 85 | join(usb_fut, echo_fut).await; | ||
| 86 | } | ||
| 87 | |||
| 88 | struct Disconnected {} | ||
| 89 | |||
| 90 | impl From<EndpointError> for Disconnected { | ||
| 91 | fn from(val: EndpointError) -> Self { | ||
| 92 | match val { | ||
| 93 | EndpointError::BufferOverflow => panic!("Buffer overflow"), | ||
| 94 | EndpointError::Disabled => Disconnected {}, | ||
| 95 | } | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { | ||
| 100 | let mut buf = [0; 64]; | ||
| 101 | loop { | ||
| 102 | let n = class.read_packet(&mut buf).await?; | ||
| 103 | let data = &buf[..n]; | ||
| 104 | info!("data: {:x}", data); | ||
| 105 | class.write_packet(data).await?; | ||
| 106 | } | ||
| 107 | } | ||
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 | |||
| 11 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32h743bi", "time-driver-any", "exti", "unstable-pac", "unstable-traits"] } | 11 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32h743bi", "time-driver-any", "exti", "unstable-pac", "unstable-traits"] } |
| 12 | embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "unstable-traits"] } | 12 | embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "unstable-traits"] } |
| 13 | embedded-io = { version = "0.4.0", features = ["async"] } | 13 | embedded-io = { version = "0.4.0", features = ["async"] } |
| 14 | embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } | ||
| 14 | 15 | ||
| 15 | defmt = "0.3" | 16 | defmt = "0.3" |
| 16 | defmt-rtt = "0.4" | 17 | 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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use defmt::{panic, *}; | ||
| 6 | use embassy_executor::Spawner; | ||
| 7 | use embassy_stm32::time::mhz; | ||
| 8 | use embassy_stm32::usb_otg::{Driver, Instance}; | ||
| 9 | use embassy_stm32::{interrupt, Config}; | ||
| 10 | use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; | ||
| 11 | use embassy_usb::driver::EndpointError; | ||
| 12 | use embassy_usb::Builder; | ||
| 13 | use futures::future::join; | ||
| 14 | use {defmt_rtt as _, panic_probe as _}; | ||
| 15 | |||
| 16 | #[embassy_executor::main] | ||
| 17 | async fn main(_spawner: Spawner) { | ||
| 18 | info!("Hello World!"); | ||
| 19 | |||
| 20 | let mut config = Config::default(); | ||
| 21 | config.rcc.sys_ck = Some(mhz(400)); | ||
| 22 | config.rcc.hclk = Some(mhz(200)); | ||
| 23 | config.rcc.pll1.q_ck = Some(mhz(100)); | ||
| 24 | let p = embassy_stm32::init(config); | ||
| 25 | |||
| 26 | // Create the driver, from the HAL. | ||
| 27 | let irq = interrupt::take!(OTG_FS); | ||
| 28 | let mut ep_out_buffer = [0u8; 256]; | ||
| 29 | let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer); | ||
| 30 | |||
| 31 | // Create embassy-usb Config | ||
| 32 | let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); | ||
| 33 | config.manufacturer = Some("Embassy"); | ||
| 34 | config.product = Some("USB-serial example"); | ||
| 35 | config.serial_number = Some("12345678"); | ||
| 36 | |||
| 37 | // Required for windows compatiblity. | ||
| 38 | // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help | ||
| 39 | config.device_class = 0xEF; | ||
| 40 | config.device_sub_class = 0x02; | ||
| 41 | config.device_protocol = 0x01; | ||
| 42 | config.composite_with_iads = true; | ||
| 43 | |||
| 44 | // Create embassy-usb DeviceBuilder using the driver and config. | ||
| 45 | // It needs some buffers for building the descriptors. | ||
| 46 | let mut device_descriptor = [0; 256]; | ||
| 47 | let mut config_descriptor = [0; 256]; | ||
| 48 | let mut bos_descriptor = [0; 256]; | ||
| 49 | let mut control_buf = [0; 64]; | ||
| 50 | |||
| 51 | let mut state = State::new(); | ||
| 52 | |||
| 53 | let mut builder = Builder::new( | ||
| 54 | driver, | ||
| 55 | config, | ||
| 56 | &mut device_descriptor, | ||
| 57 | &mut config_descriptor, | ||
| 58 | &mut bos_descriptor, | ||
| 59 | &mut control_buf, | ||
| 60 | None, | ||
| 61 | ); | ||
| 62 | |||
| 63 | // Create classes on the builder. | ||
| 64 | let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); | ||
| 65 | |||
| 66 | // Build the builder. | ||
| 67 | let mut usb = builder.build(); | ||
| 68 | |||
| 69 | // Run the USB device. | ||
| 70 | let usb_fut = usb.run(); | ||
| 71 | |||
| 72 | // Do stuff with the class! | ||
| 73 | let echo_fut = async { | ||
| 74 | loop { | ||
| 75 | class.wait_connection().await; | ||
| 76 | info!("Connected"); | ||
| 77 | let _ = echo(&mut class).await; | ||
| 78 | info!("Disconnected"); | ||
| 79 | } | ||
| 80 | }; | ||
| 81 | |||
| 82 | // Run everything concurrently. | ||
| 83 | // If we had made everything `'static` above instead, we could do this using separate tasks instead. | ||
| 84 | join(usb_fut, echo_fut).await; | ||
| 85 | } | ||
| 86 | |||
| 87 | struct Disconnected {} | ||
| 88 | |||
| 89 | impl From<EndpointError> for Disconnected { | ||
| 90 | fn from(val: EndpointError) -> Self { | ||
| 91 | match val { | ||
| 92 | EndpointError::BufferOverflow => panic!("Buffer overflow"), | ||
| 93 | EndpointError::Disabled => Disconnected {}, | ||
| 94 | } | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { | ||
| 99 | let mut buf = [0; 64]; | ||
| 100 | loop { | ||
| 101 | let n = class.read_packet(&mut buf).await?; | ||
| 102 | let data = &buf[..n]; | ||
| 103 | info!("data: {:x}", data); | ||
| 104 | class.write_packet(data).await?; | ||
| 105 | } | ||
| 106 | } | ||
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 | |||
| 12 | embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } | 12 | embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } |
| 13 | embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } | 13 | embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } |
| 14 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l4s5vi", "time-driver-any", "exti", "unstable-traits"] } | 14 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l4s5vi", "time-driver-any", "exti", "unstable-traits"] } |
| 15 | embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } | ||
| 15 | 16 | ||
| 16 | defmt = "0.3" | 17 | defmt = "0.3" |
| 17 | defmt-rtt = "0.4" | 18 | defmt-rtt = "0.4" |
| @@ -26,6 +27,3 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa | |||
| 26 | heapless = { version = "0.7.5", default-features = false } | 27 | heapless = { version = "0.7.5", default-features = false } |
| 27 | 28 | ||
| 28 | micromath = "2.0.0" | 29 | micromath = "2.0.0" |
| 29 | usb-device = "0.2" | ||
| 30 | usbd-serial = "0.1.1" | ||
| 31 | |||
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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use defmt::{panic, *}; | ||
| 6 | use defmt_rtt as _; // global logger | ||
| 7 | use embassy_executor::Spawner; | ||
| 8 | use embassy_stm32::rcc::*; | ||
| 9 | use embassy_stm32::usb_otg::{Driver, Instance}; | ||
| 10 | use embassy_stm32::{interrupt, Config}; | ||
| 11 | use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; | ||
| 12 | use embassy_usb::driver::EndpointError; | ||
| 13 | use embassy_usb::Builder; | ||
| 14 | use futures::future::join; | ||
| 15 | use panic_probe as _; | ||
| 16 | |||
| 17 | #[embassy_executor::main] | ||
| 18 | async fn main(_spawner: Spawner) { | ||
| 19 | info!("Hello World!"); | ||
| 20 | |||
| 21 | let mut config = Config::default(); | ||
| 22 | config.rcc.mux = ClockSrc::PLL(PLLSource::HSI16, PLLClkDiv::Div2, PLLSrcDiv::Div1, PLLMul::Mul10, None); | ||
| 23 | config.rcc.hsi48 = true; | ||
| 24 | |||
| 25 | let p = embassy_stm32::init(config); | ||
| 26 | |||
| 27 | // Create the driver, from the HAL. | ||
| 28 | let irq = interrupt::take!(OTG_FS); | ||
| 29 | let mut ep_out_buffer = [0u8; 256]; | ||
| 30 | let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer); | ||
| 31 | |||
| 32 | // Create embassy-usb Config | ||
| 33 | let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); | ||
| 34 | config.max_packet_size_0 = 64; | ||
| 35 | config.manufacturer = Some("Embassy"); | ||
| 36 | config.product = Some("USB-serial example"); | ||
| 37 | config.serial_number = Some("12345678"); | ||
| 38 | |||
| 39 | // Required for windows compatiblity. | ||
| 40 | // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help | ||
| 41 | config.device_class = 0xEF; | ||
| 42 | config.device_sub_class = 0x02; | ||
| 43 | config.device_protocol = 0x01; | ||
| 44 | config.composite_with_iads = true; | ||
| 45 | |||
| 46 | // Create embassy-usb DeviceBuilder using the driver and config. | ||
| 47 | // It needs some buffers for building the descriptors. | ||
| 48 | let mut device_descriptor = [0; 256]; | ||
| 49 | let mut config_descriptor = [0; 256]; | ||
| 50 | let mut bos_descriptor = [0; 256]; | ||
| 51 | let mut control_buf = [0; 64]; | ||
| 52 | |||
| 53 | let mut state = State::new(); | ||
| 54 | |||
| 55 | let mut builder = Builder::new( | ||
| 56 | driver, | ||
| 57 | config, | ||
| 58 | &mut device_descriptor, | ||
| 59 | &mut config_descriptor, | ||
| 60 | &mut bos_descriptor, | ||
| 61 | &mut control_buf, | ||
| 62 | None, | ||
| 63 | ); | ||
| 64 | |||
| 65 | // Create classes on the builder. | ||
| 66 | let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); | ||
| 67 | |||
| 68 | // Build the builder. | ||
| 69 | let mut usb = builder.build(); | ||
| 70 | |||
| 71 | // Run the USB device. | ||
| 72 | let usb_fut = usb.run(); | ||
| 73 | |||
| 74 | // Do stuff with the class! | ||
| 75 | let echo_fut = async { | ||
| 76 | loop { | ||
| 77 | class.wait_connection().await; | ||
| 78 | info!("Connected"); | ||
| 79 | let _ = echo(&mut class).await; | ||
| 80 | info!("Disconnected"); | ||
| 81 | } | ||
| 82 | }; | ||
| 83 | |||
| 84 | // Run everything concurrently. | ||
| 85 | // If we had made everything `'static` above instead, we could do this using separate tasks instead. | ||
| 86 | join(usb_fut, echo_fut).await; | ||
| 87 | } | ||
| 88 | |||
| 89 | struct Disconnected {} | ||
| 90 | |||
| 91 | impl From<EndpointError> for Disconnected { | ||
| 92 | fn from(val: EndpointError) -> Self { | ||
| 93 | match val { | ||
| 94 | EndpointError::BufferOverflow => panic!("Buffer overflow"), | ||
| 95 | EndpointError::Disabled => Disconnected {}, | ||
| 96 | } | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { | ||
| 101 | let mut buf = [0; 64]; | ||
| 102 | loop { | ||
| 103 | let n = class.read_packet(&mut buf).await?; | ||
| 104 | let data = &buf[..n]; | ||
| 105 | info!("data: {:x}", data); | ||
| 106 | class.write_packet(data).await?; | ||
| 107 | } | ||
| 108 | } | ||
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 | |||
| 9 | embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } | 9 | embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } |
| 10 | embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } | 10 | embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } |
| 11 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32u585ai", "time-driver-any", "memory-x" ] } | 11 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32u585ai", "time-driver-any", "memory-x" ] } |
| 12 | embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } | ||
| 12 | 13 | ||
| 13 | defmt = "0.3" | 14 | defmt = "0.3" |
| 14 | defmt-rtt = "0.4" | 15 | 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 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use defmt::{panic, *}; | ||
| 6 | use defmt_rtt as _; // global logger | ||
| 7 | use embassy_executor::Spawner; | ||
| 8 | use embassy_stm32::rcc::*; | ||
| 9 | use embassy_stm32::usb_otg::{Driver, Instance}; | ||
| 10 | use embassy_stm32::{interrupt, Config}; | ||
| 11 | use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; | ||
| 12 | use embassy_usb::driver::EndpointError; | ||
| 13 | use embassy_usb::Builder; | ||
| 14 | use futures::future::join; | ||
| 15 | use panic_probe as _; | ||
| 16 | |||
| 17 | #[embassy_executor::main] | ||
| 18 | async fn main(_spawner: Spawner) { | ||
| 19 | info!("Hello World!"); | ||
| 20 | |||
| 21 | let mut config = Config::default(); | ||
| 22 | config.rcc.mux = ClockSrc::PLL1R(PllSrc::HSI16, PllM::Div2, PllN::Mul10, PllClkDiv::NotDivided); | ||
| 23 | //config.rcc.mux = ClockSrc::MSI(MSIRange::Range48mhz); | ||
| 24 | config.rcc.hsi48 = true; | ||
| 25 | |||
| 26 | let p = embassy_stm32::init(config); | ||
| 27 | |||
| 28 | // Create the driver, from the HAL. | ||
| 29 | let irq = interrupt::take!(OTG_FS); | ||
| 30 | let mut ep_out_buffer = [0u8; 256]; | ||
| 31 | let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer); | ||
| 32 | |||
| 33 | // Create embassy-usb Config | ||
| 34 | let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); | ||
| 35 | config.manufacturer = Some("Embassy"); | ||
| 36 | config.product = Some("USB-serial example"); | ||
| 37 | config.serial_number = Some("12345678"); | ||
| 38 | |||
| 39 | // Required for windows compatiblity. | ||
| 40 | // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help | ||
| 41 | config.device_class = 0xEF; | ||
| 42 | config.device_sub_class = 0x02; | ||
| 43 | config.device_protocol = 0x01; | ||
| 44 | config.composite_with_iads = true; | ||
| 45 | |||
| 46 | // Create embassy-usb DeviceBuilder using the driver and config. | ||
| 47 | // It needs some buffers for building the descriptors. | ||
| 48 | let mut device_descriptor = [0; 256]; | ||
| 49 | let mut config_descriptor = [0; 256]; | ||
| 50 | let mut bos_descriptor = [0; 256]; | ||
| 51 | let mut control_buf = [0; 64]; | ||
| 52 | |||
| 53 | let mut state = State::new(); | ||
| 54 | |||
| 55 | let mut builder = Builder::new( | ||
| 56 | driver, | ||
| 57 | config, | ||
| 58 | &mut device_descriptor, | ||
| 59 | &mut config_descriptor, | ||
| 60 | &mut bos_descriptor, | ||
| 61 | &mut control_buf, | ||
| 62 | None, | ||
| 63 | ); | ||
| 64 | |||
| 65 | // Create classes on the builder. | ||
| 66 | let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); | ||
| 67 | |||
| 68 | // Build the builder. | ||
| 69 | let mut usb = builder.build(); | ||
| 70 | |||
| 71 | // Run the USB device. | ||
| 72 | let usb_fut = usb.run(); | ||
| 73 | |||
| 74 | // Do stuff with the class! | ||
| 75 | let echo_fut = async { | ||
| 76 | loop { | ||
| 77 | class.wait_connection().await; | ||
| 78 | info!("Connected"); | ||
| 79 | let _ = echo(&mut class).await; | ||
| 80 | info!("Disconnected"); | ||
| 81 | } | ||
| 82 | }; | ||
| 83 | |||
| 84 | // Run everything concurrently. | ||
| 85 | // If we had made everything `'static` above instead, we could do this using separate tasks instead. | ||
| 86 | join(usb_fut, echo_fut).await; | ||
| 87 | } | ||
| 88 | |||
| 89 | struct Disconnected {} | ||
| 90 | |||
| 91 | impl From<EndpointError> for Disconnected { | ||
| 92 | fn from(val: EndpointError) -> Self { | ||
| 93 | match val { | ||
| 94 | EndpointError::BufferOverflow => panic!("Buffer overflow"), | ||
| 95 | EndpointError::Disabled => Disconnected {}, | ||
| 96 | } | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { | ||
| 101 | let mut buf = [0; 64]; | ||
| 102 | loop { | ||
| 103 | let n = class.read_packet(&mut buf).await?; | ||
| 104 | let data = &buf[..n]; | ||
| 105 | info!("data: {:x}", data); | ||
| 106 | class.write_packet(data).await?; | ||
| 107 | } | ||
| 108 | } | ||
diff --git a/stm32-data b/stm32-data | |||
| Subproject 14a448c318192fe9da1c95a4de1beb4ec4892f1 | Subproject 844793fc3da2ba3f12ab6a69b78cd8e6fb5497b | ||
