diff --git a/CHANGELOG.md b/CHANGELOG.md index 01c3195b..48101f0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added `micros_since` and `reset` methods to timer - Added `select_frequency` method to RTC - Unidirectional DMA support for SPI (TX only) +- Added USB driver for `stm32f102` and `stm32f103` devices ### Breaking changes diff --git a/Cargo.toml b/Cargo.toml index e1ab3209..50d5e74b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,11 @@ version = "0.2.2" version = "0.2.3" features = ["unproven"] +[dependencies.stm32-usbd] +version = "0.5.0" +features = ["ram_access_1x16"] +optional = true + [dev-dependencies] panic-halt = "0.2.0" panic-semihosting = "0.5.2" @@ -48,6 +53,8 @@ heapless = "0.4.3" m = "0.1.1" mfrc522 = "0.2.0" serde_derive = "1.0.90" +usb-device = "0.2.3" +usbd-serial = "0.1.0" [dev-dependencies.byteorder] default-features = false @@ -108,3 +115,15 @@ codegen-units = 1 codegen-units = 1 debug = true lto = true + +[[example]] +name = "usb_serial" +required-features = ["rt", "stm32f103", "stm32-usbd"] + +[[example]] +name = "usb_serial_interrupt" +required-features = ["rt", "stm32f103", "stm32-usbd"] + +[[example]] +name = "usb_serial_rtfm" +required-features = ["rt", "stm32f103", "stm32-usbd"] diff --git a/examples/usb_serial.rs b/examples/usb_serial.rs new file mode 100644 index 00000000..39fefd3c --- /dev/null +++ b/examples/usb_serial.rs @@ -0,0 +1,96 @@ +//! CDC-ACM serial port example using polling in a busy loop. +//! Target board: Blue Pill +#![no_std] +#![no_main] + +extern crate panic_semihosting; + +use cortex_m::asm::delay; +use cortex_m_rt::entry; +use embedded_hal::digital::v2::OutputPin; +use stm32f1xx_hal::usb::{Peripheral, UsbBus}; +use stm32f1xx_hal::{prelude::*, stm32}; +use usb_device::prelude::*; +use usbd_serial::{SerialPort, USB_CLASS_CDC}; + +#[entry] +fn main() -> ! { + let dp = stm32::Peripherals::take().unwrap(); + + let mut flash = dp.FLASH.constrain(); + let mut rcc = dp.RCC.constrain(); + + let clocks = rcc + .cfgr + .use_hse(8.mhz()) + .sysclk(48.mhz()) + .pclk1(24.mhz()) + .freeze(&mut flash.acr); + + assert!(clocks.usbclk_valid()); + + // Configure the on-board LED (PC13, green) + let mut gpioc = dp.GPIOC.split(&mut rcc.apb2); + let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh); + led.set_high(); // Turn off + + let mut gpioa = dp.GPIOA.split(&mut rcc.apb2); + + // BluePill board has a pull-up resistor on the D+ line. + // Pull the D+ pin down to send a RESET condition to the USB bus. + // This forced reset is needed only for development, without it host + // will not reset your device when you upload new firmware. + let mut usb_dp = gpioa.pa12.into_push_pull_output(&mut gpioa.crh); + usb_dp.set_low(); + delay(clocks.sysclk().0 / 100); + + let usb = Peripheral { + usb: dp.USB, + pin_dm: gpioa.pa11, + pin_dp: usb_dp.into_floating_input(&mut gpioa.crh), + }; + let usb_bus = UsbBus::new(usb); + + let mut serial = SerialPort::new(&usb_bus); + + 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(); // 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(); // Turn off + } +} diff --git a/examples/usb_serial_interrupt.rs b/examples/usb_serial_interrupt.rs new file mode 100644 index 00000000..4acb62a2 --- /dev/null +++ b/examples/usb_serial_interrupt.rs @@ -0,0 +1,118 @@ +//! CDC-ACM serial port example using interrupts. +//! Target board: Blue Pill +#![no_std] +#![no_main] + +extern crate panic_semihosting; + +use cortex_m::asm::{delay, wfi}; +use cortex_m_rt::entry; +use embedded_hal::digital::v2::OutputPin; +use stm32f1xx_hal::stm32::{interrupt, Interrupt}; +use stm32f1xx_hal::usb::{Peripheral, UsbBus, UsbBusType}; +use stm32f1xx_hal::{prelude::*, stm32}; +use usb_device::{bus::UsbBusAllocator, prelude::*}; +use usbd_serial::{SerialPort, USB_CLASS_CDC}; + +static mut USB_BUS: Option> = None; +static mut USB_SERIAL: Option> = None; +static mut USB_DEVICE: Option> = None; + +#[entry] +fn main() -> ! { + let p = cortex_m::Peripherals::take().unwrap(); + let dp = stm32::Peripherals::take().unwrap(); + + let mut flash = dp.FLASH.constrain(); + let mut rcc = dp.RCC.constrain(); + + let clocks = rcc + .cfgr + .use_hse(8.mhz()) + .sysclk(48.mhz()) + .pclk1(24.mhz()) + .freeze(&mut flash.acr); + + assert!(clocks.usbclk_valid()); + + let mut gpioa = dp.GPIOA.split(&mut rcc.apb2); + + // BluePill board has a pull-up resistor on the D+ line. + // Pull the D+ pin down to send a RESET condition to the USB bus. + // This forced reset is needed only for development, without it host + // will not reset your device when you upload new firmware. + let mut usb_dp = gpioa.pa12.into_push_pull_output(&mut gpioa.crh); + usb_dp.set_low(); + delay(clocks.sysclk().0 / 100); + + let usb_dm = gpioa.pa11; + let usb_dp = usb_dp.into_floating_input(&mut gpioa.crh); + + let usb = Peripheral { + usb: dp.USB, + pin_dm: usb_dm, + pin_dp: usb_dp, + }; + + // Unsafe to allow access to static variables + unsafe { + let bus = UsbBus::new(usb); + + USB_BUS = Some(bus); + + USB_SERIAL = Some(SerialPort::new(USB_BUS.as_ref().unwrap())); + + let usb_dev = UsbDeviceBuilder::new(USB_BUS.as_ref().unwrap(), UsbVidPid(0x16c0, 0x27dd)) + .manufacturer("Fake company") + .product("Serial port") + .serial_number("TEST") + .device_class(USB_CLASS_CDC) + .build(); + + USB_DEVICE = Some(usb_dev); + } + + let mut nvic = p.NVIC; + + nvic.enable(Interrupt::USB_HP_CAN_TX); + nvic.enable(Interrupt::USB_LP_CAN_RX0); + + loop { + wfi(); + } +} + +#[interrupt] +fn USB_HP_CAN_TX() { + usb_interrupt(); +} + +#[interrupt] +fn USB_LP_CAN_RX0() { + usb_interrupt(); +} + +fn usb_interrupt() { + let usb_dev = unsafe { USB_DEVICE.as_mut().unwrap() }; + let serial = unsafe { USB_SERIAL.as_mut().unwrap() }; + + if !usb_dev.poll(&mut [serial]) { + return; + } + + let mut buf = [0u8; 8]; + + match serial.read(&mut buf) { + Ok(count) if count > 0 => { + // Echo back in upper case + for c in buf[0..count].iter_mut() { + if 0x61 <= *c && *c <= 0x7a { + *c &= !0x20; + } + } + + serial.write(&buf[0..count]).ok(); + } + _ => {} + } +} diff --git a/examples/usb_serial_rtfm.rs b/examples/usb_serial_rtfm.rs new file mode 100644 index 00000000..527310c2 --- /dev/null +++ b/examples/usb_serial_rtfm.rs @@ -0,0 +1,107 @@ +//! CDC-ACM serial port example using cortex-m-rtfm. +//! Target board: Blue Pill +#![no_main] +#![no_std] +#![allow(non_snake_case)] + +extern crate panic_semihosting; + +use cortex_m::asm::delay; +use embedded_hal::digital::v2::OutputPin; +use rtfm::app; +use stm32f1xx_hal::prelude::*; +use stm32f1xx_hal::usb::{Peripheral, UsbBus, UsbBusType}; +use usb_device::bus; +use usb_device::prelude::*; +use usbd_serial::{SerialPort, USB_CLASS_CDC}; + +#[app(device = stm32f1xx_hal::stm32)] +const APP: () = { + static mut USB_DEV: UsbDevice<'static, UsbBusType> = (); + static mut SERIAL: SerialPort<'static, UsbBusType> = (); + + #[init] + fn init() { + static mut USB_BUS: Option> = None; + + let mut flash = device.FLASH.constrain(); + let mut rcc = device.RCC.constrain(); + + let clocks = rcc + .cfgr + .use_hse(8.mhz()) + .sysclk(48.mhz()) + .pclk1(24.mhz()) + .freeze(&mut flash.acr); + + assert!(clocks.usbclk_valid()); + + let mut gpioa = device.GPIOA.split(&mut rcc.apb2); + + // BluePill board has a pull-up resistor on the D+ line. + // Pull the D+ pin down to send a RESET condition to the USB bus. + // This forced reset is needed only for development, without it host + // will not reset your device when you upload new firmware. + let mut usb_dp = gpioa.pa12.into_push_pull_output(&mut gpioa.crh); + usb_dp.set_low(); + delay(clocks.sysclk().0 / 100); + + let usb_dm = gpioa.pa11; + let usb_dp = usb_dp.into_floating_input(&mut gpioa.crh); + + let usb = Peripheral { + usb: device.USB, + pin_dm: usb_dm, + pin_dp: usb_dp, + }; + + *USB_BUS = Some(UsbBus::new(usb)); + + let serial = SerialPort::new(USB_BUS.as_ref().unwrap()); + + let usb_dev = UsbDeviceBuilder::new(USB_BUS.as_ref().unwrap(), UsbVidPid(0x16c0, 0x27dd)) + .manufacturer("Fake company") + .product("Serial port") + .serial_number("TEST") + .device_class(USB_CLASS_CDC) + .build(); + + USB_DEV = usb_dev; + SERIAL = serial; + } + + #[interrupt(resources = [USB_DEV, SERIAL])] + fn USB_HP_CAN_TX() { + usb_poll(&mut resources.USB_DEV, &mut resources.SERIAL); + } + + #[interrupt(resources = [USB_DEV, SERIAL])] + fn USB_LP_CAN_RX0() { + usb_poll(&mut resources.USB_DEV, &mut resources.SERIAL); + } +}; + +fn usb_poll( + usb_dev: &mut UsbDevice<'static, B>, + serial: &mut SerialPort<'static, B>, +) { + if !usb_dev.poll(&mut [serial]) { + return; + } + + let mut buf = [0u8; 8]; + + match serial.read(&mut buf) { + Ok(count) if count > 0 => { + // Echo back in upper case + for c in buf[0..count].iter_mut() { + if 0x61 <= *c && *c <= 0x7a { + *c &= !0x20; + } + } + + serial.write(&buf[0..count]).ok(); + } + _ => {} + } +} diff --git a/src/lib.rs b/src/lib.rs index 29af657a..f57dd17b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -160,5 +160,10 @@ pub mod spi; pub mod time; #[cfg(feature = "device-selected")] pub mod timer; +#[cfg(all( + feature = "stm32-usbd", + any(feature = "stm32f102", feature = "stm32f103") +))] +pub mod usb; #[cfg(feature = "device-selected")] pub mod watchdog; diff --git a/src/usb.rs b/src/usb.rs new file mode 100644 index 00000000..9a6fd9b9 --- /dev/null +++ b/src/usb.rs @@ -0,0 +1,44 @@ +//! USB peripheral + +use crate::pac::{RCC, USB}; +use stm32_usbd::UsbPeripheral; + +use crate::gpio::gpioa::{PA11, PA12}; +use crate::gpio::{Floating, Input}; +pub use stm32_usbd::UsbBus; + +pub struct Peripheral { + pub usb: USB, + pub pin_dm: PA11>, + pub pin_dp: PA12>, +} + +unsafe impl Sync for Peripheral {} + +unsafe impl UsbPeripheral for Peripheral { + const REGISTERS: *const () = USB::ptr() as *const (); + const DP_PULL_UP_FEATURE: bool = false; + const EP_MEMORY: *const () = 0x4000_6000 as _; + const EP_MEMORY_SIZE: usize = 512; + + fn enable() { + let rcc = unsafe { (&*RCC::ptr()) }; + + cortex_m::interrupt::free(|_| { + // Enable USB peripheral + rcc.apb1enr.modify(|_, w| w.usben().set_bit()); + + // Reset USB peripheral + rcc.apb1rstr.modify(|_, w| w.usbrst().set_bit()); + rcc.apb1rstr.modify(|_, w| w.usbrst().clear_bit()); + }); + } + + fn startup_delay() { + // There is a chip specific startup delay. For STM32F103xx it's 1µs and this should wait for + // at least that long. + cortex_m::asm::delay(72); + } +} + +pub type UsbBusType = UsbBus;