657: Async usb stack r=Dirbaio a=Dirbaio

TODO

- [x] Make it work on nRF
- [x] Add a way for classes to handle their own EP0 control requests - thanks `@alexmoon!`
- [x] Handle CONTROL OUT requests with data.
- [ ] Impl AsyncRead/AsyncWrite for CDC ACM -- will do later, it's not trivial
- [x] Cleanup unwraps/asserts/panics
- [x] Cleanup logs (make everything trace/debug, not info)
- [ ] Port synopsys-usb-otg
- [ ] Port stm32-usbd
- [ ] Add more classes? HID, MSD?


Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
Co-authored-by: alexmoon <alex.r.moon@gmail.com>
This commit is contained in:
bors[bot]
2022-04-07 23:03:39 +00:00
committed by GitHub
30 changed files with 4855 additions and 191 deletions

View File

@ -1,16 +1,19 @@
[package]
authors = ["Dario Nieuwenhuis <dirbaio@dirbaio.net>"]
edition = "2018"
edition = "2021"
name = "embassy-nrf-examples"
version = "0.1.0"
[features]
default = ["nightly"]
nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits"]
nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embassy-usb-serial", "embassy-usb-hid"]
[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"], optional = true }
embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"], optional = true }
embassy-usb-hid = { version = "0.1.0", path = "../../embassy-usb-hid", features = ["defmt"], optional = true }
defmt = "0.3"
defmt-rtt = "0.3"
@ -21,6 +24,5 @@ panic-probe = { version = "0.3", features = ["print-defmt"] }
futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
rand = { version = "0.8.4", default-features = false }
embedded-storage = "0.3.0"
usb-device = "0.2"
usbd-serial = "0.1.1"
usbd-hid = "0.5.2"
serde = { version = "1.0.136", default-features = false }

View File

@ -0,0 +1,148 @@
#![no_std]
#![no_main]
#![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)]
use core::mem;
use defmt::*;
use embassy::executor::Spawner;
use embassy::time::Duration;
use embassy_nrf::gpio::{Input, Pin, Pull};
use embassy_nrf::interrupt;
use embassy_nrf::pac;
use embassy_nrf::usb::Driver;
use embassy_nrf::Peripherals;
use embassy_usb::control::OutResponse;
use embassy_usb::{Config, UsbDeviceBuilder};
use embassy_usb_hid::{HidClass, ReportId, RequestHandler, State};
use futures::future::join;
use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor};
use defmt_rtt as _; // global logger
use panic_probe as _;
#[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");
// Create the driver, from the HAL.
let irq = interrupt::take!(USBD);
let driver = Driver::new(p.USBD, irq);
// Create embassy-usb Config
let mut config = Config::new(0xc0de, 0xcafe);
config.manufacturer = Some("Tactile Engineering");
config.product = Some("Testy");
config.serial_number = Some("12345678");
config.max_power = 100;
config.max_packet_size_0 = 64;
// Create embassy-usb DeviceBuilder using the driver and config.
// It needs some buffers for building the descriptors.
let mut device_descriptor = [0; 256];
let mut config_descriptor = [0; 256];
let mut bos_descriptor = [0; 256];
let mut control_buf = [0; 16];
let request_handler = MyRequestHandler {};
let mut state = State::<8, 1>::new();
let mut builder = UsbDeviceBuilder::new(
driver,
config,
&mut device_descriptor,
&mut config_descriptor,
&mut bos_descriptor,
&mut control_buf,
);
// Create classes on the builder.
let hid = HidClass::with_output_ep(
&mut builder,
&mut state,
KeyboardReport::desc(),
Some(&request_handler),
60,
64,
);
// Build the builder.
let mut usb = builder.build().await;
// Run the USB device.
let usb_fut = usb.run();
let mut button = Input::new(p.P0_11.degrade(), Pull::Up);
let (mut hid_in, hid_out) = hid.split();
// Do stuff with the class!
let in_fut = async {
loop {
button.wait_for_low().await;
info!("PRESSED");
let report = KeyboardReport {
keycodes: [4, 0, 0, 0, 0, 0],
leds: 0,
modifier: 0,
reserved: 0,
};
match hid_in.serialize(&report).await {
Ok(()) => {}
Err(e) => warn!("Failed to send report: {:?}", e),
};
button.wait_for_high().await;
info!("RELEASED");
let report = KeyboardReport {
keycodes: [0, 0, 0, 0, 0, 0],
leds: 0,
modifier: 0,
reserved: 0,
};
match hid_in.serialize(&report).await {
Ok(()) => {}
Err(e) => warn!("Failed to send report: {:?}", e),
};
}
};
let out_fut = async {
hid_out.run(false, &request_handler).await;
};
// Run everything concurrently.
// If we had made everything `'static` above instead, we could do this using separate tasks instead.
join(usb_fut, join(in_fut, out_fut)).await;
}
struct MyRequestHandler {}
impl RequestHandler for MyRequestHandler {
fn get_report(&self, id: ReportId, _buf: &mut [u8]) -> Option<usize> {
info!("Get report for {:?}", id);
None
}
fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse {
info!("Set report for {:?}: {=[u8]}", id, data);
OutResponse::Accepted
}
fn set_idle(&self, id: Option<ReportId>, dur: Duration) {
info!("Set idle rate for {:?} to {:?}", id, dur);
}
fn get_idle(&self, id: Option<ReportId>) -> Option<Duration> {
info!("Get idle rate for {:?}", id);
None
}
}

View File

@ -0,0 +1,129 @@
#![no_std]
#![no_main]
#![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)]
use core::mem;
use defmt::*;
use embassy::executor::Spawner;
use embassy::time::{Duration, Timer};
use embassy_nrf::interrupt;
use embassy_nrf::pac;
use embassy_nrf::usb::Driver;
use embassy_nrf::Peripherals;
use embassy_usb::control::OutResponse;
use embassy_usb::{Config, UsbDeviceBuilder};
use embassy_usb_hid::{HidClass, ReportId, RequestHandler, State};
use futures::future::join;
use usbd_hid::descriptor::{MouseReport, SerializedDescriptor};
use defmt_rtt as _; // global logger
use panic_probe as _;
#[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");
// Create the driver, from the HAL.
let irq = interrupt::take!(USBD);
let driver = Driver::new(p.USBD, irq);
// Create embassy-usb Config
let mut config = Config::new(0xc0de, 0xcafe);
config.manufacturer = Some("Tactile Engineering");
config.product = Some("Testy");
config.serial_number = Some("12345678");
config.max_power = 100;
// Create embassy-usb DeviceBuilder using the driver and config.
// It needs some buffers for building the descriptors.
let mut device_descriptor = [0; 256];
let mut config_descriptor = [0; 256];
let mut bos_descriptor = [0; 256];
let mut control_buf = [0; 16];
let request_handler = MyRequestHandler {};
let mut control = State::<5, 0>::new();
let mut builder = UsbDeviceBuilder::new(
driver,
config,
&mut device_descriptor,
&mut config_descriptor,
&mut bos_descriptor,
&mut control_buf,
);
// Create classes on the builder.
let mut hid = HidClass::new(
&mut builder,
&mut control,
MouseReport::desc(),
Some(&request_handler),
60,
8,
);
// Build the builder.
let mut usb = builder.build().await;
// Run the USB device.
let usb_fut = usb.run();
// Do stuff with the class!
let hid_fut = async {
let mut y: i8 = 5;
loop {
Timer::after(Duration::from_millis(500)).await;
y = -y;
let report = MouseReport {
buttons: 0,
x: 0,
y,
wheel: 0,
pan: 0,
};
match hid.input().serialize(&report).await {
Ok(()) => {}
Err(e) => warn!("Failed to send report: {:?}", e),
}
}
};
// Run everything concurrently.
// If we had made everything `'static` above instead, we could do this using separate tasks instead.
join(usb_fut, hid_fut).await;
}
struct MyRequestHandler {}
impl RequestHandler for MyRequestHandler {
fn get_report(&self, id: ReportId, _buf: &mut [u8]) -> Option<usize> {
info!("Get report for {:?}", id);
None
}
fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse {
info!("Set report for {:?}: {=[u8]}", id, data);
OutResponse::Accepted
}
fn set_idle(&self, id: Option<ReportId>, dur: Duration) {
info!("Set idle rate for {:?} to {:?}", id, dur);
}
fn get_idle(&self, id: Option<ReportId>) -> Option<Duration> {
info!("Get idle rate for {:?}", id);
None
}
}

View File

@ -0,0 +1,113 @@
#![no_std]
#![no_main]
#![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)]
use core::mem;
use defmt::{info, panic};
use embassy::executor::Spawner;
use embassy_nrf::interrupt;
use embassy_nrf::pac;
use embassy_nrf::usb::{Driver, Instance};
use embassy_nrf::Peripherals;
use embassy_usb::driver::{ReadError, WriteError};
use embassy_usb::{Config, UsbDeviceBuilder};
use embassy_usb_serial::{CdcAcmClass, State};
use futures::future::join;
use defmt_rtt as _; // global logger
use panic_probe as _;
#[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");
// Create the driver, from the HAL.
let irq = interrupt::take!(USBD);
let driver = Driver::new(p.USBD, irq);
// Create embassy-usb Config
let config = Config::new(0xc0de, 0xcafe);
// Create embassy-usb DeviceBuilder using the driver and config.
// It needs some buffers for building the descriptors.
let mut device_descriptor = [0; 256];
let mut config_descriptor = [0; 256];
let mut bos_descriptor = [0; 256];
let mut control_buf = [0; 7];
let mut state = State::new();
let mut builder = UsbDeviceBuilder::new(
driver,
config,
&mut device_descriptor,
&mut config_descriptor,
&mut bos_descriptor,
&mut control_buf,
);
// Create classes on the builder.
let mut class = CdcAcmClass::new(&mut builder, &mut state, 64);
// Build the builder.
let mut usb = builder.build().await;
// Run the USB device.
let usb_fut = usb.run();
// Do stuff with the class!
let echo_fut = async {
loop {
class.wait_connection().await;
info!("Connected");
let _ = echo(&mut class).await;
info!("Disconnected");
}
};
// Run everything concurrently.
// If we had made everything `'static` above instead, we could do this using separate tasks instead.
join(usb_fut, echo_fut).await;
}
struct Disconnected {}
impl From<ReadError> for Disconnected {
fn from(val: ReadError) -> Self {
match val {
ReadError::BufferOverflow => panic!("Buffer overflow"),
ReadError::Disabled => Disconnected {},
}
}
}
impl From<WriteError> for Disconnected {
fn from(val: WriteError) -> Self {
match val {
WriteError::BufferOverflow => panic!("Buffer overflow"),
WriteError::Disabled => Disconnected {},
}
}
}
async fn echo<'d, T: Instance + 'd>(
class: &mut CdcAcmClass<'d, Driver<'d, T>>,
) -> Result<(), Disconnected> {
let mut buf = [0; 64];
loop {
let n = class.read_packet(&mut buf).await?;
let data = &buf[..n];
info!("data: {:x}", data);
class.write_packet(data).await?;
}
}

View File

@ -0,0 +1,122 @@
#![no_std]
#![no_main]
#![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)]
use core::mem;
use defmt::{info, panic, unwrap};
use embassy::executor::Spawner;
use embassy::util::Forever;
use embassy_nrf::pac;
use embassy_nrf::usb::Driver;
use embassy_nrf::Peripherals;
use embassy_nrf::{interrupt, peripherals};
use embassy_usb::driver::{ReadError, WriteError};
use embassy_usb::{Config, UsbDevice, UsbDeviceBuilder};
use embassy_usb_serial::{CdcAcmClass, State};
use defmt_rtt as _; // global logger
use panic_probe as _;
type MyDriver = Driver<'static, peripherals::USBD>;
#[embassy::task]
async fn usb_task(mut device: UsbDevice<'static, MyDriver>) {
device.run().await;
}
#[embassy::task]
async fn echo_task(mut class: CdcAcmClass<'static, MyDriver>) {
loop {
class.wait_connection().await;
info!("Connected");
let _ = echo(&mut class).await;
info!("Disconnected");
}
}
#[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");
// Create the driver, from the HAL.
let irq = interrupt::take!(USBD);
let driver = Driver::new(p.USBD, irq);
// Create embassy-usb Config
let config = Config::new(0xc0de, 0xcafe);
struct Resources {
device_descriptor: [u8; 256],
config_descriptor: [u8; 256],
bos_descriptor: [u8; 256],
control_buf: [u8; 7],
serial_state: State<'static>,
}
static RESOURCES: Forever<Resources> = Forever::new();
let res = RESOURCES.put(Resources {
device_descriptor: [0; 256],
config_descriptor: [0; 256],
bos_descriptor: [0; 256],
control_buf: [0; 7],
serial_state: State::new(),
});
// Create embassy-usb DeviceBuilder using the driver and config.
let mut builder = UsbDeviceBuilder::new(
driver,
config,
&mut res.device_descriptor,
&mut res.config_descriptor,
&mut res.bos_descriptor,
&mut res.control_buf,
);
// Create classes on the builder.
let class = CdcAcmClass::new(&mut builder, &mut res.serial_state, 64);
// Build the builder.
let usb = builder.build().await;
unwrap!(spawner.spawn(usb_task(usb)));
unwrap!(spawner.spawn(echo_task(class)));
}
struct Disconnected {}
impl From<ReadError> for Disconnected {
fn from(val: ReadError) -> Self {
match val {
ReadError::BufferOverflow => panic!("Buffer overflow"),
ReadError::Disabled => Disconnected {},
}
}
}
impl From<WriteError> for Disconnected {
fn from(val: WriteError) -> Self {
match val {
WriteError::BufferOverflow => panic!("Buffer overflow"),
WriteError::Disabled => Disconnected {},
}
}
}
async fn echo(class: &mut CdcAcmClass<'static, MyDriver>) -> Result<(), Disconnected> {
let mut buf = [0; 64];
loop {
let n = class.read_packet(&mut buf).await?;
let data = &buf[..n];
info!("data: {:x}", data);
class.write_packet(data).await?;
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}