From 8128d5357ef7b897c2e7c13ed4a29734bee82761 Mon Sep 17 00:00:00 2001 From: Kevin Lannen Date: Sun, 17 Apr 2022 22:08:57 -0600 Subject: [PATCH 1/2] Add usb support and usb_serial example This was tested on an STM32G473 on a custom board with an external 8MHz oscillator --- .vscode/settings.json | 1 + Cargo.toml | 11 ++++ examples/usb_serial.rs | 133 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 + src/time.rs | 1 + src/usb.rs | 47 +++++++++++++++ 6 files changed, 196 insertions(+) create mode 100644 examples/usb_serial.rs create mode 100644 src/usb.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 648fb929..c027172a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "rust-analyzer.cargo.target": "thumbv7em-none-eabihf", "rust-analyzer.cargo.features": [ "stm32g473", + "usb_fs", "defmt-logging", ] } \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 9734fe1f..0be1608c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,10 @@ version = "1.1" version = "0.3.2" optional = true +[dependencies.stm32-usbd] +version = "0.6" +optional = true + [dev-dependencies] cortex-m-rt = "0.7.2" defmt-rtt = "0.4.0" @@ -72,6 +76,8 @@ panic-rtt-target = { version = "0.1.1", features = ["cortex-m"] } mpu6050 = "0.1.4" bme680 = "0.6.0" embedded-sdmmc = "0.3.0" +usbd-serial = "0.1.1" +usb-device = "0.2.8" #TODO: Separate feature sets [features] @@ -90,6 +96,7 @@ log-itm = ["cortex-m-log/itm"] log-rtt = [] log-semihost = ["cortex-m-log/semihosting"] defmt-logging = ["defmt"] +usb = ["stm32-usbd"] [profile.dev] codegen-units = 1 @@ -102,3 +109,7 @@ debug = false codegen-units = 1 incremental = false lto = true + +[[example]] +name = "usb_serial" +required-features = ["usb"] \ No newline at end of file diff --git a/examples/usb_serial.rs b/examples/usb_serial.rs new file mode 100644 index 00000000..3d144ab8 --- /dev/null +++ b/examples/usb_serial.rs @@ -0,0 +1,133 @@ +//! CDC-ACM serial port example using polling in a busy loop. +//! This example currently requires an 8MHz external oscillator +//! and assumed an LED is connected to port A6. +//! +//! Further work could be done to setup the HSI48 and the clock +//! recovery system to generate the USB clock. + +#![no_std] +#![no_main] + +use defmt_rtt as _; + +use hal::rcc::PllMDiv; +use hal::rcc::PllNMul; +use hal::rcc::PllQDiv; +use hal::rcc::PllRDiv; +use panic_probe as _; + +use stm32g4 as _; + +#[cfg(feature = "defmt-logging")] +#[defmt::panic_handler] +fn panic() -> ! { + cortex_m::asm::udf() +} + +pub fn exit() -> ! { + loop { + cortex_m::asm::bkpt(); + } +} + +use hal::rcc::{Config, PLLSrc, Prescaler}; + +use stm32g4xx_hal as hal; + +use hal::prelude::*; +use hal::stm32; +use hal::usb::{Peripheral, UsbBus}; + +use usb_device::prelude::*; +use usbd_serial::{SerialPort, USB_CLASS_CDC}; + +#[cortex_m_rt::entry] +fn main() -> ! { + // utils::logger::init(); + + let dp = stm32::Peripherals::take().unwrap(); + + let rcc = dp.RCC.constrain(); + + // This sets the clocks up as follows + // - 8 MHz external oscillator + // - Sysclck and HCLK at 144 MHz + // - APB1 = PCLK1 = 72 MHz + // - APB2 = PCLK2 = 72 MHz + // - USB = 48 MHz + let mut rcc = rcc.freeze( + Config::new(hal::rcc::SysClockSrc::HSE(8.mhz())) + .pll_cfg(hal::rcc::PllConfig { + mux: PLLSrc::HSE(8.mhz()), + m: PllMDiv::DIV_1, + n: PllNMul::MUL_36, + r: Some(PllRDiv::DIV_2), + q: Some(PllQDiv::DIV_6), + p: None, + }) + .ahb_psc(Prescaler::Div2) + .apb1_psc(Prescaler::Div2) + .apb2_psc(Prescaler::Div2), + ); + + { + use crate::stm32::RCC; + let rcc = unsafe { &*RCC::ptr() }; + // Set clock source for USB to PLL + rcc.ccipr.modify(|_, w| w.clk48sel().pllq()); + } + + // Configure an LED + let gpioa = dp.GPIOA.split(&mut rcc); + + let mut led = gpioa.pa6.into_push_pull_output(); + + let usb = Peripheral { usb: dp.USB }; + let usb_bus = UsbBus::new(usb); + + let rx_buffer: [u8; 128] = [0; 128]; + let tx_buffer: [u8; 128] = [0; 128]; + + let mut serial = SerialPort::new_with_store(&usb_bus, rx_buffer, tx_buffer); + + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) + .manufacturer("Fake company") + .product("Serial port") + .serial_number("TEST") + .device_class(USB_CLASS_CDC) + .build(); + + loop { + if !usb_dev.poll(&mut [&mut serial]) { + continue; + } + + let mut buf = [0u8; 64]; + + match serial.read(&mut buf) { + Ok(count) if count > 0 => { + led.set_low().ok(); // Turn on + + // Echo back in upper case + for c in buf[0..count].iter_mut() { + if 0x61 <= *c && *c <= 0x7a { + *c &= !0x20; + } + } + + let mut write_offset = 0; + while write_offset < count { + match serial.write(&buf[write_offset..count]) { + Ok(len) if len > 0 => { + write_offset += len; + } + _ => {} + } + } + } + _ => {} + } + + led.set_high().ok(); // Turn off + } +} diff --git a/src/lib.rs b/src/lib.rs index 0b7d3ee4..f32f63b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,3 +85,6 @@ pub mod syscfg; pub mod time; pub mod timer; // pub mod watchdog; + +#[cfg(all(feature = "stm32-usbd", any(feature = "stm32g473",)))] +pub mod usb; diff --git a/src/time.rs b/src/time.rs index 7beeb57a..f2465a33 100644 --- a/src/time.rs +++ b/src/time.rs @@ -8,6 +8,7 @@ pub struct Instant(pub u32); pub struct Bps(pub u32); #[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Hertz(pub u32); #[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] diff --git a/src/usb.rs b/src/usb.rs new file mode 100644 index 00000000..21986d94 --- /dev/null +++ b/src/usb.rs @@ -0,0 +1,47 @@ +//! # USB peripheral. +//! +//! Mostly builds upon the [`stm32_usbd`] crate. +//! +//! ## Examples +//! +//! See [examples/usb_serial.rs] for a usage example. + +use crate::stm32::{RCC, USB}; + +use stm32_usbd::UsbPeripheral; + +pub use stm32_usbd::UsbBus; + +pub struct Peripheral { + pub usb: USB, +} + +unsafe impl Sync for Peripheral {} + +unsafe impl UsbPeripheral for Peripheral { + const REGISTERS: *const () = USB::ptr() as *const (); + const DP_PULL_UP_FEATURE: bool = true; + const EP_MEMORY: *const () = 0x4000_6000 as _; + const EP_MEMORY_SIZE: usize = 1024; + const EP_MEMORY_ACCESS_2X16: bool = true; + + fn enable() { + let rcc = unsafe { &*RCC::ptr() }; + + cortex_m::interrupt::free(|_| { + // Enable USB peripheral + rcc.apb1enr1.modify(|_, w| w.usben().enabled()); + + // Reset USB peripheral + rcc.apb1rstr1.modify(|_, w| w.usbrst().reset()); + rcc.apb1rstr1.modify(|_, w| w.usbrst().clear_bit()); + }); + } + + fn startup_delay() { + // There is a chip specific startup delay. It is not specified for the STM32G4 but the STM32F103 is 1 us to delay for 170 cycles minimum + cortex_m::asm::delay(170); + } +} + +pub type UsbBusType = UsbBus; From 91f65b83977f87b21c923067b621035c331fe058 Mon Sep 17 00:00:00 2001 From: Kevin Lannen Date: Wed, 27 Apr 2022 21:44:04 -0600 Subject: [PATCH 2/2] Use HSI48 and clock recovery system to clock USB --- examples/usb_serial.rs | 17 ++++++----- src/rcc/clock_recovery_system.rs | 51 ++++++++++++++++++++++++++++++++ src/rcc/mod.rs | 11 +++++++ src/usb.rs | 15 ++++++++++ 4 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 src/rcc/clock_recovery_system.rs diff --git a/examples/usb_serial.rs b/examples/usb_serial.rs index 3d144ab8..acd41c3d 100644 --- a/examples/usb_serial.rs +++ b/examples/usb_serial.rs @@ -14,6 +14,8 @@ use hal::rcc::PllMDiv; use hal::rcc::PllNMul; use hal::rcc::PllQDiv; use hal::rcc::PllRDiv; +use hal::usb::configure_usb_clock_source; +use hal::usb::ClockSource; use panic_probe as _; use stm32g4 as _; @@ -32,6 +34,8 @@ pub fn exit() -> ! { use hal::rcc::{Config, PLLSrc, Prescaler}; +use hal::rcc::clock_recovery_system::{CrsConfig, CrsExt}; + use stm32g4xx_hal as hal; use hal::prelude::*; @@ -43,8 +47,6 @@ use usbd_serial::{SerialPort, USB_CLASS_CDC}; #[cortex_m_rt::entry] fn main() -> ! { - // utils::logger::init(); - let dp = stm32::Peripherals::take().unwrap(); let rcc = dp.RCC.constrain(); @@ -70,12 +72,11 @@ fn main() -> ! { .apb2_psc(Prescaler::Div2), ); - { - use crate::stm32::RCC; - let rcc = unsafe { &*RCC::ptr() }; - // Set clock source for USB to PLL - rcc.ccipr.modify(|_, w| w.clk48sel().pllq()); - } + // Example of setting up the HSI48 with the clock recovery system. + let crs = dp.CRS.constrain(); + let crs_config = CrsConfig {}; + crs.configure(crs_config, &rcc); + configure_usb_clock_source(ClockSource::Hsi48, &rcc); // Configure an LED let gpioa = dp.GPIOA.split(&mut rcc); diff --git a/src/rcc/clock_recovery_system.rs b/src/rcc/clock_recovery_system.rs new file mode 100644 index 00000000..a4488e3d --- /dev/null +++ b/src/rcc/clock_recovery_system.rs @@ -0,0 +1,51 @@ +use crate::stm32::CRS; + +use super::Rcc; + +use crate::rcc::Enable; + +pub struct CrsConfig {} + +pub struct Crs { + rb: CRS, +} + +impl Crs { + /// Sets up the clock recovery system for the HSI48 oscillator. + /// TODO: make this configurable for more than just USB applications. + pub fn configure(self, crs_config: CrsConfig, rcc: &Rcc) -> Self { + // TODO: This needs to ensure that the HSI48 is enabled + // and then setup the CRS. For now this just needs to use + // the USB sync as the trigger system. + + rcc.enable_hsi48(); + + // Enable the clock recovery system + CRS::enable(&rcc.rb); + + // Set to b10 for USB SOF as source + self.rb + .cfgr + .modify(|_, w| unsafe { w.syncsrc().bits(0b10) }); + + self.rb.cr.modify(|_, w| { + // Set autotrim enabled. + w.autotrimen().set_bit(); + // Enable CRS + w.cen().set_bit() + }); + + self + } +} + +pub trait CrsExt { + /// Constrains the `CRS` peripheral so that it can only be setup once. + fn constrain(self) -> Crs; +} + +impl CrsExt for CRS { + fn constrain(self) -> Crs { + Crs { rb: self } + } +} diff --git a/src/rcc/mod.rs b/src/rcc/mod.rs index 0b8279ab..7e440f64 100644 --- a/src/rcc/mod.rs +++ b/src/rcc/mod.rs @@ -1,6 +1,7 @@ use crate::stm32::{rcc, FLASH, PWR, RCC}; use crate::time::{Hertz, U32Ext}; +pub mod clock_recovery_system; mod clockout; mod config; mod enable; @@ -290,6 +291,16 @@ impl Rcc { self.rb.csr.modify(|_, w| w.lsion().set_bit()); while self.rb.csr.read().lsirdy().bit_is_clear() {} } + + pub(crate) fn enable_hsi48(&self) { + self.rb.crrcr.modify(|_, w| w.hsi48on().set_bit()); + + loop { + if self.rb.crrcr.read().hsi48rdy().bit() { + break; + } + } + } } /// Extension trait that constrains the `RCC` peripheral diff --git a/src/usb.rs b/src/usb.rs index 21986d94..3b7832a3 100644 --- a/src/usb.rs +++ b/src/usb.rs @@ -12,6 +12,8 @@ use stm32_usbd::UsbPeripheral; pub use stm32_usbd::UsbBus; +use crate::rcc::Rcc; + pub struct Peripheral { pub usb: USB, } @@ -44,4 +46,17 @@ unsafe impl UsbPeripheral for Peripheral { } } +pub enum ClockSource { + Hsi48, + PllQ, +} + +#[inline(always)] +pub fn configure_usb_clock_source(cs: ClockSource, rcc: &Rcc) { + rcc.rb.ccipr.modify(|_, w| match cs { + ClockSource::Hsi48 => w.clk48sel().hsi48(), + ClockSource::PllQ => w.clk48sel().pllq(), + }); +} + pub type UsbBusType = UsbBus;