wip: experimental async usb stack
This commit is contained in:
@ -10,7 +10,8 @@ nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits"]
|
||||
|
||||
[dependencies]
|
||||
embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] }
|
||||
embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] }
|
||||
embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] }
|
||||
embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
|
||||
|
||||
defmt = "0.3"
|
||||
defmt-rtt = "0.3"
|
||||
@ -22,5 +23,3 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa
|
||||
rand = { version = "0.8.4", default-features = false }
|
||||
embedded-storage = "0.3.0"
|
||||
|
||||
usb-device = "0.2"
|
||||
usbd-serial = "0.1.1"
|
||||
|
356
examples/nrf/src/bin/usb/cdc_acm.rs
Normal file
356
examples/nrf/src/bin/usb/cdc_acm.rs
Normal file
@ -0,0 +1,356 @@
|
||||
use core::convert::TryInto;
|
||||
use core::mem;
|
||||
use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError};
|
||||
use embassy_usb::{driver::Driver, types::*, UsbDeviceBuilder};
|
||||
|
||||
/// 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<'d, D: Driver<'d>> {
|
||||
comm_if: InterfaceNumber,
|
||||
comm_ep: D::EndpointIn,
|
||||
data_if: InterfaceNumber,
|
||||
read_ep: D::EndpointOut,
|
||||
write_ep: D::EndpointIn,
|
||||
line_coding: LineCoding,
|
||||
dtr: bool,
|
||||
rts: bool,
|
||||
}
|
||||
|
||||
impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> {
|
||||
/// 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(builder: &mut UsbDeviceBuilder<'d, D>, max_packet_size: u16) -> Self {
|
||||
let comm_if = builder.alloc_interface();
|
||||
let comm_ep = builder.alloc_interrupt_endpoint_in(8, 255);
|
||||
let data_if = builder.alloc_interface();
|
||||
let read_ep = builder.alloc_bulk_endpoint_out(max_packet_size);
|
||||
let write_ep = builder.alloc_bulk_endpoint_in(max_packet_size);
|
||||
|
||||
builder
|
||||
.config_descriptor
|
||||
.iad(
|
||||
comm_if,
|
||||
2,
|
||||
USB_CLASS_CDC,
|
||||
CDC_SUBCLASS_ACM,
|
||||
CDC_PROTOCOL_NONE,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
builder
|
||||
.config_descriptor
|
||||
.interface(comm_if, USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE)
|
||||
.unwrap();
|
||||
|
||||
builder
|
||||
.config_descriptor
|
||||
.write(
|
||||
CS_INTERFACE,
|
||||
&[
|
||||
CDC_TYPE_HEADER, // bDescriptorSubtype
|
||||
0x10,
|
||||
0x01, // bcdCDC (1.10)
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
builder
|
||||
.config_descriptor
|
||||
.write(
|
||||
CS_INTERFACE,
|
||||
&[
|
||||
CDC_TYPE_ACM, // bDescriptorSubtype
|
||||
0x00, // bmCapabilities
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
builder
|
||||
.config_descriptor
|
||||
.write(
|
||||
CS_INTERFACE,
|
||||
&[
|
||||
CDC_TYPE_UNION, // bDescriptorSubtype
|
||||
comm_if.into(), // bControlInterface
|
||||
data_if.into(), // bSubordinateInterface
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
builder
|
||||
.config_descriptor
|
||||
.write(
|
||||
CS_INTERFACE,
|
||||
&[
|
||||
CDC_TYPE_CALL_MANAGEMENT, // bDescriptorSubtype
|
||||
0x00, // bmCapabilities
|
||||
data_if.into(), // bDataInterface
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
builder.config_descriptor.endpoint(comm_ep.info()).unwrap();
|
||||
|
||||
builder
|
||||
.config_descriptor
|
||||
.interface(data_if, USB_CLASS_CDC_DATA, 0x00, 0x00)
|
||||
.unwrap();
|
||||
|
||||
builder.config_descriptor.endpoint(write_ep.info()).unwrap();
|
||||
builder.config_descriptor.endpoint(read_ep.info()).unwrap();
|
||||
|
||||
CdcAcmClass {
|
||||
comm_if,
|
||||
comm_ep,
|
||||
data_if,
|
||||
read_ep,
|
||||
write_ep,
|
||||
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.info().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 async fn write_packet(&mut self, data: &[u8]) -> Result<(), WriteError> {
|
||||
self.write_ep.write(data).await
|
||||
}
|
||||
|
||||
/// Reads a single packet from the OUT endpoint.
|
||||
pub async fn read_packet(&mut self, data: &mut [u8]) -> Result<usize, ReadError> {
|
||||
self.read_ep.read(data).await
|
||||
}
|
||||
|
||||
/// Gets the address of the IN endpoint.
|
||||
pub(crate) fn write_ep_address(&self) -> EndpointAddress {
|
||||
self.write_ep.info().addr
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
impl<B: UsbBus> UsbClass<B> for CdcAcmClass<'_, B> {
|
||||
fn get_configuration_descriptors(&self, builder.config_descriptor: &mut Descriptorbuilder.config_descriptor) -> Result<()> {
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
53
examples/nrf/src/bin/usb/main.rs
Normal file
53
examples/nrf/src/bin/usb/main.rs
Normal file
@ -0,0 +1,53 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
|
||||
#[path = "../../example_common.rs"]
|
||||
mod example_common;
|
||||
|
||||
mod cdc_acm;
|
||||
|
||||
use core::mem;
|
||||
use defmt::*;
|
||||
use embassy::executor::Spawner;
|
||||
use embassy_nrf::interrupt;
|
||||
use embassy_nrf::pac;
|
||||
use embassy_nrf::usb::{self, Driver};
|
||||
use embassy_nrf::Peripherals;
|
||||
use embassy_usb::{Config, UsbDeviceBuilder};
|
||||
|
||||
use crate::cdc_acm::CdcAcmClass;
|
||||
|
||||
#[embassy::main]
|
||||
async fn main(_spawner: Spawner, p: Peripherals) {
|
||||
let clock: pac::CLOCK = unsafe { mem::transmute(()) };
|
||||
let power: pac::POWER = unsafe { mem::transmute(()) };
|
||||
|
||||
info!("Enabling ext hfosc...");
|
||||
clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) });
|
||||
while clock.events_hfclkstarted.read().bits() != 1 {}
|
||||
|
||||
info!("Waiting for vbus...");
|
||||
while !power.usbregstatus.read().vbusdetect().is_vbus_present() {}
|
||||
info!("vbus OK");
|
||||
|
||||
let irq = interrupt::take!(USBD);
|
||||
let driver = Driver::new(p.USBD, irq);
|
||||
let config = Config::new(0xc0de, 0xcafe);
|
||||
let mut device_descriptor = [0; 256];
|
||||
let mut config_descriptor = [0; 256];
|
||||
let mut bos_descriptor = [0; 256];
|
||||
|
||||
let mut builder = UsbDeviceBuilder::new(
|
||||
driver,
|
||||
config,
|
||||
&mut device_descriptor,
|
||||
&mut config_descriptor,
|
||||
&mut bos_descriptor,
|
||||
);
|
||||
|
||||
let mut class = CdcAcmClass::new(&mut builder, 64);
|
||||
|
||||
let mut usb = builder.build();
|
||||
usb.run().await;
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
|
||||
use defmt::{info, unwrap};
|
||||
use embassy::executor::Spawner;
|
||||
use embassy::interrupt::InterruptExt;
|
||||
use embassy::io::{AsyncBufReadExt, AsyncWriteExt};
|
||||
use embassy_nrf::usb::{State, Usb, UsbBus, UsbSerial};
|
||||
use embassy_nrf::{interrupt, Peripherals};
|
||||
use futures::pin_mut;
|
||||
use usb_device::device::{UsbDeviceBuilder, UsbVidPid};
|
||||
|
||||
use defmt_rtt as _; // global logger
|
||||
use panic_probe as _; // print out panic messages
|
||||
|
||||
#[embassy::main]
|
||||
async fn main(_spawner: Spawner, p: Peripherals) {
|
||||
let mut rx_buffer = [0u8; 64];
|
||||
// we send back input + cr + lf
|
||||
let mut tx_buffer = [0u8; 66];
|
||||
|
||||
let usb_bus = UsbBus::new(p.USBD);
|
||||
|
||||
let serial = UsbSerial::new(&usb_bus, &mut rx_buffer, &mut tx_buffer);
|
||||
|
||||
let device = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd))
|
||||
.manufacturer("Fake company")
|
||||
.product("Serial port")
|
||||
.serial_number("TEST")
|
||||
.device_class(0x02)
|
||||
.build();
|
||||
|
||||
let irq = interrupt::take!(USBD);
|
||||
irq.set_priority(interrupt::Priority::P3);
|
||||
|
||||
let mut state = State::new();
|
||||
let usb = unsafe { Usb::new(&mut state, device, serial, irq) };
|
||||
pin_mut!(usb);
|
||||
|
||||
let (mut reader, mut writer) = usb.as_ref().take_serial_0();
|
||||
|
||||
info!("usb initialized!");
|
||||
|
||||
unwrap!(
|
||||
writer
|
||||
.write_all(b"\r\nInput returned upper cased on CR+LF\r\n")
|
||||
.await
|
||||
);
|
||||
|
||||
let mut buf = [0u8; 64];
|
||||
loop {
|
||||
let mut n = 0;
|
||||
|
||||
async {
|
||||
loop {
|
||||
let char = unwrap!(reader.read_byte().await);
|
||||
|
||||
// throw away, read more on cr, exit on lf
|
||||
if char == b'\r' {
|
||||
continue;
|
||||
} else if char == b'\n' {
|
||||
break;
|
||||
}
|
||||
|
||||
buf[n] = char;
|
||||
n += 1;
|
||||
|
||||
// stop if we're out of room
|
||||
if n == buf.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
.await;
|
||||
|
||||
if n > 0 {
|
||||
for char in buf[..n].iter_mut() {
|
||||
// upper case
|
||||
if 0x61 <= *char && *char <= 0x7a {
|
||||
*char &= !0x20;
|
||||
}
|
||||
}
|
||||
unwrap!(writer.write_all(&buf[..n]).await);
|
||||
unwrap!(writer.write_all(b"\r\n").await);
|
||||
unwrap!(writer.flush().await);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
|
||||
use defmt::{info, unwrap};
|
||||
use embassy::executor::Spawner;
|
||||
use embassy::interrupt::InterruptExt;
|
||||
use embassy::io::{read_line, AsyncWriteExt};
|
||||
use embassy_nrf::usb::{State, Usb, UsbBus, UsbSerial};
|
||||
use embassy_nrf::{interrupt, Peripherals};
|
||||
use futures::pin_mut;
|
||||
use usb_device::device::{UsbDeviceBuilder, UsbVidPid};
|
||||
|
||||
use defmt_rtt as _; // global logger
|
||||
use panic_probe as _; // print out panic messages
|
||||
|
||||
#[embassy::main]
|
||||
async fn main(_spawner: Spawner, p: Peripherals) {
|
||||
let mut rx_buffer = [0u8; 64];
|
||||
// we send back input + cr + lf
|
||||
let mut tx_buffer = [0u8; 66];
|
||||
|
||||
let usb_bus = UsbBus::new(p.USBD);
|
||||
|
||||
let serial = UsbSerial::new(&usb_bus, &mut rx_buffer, &mut tx_buffer);
|
||||
|
||||
let device = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd))
|
||||
.manufacturer("Fake company")
|
||||
.product("Serial port")
|
||||
.serial_number("TEST")
|
||||
.device_class(0x02)
|
||||
.build();
|
||||
|
||||
let irq = interrupt::take!(USBD);
|
||||
irq.set_priority(interrupt::Priority::P3);
|
||||
|
||||
let mut state = State::new();
|
||||
let usb = unsafe { Usb::new(&mut state, device, serial, irq) };
|
||||
pin_mut!(usb);
|
||||
|
||||
let (mut reader, mut writer) = usb.as_ref().take_serial_0();
|
||||
|
||||
info!("usb initialized!");
|
||||
|
||||
unwrap!(
|
||||
writer
|
||||
.write_all(b"\r\nInput returned upper cased on CR+LF\r\n")
|
||||
.await
|
||||
);
|
||||
|
||||
let mut buf = [0u8; 64];
|
||||
loop {
|
||||
let n = unwrap!(read_line(&mut reader, &mut buf).await);
|
||||
|
||||
for char in buf[..n].iter_mut() {
|
||||
// upper case
|
||||
if 0x61 <= *char && *char <= 0x7a {
|
||||
*char &= !0x20;
|
||||
}
|
||||
}
|
||||
|
||||
unwrap!(writer.write_all(&buf[..n]).await);
|
||||
unwrap!(writer.write_all(b"\r\n").await);
|
||||
unwrap!(writer.flush().await);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user