From 501d3942e8b6b04fd1cb4c14728ba4c2e118d8b5 Mon Sep 17 00:00:00 2001 From: Dave Marples <dave@marples.net> Date: Thu, 5 Dec 2024 16:35:56 +0000 Subject: [PATCH 1/2] Add support for stm32u595/5a5 OTG_HS in client mode --- embassy-stm32/src/usb/mod.rs | 20 ++++ embassy-stm32/src/usb/otg.rs | 32 ++++-- examples/stm32u5/src/bin/usb_hs_serial.rs | 129 ++++++++++++++++++++++ 3 files changed, 174 insertions(+), 7 deletions(-) create mode 100644 examples/stm32u5/src/bin/usb_hs_serial.rs diff --git a/embassy-stm32/src/usb/mod.rs b/embassy-stm32/src/usb/mod.rs index a473285bf8..9737b9b3f8 100644 --- a/embassy-stm32/src/usb/mod.rs +++ b/embassy-stm32/src/usb/mod.rs @@ -26,6 +26,7 @@ fn common_init<T: Instance>() { // Clock might not be exact 48Mhz due to rounding errors in PLL calculation, or if the user // has tight clock restrictions due to something else (like audio). #[cfg(not(stm32h7rs))] + #[cfg(not(all(stm32u5, peri_usb_otg_hs)))] if freq.0.abs_diff(48_000_000) > 120_000 { panic!( "USB clock should be 48Mhz but is {} Hz. Please double-check your RCC settings.", @@ -33,6 +34,15 @@ fn common_init<T: Instance>() { ) } + // For OTG-HS on STM32U5 only the 32MHz clock is fast enough (Table 762, Sect 73.14.4) + #[cfg(all(stm32u5, peri_usb_otg_hs))] + if freq.0.abs_diff(32_000_000) > 120_000 { + panic!( + "USB clock should be 32Mhz but is {} Hz. Please double-check your RCC settings.", + freq.0 + ) + } + #[cfg(any(stm32l4, stm32l5, stm32wb, stm32u0))] critical_section::with(|_| crate::pac::PWR.cr2().modify(|w| w.set_usv(true))); @@ -90,6 +100,16 @@ fn common_init<T: Instance>() { // Wait for USB power to stabilize while !crate::pac::PWR.svmsr().read().vddusbrdy() {} + + // Now set up transceiver power if it's a OTG-HS + #[cfg(peri_usb_otg_hs)] + { + crate::pac::PWR.vosr().modify(|w| { + w.set_usbpwren(true); + w.set_usbboosten(true); + }); + while !crate::pac::PWR.vosr().read().usbboostrdy() {} + } } T::Interrupt::unpend(); diff --git a/embassy-stm32/src/usb/otg.rs b/embassy-stm32/src/usb/otg.rs index 6ca28b1563..d1b38a558a 100644 --- a/embassy-stm32/src/usb/otg.rs +++ b/embassy-stm32/src/usb/otg.rs @@ -26,7 +26,6 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl unsafe fn on_interrupt() { let r = T::regs(); let state = T::state(); - on_interrupt_impl(r, state, T::ENDPOINT_COUNT); } } @@ -103,15 +102,18 @@ impl<'d, T: Instance> Driver<'d, T> { pub fn new_hs( _peri: impl Peripheral<P = T> + 'd, _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd, - dp: impl Peripheral<P = impl DpPin<T>> + 'd, - dm: impl Peripheral<P = impl DmPin<T>> + 'd, + _dp: impl Peripheral<P = impl DpPin<T>> + 'd, + _dm: impl Peripheral<P = impl DmPin<T>> + 'd, ep_out_buffer: &'d mut [u8], config: Config, ) -> Self { - into_ref!(dp, dm); - - dp.set_as_af(dp.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); - dm.set_as_af(dm.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + // For STM32U5 High speed pins need to be left in analog mode + #[cfg(not(all(stm32u5, peri_usb_otg_hs)))] + { + into_ref!(_dp, _dm); + _dp.set_as_af(_dp.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + _dm.set_as_af(_dm.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + } let instance = OtgInstance { regs: T::regs(), @@ -311,6 +313,22 @@ impl<'d, T: Instance> Bus<'d, T> { }); }); + #[cfg(all(stm32u5, peri_usb_otg_hs))] + { + // Only the 32MHz clock is suitable here, which the magic number represents + crate::pac::SYSCFG.otghsphycr().modify(|w| { + w.set_clksel(11); + w.set_en(true); + }); + + critical_section::with(|_| { + crate::pac::RCC.ahb2enr1().modify(|w| { + w.set_usb_otg_hsen(true); + w.set_usb_otg_hs_phyen(true); + }); + }); + } + let r = T::regs(); let core_id = r.cid().read().0; trace!("Core id {:08x}", core_id); diff --git a/examples/stm32u5/src/bin/usb_hs_serial.rs b/examples/stm32u5/src/bin/usb_hs_serial.rs new file mode 100644 index 0000000000..5549e2cbb6 --- /dev/null +++ b/examples/stm32u5/src/bin/usb_hs_serial.rs @@ -0,0 +1,129 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use defmt_rtt as _; // global logger +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + OTG_HS => usb::InterruptHandler<peripherals::USB_OTG_HS>; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + use embassy_stm32::time::Hertz; + config.rcc.hse = Some(Hse { + freq: Hertz(16_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV2, // HSE / 2 = 8MHz + mul: PllMul::MUL60, // 8MHz * 60 = 480MHz + divr: Some(PllDiv::DIV3), // 480MHz / 3 = 160MHz (sys_ck) + divq: Some(PllDiv::DIV10), // 480MHz / 10 = 48MHz (USB) + divp: Some(PllDiv::DIV15), // 480MHz / 15 = 32MHz (USBOTG) + }); + config.rcc.mux.otghssel = mux::Otghssel::PLL1_P; + config.rcc.voltage_range = VoltageScale::RANGE1; + config.rcc.sys = Sysclk::PLL1_R; + } + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb::Config::default(); + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + config.vbus_detection = false; + let driver = Driver::new_hs(p.USB_OTG_HS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From<EndpointError> for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} From a0e056a629c166b38c536c68afbf852819f10143 Mon Sep 17 00:00:00 2001 From: Marvin Drees <marvin.drees@9elements.com> Date: Fri, 6 Dec 2024 16:22:08 +0100 Subject: [PATCH 2/2] Update STM32U5 OTG HS clock handling Signed-off-by: Marvin Drees <marvin.drees@9elements.com> --- embassy-stm32/src/rcc/u5.rs | 29 +++++++++++++++++++++++++++++ embassy-stm32/src/usb/mod.rs | 14 ++------------ embassy-stm32/src/usb/otg.rs | 2 -- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/embassy-stm32/src/rcc/u5.rs b/embassy-stm32/src/rcc/u5.rs index af99c77bc8..dc77dc540b 100644 --- a/embassy-stm32/src/rcc/u5.rs +++ b/embassy-stm32/src/rcc/u5.rs @@ -1,9 +1,13 @@ pub use crate::pac::pwr::vals::Vos as VoltageScale; +#[cfg(all(peri_usb_otg_hs))] +pub use crate::pac::rcc::vals::Otghssel; pub use crate::pac::rcc::vals::{ Hpre as AHBPrescaler, Msirange, Msirange as MSIRange, Plldiv as PllDiv, Pllm as PllPreDiv, Plln as PllMul, Pllsrc as PllSource, Ppre as APBPrescaler, Sw as Sysclk, }; use crate::pac::rcc::vals::{Hseext, Msirgsel, Pllmboost, Pllrge}; +#[cfg(all(peri_usb_otg_hs))] +pub use crate::pac::{syscfg::vals::Usbrefcksel, SYSCFG}; use crate::pac::{FLASH, PWR, RCC}; use crate::rcc::LSI_FREQ; use crate::time::Hertz; @@ -295,6 +299,31 @@ pub(crate) unsafe fn init(config: Config) { let rtc = config.ls.init(); + #[cfg(all(stm32u5, peri_usb_otg_hs))] + let usb_refck = match config.mux.otghssel { + Otghssel::HSE => hse, + Otghssel::HSE_DIV_2 => hse.map(|hse_val| hse_val / 2u8), + Otghssel::PLL1_P => pll1.p, + Otghssel::PLL1_P_DIV_2 => pll1.p.map(|pll1p_val| pll1p_val / 2u8), + }; + #[cfg(all(stm32u5, peri_usb_otg_hs))] + let usb_refck_sel = match usb_refck { + Some(clk_val) => match clk_val { + Hertz(16_000_000) => Usbrefcksel::MHZ16, + Hertz(19_200_000) => Usbrefcksel::MHZ19_2, + Hertz(20_000_000) => Usbrefcksel::MHZ20, + Hertz(24_000_000) => Usbrefcksel::MHZ24, + Hertz(26_000_000) => Usbrefcksel::MHZ26, + Hertz(32_000_000) => Usbrefcksel::MHZ32, + _ => panic!("cannot select OTG_HS reference clock with source frequency of {} Hz, must be one of 16, 19.2, 20, 24, 26, 32 MHz", clk_val), + }, + None => Usbrefcksel::MHZ24, + }; + #[cfg(all(stm32u5, peri_usb_otg_hs))] + SYSCFG.otghsphycr().modify(|w| { + w.set_clksel(usb_refck_sel); + }); + let lse = config.ls.lse.map(|l| l.frequency); let lsi = config.ls.lsi.then_some(LSI_FREQ); diff --git a/embassy-stm32/src/usb/mod.rs b/embassy-stm32/src/usb/mod.rs index 9737b9b3f8..ae5963420d 100644 --- a/embassy-stm32/src/usb/mod.rs +++ b/embassy-stm32/src/usb/mod.rs @@ -15,7 +15,7 @@ fn common_init<T: Instance>() { let freq = T::frequency(); // On the H7RS, the USBPHYC embeds a PLL accepting one of the input frequencies listed below and providing 48MHz to OTG_FS and 60MHz to OTG_HS internally - #[cfg(stm32h7rs)] + #[cfg(any(stm32h7rs, all(stm32u5, peri_usb_otg_hs)))] if ![16_000_000, 19_200_000, 20_000_000, 24_000_000, 26_000_000, 32_000_000].contains(&freq.0) { panic!( "USB clock should be one of 16, 19.2, 20, 24, 26, 32Mhz but is {} Hz. Please double-check your RCC settings.", @@ -25,8 +25,7 @@ fn common_init<T: Instance>() { // Check frequency is within the 0.25% tolerance allowed by the spec. // Clock might not be exact 48Mhz due to rounding errors in PLL calculation, or if the user // has tight clock restrictions due to something else (like audio). - #[cfg(not(stm32h7rs))] - #[cfg(not(all(stm32u5, peri_usb_otg_hs)))] + #[cfg(not(any(stm32h7rs, all(stm32u5, peri_usb_otg_hs))))] if freq.0.abs_diff(48_000_000) > 120_000 { panic!( "USB clock should be 48Mhz but is {} Hz. Please double-check your RCC settings.", @@ -34,15 +33,6 @@ fn common_init<T: Instance>() { ) } - // For OTG-HS on STM32U5 only the 32MHz clock is fast enough (Table 762, Sect 73.14.4) - #[cfg(all(stm32u5, peri_usb_otg_hs))] - if freq.0.abs_diff(32_000_000) > 120_000 { - panic!( - "USB clock should be 32Mhz but is {} Hz. Please double-check your RCC settings.", - freq.0 - ) - } - #[cfg(any(stm32l4, stm32l5, stm32wb, stm32u0))] critical_section::with(|_| crate::pac::PWR.cr2().modify(|w| w.set_usv(true))); diff --git a/embassy-stm32/src/usb/otg.rs b/embassy-stm32/src/usb/otg.rs index d1b38a558a..d3c7978e48 100644 --- a/embassy-stm32/src/usb/otg.rs +++ b/embassy-stm32/src/usb/otg.rs @@ -315,9 +315,7 @@ impl<'d, T: Instance> Bus<'d, T> { #[cfg(all(stm32u5, peri_usb_otg_hs))] { - // Only the 32MHz clock is suitable here, which the magic number represents crate::pac::SYSCFG.otghsphycr().modify(|w| { - w.set_clksel(11); w.set_en(true); });