diff --git a/Cargo.toml b/Cargo.toml index 5cf19c54..b78d4e2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ bitflags = "1.2" vcell = "0.1" static_assertions = "1.1" fugit = "0.3.5" +stm32-usbd = { version = "0.7.0", optional = true } fixed = { version = "1.28.0", optional = true } [dependencies.cortex-m] @@ -72,11 +73,14 @@ cfg-if = "0.1.10" mpu6050 = "0.1.4" bme680 = "0.6.0" embedded-sdmmc = "0.3.0" +usb-device = { version = "0.3.2", features = ["defmt"] } +usbd-serial = "0.2.2" #TODO: Separate feature sets [features] default = ["rt"] rt = ["stm32g4/rt"] +usb = ["dep:stm32-usbd"] stm32g431 = ["stm32g4/stm32g431"] stm32g441 = ["stm32g4/stm32g441"] stm32g471 = ["stm32g4/stm32g471"] @@ -108,6 +112,10 @@ lto = true name = "flash_with_rtic" required-features = ["stm32g474"] +[[example]] +name = "usb_serial" +required-features = ["usb"] + [[example]] name = "cordic" required-features = ["cordic"] diff --git a/examples/usb_serial.rs b/examples/usb_serial.rs new file mode 100644 index 00000000..59e43328 --- /dev/null +++ b/examples/usb_serial.rs @@ -0,0 +1,90 @@ +//! CDC-ACM serial port example using polling in a busy loop. +#![deny(warnings)] +#![deny(unsafe_code)] +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use hal::prelude::*; +use hal::pwr::PwrExt; +use hal::usb::{Peripheral, UsbBus}; +use hal::{rcc, stm32}; +use stm32g4xx_hal as hal; + +use usb_device::prelude::*; +use usbd_serial::{SerialPort, USB_CLASS_CDC}; + +use panic_probe as _; + +#[macro_use] +mod utils; + +#[entry] +fn main() -> ! { + utils::logger::init(); + + let dp = stm32::Peripherals::take().expect("cannot take peripherals"); + let pwr = dp.PWR.constrain().freeze(); + let mut rcc = dp.RCC.freeze(rcc::Config::hsi(), pwr); + rcc.enable_hsi48(); + + let gpioa = dp.GPIOA.split(&mut rcc); + + let mut led = gpioa.pa5.into_push_pull_output(); + led.set_low().ok(); + + let usb_dm = gpioa.pa11.into_alternate(); + let usb_dp = gpioa.pa12.into_alternate(); + + let usb = Peripheral { + usb: dp.USB, + pin_dm: usb_dm, + pin_dp: usb_dp, + }; + let usb_bus = UsbBus::new(usb); + + let mut serial = SerialPort::new(&usb_bus); + + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) + .strings(&[StringDescriptors::default() + .manufacturer("Fake company") + .product("Serial port") + .serial_number("TEST")]) + .unwrap() + .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_high().ok(); + + // 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_low().ok(); + } +} diff --git a/src/gpio.rs b/src/gpio.rs index 967d7c7d..bd43df0c 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -342,6 +342,8 @@ macro_rules! gpio { _mode: PhantomData, } + impl crate::Sealed for $PXi {} + #[allow(clippy::from_over_into)] impl Into<$PXi>> for $PXi { fn into(self) -> $PXi> { diff --git a/src/lib.rs b/src/lib.rs index fc0d9112..93c12d07 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,3 +100,5 @@ pub mod time; pub mod timer; // pub mod watchdog; pub mod independent_watchdog; +#[cfg(feature = "usb")] +pub mod usb; diff --git a/src/rcc/mod.rs b/src/rcc/mod.rs index 61e7aa32..c901ee7e 100644 --- a/src/rcc/mod.rs +++ b/src/rcc/mod.rs @@ -440,6 +440,11 @@ impl Rcc { while self.rb.csr.read().lsirdy().bit_is_clear() {} } + pub fn enable_hsi48(&self) { + self.rb.crrcr.modify(|_, w| w.hsi48on().set_bit()); + while self.rb.crrcr.read().hsi48rdy().bit_is_clear() {} + } + pub fn get_reset_reason(&self) -> ResetReason { let csr = self.rb.csr.read(); diff --git a/src/usb.rs b/src/usb.rs new file mode 100644 index 00000000..0d9181c8 --- /dev/null +++ b/src/usb.rs @@ -0,0 +1,69 @@ +//! USB peripheral. +//! +//! Provides the required implementation for use of the [`stm32-usbd`] crate. + +pub use stm32_usbd::UsbBus; + +use crate::gpio; +use crate::gpio::gpioa::{PA11, PA12}; +use crate::rcc::{Enable, Reset}; +use crate::stm32::{RCC, USB}; +use core::fmt; +use stm32_usbd::UsbPeripheral; + +/// Trait implemented by all pins that can be the "D-" pin for the USB peripheral +pub trait DmPin: crate::Sealed {} + +/// Trait implemented by all pins that can be the "D+" pin for the USB peripheral +pub trait DpPin: crate::Sealed {} + +impl DmPin for PA11> {} +impl DpPin for PA12> {} + +pub struct Peripheral { + /// USB register block + pub usb: USB, + /// Data negative pin + pub pin_dm: Dm, + /// Data positive pin + pub pin_dp: Dp, +} + +impl fmt::Debug for Peripheral +where + Dm: DmPin + fmt::Debug, + Dp: DpPin + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Peripheral") + .field("usb", &"USB") + .field("pin_dm", &self.pin_dm) + .field("pin_dp", &self.pin_dp) + .finish() + } +} + +// SAFETY: Implementation of Peripheral is thread-safe by using cricitcal sections to ensure +// mutually exclusive access to the USB peripheral +unsafe impl Sync for Peripheral {} + +// SAFETY: The peripheral has the same regiter blockout as the STM32 USBFS +unsafe impl UsbPeripheral for Peripheral { + const REGISTERS: *const () = USB::ptr().cast::<()>(); + 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() { + cortex_m::interrupt::free(|_| unsafe { + let rcc_ptr = &(*RCC::ptr()); + USB::enable(rcc_ptr); + USB::reset(rcc_ptr); + }); + } + + fn startup_delay() { + // not required + } +}