Skip to content

Commit

Permalink
Merge pull request #3613 from mubes/stm32u5_otg_hs_support
Browse files Browse the repository at this point in the history
Add support for stm32u595/5a5 OTG_HS in client mode
  • Loading branch information
Dirbaio authored Dec 13, 2024
2 parents 406d377 + a0e056a commit e44ee26
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 9 deletions.
29 changes: 29 additions & 0 deletions embassy-stm32/src/rcc/u5.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);

Expand Down
14 changes: 12 additions & 2 deletions embassy-stm32/src/usb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand All @@ -25,7 +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(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.",
Expand Down Expand Up @@ -90,6 +90,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();
Expand Down
30 changes: 23 additions & 7 deletions embassy-stm32/src/usb/otg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -311,6 +313,20 @@ impl<'d, T: Instance> Bus<'d, T> {
});
});

#[cfg(all(stm32u5, peri_usb_otg_hs))]
{
crate::pac::SYSCFG.otghsphycr().modify(|w| {
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);
Expand Down
129 changes: 129 additions & 0 deletions examples/stm32u5/src/bin/usb_hs_serial.rs
Original file line number Diff line number Diff line change
@@ -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?;
}
}

0 comments on commit e44ee26

Please sign in to comment.