Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

USB device support and usb_serial example #50

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"rust-analyzer.cargo.target": "thumbv7em-none-eabihf",
"rust-analyzer.cargo.features": [
"stm32g473",
"usb_fs",
"defmt-logging",
]
}
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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]
Expand All @@ -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
Expand All @@ -102,3 +109,7 @@ debug = false
codegen-units = 1
incremental = false
lto = true

[[example]]
name = "usb_serial"
required-features = ["usb"]
134 changes: 134 additions & 0 deletions examples/usb_serial.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//! 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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is done now

//! 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 hal::usb::configure_usb_clock_source;
use hal::usb::ClockSource;
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 hal::rcc::clock_recovery_system::{CrsConfig, CrsExt};

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() -> ! {
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(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we have the CRS support now this could be reworked to not depend on the HSE at all. Would make it easier to run on other hardware.

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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought it was worth mentioning because I noticed in test, this div2 is setting the HCLK to 72Mhz -- differs from the comment above.

.apb1_psc(Prescaler::Div2)
.apb2_psc(Prescaler::Div2),
);

// 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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LED is probably a distraction. Take it out to simplify stuff.


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]) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A separate example or rework this one to poll in an interrupt would be good. Calling poll from the main loop is not super useful for anything but the most trivial of projects.

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
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,6 @@ pub mod syscfg;
pub mod time;
pub mod timer;
// pub mod watchdog;

#[cfg(all(feature = "stm32-usbd", any(feature = "stm32g473",)))]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The machine feature needs changed here. Do all G4's have USB?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per ST's website, yes they all have USB. I've been bringing a G431 up and this is the only line that needed adjusting. G431 is the minimum spec G4 available.

pub mod usb;
51 changes: 51 additions & 0 deletions src/rcc/clock_recovery_system.rs
Original file line number Diff line number Diff line change
@@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of this is done

// 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 }
}
}
11 changes: 11 additions & 0 deletions src/rcc/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
62 changes: 62 additions & 0 deletions src/usb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//! # 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;

use crate::rcc::Rcc;

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 enum ClockSource {
Hsi48,
PllQ,
}

#[inline(always)]
pub fn configure_usb_clock_source(cs: ClockSource, rcc: &Rcc) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the HSI48 is selected, can this do the configuration of it?

rcc.rb.ccipr.modify(|_, w| match cs {
ClockSource::Hsi48 => w.clk48sel().hsi48(),
ClockSource::PllQ => w.clk48sel().pllq(),
});
}

pub type UsbBusType = UsbBus<Peripheral>;