-
Notifications
You must be signed in to change notification settings - Fork 48
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
//! 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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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]) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -85,3 +85,6 @@ pub mod syscfg; | |
pub mod time; | ||
pub mod timer; | ||
// pub mod watchdog; | ||
|
||
#[cfg(all(feature = "stm32-usbd", any(feature = "stm32g473",)))] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The machine feature needs changed here. Do all G4's have USB? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 } | ||
} | ||
} |
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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>; |
There was a problem hiding this comment.
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