Start working on usb serial
This commit is contained in:
parent
6719da3b6e
commit
890e93b4f0
@ -46,7 +46,8 @@ cortex-m = "0.7.1"
|
||||
cortex-m-rt = "0.6.13"
|
||||
embedded-hal = { version = "0.2.4" }
|
||||
panic-probe = "0.1.0"
|
||||
stm32f4xx-hal = { version = "0.8.3", features = ["rt"], git = "https://github.com/stm32-rs/stm32f4xx-hal.git"}
|
||||
stm32f4xx-hal = { version = "0.8.3", features = ["rt", "usb_fs"], git = "https://github.com/stm32-rs/stm32f4xx-hal.git"}
|
||||
futures = { version = "0.3.8", default-features = false, features = ["async-await"] }
|
||||
rtt-target = { version = "0.3", features = ["cortex-m"] }
|
||||
bxcan = "0.5.0"
|
||||
usb-device = "0.2.7"
|
||||
|
119
embassy-stm32f4-examples/src/bin/usb_serial.rs
Normal file
119
embassy-stm32f4-examples/src/bin/usb_serial.rs
Normal file
@ -0,0 +1,119 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
|
||||
#[path = "../example_common.rs"]
|
||||
mod example_common;
|
||||
use example_common::*;
|
||||
|
||||
use cortex_m_rt::entry;
|
||||
use defmt::panic;
|
||||
use embassy::executor::{task, Executor};
|
||||
use embassy::io::{AsyncBufReadExt, AsyncWriteExt};
|
||||
use embassy::time::{Duration, Timer};
|
||||
use embassy::util::Forever;
|
||||
use embassy_stm32f4::interrupt::OwnedInterrupt;
|
||||
use embassy_stm32f4::usb::Usb;
|
||||
use embassy_stm32f4::usb_serial::UsbSerial;
|
||||
use embassy_stm32f4::{interrupt, pac, rtc};
|
||||
use futures::future::{select, Either};
|
||||
use futures::pin_mut;
|
||||
use stm32f4xx_hal::otg_fs::{UsbBus, USB};
|
||||
use stm32f4xx_hal::prelude::*;
|
||||
use usb_device::bus::UsbBusAllocator;
|
||||
use usb_device::prelude::*;
|
||||
|
||||
#[task]
|
||||
async fn run1(bus: &'static mut UsbBusAllocator<UsbBus<USB>>) {
|
||||
info!("Async task");
|
||||
|
||||
let mut read_buf = [0u8; 128];
|
||||
let mut write_buf = [0u8; 128];
|
||||
let serial = UsbSerial::new(bus, &mut read_buf, &mut write_buf);
|
||||
|
||||
let device = UsbDeviceBuilder::new(bus, UsbVidPid(0x16c0, 0x27dd))
|
||||
.manufacturer("Fake company")
|
||||
.product("Serial port")
|
||||
.serial_number("TEST")
|
||||
.device_class(0x02)
|
||||
.build();
|
||||
|
||||
let irq = interrupt::take!(OTG_FS);
|
||||
irq.set_priority(interrupt::Priority::Level3);
|
||||
|
||||
let usb = Usb::new(device, serial, irq);
|
||||
pin_mut!(usb);
|
||||
|
||||
let (mut read_interface, mut write_interface) = usb.as_mut().into_ref().take_serial();
|
||||
|
||||
let mut buf = [0u8; 5];
|
||||
loop {
|
||||
let recv_fut = read_interface.read(&mut buf);
|
||||
let timeout = Timer::after(Duration::from_ticks(32768 * 3));
|
||||
|
||||
match select(recv_fut, timeout).await {
|
||||
Either::Left((recv, _)) => {
|
||||
let recv = unwrap!(recv);
|
||||
unwrap!(write_interface.write_all(&buf[..recv]).await);
|
||||
}
|
||||
Either::Right(_) => {
|
||||
unwrap!(write_interface.write_all(b"Hello\r\n").await);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static RTC: Forever<rtc::RTC<pac::TIM2>> = Forever::new();
|
||||
static ALARM: Forever<rtc::Alarm<pac::TIM2>> = Forever::new();
|
||||
static EXECUTOR: Forever<Executor> = Forever::new();
|
||||
static USB_BUS: Forever<UsbBusAllocator<UsbBus<USB>>> = Forever::new();
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
static mut EP_MEMORY: [u32; 1024] = [0; 1024];
|
||||
|
||||
info!("Hello World!");
|
||||
|
||||
let p = unwrap!(pac::Peripherals::take());
|
||||
|
||||
p.RCC.ahb1enr.modify(|_, w| w.dma1en().enabled());
|
||||
let rcc = p.RCC.constrain();
|
||||
let clocks = rcc
|
||||
.cfgr
|
||||
.use_hse(25.mhz())
|
||||
.sysclk(48.mhz())
|
||||
.require_pll48clk()
|
||||
.freeze();
|
||||
|
||||
p.DBGMCU.cr.modify(|_, w| {
|
||||
w.dbg_sleep().set_bit();
|
||||
w.dbg_standby().set_bit();
|
||||
w.dbg_stop().set_bit()
|
||||
});
|
||||
|
||||
let rtc = RTC.put(rtc::RTC::new(p.TIM2, interrupt::take!(TIM2), clocks));
|
||||
rtc.start();
|
||||
|
||||
unsafe { embassy::time::set_clock(rtc) };
|
||||
|
||||
let alarm = ALARM.put(rtc.alarm1());
|
||||
let executor = EXECUTOR.put(Executor::new());
|
||||
executor.set_alarm(alarm);
|
||||
|
||||
let gpioa = p.GPIOA.split();
|
||||
let usb = USB {
|
||||
usb_global: p.OTG_FS_GLOBAL,
|
||||
usb_device: p.OTG_FS_DEVICE,
|
||||
usb_pwrclk: p.OTG_FS_PWRCLK,
|
||||
pin_dm: gpioa.pa11.into_alternate_af10(),
|
||||
pin_dp: gpioa.pa12.into_alternate_af10(),
|
||||
hclk: clocks.hclk(),
|
||||
};
|
||||
// Rust analyzer isn't recognizing the static ref magic `cortex-m` does
|
||||
#[allow(unused_unsafe)]
|
||||
let usb_bus = USB_BUS.put(UsbBus::new(usb, unsafe { EP_MEMORY }));
|
||||
|
||||
executor.run(move |spawner| {
|
||||
unwrap!(spawner.spawn(run1(usb_bus)));
|
||||
});
|
||||
}
|
@ -41,3 +41,4 @@ embedded-dma = { version = "0.1.2" }
|
||||
stm32f4xx-hal = { version = "0.8.3", features = ["rt", "can"], git = "https://github.com/stm32-rs/stm32f4xx-hal.git"}
|
||||
bxcan = "0.5.0"
|
||||
nb = "*"
|
||||
usb-device = "0.2.7"
|
||||
|
338
embassy-stm32f4/src/cdc_acm.rs
Normal file
338
embassy-stm32f4/src/cdc_acm.rs
Normal file
@ -0,0 +1,338 @@
|
||||
// Copied from https://github.com/mvirkkunen/usbd-serial
|
||||
#![allow(dead_code)]
|
||||
|
||||
use core::convert::TryInto;
|
||||
use core::mem;
|
||||
use usb_device::class_prelude::*;
|
||||
use usb_device::Result;
|
||||
|
||||
/// This should be used as `device_class` when building the `UsbDevice`.
|
||||
pub const USB_CLASS_CDC: u8 = 0x02;
|
||||
|
||||
const USB_CLASS_CDC_DATA: u8 = 0x0a;
|
||||
const CDC_SUBCLASS_ACM: u8 = 0x02;
|
||||
const CDC_PROTOCOL_NONE: u8 = 0x00;
|
||||
|
||||
const CS_INTERFACE: u8 = 0x24;
|
||||
const CDC_TYPE_HEADER: u8 = 0x00;
|
||||
const CDC_TYPE_CALL_MANAGEMENT: u8 = 0x01;
|
||||
const CDC_TYPE_ACM: u8 = 0x02;
|
||||
const CDC_TYPE_UNION: u8 = 0x06;
|
||||
|
||||
const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00;
|
||||
#[allow(unused)]
|
||||
const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01;
|
||||
const REQ_SET_LINE_CODING: u8 = 0x20;
|
||||
const REQ_GET_LINE_CODING: u8 = 0x21;
|
||||
const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22;
|
||||
|
||||
/// Packet level implementation of a CDC-ACM serial port.
|
||||
///
|
||||
/// This class can be used directly and it has the least overhead due to directly reading and
|
||||
/// writing USB packets with no intermediate buffers, but it will not act like a stream-like serial
|
||||
/// port. The following constraints must be followed if you use this class directly:
|
||||
///
|
||||
/// - `read_packet` must be called with a buffer large enough to hold max_packet_size bytes, and the
|
||||
/// method will return a `WouldBlock` error if there is no packet to be read.
|
||||
/// - `write_packet` must not be called with a buffer larger than max_packet_size bytes, and the
|
||||
/// method will return a `WouldBlock` error if the previous packet has not been sent yet.
|
||||
/// - If you write a packet that is exactly max_packet_size bytes long, it won't be processed by the
|
||||
/// host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP)
|
||||
/// can be sent if there is no other data to send. This is because USB bulk transactions must be
|
||||
/// terminated with a short packet, even if the bulk endpoint is used for stream-like data.
|
||||
pub struct CdcAcmClass<'a, B: UsbBus> {
|
||||
comm_if: InterfaceNumber,
|
||||
comm_ep: EndpointIn<'a, B>,
|
||||
data_if: InterfaceNumber,
|
||||
read_ep: EndpointOut<'a, B>,
|
||||
write_ep: EndpointIn<'a, B>,
|
||||
line_coding: LineCoding,
|
||||
dtr: bool,
|
||||
rts: bool,
|
||||
}
|
||||
|
||||
impl<B: UsbBus> CdcAcmClass<'_, B> {
|
||||
/// Creates a new CdcAcmClass with the provided UsbBus and max_packet_size in bytes. For
|
||||
/// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64.
|
||||
pub fn new(alloc: &UsbBusAllocator<B>, max_packet_size: u16) -> CdcAcmClass<'_, B> {
|
||||
CdcAcmClass {
|
||||
comm_if: alloc.interface(),
|
||||
comm_ep: alloc.interrupt(8, 255),
|
||||
data_if: alloc.interface(),
|
||||
read_ep: alloc.bulk(max_packet_size),
|
||||
write_ep: alloc.bulk(max_packet_size),
|
||||
line_coding: LineCoding {
|
||||
stop_bits: StopBits::One,
|
||||
data_bits: 8,
|
||||
parity_type: ParityType::None,
|
||||
data_rate: 8_000,
|
||||
},
|
||||
dtr: false,
|
||||
rts: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the maximum packet size in bytes.
|
||||
pub fn max_packet_size(&self) -> u16 {
|
||||
// The size is the same for both endpoints.
|
||||
self.read_ep.max_packet_size()
|
||||
}
|
||||
|
||||
/// Gets the current line coding. The line coding contains information that's mainly relevant
|
||||
/// for USB to UART serial port emulators, and can be ignored if not relevant.
|
||||
pub fn line_coding(&self) -> &LineCoding {
|
||||
&self.line_coding
|
||||
}
|
||||
|
||||
/// Gets the DTR (data terminal ready) state
|
||||
pub fn dtr(&self) -> bool {
|
||||
self.dtr
|
||||
}
|
||||
|
||||
/// Gets the RTS (request to send) state
|
||||
pub fn rts(&self) -> bool {
|
||||
self.rts
|
||||
}
|
||||
|
||||
/// Writes a single packet into the IN endpoint.
|
||||
pub fn write_packet(&mut self, data: &[u8]) -> Result<usize> {
|
||||
self.write_ep.write(data)
|
||||
}
|
||||
|
||||
/// Reads a single packet from the OUT endpoint.
|
||||
pub fn read_packet(&mut self, data: &mut [u8]) -> Result<usize> {
|
||||
self.read_ep.read(data)
|
||||
}
|
||||
|
||||
/// Gets the address of the IN endpoint.
|
||||
pub fn write_ep_address(&self) -> EndpointAddress {
|
||||
self.write_ep.address()
|
||||
}
|
||||
|
||||
/// Gets the address of the OUT endpoint.
|
||||
pub fn read_ep_address(&self) -> EndpointAddress {
|
||||
self.read_ep.address()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: UsbBus> UsbClass<B> for CdcAcmClass<'_, B> {
|
||||
fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<()> {
|
||||
writer.iad(
|
||||
self.comm_if,
|
||||
2,
|
||||
USB_CLASS_CDC,
|
||||
CDC_SUBCLASS_ACM,
|
||||
CDC_PROTOCOL_NONE,
|
||||
)?;
|
||||
|
||||
writer.interface(
|
||||
self.comm_if,
|
||||
USB_CLASS_CDC,
|
||||
CDC_SUBCLASS_ACM,
|
||||
CDC_PROTOCOL_NONE,
|
||||
)?;
|
||||
|
||||
writer.write(
|
||||
CS_INTERFACE,
|
||||
&[
|
||||
CDC_TYPE_HEADER, // bDescriptorSubtype
|
||||
0x10,
|
||||
0x01, // bcdCDC (1.10)
|
||||
],
|
||||
)?;
|
||||
|
||||
writer.write(
|
||||
CS_INTERFACE,
|
||||
&[
|
||||
CDC_TYPE_ACM, // bDescriptorSubtype
|
||||
0x00, // bmCapabilities
|
||||
],
|
||||
)?;
|
||||
|
||||
writer.write(
|
||||
CS_INTERFACE,
|
||||
&[
|
||||
CDC_TYPE_UNION, // bDescriptorSubtype
|
||||
self.comm_if.into(), // bControlInterface
|
||||
self.data_if.into(), // bSubordinateInterface
|
||||
],
|
||||
)?;
|
||||
|
||||
writer.write(
|
||||
CS_INTERFACE,
|
||||
&[
|
||||
CDC_TYPE_CALL_MANAGEMENT, // bDescriptorSubtype
|
||||
0x00, // bmCapabilities
|
||||
self.data_if.into(), // bDataInterface
|
||||
],
|
||||
)?;
|
||||
|
||||
writer.endpoint(&self.comm_ep)?;
|
||||
|
||||
writer.interface(self.data_if, USB_CLASS_CDC_DATA, 0x00, 0x00)?;
|
||||
|
||||
writer.endpoint(&self.write_ep)?;
|
||||
writer.endpoint(&self.read_ep)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.line_coding = LineCoding::default();
|
||||
self.dtr = false;
|
||||
self.rts = false;
|
||||
}
|
||||
|
||||
fn control_in(&mut self, xfer: ControlIn<B>) {
|
||||
let req = xfer.request();
|
||||
|
||||
if !(req.request_type == control::RequestType::Class
|
||||
&& req.recipient == control::Recipient::Interface
|
||||
&& req.index == u8::from(self.comm_if) as u16)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
match req.request {
|
||||
// REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below.
|
||||
REQ_GET_LINE_CODING if req.length == 7 => {
|
||||
xfer.accept(|data| {
|
||||
data[0..4].copy_from_slice(&self.line_coding.data_rate.to_le_bytes());
|
||||
data[4] = self.line_coding.stop_bits as u8;
|
||||
data[5] = self.line_coding.parity_type as u8;
|
||||
data[6] = self.line_coding.data_bits;
|
||||
|
||||
Ok(7)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
_ => {
|
||||
xfer.reject().ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn control_out(&mut self, xfer: ControlOut<B>) {
|
||||
let req = xfer.request();
|
||||
|
||||
if !(req.request_type == control::RequestType::Class
|
||||
&& req.recipient == control::Recipient::Interface
|
||||
&& req.index == u8::from(self.comm_if) as u16)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
match req.request {
|
||||
REQ_SEND_ENCAPSULATED_COMMAND => {
|
||||
// We don't actually support encapsulated commands but pretend we do for standards
|
||||
// compatibility.
|
||||
xfer.accept().ok();
|
||||
}
|
||||
REQ_SET_LINE_CODING if xfer.data().len() >= 7 => {
|
||||
self.line_coding.data_rate =
|
||||
u32::from_le_bytes(xfer.data()[0..4].try_into().unwrap());
|
||||
self.line_coding.stop_bits = xfer.data()[4].into();
|
||||
self.line_coding.parity_type = xfer.data()[5].into();
|
||||
self.line_coding.data_bits = xfer.data()[6];
|
||||
|
||||
xfer.accept().ok();
|
||||
}
|
||||
REQ_SET_CONTROL_LINE_STATE => {
|
||||
self.dtr = (req.value & 0x0001) != 0;
|
||||
self.rts = (req.value & 0x0002) != 0;
|
||||
|
||||
xfer.accept().ok();
|
||||
}
|
||||
_ => {
|
||||
xfer.reject().ok();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of stop bits for LineCoding
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum StopBits {
|
||||
/// 1 stop bit
|
||||
One = 0,
|
||||
|
||||
/// 1.5 stop bits
|
||||
OnePointFive = 1,
|
||||
|
||||
/// 2 stop bits
|
||||
Two = 2,
|
||||
}
|
||||
|
||||
impl From<u8> for StopBits {
|
||||
fn from(value: u8) -> Self {
|
||||
if value <= 2 {
|
||||
unsafe { mem::transmute(value) }
|
||||
} else {
|
||||
StopBits::One
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parity for LineCoding
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum ParityType {
|
||||
None = 0,
|
||||
Odd = 1,
|
||||
Event = 2,
|
||||
Mark = 3,
|
||||
Space = 4,
|
||||
}
|
||||
|
||||
impl From<u8> for ParityType {
|
||||
fn from(value: u8) -> Self {
|
||||
if value <= 4 {
|
||||
unsafe { mem::transmute(value) }
|
||||
} else {
|
||||
ParityType::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Line coding parameters
|
||||
///
|
||||
/// This is provided by the host for specifying the standard UART parameters such as baud rate. Can
|
||||
/// be ignored if you don't plan to interface with a physical UART.
|
||||
pub struct LineCoding {
|
||||
stop_bits: StopBits,
|
||||
data_bits: u8,
|
||||
parity_type: ParityType,
|
||||
data_rate: u32,
|
||||
}
|
||||
|
||||
impl LineCoding {
|
||||
/// Gets the number of stop bits for UART communication.
|
||||
pub fn stop_bits(&self) -> StopBits {
|
||||
self.stop_bits
|
||||
}
|
||||
|
||||
/// Gets the number of data bits for UART communication.
|
||||
pub fn data_bits(&self) -> u8 {
|
||||
self.data_bits
|
||||
}
|
||||
|
||||
/// Gets the parity type for UART communication.
|
||||
pub fn parity_type(&self) -> ParityType {
|
||||
self.parity_type
|
||||
}
|
||||
|
||||
/// Gets the data rate in bits per second for UART communication.
|
||||
pub fn data_rate(&self) -> u32 {
|
||||
self.data_rate
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LineCoding {
|
||||
fn default() -> Self {
|
||||
LineCoding {
|
||||
stop_bits: StopBits::One,
|
||||
data_bits: 8,
|
||||
parity_type: ParityType::None,
|
||||
data_rate: 8_000,
|
||||
}
|
||||
}
|
||||
}
|
@ -316,3 +316,10 @@ pub mod exti;
|
||||
pub mod qei;
|
||||
pub mod rtc;
|
||||
pub mod serial;
|
||||
pub mod usb;
|
||||
pub mod usb_serial;
|
||||
pub mod util;
|
||||
|
||||
pub(crate) mod cdc_acm;
|
||||
|
||||
pub use cortex_m_rt::interrupt;
|
||||
|
130
embassy-stm32f4/src/usb.rs
Normal file
130
embassy-stm32f4/src/usb.rs
Normal file
@ -0,0 +1,130 @@
|
||||
use core::cell::RefCell;
|
||||
use core::marker::PhantomData;
|
||||
use core::pin::Pin;
|
||||
|
||||
use usb_device::bus::UsbBus;
|
||||
use usb_device::class::UsbClass;
|
||||
use usb_device::device::UsbDevice;
|
||||
|
||||
use crate::interrupt;
|
||||
use crate::usb_serial::{ReadInterface, UsbSerial, WriteInterface};
|
||||
use crate::util::peripheral::{PeripheralMutex, PeripheralState};
|
||||
|
||||
pub struct State<'bus, B: UsbBus, T: ClassSet<B>> {
|
||||
device: UsbDevice<'bus, B>,
|
||||
pub(crate) classes: T,
|
||||
}
|
||||
|
||||
pub struct Usb<'bus, B: UsbBus, T: ClassSet<B>> {
|
||||
// Don't you dare moving out `PeripheralMutex`
|
||||
inner: RefCell<PeripheralMutex<State<'bus, B, T>>>,
|
||||
}
|
||||
|
||||
impl<'bus, B, T> Usb<'bus, B, T>
|
||||
where
|
||||
B: UsbBus,
|
||||
T: ClassSet<B>,
|
||||
{
|
||||
pub fn new<S: IntoClassSet<B, T>>(
|
||||
device: UsbDevice<'bus, B>,
|
||||
class_set: S,
|
||||
irq: interrupt::OTG_FSInterrupt,
|
||||
) -> Self {
|
||||
let state = State {
|
||||
device,
|
||||
classes: class_set.into_class_set(),
|
||||
};
|
||||
let mutex = PeripheralMutex::new(state, irq);
|
||||
Self {
|
||||
inner: RefCell::new(mutex),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(self: Pin<&mut Self>) {
|
||||
let this = unsafe { self.get_unchecked_mut() };
|
||||
let mut mutex = this.inner.borrow_mut();
|
||||
let mutex = unsafe { Pin::new_unchecked(&mut *mutex) };
|
||||
|
||||
// Use inner to register the irq
|
||||
mutex.with(|_, _| {});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'bus, 'c, B, T> Usb<'bus, B, T>
|
||||
where
|
||||
B: UsbBus,
|
||||
T: ClassSet<B> + SerialState<'bus, 'c, B>,
|
||||
{
|
||||
pub fn take_serial<'a>(
|
||||
self: Pin<&'a Self>,
|
||||
) -> (
|
||||
ReadInterface<'a, 'bus, 'c, B, T>,
|
||||
WriteInterface<'a, 'bus, 'c, B, T>,
|
||||
) {
|
||||
let this = self.get_ref();
|
||||
|
||||
let r = ReadInterface {
|
||||
inner: &this.inner,
|
||||
_buf_lifetime: PhantomData,
|
||||
};
|
||||
|
||||
let w = WriteInterface {
|
||||
inner: &this.inner,
|
||||
_buf_lifetime: PhantomData,
|
||||
};
|
||||
(r, w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'bus, B, T> PeripheralState for State<'bus, B, T>
|
||||
where
|
||||
B: UsbBus,
|
||||
T: ClassSet<B>,
|
||||
{
|
||||
type Interrupt = interrupt::OTG_FSInterrupt;
|
||||
fn on_interrupt(&mut self) {
|
||||
self.classes.poll_all(&mut self.device);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ClassSet<B: UsbBus> {
|
||||
fn poll_all(&mut self, device: &mut UsbDevice<'_, B>) -> bool;
|
||||
}
|
||||
|
||||
pub trait IntoClassSet<B: UsbBus, C: ClassSet<B>> {
|
||||
fn into_class_set(self) -> C;
|
||||
}
|
||||
|
||||
pub struct ClassSet1<B: UsbBus, T: UsbClass<B>> {
|
||||
class: T,
|
||||
_bus: PhantomData<B>,
|
||||
}
|
||||
|
||||
impl<B, T> ClassSet<B> for ClassSet1<B, T>
|
||||
where
|
||||
B: UsbBus,
|
||||
T: UsbClass<B>,
|
||||
{
|
||||
fn poll_all(&mut self, device: &mut UsbDevice<'_, B>) -> bool {
|
||||
device.poll(&mut [&mut self.class])
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: UsbBus, T: UsbClass<B>> IntoClassSet<B, ClassSet1<B, T>> for T {
|
||||
fn into_class_set(self) -> ClassSet1<B, T> {
|
||||
ClassSet1 {
|
||||
class: self,
|
||||
_bus: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SerialState<'bus, 'a, B: UsbBus> {
|
||||
fn get_serial(&mut self) -> &mut UsbSerial<'bus, 'a, B>;
|
||||
}
|
||||
|
||||
impl<'bus, 'a, B: UsbBus> SerialState<'bus, 'a, B> for ClassSet1<B, UsbSerial<'bus, 'a, B>> {
|
||||
fn get_serial(&mut self) -> &mut UsbSerial<'bus, 'a, B> {
|
||||
&mut self.class
|
||||
}
|
||||
}
|
290
embassy-stm32f4/src/usb_serial.rs
Normal file
290
embassy-stm32f4/src/usb_serial.rs
Normal file
@ -0,0 +1,290 @@
|
||||
use core::cell::RefCell;
|
||||
use core::marker::{PhantomData, PhantomPinned};
|
||||
use core::pin::Pin;
|
||||
use core::task::{Context, Poll};
|
||||
|
||||
use embassy::io::{self, AsyncBufRead, AsyncWrite};
|
||||
use embassy::util::WakerRegistration;
|
||||
use usb_device::bus::UsbBus;
|
||||
use usb_device::class_prelude::*;
|
||||
use usb_device::UsbError;
|
||||
|
||||
use crate::cdc_acm::CdcAcmClass;
|
||||
use crate::usb::{ClassSet, SerialState, State};
|
||||
use crate::util::peripheral::PeripheralMutex;
|
||||
use crate::util::ring_buffer::RingBuffer;
|
||||
|
||||
pub struct ReadInterface<'a, 'bus, 'c, B: UsbBus, T: SerialState<'bus, 'c, B> + ClassSet<B>> {
|
||||
// Don't you dare moving out `PeripheralMutex`
|
||||
pub(crate) inner: &'a RefCell<PeripheralMutex<State<'bus, B, T>>>,
|
||||
pub(crate) _buf_lifetime: PhantomData<&'c T>,
|
||||
}
|
||||
|
||||
/// Write interface for USB CDC_ACM
|
||||
///
|
||||
/// This interface is buffered, meaning that after the write returns the bytes might not be fully
|
||||
/// on the wire just yet
|
||||
pub struct WriteInterface<'a, 'bus, 'c, B: UsbBus, T: SerialState<'bus, 'c, B> + ClassSet<B>> {
|
||||
// Don't you dare moving out `PeripheralMutex`
|
||||
pub(crate) inner: &'a RefCell<PeripheralMutex<State<'bus, B, T>>>,
|
||||
pub(crate) _buf_lifetime: PhantomData<&'c T>,
|
||||
}
|
||||
|
||||
impl<'a, 'bus, 'c, B, T> AsyncBufRead for ReadInterface<'a, 'bus, 'c, B, T>
|
||||
where
|
||||
B: UsbBus,
|
||||
T: SerialState<'bus, 'c, B> + ClassSet<B>,
|
||||
{
|
||||
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
|
||||
let this = self.get_mut();
|
||||
let mut mutex = this.inner.borrow_mut();
|
||||
let mutex = unsafe { Pin::new_unchecked(&mut *mutex) };
|
||||
mutex.with(|state, _irq| {
|
||||
let serial = state.classes.get_serial();
|
||||
let serial = Pin::new(serial);
|
||||
|
||||
match serial.poll_fill_buf(cx) {
|
||||
Poll::Ready(Ok(buf)) => {
|
||||
let buf: &[u8] = buf;
|
||||
let buf: &[u8] = unsafe { core::mem::transmute(buf) };
|
||||
Poll::Ready(Ok(buf))
|
||||
}
|
||||
Poll::Ready(Err(_)) => Poll::Ready(Err(io::Error::Other)),
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn consume(self: Pin<&mut Self>, amt: usize) {
|
||||
let this = self.get_mut();
|
||||
let mut mutex = this.inner.borrow_mut();
|
||||
let mutex = unsafe { Pin::new_unchecked(&mut *mutex) };
|
||||
mutex.with(|state, _irq| {
|
||||
let serial = state.classes.get_serial();
|
||||
let serial = Pin::new(serial);
|
||||
|
||||
serial.consume(amt);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'bus, 'c, B, T> AsyncWrite for WriteInterface<'a, 'bus, 'c, B, T>
|
||||
where
|
||||
B: UsbBus,
|
||||
T: SerialState<'bus, 'c, B> + ClassSet<B>,
|
||||
{
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
let this = self.get_mut();
|
||||
let mut mutex = this.inner.borrow_mut();
|
||||
let mutex = unsafe { Pin::new_unchecked(&mut *mutex) };
|
||||
mutex.with(|state, _irq| {
|
||||
let serial = state.classes.get_serial();
|
||||
let serial = Pin::new(serial);
|
||||
|
||||
serial.poll_write(cx, buf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UsbSerial<'bus, 'a, B: UsbBus> {
|
||||
inner: CdcAcmClass<'bus, B>,
|
||||
read_buf: RingBuffer<'a>,
|
||||
write_buf: RingBuffer<'a>,
|
||||
read_waker: WakerRegistration,
|
||||
write_waker: WakerRegistration,
|
||||
write_state: WriteState,
|
||||
read_error: bool,
|
||||
write_error: bool,
|
||||
}
|
||||
|
||||
impl<'bus, 'a, B: UsbBus> AsyncBufRead for UsbSerial<'bus, 'a, B> {
|
||||
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
|
||||
let this = self.get_mut();
|
||||
|
||||
if this.read_error {
|
||||
this.read_error = false;
|
||||
return Poll::Ready(Err(io::Error::Other));
|
||||
}
|
||||
|
||||
let buf = this.read_buf.pop_buf();
|
||||
if buf.is_empty() {
|
||||
this.read_waker.register(cx.waker());
|
||||
return Poll::Pending;
|
||||
}
|
||||
Poll::Ready(Ok(buf))
|
||||
}
|
||||
|
||||
fn consume(self: Pin<&mut Self>, amt: usize) {
|
||||
self.get_mut().read_buf.pop(amt);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'bus, 'a, B: UsbBus> AsyncWrite for UsbSerial<'bus, 'a, B> {
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
let this = self.get_mut();
|
||||
|
||||
if this.write_error {
|
||||
this.write_error = false;
|
||||
return Poll::Ready(Err(io::Error::Other));
|
||||
}
|
||||
|
||||
let write_buf = this.write_buf.push_buf();
|
||||
if write_buf.is_empty() {
|
||||
this.write_waker.register(cx.waker());
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
let count = write_buf.len().min(buf.len());
|
||||
write_buf[..count].copy_from_slice(&buf[..count]);
|
||||
this.write_buf.push(count);
|
||||
|
||||
this.flush_write();
|
||||
Poll::Ready(Ok(count))
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps track of the type of the last written packet.
|
||||
enum WriteState {
|
||||
/// No packets in-flight
|
||||
Idle,
|
||||
|
||||
/// Short packet currently in-flight
|
||||
Short,
|
||||
|
||||
/// Full packet current in-flight. A full packet must be followed by a short packet for the host
|
||||
/// OS to see the transaction. The data is the number of subsequent full packets sent so far. A
|
||||
/// short packet is forced every SHORT_PACKET_INTERVAL packets so that the OS sees data in a
|
||||
/// timely manner.
|
||||
Full(usize),
|
||||
}
|
||||
|
||||
impl<'bus, 'a, B: UsbBus> UsbSerial<'bus, 'a, B> {
|
||||
pub fn new(
|
||||
alloc: &'bus UsbBusAllocator<B>,
|
||||
read_buf: &'a mut [u8],
|
||||
write_buf: &'a mut [u8],
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: CdcAcmClass::new(alloc, 64),
|
||||
read_buf: RingBuffer::new(read_buf),
|
||||
write_buf: RingBuffer::new(write_buf),
|
||||
read_waker: WakerRegistration::new(),
|
||||
write_waker: WakerRegistration::new(),
|
||||
write_state: WriteState::Idle,
|
||||
read_error: false,
|
||||
write_error: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_write(&mut self) {
|
||||
/// If this many full size packets have been sent in a row, a short packet will be sent so that the
|
||||
/// host sees the data in a timely manner.
|
||||
const SHORT_PACKET_INTERVAL: usize = 10;
|
||||
|
||||
let full_size_packets = match self.write_state {
|
||||
WriteState::Full(c) => c,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
let ep_size = self.inner.max_packet_size() as usize;
|
||||
let max_size = if full_size_packets > SHORT_PACKET_INTERVAL {
|
||||
ep_size - 1
|
||||
} else {
|
||||
ep_size
|
||||
};
|
||||
|
||||
let buf = {
|
||||
let buf = self.write_buf.pop_buf();
|
||||
if buf.len() > max_size {
|
||||
&buf[..max_size]
|
||||
} else {
|
||||
buf
|
||||
}
|
||||
};
|
||||
|
||||
if !buf.is_empty() {
|
||||
let count = match self.inner.write_packet(buf) {
|
||||
Ok(c) => c,
|
||||
Err(UsbError::WouldBlock) => 0,
|
||||
Err(_) => {
|
||||
self.write_error = true;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if buf.len() == ep_size {
|
||||
self.write_state = WriteState::Full(full_size_packets + 1);
|
||||
} else {
|
||||
self.write_state = WriteState::Short;
|
||||
}
|
||||
self.write_buf.pop(count);
|
||||
} else if full_size_packets > 0 {
|
||||
if let Err(e) = self.inner.write_packet(&[]) {
|
||||
if !matches!(e, UsbError::WouldBlock) {
|
||||
self.write_error = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
self.write_state = WriteState::Idle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> UsbClass<B> for UsbSerial<'_, '_, B>
|
||||
where
|
||||
B: UsbBus,
|
||||
{
|
||||
fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<(), UsbError> {
|
||||
self.inner.get_configuration_descriptors(writer)
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
self.read_buf.clear();
|
||||
self.write_buf.clear();
|
||||
self.write_state = WriteState::Idle;
|
||||
}
|
||||
|
||||
fn endpoint_in_complete(&mut self, addr: EndpointAddress) {
|
||||
if addr == self.inner.write_ep_address() {
|
||||
self.write_waker.wake();
|
||||
|
||||
self.flush_write();
|
||||
}
|
||||
}
|
||||
|
||||
fn endpoint_out(&mut self, addr: EndpointAddress) {
|
||||
if addr == self.inner.read_ep_address() {
|
||||
let buf = self.read_buf.push_buf();
|
||||
let count = match self.inner.read_packet(buf) {
|
||||
Ok(c) => c,
|
||||
Err(UsbError::WouldBlock) => 0,
|
||||
Err(_) => {
|
||||
self.read_error = true;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if count > 0 {
|
||||
self.read_buf.push(count);
|
||||
self.read_waker.wake();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn control_in(&mut self, xfer: ControlIn<B>) {
|
||||
self.inner.control_in(xfer);
|
||||
}
|
||||
|
||||
fn control_out(&mut self, xfer: ControlOut<B>) {
|
||||
self.inner.control_out(xfer);
|
||||
}
|
||||
}
|
12
embassy-stm32f4/src/util/mod.rs
Normal file
12
embassy-stm32f4/src/util/mod.rs
Normal file
@ -0,0 +1,12 @@
|
||||
pub mod peripheral;
|
||||
pub mod ring_buffer;
|
||||
|
||||
/// Low power blocking wait loop using WFE/SEV.
|
||||
pub fn low_power_wait_until(mut condition: impl FnMut() -> bool) {
|
||||
while !condition() {
|
||||
// WFE might "eat" an event that would have otherwise woken the executor.
|
||||
cortex_m::asm::wfe();
|
||||
}
|
||||
// Retrigger an event to be transparent to the executor.
|
||||
cortex_m::asm::sev();
|
||||
}
|
78
embassy-stm32f4/src/util/peripheral.rs
Normal file
78
embassy-stm32f4/src/util/peripheral.rs
Normal file
@ -0,0 +1,78 @@
|
||||
use core::cell::UnsafeCell;
|
||||
use core::marker::{PhantomData, PhantomPinned};
|
||||
use core::pin::Pin;
|
||||
use core::sync::atomic::{compiler_fence, Ordering};
|
||||
|
||||
use crate::interrupt::OwnedInterrupt;
|
||||
|
||||
pub trait PeripheralState {
|
||||
type Interrupt: OwnedInterrupt;
|
||||
fn on_interrupt(&mut self);
|
||||
}
|
||||
|
||||
pub struct PeripheralMutex<S: PeripheralState> {
|
||||
inner: Option<(UnsafeCell<S>, S::Interrupt)>,
|
||||
_not_send: PhantomData<*mut ()>,
|
||||
_pinned: PhantomPinned,
|
||||
}
|
||||
|
||||
impl<S: PeripheralState> PeripheralMutex<S> {
|
||||
pub fn new(state: S, irq: S::Interrupt) -> Self {
|
||||
Self {
|
||||
inner: Some((UnsafeCell::new(state), irq)),
|
||||
_not_send: PhantomData,
|
||||
_pinned: PhantomPinned,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with<R>(self: Pin<&mut Self>, f: impl FnOnce(&mut S, &mut S::Interrupt) -> R) -> R {
|
||||
let this = unsafe { self.get_unchecked_mut() };
|
||||
let (state, irq) = unwrap!(this.inner.as_mut());
|
||||
|
||||
irq.disable();
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
|
||||
irq.set_handler(
|
||||
|p| {
|
||||
// Safety: it's OK to get a &mut to the state, since
|
||||
// - We're in the IRQ, no one else can't preempt us
|
||||
// - We can't have preempted a with() call because the irq is disabled during it.
|
||||
let state = unsafe { &mut *(p as *mut S) };
|
||||
state.on_interrupt();
|
||||
},
|
||||
state.get() as *mut (),
|
||||
);
|
||||
|
||||
// Safety: it's OK to get a &mut to the state, since the irq is disabled.
|
||||
let state = unsafe { &mut *state.get() };
|
||||
|
||||
let r = f(state, irq);
|
||||
|
||||
compiler_fence(Ordering::SeqCst);
|
||||
irq.enable();
|
||||
|
||||
r
|
||||
}
|
||||
|
||||
pub fn try_free(self: Pin<&mut Self>) -> Option<(S, S::Interrupt)> {
|
||||
let this = unsafe { self.get_unchecked_mut() };
|
||||
this.inner.take().map(|(state, irq)| {
|
||||
irq.disable();
|
||||
irq.remove_handler();
|
||||
(state.into_inner(), irq)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn free(self: Pin<&mut Self>) -> (S, S::Interrupt) {
|
||||
unwrap!(self.try_free())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: PeripheralState> Drop for PeripheralMutex<S> {
|
||||
fn drop(&mut self) {
|
||||
if let Some((_state, irq)) = &mut self.inner {
|
||||
irq.disable();
|
||||
irq.remove_handler();
|
||||
}
|
||||
}
|
||||
}
|
86
embassy-stm32f4/src/util/ring_buffer.rs
Normal file
86
embassy-stm32f4/src/util/ring_buffer.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use crate::fmt::{assert, *};
|
||||
|
||||
pub struct RingBuffer<'a> {
|
||||
buf: &'a mut [u8],
|
||||
start: usize,
|
||||
end: usize,
|
||||
empty: bool,
|
||||
}
|
||||
|
||||
impl<'a> RingBuffer<'a> {
|
||||
pub fn new(buf: &'a mut [u8]) -> Self {
|
||||
Self {
|
||||
buf,
|
||||
start: 0,
|
||||
end: 0,
|
||||
empty: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_buf(&mut self) -> &mut [u8] {
|
||||
if self.start == self.end && !self.empty {
|
||||
trace!(" ringbuf: push_buf empty");
|
||||
return &mut self.buf[..0];
|
||||
}
|
||||
|
||||
let n = if self.start <= self.end {
|
||||
self.buf.len() - self.end
|
||||
} else {
|
||||
self.start - self.end
|
||||
};
|
||||
|
||||
trace!(" ringbuf: push_buf {:?}..{:?}", self.end, self.end + n);
|
||||
&mut self.buf[self.end..self.end + n]
|
||||
}
|
||||
|
||||
pub fn push(&mut self, n: usize) {
|
||||
trace!(" ringbuf: push {:?}", n);
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
self.end = self.wrap(self.end + n);
|
||||
self.empty = false;
|
||||
}
|
||||
|
||||
pub fn pop_buf(&mut self) -> &mut [u8] {
|
||||
if self.empty {
|
||||
trace!(" ringbuf: pop_buf empty");
|
||||
return &mut self.buf[..0];
|
||||
}
|
||||
|
||||
let n = if self.end <= self.start {
|
||||
self.buf.len() - self.start
|
||||
} else {
|
||||
self.end - self.start
|
||||
};
|
||||
|
||||
trace!(" ringbuf: pop_buf {:?}..{:?}", self.start, self.start + n);
|
||||
&mut self.buf[self.start..self.start + n]
|
||||
}
|
||||
|
||||
pub fn pop(&mut self, n: usize) {
|
||||
trace!(" ringbuf: pop {:?}", n);
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
self.start = self.wrap(self.start + n);
|
||||
self.empty = self.start == self.end;
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.start = 0;
|
||||
self.end = 0;
|
||||
self.empty = true;
|
||||
}
|
||||
|
||||
fn wrap(&self, n: usize) -> usize {
|
||||
assert!(n <= self.buf.len());
|
||||
if n == self.buf.len() {
|
||||
0
|
||||
} else {
|
||||
n
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user