Skip to content

Add USB driver #135

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

Merged
merged 2 commits into from
Nov 13, 2019
Merged
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
19 changes: 19 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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"]
96 changes: 96 additions & 0 deletions examples/usb_serial.rs
Original file line number Diff line number Diff line change
@@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need that? I've removed it from my keyboard, and it still work fine.

Copy link
Member Author

Choose a reason for hiding this comment

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

This code is needed only for development. Without it host doesn't reset your device when you upload new firmware.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe that could be documented in the comment above.


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
}
}
118 changes: 118 additions & 0 deletions examples/usb_serial_interrupt.rs
Original file line number Diff line number Diff line change
@@ -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<UsbBusAllocator<UsbBusType>> = None;
static mut USB_SERIAL: Option<usbd_serial::SerialPort<UsbBusType>> = None;
static mut USB_DEVICE: Option<UsbDevice<UsbBusType>> = 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();
}
_ => {}
}
}
107 changes: 107 additions & 0 deletions examples/usb_serial_rtfm.rs
Original file line number Diff line number Diff line change
@@ -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<bus::UsbBusAllocator<UsbBusType>> = 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<B: bus::UsbBus>(
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();
}
_ => {}
}
}
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Loading