From 37598a5b3792ec1b763b5c16fe422c9e1347d7d6 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 9 Mar 2022 01:34:35 +0100 Subject: wip: experimental async usb stack --- examples/nrf/Cargo.toml | 5 +- examples/nrf/src/bin/usb/cdc_acm.rs | 356 ++++++++++++++++++++++++++++++++++++ examples/nrf/src/bin/usb/main.rs | 53 ++++++ examples/nrf/src/bin/usb_uart.rs | 89 --------- examples/nrf/src/bin/usb_uart_io.rs | 66 ------- 5 files changed, 411 insertions(+), 158 deletions(-) create mode 100644 examples/nrf/src/bin/usb/cdc_acm.rs create mode 100644 examples/nrf/src/bin/usb/main.rs delete mode 100644 examples/nrf/src/bin/usb_uart.rs delete mode 100644 examples/nrf/src/bin/usb_uart_io.rs (limited to 'examples') diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index a704eb3bc..fb846b3a9 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -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" diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs new file mode 100644 index 000000000..345d00389 --- /dev/null +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -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 { + 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 UsbClass 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) { + 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) { + 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 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 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, + } + } +} diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb/main.rs new file mode 100644 index 000000000..21ca2ba4f --- /dev/null +++ b/examples/nrf/src/bin/usb/main.rs @@ -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; +} diff --git a/examples/nrf/src/bin/usb_uart.rs b/examples/nrf/src/bin/usb_uart.rs deleted file mode 100644 index d283dccd1..000000000 --- a/examples/nrf/src/bin/usb_uart.rs +++ /dev/null @@ -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); - } - } -} diff --git a/examples/nrf/src/bin/usb_uart_io.rs b/examples/nrf/src/bin/usb_uart_io.rs deleted file mode 100644 index ef2629844..000000000 --- a/examples/nrf/src/bin/usb_uart_io.rs +++ /dev/null @@ -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); - } -} -- cgit From 77ceced036d574c7d67259b85e1d61b96e82d0d3 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 9 Mar 2022 23:06:27 +0100 Subject: Working CDC-ACM host->device --- examples/nrf/src/bin/usb/cdc_acm.rs | 17 +++++++++-------- examples/nrf/src/bin/usb/main.rs | 15 ++++++++++++++- 2 files changed, 23 insertions(+), 9 deletions(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index 345d00389..b7c112ae6 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -38,14 +38,15 @@ const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; /// 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, + // TODO not pub + pub comm_if: InterfaceNumber, + pub comm_ep: D::EndpointIn, + pub data_if: InterfaceNumber, + pub read_ep: D::EndpointOut, + pub write_ep: D::EndpointIn, + pub line_coding: LineCoding, + pub dtr: bool, + pub rts: bool, } impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb/main.rs index 21ca2ba4f..d175766bb 100644 --- a/examples/nrf/src/bin/usb/main.rs +++ b/examples/nrf/src/bin/usb/main.rs @@ -14,7 +14,9 @@ use embassy_nrf::interrupt; use embassy_nrf::pac; use embassy_nrf::usb::{self, Driver}; use embassy_nrf::Peripherals; +use embassy_usb::driver::EndpointOut; use embassy_usb::{Config, UsbDeviceBuilder}; +use futures::future::{join, select}; use crate::cdc_acm::CdcAcmClass; @@ -49,5 +51,16 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut class = CdcAcmClass::new(&mut builder, 64); let mut usb = builder.build(); - usb.run().await; + + let fut1 = usb.run(); + let fut2 = async { + let mut buf = [0; 64]; + loop { + let n = class.read_ep.read(&mut buf).await.unwrap(); + let data = &buf[..n]; + info!("data: {:x}", data); + } + }; + + join(fut1, fut2).await; } -- cgit From 0320500f0f14d03aecfe3ee7482a5cf76ec8844c Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 10 Mar 2022 01:05:33 +0100 Subject: Working CDC-ACM device->host --- examples/nrf/Cargo.toml | 2 +- examples/nrf/src/bin/usb/main.rs | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) (limited to 'examples') diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index fb846b3a9..59e5de026 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["Dario Nieuwenhuis "] -edition = "2018" +edition = "2021" name = "embassy-nrf-examples" version = "0.1.0" diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb/main.rs index d175766bb..014ad5c6e 100644 --- a/examples/nrf/src/bin/usb/main.rs +++ b/examples/nrf/src/bin/usb/main.rs @@ -10,13 +10,14 @@ mod cdc_acm; 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::{self, Driver}; +use embassy_nrf::usb::Driver; use embassy_nrf::Peripherals; -use embassy_usb::driver::EndpointOut; +use embassy_usb::driver::{EndpointIn, EndpointOut}; use embassy_usb::{Config, UsbDeviceBuilder}; -use futures::future::{join, select}; +use futures::future::join3; use crate::cdc_acm::CdcAcmClass; @@ -61,6 +62,15 @@ async fn main(_spawner: Spawner, p: Peripherals) { info!("data: {:x}", data); } }; + let fut3 = async { + loop { + info!("writing..."); + class.write_ep.write(b"Hello World!\r\n").await.unwrap(); + info!("written"); + + Timer::after(Duration::from_secs(1)).await; + } + }; - join(fut1, fut2).await; + join3(fut1, fut2, fut3).await; } -- cgit From 9a6d11281d33ec687e68b9b28d8489150bafff89 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 10 Mar 2022 01:10:53 +0100 Subject: Add some comments on the example. --- examples/nrf/src/bin/usb/main.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb/main.rs index 014ad5c6e..ecbdc3461 100644 --- a/examples/nrf/src/bin/usb/main.rs +++ b/examples/nrf/src/bin/usb/main.rs @@ -34,13 +34,18 @@ async fn main(_spawner: Spawner, p: Peripherals) { 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 builder = UsbDeviceBuilder::new( driver, config, @@ -49,11 +54,16 @@ async fn main(_spawner: Spawner, p: Peripherals) { &mut bos_descriptor, ); + // Create classes on the builder. let mut class = CdcAcmClass::new(&mut builder, 64); + // Build the builder. let mut usb = builder.build(); + // Run the USB device. let fut1 = usb.run(); + + // Do stuff with the classes let fut2 = async { let mut buf = [0; 64]; loop { @@ -72,5 +82,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { } }; + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. join3(fut1, fut2, fut3).await; } -- cgit From bdc6e0481c42d20d5cca19dfc8ec56306e47296e Mon Sep 17 00:00:00 2001 From: alexmoon Date: Fri, 25 Mar 2022 16:46:14 -0400 Subject: Add support for USB classes handling control requests. --- examples/nrf/src/bin/usb/cdc_acm.rs | 127 +++++++++++++++++++++++++++++++----- examples/nrf/src/bin/usb/main.rs | 3 +- 2 files changed, 114 insertions(+), 16 deletions(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index b7c112ae6..eebf89221 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -1,5 +1,8 @@ -use core::convert::TryInto; +use core::future::Future; use core::mem; +use defmt::info; +use embassy_usb::class::{ControlInRequestStatus, RequestStatus, UsbClass}; +use embassy_usb::control::{self, Request}; use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError}; use embassy_usb::{driver::Driver, types::*, UsbDeviceBuilder}; @@ -39,16 +42,107 @@ const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; /// terminated with a short packet, even if the bulk endpoint is used for stream-like data. pub struct CdcAcmClass<'d, D: Driver<'d>> { // TODO not pub - pub comm_if: InterfaceNumber, pub comm_ep: D::EndpointIn, pub data_if: InterfaceNumber, pub read_ep: D::EndpointOut, pub write_ep: D::EndpointIn, + pub control: CdcAcmControl, +} + +pub struct CdcAcmControl { + pub comm_if: InterfaceNumber, pub line_coding: LineCoding, pub dtr: bool, pub rts: bool, } +impl<'d, D: Driver<'d>> UsbClass<'d, D> for CdcAcmControl { + type ControlOutFuture<'a> = impl Future + 'a where Self: 'a, 'd: 'a, D: 'a; + type ControlInFuture<'a> = impl Future + 'a where Self: 'a, 'd: 'a, D: 'a; + + fn reset(&mut self) { + self.line_coding = LineCoding::default(); + self.dtr = false; + self.rts = false; + } + + fn control_out<'a>( + &'a mut self, + req: control::Request, + data: &'a [u8], + ) -> Self::ControlOutFuture<'a> + where + 'd: 'a, + D: 'a, + { + async move { + if !(req.request_type == control::RequestType::Class + && req.recipient == control::Recipient::Interface + && req.index == u8::from(self.comm_if) as u16) + { + return RequestStatus::Unhandled; + } + + match req.request { + REQ_SEND_ENCAPSULATED_COMMAND => { + // We don't actually support encapsulated commands but pretend we do for standards + // compatibility. + RequestStatus::Accepted + } + REQ_SET_LINE_CODING if data.len() >= 7 => { + self.line_coding.data_rate = u32::from_le_bytes(data[0..4].try_into().unwrap()); + self.line_coding.stop_bits = data[4].into(); + self.line_coding.parity_type = data[5].into(); + self.line_coding.data_bits = data[6]; + info!("Set line coding to: {:?}", self.line_coding); + + RequestStatus::Accepted + } + REQ_SET_CONTROL_LINE_STATE => { + self.dtr = (req.value & 0x0001) != 0; + self.rts = (req.value & 0x0002) != 0; + info!("Set dtr {}, rts {}", self.dtr, self.rts); + + RequestStatus::Accepted + } + _ => RequestStatus::Rejected, + } + } + } + + fn control_in<'a>( + &'a mut self, + req: Request, + control: embassy_usb::class::ControlIn<'a, 'd, D>, + ) -> Self::ControlInFuture<'a> + where + 'd: 'a, + { + async move { + if !(req.request_type == control::RequestType::Class + && req.recipient == control::Recipient::Interface + && req.index == u8::from(self.comm_if) as u16) + { + return control.ignore(); + } + + match req.request { + // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. + REQ_GET_LINE_CODING if req.length == 7 => { + info!("Sending line coding"); + let mut data = [0; 7]; + 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; + control.accept(&data).await + } + _ => control.reject(), + } + } + } +} + 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. @@ -133,19 +227,21 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { 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, + control: CdcAcmControl { + comm_if, + dtr: false, + rts: false, + line_coding: LineCoding { + stop_bits: StopBits::One, + data_bits: 8, + parity_type: ParityType::None, + data_rate: 8_000, + }, }, - dtr: false, - rts: false, } } @@ -158,17 +254,17 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { /// 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 + &self.control.line_coding } /// Gets the DTR (data terminal ready) state pub fn dtr(&self) -> bool { - self.dtr + self.control.dtr } /// Gets the RTS (request to send) state pub fn rts(&self) -> bool { - self.rts + self.control.rts } /// Writes a single packet into the IN endpoint. @@ -270,7 +366,7 @@ impl UsbClass for CdcAcmClass<'_, B> { */ /// Number of stop bits for LineCoding -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, defmt::Format)] pub enum StopBits { /// 1 stop bit One = 0, @@ -293,7 +389,7 @@ impl From for StopBits { } /// Parity for LineCoding -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, defmt::Format)] pub enum ParityType { None = 0, Odd = 1, @@ -316,6 +412,7 @@ impl From for ParityType { /// /// 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. +#[derive(defmt::Format)] pub struct LineCoding { stop_bits: StopBits, data_bits: u8, diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb/main.rs index ecbdc3461..71285579c 100644 --- a/examples/nrf/src/bin/usb/main.rs +++ b/examples/nrf/src/bin/usb/main.rs @@ -1,5 +1,6 @@ #![no_std] #![no_main] +#![feature(generic_associated_types)] #![feature(type_alias_impl_trait)] #[path = "../../example_common.rs"] @@ -58,7 +59,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut class = CdcAcmClass::new(&mut builder, 64); // Build the builder. - let mut usb = builder.build(); + let mut usb = builder.build(class.control); // Run the USB device. let fut1 = usb.run(); -- cgit From 52c622b1cd02ba13accc84cd57e90b04797f0405 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Sun, 27 Mar 2022 17:12:57 -0400 Subject: Use trait objects instead of generics for UsbDevice::classes --- examples/nrf/src/bin/usb/cdc_acm.rs | 117 +++++++++++++++--------------------- examples/nrf/src/bin/usb/main.rs | 4 +- 2 files changed, 52 insertions(+), 69 deletions(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index eebf89221..5e4abfea2 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -1,4 +1,3 @@ -use core::future::Future; use core::mem; use defmt::info; use embassy_usb::class::{ControlInRequestStatus, RequestStatus, UsbClass}; @@ -56,89 +55,71 @@ pub struct CdcAcmControl { pub rts: bool, } -impl<'d, D: Driver<'d>> UsbClass<'d, D> for CdcAcmControl { - type ControlOutFuture<'a> = impl Future + 'a where Self: 'a, 'd: 'a, D: 'a; - type ControlInFuture<'a> = impl Future + 'a where Self: 'a, 'd: 'a, D: 'a; - +impl UsbClass for CdcAcmControl { fn reset(&mut self) { self.line_coding = LineCoding::default(); self.dtr = false; self.rts = false; } - fn control_out<'a>( - &'a mut self, - req: control::Request, - data: &'a [u8], - ) -> Self::ControlOutFuture<'a> - where - 'd: 'a, - D: 'a, - { - async move { - if !(req.request_type == control::RequestType::Class - && req.recipient == control::Recipient::Interface - && req.index == u8::from(self.comm_if) as u16) - { - return RequestStatus::Unhandled; + fn control_out(&mut self, req: control::Request, data: &[u8]) -> RequestStatus { + if !(req.request_type == control::RequestType::Class + && req.recipient == control::Recipient::Interface + && req.index == u8::from(self.comm_if) as u16) + { + return RequestStatus::Unhandled; + } + + match req.request { + REQ_SEND_ENCAPSULATED_COMMAND => { + // We don't actually support encapsulated commands but pretend we do for standards + // compatibility. + RequestStatus::Accepted } + REQ_SET_LINE_CODING if data.len() >= 7 => { + self.line_coding.data_rate = u32::from_le_bytes(data[0..4].try_into().unwrap()); + self.line_coding.stop_bits = data[4].into(); + self.line_coding.parity_type = data[5].into(); + self.line_coding.data_bits = data[6]; + info!("Set line coding to: {:?}", self.line_coding); + + RequestStatus::Accepted + } + REQ_SET_CONTROL_LINE_STATE => { + self.dtr = (req.value & 0x0001) != 0; + self.rts = (req.value & 0x0002) != 0; + info!("Set dtr {}, rts {}", self.dtr, self.rts); - match req.request { - REQ_SEND_ENCAPSULATED_COMMAND => { - // We don't actually support encapsulated commands but pretend we do for standards - // compatibility. - RequestStatus::Accepted - } - REQ_SET_LINE_CODING if data.len() >= 7 => { - self.line_coding.data_rate = u32::from_le_bytes(data[0..4].try_into().unwrap()); - self.line_coding.stop_bits = data[4].into(); - self.line_coding.parity_type = data[5].into(); - self.line_coding.data_bits = data[6]; - info!("Set line coding to: {:?}", self.line_coding); - - RequestStatus::Accepted - } - REQ_SET_CONTROL_LINE_STATE => { - self.dtr = (req.value & 0x0001) != 0; - self.rts = (req.value & 0x0002) != 0; - info!("Set dtr {}, rts {}", self.dtr, self.rts); - - RequestStatus::Accepted - } - _ => RequestStatus::Rejected, + RequestStatus::Accepted } + _ => RequestStatus::Rejected, } } fn control_in<'a>( - &'a mut self, + &mut self, req: Request, - control: embassy_usb::class::ControlIn<'a, 'd, D>, - ) -> Self::ControlInFuture<'a> - where - 'd: 'a, - { - async move { - if !(req.request_type == control::RequestType::Class - && req.recipient == control::Recipient::Interface - && req.index == u8::from(self.comm_if) as u16) - { - return control.ignore(); - } + control: embassy_usb::class::ControlIn<'a>, + ) -> ControlInRequestStatus<'a> { + if !(req.request_type == control::RequestType::Class + && req.recipient == control::Recipient::Interface + && req.index == u8::from(self.comm_if) as u16) + { + return control.ignore(); + } - match req.request { - // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. - REQ_GET_LINE_CODING if req.length == 7 => { - info!("Sending line coding"); - let mut data = [0; 7]; - 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; - control.accept(&data).await - } - _ => control.reject(), + match req.request { + // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. + REQ_GET_LINE_CODING if req.length == 7 => { + info!("Sending line coding"); + let mut data = [0; 7]; + 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; + control.accept(&data) } + _ => control.reject(), } } } diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb/main.rs index 71285579c..73ac3a21f 100644 --- a/examples/nrf/src/bin/usb/main.rs +++ b/examples/nrf/src/bin/usb/main.rs @@ -16,6 +16,7 @@ use embassy_nrf::interrupt; use embassy_nrf::pac; use embassy_nrf::usb::Driver; use embassy_nrf::Peripherals; +use embassy_usb::class::UsbClass; use embassy_usb::driver::{EndpointIn, EndpointOut}; use embassy_usb::{Config, UsbDeviceBuilder}; use futures::future::join3; @@ -59,7 +60,8 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut class = CdcAcmClass::new(&mut builder, 64); // Build the builder. - let mut usb = builder.build(class.control); + let mut classes: [&mut dyn UsbClass; 1] = [&mut class.control]; + let mut usb = builder.build(&mut classes); // Run the USB device. let fut1 = usb.run(); -- cgit From a2f5763a67441e5e6c981407938c21c58e9eb669 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 28 Mar 2022 02:20:01 +0200 Subject: usb: add `add_class` to builder, so that `FooBarClass::new(&mut builder)` can set up everything. --- examples/nrf/src/bin/usb/cdc_acm.rs | 207 +++++++++++++++--------------------- examples/nrf/src/bin/usb/main.rs | 11 +- 2 files changed, 91 insertions(+), 127 deletions(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index 5e4abfea2..92cc16eb4 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -1,5 +1,8 @@ -use core::mem; +use core::cell::{Cell, UnsafeCell}; +use core::mem::{self, MaybeUninit}; +use core::sync::atomic::{AtomicBool, Ordering}; use defmt::info; +use embassy::blocking_mutex::CriticalSectionMutex; use embassy_usb::class::{ControlInRequestStatus, RequestStatus, UsbClass}; use embassy_usb::control::{self, Request}; use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError}; @@ -25,6 +28,18 @@ const REQ_SET_LINE_CODING: u8 = 0x20; const REQ_GET_LINE_CODING: u8 = 0x21; const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; +pub struct State { + control: MaybeUninit, +} + +impl State { + pub fn new() -> Self { + Self { + control: MaybeUninit::uninit(), + } + } +} + /// 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 @@ -45,21 +60,32 @@ pub struct CdcAcmClass<'d, D: Driver<'d>> { pub data_if: InterfaceNumber, pub read_ep: D::EndpointOut, pub write_ep: D::EndpointIn, - pub control: CdcAcmControl, + control: &'d ControlShared, +} + +struct Control { + comm_if: InterfaceNumber, + shared: UnsafeCell, } -pub struct CdcAcmControl { - pub comm_if: InterfaceNumber, - pub line_coding: LineCoding, - pub dtr: bool, - pub rts: bool, +/// Shared data between Control and CdcAcmClass +struct ControlShared { + line_coding: CriticalSectionMutex>, + dtr: AtomicBool, + rts: AtomicBool, } -impl UsbClass for CdcAcmControl { +impl Control { + fn shared(&mut self) -> &ControlShared { + unsafe { &*(self.shared.get() as *const _) } + } +} +impl UsbClass for Control { fn reset(&mut self) { - self.line_coding = LineCoding::default(); - self.dtr = false; - self.rts = false; + let shared = self.shared(); + shared.line_coding.lock(|x| x.set(LineCoding::default())); + shared.dtr.store(false, Ordering::Relaxed); + shared.rts.store(false, Ordering::Relaxed); } fn control_out(&mut self, req: control::Request, data: &[u8]) -> RequestStatus { @@ -77,18 +103,25 @@ impl UsbClass for CdcAcmControl { RequestStatus::Accepted } REQ_SET_LINE_CODING if data.len() >= 7 => { - self.line_coding.data_rate = u32::from_le_bytes(data[0..4].try_into().unwrap()); - self.line_coding.stop_bits = data[4].into(); - self.line_coding.parity_type = data[5].into(); - self.line_coding.data_bits = data[6]; - info!("Set line coding to: {:?}", self.line_coding); + let coding = LineCoding { + data_rate: u32::from_le_bytes(data[0..4].try_into().unwrap()), + stop_bits: data[4].into(), + parity_type: data[5].into(), + data_bits: data[6], + }; + self.shared().line_coding.lock(|x| x.set(coding)); + info!("Set line coding to: {:?}", coding); RequestStatus::Accepted } REQ_SET_CONTROL_LINE_STATE => { - self.dtr = (req.value & 0x0001) != 0; - self.rts = (req.value & 0x0002) != 0; - info!("Set dtr {}, rts {}", self.dtr, self.rts); + let dtr = (req.value & 0x0001) != 0; + let rts = (req.value & 0x0002) != 0; + + let shared = self.shared(); + shared.dtr.store(dtr, Ordering::Relaxed); + shared.rts.store(rts, Ordering::Relaxed); + info!("Set dtr {}, rts {}", dtr, rts); RequestStatus::Accepted } @@ -112,11 +145,12 @@ impl UsbClass for CdcAcmControl { // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. REQ_GET_LINE_CODING if req.length == 7 => { info!("Sending line coding"); + let coding = self.shared().line_coding.lock(|x| x.get()); let mut data = [0; 7]; - 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; + data[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); + data[4] = coding.stop_bits as u8; + data[5] = coding.parity_type as u8; + data[6] = coding.data_bits; control.accept(&data) } _ => control.reject(), @@ -127,7 +161,11 @@ impl UsbClass for CdcAcmControl { 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 { + pub fn new( + builder: &mut UsbDeviceBuilder<'d, D>, + state: &'d mut State, + 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(); @@ -207,22 +245,29 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { builder.config_descriptor.endpoint(write_ep.info()).unwrap(); builder.config_descriptor.endpoint(read_ep.info()).unwrap(); + let control = state.control.write(Control { + comm_if, + shared: UnsafeCell::new(ControlShared { + dtr: AtomicBool::new(false), + rts: AtomicBool::new(false), + line_coding: CriticalSectionMutex::new(Cell::new(LineCoding { + stop_bits: StopBits::One, + data_bits: 8, + parity_type: ParityType::None, + data_rate: 8_000, + })), + }), + }); + + let control_shared = unsafe { &*(control.shared.get() as *const _) }; + builder.add_class(control); + CdcAcmClass { comm_ep, data_if, read_ep, write_ep, - control: CdcAcmControl { - comm_if, - dtr: false, - rts: false, - line_coding: LineCoding { - stop_bits: StopBits::One, - data_bits: 8, - parity_type: ParityType::None, - data_rate: 8_000, - }, - }, + control: control_shared, } } @@ -234,18 +279,18 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { /// 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.control.line_coding + pub fn line_coding(&self) -> LineCoding { + self.control.line_coding.lock(|x| x.get()) } /// Gets the DTR (data terminal ready) state pub fn dtr(&self) -> bool { - self.control.dtr + self.control.dtr.load(Ordering::Relaxed) } /// Gets the RTS (request to send) state pub fn rts(&self) -> bool { - self.control.rts + self.control.rts.load(Ordering::Relaxed) } /// Writes a single packet into the IN endpoint. @@ -264,88 +309,6 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { } } -/* -impl UsbClass 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) { - 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) { - 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, defmt::Format)] pub enum StopBits { @@ -393,7 +356,7 @@ impl From for ParityType { /// /// 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. -#[derive(defmt::Format)] +#[derive(Clone, Copy, defmt::Format)] pub struct LineCoding { stop_bits: StopBits, data_bits: u8, diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb/main.rs index 73ac3a21f..398dd07b6 100644 --- a/examples/nrf/src/bin/usb/main.rs +++ b/examples/nrf/src/bin/usb/main.rs @@ -16,12 +16,11 @@ use embassy_nrf::interrupt; use embassy_nrf::pac; use embassy_nrf::usb::Driver; use embassy_nrf::Peripherals; -use embassy_usb::class::UsbClass; use embassy_usb::driver::{EndpointIn, EndpointOut}; use embassy_usb::{Config, UsbDeviceBuilder}; use futures::future::join3; -use crate::cdc_acm::CdcAcmClass; +use crate::cdc_acm::{CdcAcmClass, State}; #[embassy::main] async fn main(_spawner: Spawner, p: Peripherals) { @@ -48,6 +47,9 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut device_descriptor = [0; 256]; let mut config_descriptor = [0; 256]; let mut bos_descriptor = [0; 256]; + + let mut state = State::new(); + let mut builder = UsbDeviceBuilder::new( driver, config, @@ -57,11 +59,10 @@ async fn main(_spawner: Spawner, p: Peripherals) { ); // Create classes on the builder. - let mut class = CdcAcmClass::new(&mut builder, 64); + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); // Build the builder. - let mut classes: [&mut dyn UsbClass; 1] = [&mut class.control]; - let mut usb = builder.build(&mut classes); + let mut usb = builder.build(); // Run the USB device. let fut1 = usb.run(); -- cgit From 15cc97d794d8b4baa6c1a8f1ed6c64468701c9e7 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 28 Mar 2022 03:19:07 +0200 Subject: usb: associate ControlHandlers with interfaces, automatically route requests. --- examples/nrf/src/bin/usb/cdc_acm.rs | 53 +++++++++++++------------------------ 1 file changed, 18 insertions(+), 35 deletions(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index 92cc16eb4..f4d429792 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -3,7 +3,7 @@ use core::mem::{self, MaybeUninit}; use core::sync::atomic::{AtomicBool, Ordering}; use defmt::info; use embassy::blocking_mutex::CriticalSectionMutex; -use embassy_usb::class::{ControlInRequestStatus, RequestStatus, UsbClass}; +use embassy_usb::class::{ControlHandler, ControlInRequestStatus, RequestStatus}; use embassy_usb::control::{self, Request}; use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError}; use embassy_usb::{driver::Driver, types::*, UsbDeviceBuilder}; @@ -64,7 +64,6 @@ pub struct CdcAcmClass<'d, D: Driver<'d>> { } struct Control { - comm_if: InterfaceNumber, shared: UnsafeCell, } @@ -80,7 +79,7 @@ impl Control { unsafe { &*(self.shared.get() as *const _) } } } -impl UsbClass for Control { +impl ControlHandler for Control { fn reset(&mut self) { let shared = self.shared(); shared.line_coding.lock(|x| x.set(LineCoding::default())); @@ -89,13 +88,6 @@ impl UsbClass for Control { } fn control_out(&mut self, req: control::Request, data: &[u8]) -> RequestStatus { - if !(req.request_type == control::RequestType::Class - && req.recipient == control::Recipient::Interface - && req.index == u8::from(self.comm_if) as u16) - { - return RequestStatus::Unhandled; - } - match req.request { REQ_SEND_ENCAPSULATED_COMMAND => { // We don't actually support encapsulated commands but pretend we do for standards @@ -134,13 +126,6 @@ impl UsbClass for Control { req: Request, control: embassy_usb::class::ControlIn<'a>, ) -> ControlInRequestStatus<'a> { - if !(req.request_type == control::RequestType::Class - && req.recipient == control::Recipient::Interface - && req.index == u8::from(self.comm_if) as u16) - { - return control.ignore(); - } - match req.request { // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. REQ_GET_LINE_CODING if req.length == 7 => { @@ -166,7 +151,22 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { state: &'d mut State, max_packet_size: u16, ) -> Self { - let comm_if = builder.alloc_interface(); + let control = state.control.write(Control { + shared: UnsafeCell::new(ControlShared { + dtr: AtomicBool::new(false), + rts: AtomicBool::new(false), + line_coding: CriticalSectionMutex::new(Cell::new(LineCoding { + stop_bits: StopBits::One, + data_bits: 8, + parity_type: ParityType::None, + data_rate: 8_000, + })), + }), + }); + + let control_shared = unsafe { &*(control.shared.get() as *const _) }; + + let comm_if = builder.alloc_interface_with_handler(control); 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); @@ -245,23 +245,6 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { builder.config_descriptor.endpoint(write_ep.info()).unwrap(); builder.config_descriptor.endpoint(read_ep.info()).unwrap(); - let control = state.control.write(Control { - comm_if, - shared: UnsafeCell::new(ControlShared { - dtr: AtomicBool::new(false), - rts: AtomicBool::new(false), - line_coding: CriticalSectionMutex::new(Cell::new(LineCoding { - stop_bits: StopBits::One, - data_bits: 8, - parity_type: ParityType::None, - data_rate: 8_000, - })), - }), - }); - - let control_shared = unsafe { &*(control.shared.get() as *const _) }; - builder.add_class(control); - CdcAcmClass { comm_ep, data_if, -- cgit From 2b547f311efc7feaa3afbb9f1bf4100c5502839e Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 28 Mar 2022 03:27:21 +0200 Subject: usb: move all control-related stuff to `mod control`. --- examples/nrf/src/bin/usb/cdc_acm.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index f4d429792..25c3108a9 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -3,8 +3,9 @@ use core::mem::{self, MaybeUninit}; use core::sync::atomic::{AtomicBool, Ordering}; use defmt::info; use embassy::blocking_mutex::CriticalSectionMutex; -use embassy_usb::class::{ControlHandler, ControlInRequestStatus, RequestStatus}; -use embassy_usb::control::{self, Request}; +use embassy_usb::control::{ + self, ControlHandler, ControlIn, ControlInRequestStatus, Request, RequestStatus, +}; use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError}; use embassy_usb::{driver::Driver, types::*, UsbDeviceBuilder}; @@ -124,7 +125,7 @@ impl ControlHandler for Control { fn control_in<'a>( &mut self, req: Request, - control: embassy_usb::class::ControlIn<'a>, + control: ControlIn<'a>, ) -> ControlInRequestStatus<'a> { match req.request { // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. -- cgit From bfce731982af1f053b1b727e49e920fc496a9546 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 28 Mar 2022 03:30:08 +0200 Subject: usb: nicer names for control structs. --- examples/nrf/src/bin/usb/cdc_acm.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index 25c3108a9..141c6ecd1 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -3,9 +3,7 @@ use core::mem::{self, MaybeUninit}; use core::sync::atomic::{AtomicBool, Ordering}; use defmt::info; use embassy::blocking_mutex::CriticalSectionMutex; -use embassy_usb::control::{ - self, ControlHandler, ControlIn, ControlInRequestStatus, Request, RequestStatus, -}; +use embassy_usb::control::{self, ControlHandler, ControlIn, InResponse, OutResponse, Request}; use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError}; use embassy_usb::{driver::Driver, types::*, UsbDeviceBuilder}; @@ -88,12 +86,12 @@ impl ControlHandler for Control { shared.rts.store(false, Ordering::Relaxed); } - fn control_out(&mut self, req: control::Request, data: &[u8]) -> RequestStatus { + fn control_out(&mut self, req: control::Request, data: &[u8]) -> OutResponse { match req.request { REQ_SEND_ENCAPSULATED_COMMAND => { // We don't actually support encapsulated commands but pretend we do for standards // compatibility. - RequestStatus::Accepted + OutResponse::Accepted } REQ_SET_LINE_CODING if data.len() >= 7 => { let coding = LineCoding { @@ -105,7 +103,7 @@ impl ControlHandler for Control { self.shared().line_coding.lock(|x| x.set(coding)); info!("Set line coding to: {:?}", coding); - RequestStatus::Accepted + OutResponse::Accepted } REQ_SET_CONTROL_LINE_STATE => { let dtr = (req.value & 0x0001) != 0; @@ -116,17 +114,13 @@ impl ControlHandler for Control { shared.rts.store(rts, Ordering::Relaxed); info!("Set dtr {}, rts {}", dtr, rts); - RequestStatus::Accepted + OutResponse::Accepted } - _ => RequestStatus::Rejected, + _ => OutResponse::Rejected, } } - fn control_in<'a>( - &mut self, - req: Request, - control: ControlIn<'a>, - ) -> ControlInRequestStatus<'a> { + fn control_in<'a>(&mut self, req: Request, control: ControlIn<'a>) -> InResponse<'a> { match req.request { // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. REQ_GET_LINE_CODING if req.length == 7 => { -- cgit From e99a3a1da42649bb9ad9a64508d14d6461be331d Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 28 Mar 2022 03:34:24 +0200 Subject: usb: simplify buffer handling for Control IN transfers. --- examples/nrf/src/bin/usb/cdc_acm.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index 141c6ecd1..4be35fd3f 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -3,7 +3,7 @@ use core::mem::{self, MaybeUninit}; use core::sync::atomic::{AtomicBool, Ordering}; use defmt::info; use embassy::blocking_mutex::CriticalSectionMutex; -use embassy_usb::control::{self, ControlHandler, ControlIn, InResponse, OutResponse, Request}; +use embassy_usb::control::{self, ControlHandler, InResponse, OutResponse, Request}; use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError}; use embassy_usb::{driver::Driver, types::*, UsbDeviceBuilder}; @@ -120,20 +120,19 @@ impl ControlHandler for Control { } } - fn control_in<'a>(&mut self, req: Request, control: ControlIn<'a>) -> InResponse<'a> { + fn control_in(&mut self, req: Request, resp: &mut [u8]) -> InResponse { match req.request { // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. REQ_GET_LINE_CODING if req.length == 7 => { info!("Sending line coding"); let coding = self.shared().line_coding.lock(|x| x.get()); - let mut data = [0; 7]; - data[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); - data[4] = coding.stop_bits as u8; - data[5] = coding.parity_type as u8; - data[6] = coding.data_bits; - control.accept(&data) + resp[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); + resp[4] = coding.stop_bits as u8; + resp[5] = coding.parity_type as u8; + resp[6] = coding.data_bits; + InResponse::Accepted(7) } - _ => control.reject(), + _ => InResponse::Rejected, } } } -- cgit From a22639ad920fccfd909602f5c69147be911650eb Mon Sep 17 00:00:00 2001 From: alexmoon Date: Mon, 28 Mar 2022 10:49:17 -0400 Subject: Remove UnsafeCell from cdc_acm::Control --- examples/nrf/src/bin/usb/cdc_acm.rs | 53 ++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 22 deletions(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index 4be35fd3f..4b4925937 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -1,4 +1,4 @@ -use core::cell::{Cell, UnsafeCell}; +use core::cell::Cell; use core::mem::{self, MaybeUninit}; use core::sync::atomic::{AtomicBool, Ordering}; use defmt::info; @@ -27,14 +27,16 @@ const REQ_SET_LINE_CODING: u8 = 0x20; const REQ_GET_LINE_CODING: u8 = 0x21; const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; -pub struct State { - control: MaybeUninit, +pub struct State<'a> { + control: MaybeUninit>, + shared: ControlShared, } -impl State { +impl<'a> State<'a> { pub fn new() -> Self { Self { control: MaybeUninit::uninit(), + shared: Default::default(), } } } @@ -62,8 +64,8 @@ pub struct CdcAcmClass<'d, D: Driver<'d>> { control: &'d ControlShared, } -struct Control { - shared: UnsafeCell, +struct Control<'a> { + shared: &'a ControlShared, } /// Shared data between Control and CdcAcmClass @@ -73,12 +75,28 @@ struct ControlShared { rts: AtomicBool, } -impl Control { - fn shared(&mut self) -> &ControlShared { - unsafe { &*(self.shared.get() as *const _) } +impl Default for ControlShared { + fn default() -> Self { + ControlShared { + dtr: AtomicBool::new(false), + rts: AtomicBool::new(false), + line_coding: CriticalSectionMutex::new(Cell::new(LineCoding { + stop_bits: StopBits::One, + data_bits: 8, + parity_type: ParityType::None, + data_rate: 8_000, + })), + } + } +} + +impl<'a> Control<'a> { + fn shared(&mut self) -> &'a ControlShared { + self.shared } } -impl ControlHandler for Control { + +impl<'a> ControlHandler for Control<'a> { fn reset(&mut self) { let shared = self.shared(); shared.line_coding.lock(|x| x.set(LineCoding::default())); @@ -142,23 +160,14 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { /// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64. pub fn new( builder: &mut UsbDeviceBuilder<'d, D>, - state: &'d mut State, + state: &'d mut State<'d>, max_packet_size: u16, ) -> Self { let control = state.control.write(Control { - shared: UnsafeCell::new(ControlShared { - dtr: AtomicBool::new(false), - rts: AtomicBool::new(false), - line_coding: CriticalSectionMutex::new(Cell::new(LineCoding { - stop_bits: StopBits::One, - data_bits: 8, - parity_type: ParityType::None, - data_rate: 8_000, - })), - }), + shared: &state.shared, }); - let control_shared = unsafe { &*(control.shared.get() as *const _) }; + let control_shared = &state.shared; let comm_if = builder.alloc_interface_with_handler(control); let comm_ep = builder.alloc_interrupt_endpoint_in(8, 255); -- cgit From c53bb7394a20e180e2ac7e81cc468025018bd1da Mon Sep 17 00:00:00 2001 From: alexmoon Date: Mon, 28 Mar 2022 20:10:13 -0400 Subject: Switch to ControlHandler owned bufs for control_in() --- examples/nrf/src/bin/usb/cdc_acm.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index 4b4925937..2a78324fe 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -66,6 +66,7 @@ pub struct CdcAcmClass<'d, D: Driver<'d>> { struct Control<'a> { shared: &'a ControlShared, + buf: [u8; 7], } /// Shared data between Control and CdcAcmClass @@ -138,17 +139,17 @@ impl<'a> ControlHandler for Control<'a> { } } - fn control_in(&mut self, req: Request, resp: &mut [u8]) -> InResponse { + fn control_in(&mut self, req: Request) -> InResponse<'_> { match req.request { // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. REQ_GET_LINE_CODING if req.length == 7 => { info!("Sending line coding"); let coding = self.shared().line_coding.lock(|x| x.get()); - resp[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); - resp[4] = coding.stop_bits as u8; - resp[5] = coding.parity_type as u8; - resp[6] = coding.data_bits; - InResponse::Accepted(7) + self.buf[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); + self.buf[4] = coding.stop_bits as u8; + self.buf[5] = coding.parity_type as u8; + self.buf[6] = coding.data_bits; + InResponse::Accepted(&self.buf) } _ => InResponse::Rejected, } @@ -165,6 +166,7 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { ) -> Self { let control = state.control.write(Control { shared: &state.shared, + buf: [0; 7], }); let control_shared = &state.shared; -- cgit From 13370c28db244edd24029d6066ac9e448bd419c9 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Tue, 29 Mar 2022 15:09:24 -0400 Subject: Add a control_buf to UsbDevice --- examples/nrf/src/bin/usb/cdc_acm.rs | 19 ++++++++++--------- examples/nrf/src/bin/usb/main.rs | 2 ++ 2 files changed, 12 insertions(+), 9 deletions(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs index 2a78324fe..c28681dc4 100644 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ b/examples/nrf/src/bin/usb/cdc_acm.rs @@ -66,7 +66,6 @@ pub struct CdcAcmClass<'d, D: Driver<'d>> { struct Control<'a> { shared: &'a ControlShared, - buf: [u8; 7], } /// Shared data between Control and CdcAcmClass @@ -97,7 +96,7 @@ impl<'a> Control<'a> { } } -impl<'a> ControlHandler for Control<'a> { +impl<'d> ControlHandler for Control<'d> { fn reset(&mut self) { let shared = self.shared(); shared.line_coding.lock(|x| x.set(LineCoding::default())); @@ -139,17 +138,18 @@ impl<'a> ControlHandler for Control<'a> { } } - fn control_in(&mut self, req: Request) -> InResponse<'_> { + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { match req.request { // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. REQ_GET_LINE_CODING if req.length == 7 => { info!("Sending line coding"); let coding = self.shared().line_coding.lock(|x| x.get()); - self.buf[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); - self.buf[4] = coding.stop_bits as u8; - self.buf[5] = coding.parity_type as u8; - self.buf[6] = coding.data_bits; - InResponse::Accepted(&self.buf) + assert!(buf.len() >= 7); + buf[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); + buf[4] = coding.stop_bits as u8; + buf[5] = coding.parity_type as u8; + buf[6] = coding.data_bits; + InResponse::Accepted(&buf[0..7]) } _ => InResponse::Rejected, } @@ -166,11 +166,12 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { ) -> Self { let control = state.control.write(Control { shared: &state.shared, - buf: [0; 7], }); let control_shared = &state.shared; + assert!(builder.control_buf_len() >= 7); + let comm_if = builder.alloc_interface_with_handler(control); let comm_ep = builder.alloc_interrupt_endpoint_in(8, 255); let data_if = builder.alloc_interface(); diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb/main.rs index 398dd07b6..c4b9c0176 100644 --- a/examples/nrf/src/bin/usb/main.rs +++ b/examples/nrf/src/bin/usb/main.rs @@ -47,6 +47,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { 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(); @@ -56,6 +57,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { &mut device_descriptor, &mut config_descriptor, &mut bos_descriptor, + &mut control_buf, ); // Create classes on the builder. -- cgit From d1e4b3d7d5a931cd6e04b7e2fe467945ef862477 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 30 Mar 2022 01:18:37 +0200 Subject: usb: add -usb-serial crate, fix warnings and stable build. --- examples/nrf/Cargo.toml | 1 + examples/nrf/src/bin/usb/cdc_acm.rs | 387 ------------------------------------ examples/nrf/src/bin/usb/main.rs | 94 --------- examples/nrf/src/bin/usb_serial.rs | 91 +++++++++ 4 files changed, 92 insertions(+), 481 deletions(-) delete mode 100644 examples/nrf/src/bin/usb/cdc_acm.rs delete mode 100644 examples/nrf/src/bin/usb/main.rs create mode 100644 examples/nrf/src/bin/usb_serial.rs (limited to 'examples') diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index 59e5de026..58450a045 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -12,6 +12,7 @@ nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits"] 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", "unstable-pac"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] } defmt = "0.3" defmt-rtt = "0.3" diff --git a/examples/nrf/src/bin/usb/cdc_acm.rs b/examples/nrf/src/bin/usb/cdc_acm.rs deleted file mode 100644 index c28681dc4..000000000 --- a/examples/nrf/src/bin/usb/cdc_acm.rs +++ /dev/null @@ -1,387 +0,0 @@ -use core::cell::Cell; -use core::mem::{self, MaybeUninit}; -use core::sync::atomic::{AtomicBool, Ordering}; -use defmt::info; -use embassy::blocking_mutex::CriticalSectionMutex; -use embassy_usb::control::{self, ControlHandler, InResponse, OutResponse, Request}; -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; - -pub struct State<'a> { - control: MaybeUninit>, - shared: ControlShared, -} - -impl<'a> State<'a> { - pub fn new() -> Self { - Self { - control: MaybeUninit::uninit(), - shared: Default::default(), - } - } -} - -/// 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>> { - // TODO not pub - pub comm_ep: D::EndpointIn, - pub data_if: InterfaceNumber, - pub read_ep: D::EndpointOut, - pub write_ep: D::EndpointIn, - control: &'d ControlShared, -} - -struct Control<'a> { - shared: &'a ControlShared, -} - -/// Shared data between Control and CdcAcmClass -struct ControlShared { - line_coding: CriticalSectionMutex>, - dtr: AtomicBool, - rts: AtomicBool, -} - -impl Default for ControlShared { - fn default() -> Self { - ControlShared { - dtr: AtomicBool::new(false), - rts: AtomicBool::new(false), - line_coding: CriticalSectionMutex::new(Cell::new(LineCoding { - stop_bits: StopBits::One, - data_bits: 8, - parity_type: ParityType::None, - data_rate: 8_000, - })), - } - } -} - -impl<'a> Control<'a> { - fn shared(&mut self) -> &'a ControlShared { - self.shared - } -} - -impl<'d> ControlHandler for Control<'d> { - fn reset(&mut self) { - let shared = self.shared(); - shared.line_coding.lock(|x| x.set(LineCoding::default())); - shared.dtr.store(false, Ordering::Relaxed); - shared.rts.store(false, Ordering::Relaxed); - } - - fn control_out(&mut self, req: control::Request, data: &[u8]) -> OutResponse { - match req.request { - REQ_SEND_ENCAPSULATED_COMMAND => { - // We don't actually support encapsulated commands but pretend we do for standards - // compatibility. - OutResponse::Accepted - } - REQ_SET_LINE_CODING if data.len() >= 7 => { - let coding = LineCoding { - data_rate: u32::from_le_bytes(data[0..4].try_into().unwrap()), - stop_bits: data[4].into(), - parity_type: data[5].into(), - data_bits: data[6], - }; - self.shared().line_coding.lock(|x| x.set(coding)); - info!("Set line coding to: {:?}", coding); - - OutResponse::Accepted - } - REQ_SET_CONTROL_LINE_STATE => { - let dtr = (req.value & 0x0001) != 0; - let rts = (req.value & 0x0002) != 0; - - let shared = self.shared(); - shared.dtr.store(dtr, Ordering::Relaxed); - shared.rts.store(rts, Ordering::Relaxed); - info!("Set dtr {}, rts {}", dtr, rts); - - OutResponse::Accepted - } - _ => OutResponse::Rejected, - } - } - - fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { - match req.request { - // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. - REQ_GET_LINE_CODING if req.length == 7 => { - info!("Sending line coding"); - let coding = self.shared().line_coding.lock(|x| x.get()); - assert!(buf.len() >= 7); - buf[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); - buf[4] = coding.stop_bits as u8; - buf[5] = coding.parity_type as u8; - buf[6] = coding.data_bits; - InResponse::Accepted(&buf[0..7]) - } - _ => InResponse::Rejected, - } - } -} - -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>, - state: &'d mut State<'d>, - max_packet_size: u16, - ) -> Self { - let control = state.control.write(Control { - shared: &state.shared, - }); - - let control_shared = &state.shared; - - assert!(builder.control_buf_len() >= 7); - - let comm_if = builder.alloc_interface_with_handler(control); - 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_ep, - data_if, - read_ep, - write_ep, - control: control_shared, - } - } - - /// 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.control.line_coding.lock(|x| x.get()) - } - - /// Gets the DTR (data terminal ready) state - pub fn dtr(&self) -> bool { - self.control.dtr.load(Ordering::Relaxed) - } - - /// Gets the RTS (request to send) state - pub fn rts(&self) -> bool { - self.control.rts.load(Ordering::Relaxed) - } - - /// 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 { - 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 - } -} - -/// Number of stop bits for LineCoding -#[derive(Copy, Clone, PartialEq, Eq, defmt::Format)] -pub enum StopBits { - /// 1 stop bit - One = 0, - - /// 1.5 stop bits - OnePointFive = 1, - - /// 2 stop bits - Two = 2, -} - -impl From 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, defmt::Format)] -pub enum ParityType { - None = 0, - Odd = 1, - Event = 2, - Mark = 3, - Space = 4, -} - -impl From 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. -#[derive(Clone, Copy, defmt::Format)] -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, - } - } -} diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb/main.rs deleted file mode 100644 index c4b9c0176..000000000 --- a/examples/nrf/src/bin/usb/main.rs +++ /dev/null @@ -1,94 +0,0 @@ -#![no_std] -#![no_main] -#![feature(generic_associated_types)] -#![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::time::{Duration, Timer}; -use embassy_nrf::interrupt; -use embassy_nrf::pac; -use embassy_nrf::usb::Driver; -use embassy_nrf::Peripherals; -use embassy_usb::driver::{EndpointIn, EndpointOut}; -use embassy_usb::{Config, UsbDeviceBuilder}; -use futures::future::join3; - -use crate::cdc_acm::{CdcAcmClass, State}; - -#[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(); - - // Run the USB device. - let fut1 = usb.run(); - - // Do stuff with the classes - let fut2 = async { - let mut buf = [0; 64]; - loop { - let n = class.read_ep.read(&mut buf).await.unwrap(); - let data = &buf[..n]; - info!("data: {:x}", data); - } - }; - let fut3 = async { - loop { - info!("writing..."); - class.write_ep.write(b"Hello World!\r\n").await.unwrap(); - info!("written"); - - Timer::after(Duration::from_secs(1)).await; - } - }; - - // Run everything concurrently. - // If we had made everything `'static` above instead, we could do this using separate tasks instead. - join3(fut1, fut2, fut3).await; -} diff --git a/examples/nrf/src/bin/usb_serial.rs b/examples/nrf/src/bin/usb_serial.rs new file mode 100644 index 000000000..0a4ff9489 --- /dev/null +++ b/examples/nrf/src/bin/usb_serial.rs @@ -0,0 +1,91 @@ +#![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::driver::{EndpointIn, EndpointOut}; +use embassy_usb::{Config, UsbDeviceBuilder}; +use embassy_usb_serial::{CdcAcmClass, State}; +use futures::future::join3; + +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(); + + // Run the USB device. + let fut1 = usb.run(); + + // Do stuff with the classes + let fut2 = async { + let mut buf = [0; 64]; + loop { + let n = class.read_ep.read(&mut buf).await.unwrap(); + let data = &buf[..n]; + info!("data: {:x}", data); + } + }; + let fut3 = async { + loop { + info!("writing..."); + class.write_ep.write(b"Hello World!\r\n").await.unwrap(); + info!("written"); + + Timer::after(Duration::from_secs(1)).await; + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join3(fut1, fut2, fut3).await; +} -- cgit From cdb7bae51a30d30eb1bc2cc0f980870c8c76e02d Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 30 Mar 2022 02:05:09 +0200 Subject: examples/nrf: don't build usb stuff in stable. --- examples/nrf/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'examples') diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index 58450a045..aa30f3fa9 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -6,13 +6,13 @@ 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"] [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", "unstable-pac"] } -embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } -embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] } +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 } defmt = "0.3" defmt-rtt = "0.3" -- cgit From 1672fdc666188454a56b9369150e54420dc67078 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 30 Mar 2022 02:16:34 +0200 Subject: usb-serial: make inner guts private. --- examples/nrf/src/bin/usb_serial.rs | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb_serial.rs b/examples/nrf/src/bin/usb_serial.rs index 0a4ff9489..cd681c5ce 100644 --- a/examples/nrf/src/bin/usb_serial.rs +++ b/examples/nrf/src/bin/usb_serial.rs @@ -6,15 +6,13 @@ 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::driver::{EndpointIn, EndpointOut}; use embassy_usb::{Config, UsbDeviceBuilder}; use embassy_usb_serial::{CdcAcmClass, State}; -use futures::future::join3; +use futures::future::join; use defmt_rtt as _; // global logger use panic_probe as _; @@ -64,28 +62,20 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut usb = builder.build(); // Run the USB device. - let fut1 = usb.run(); + let usb_fut = usb.run(); - // Do stuff with the classes - let fut2 = async { + // Do stuff with the class! + let echo_fut = async { let mut buf = [0; 64]; loop { - let n = class.read_ep.read(&mut buf).await.unwrap(); + let n = class.read_packet(&mut buf).await.unwrap(); let data = &buf[..n]; info!("data: {:x}", data); - } - }; - let fut3 = async { - loop { - info!("writing..."); - class.write_ep.write(b"Hello World!\r\n").await.unwrap(); - info!("written"); - - Timer::after(Duration::from_secs(1)).await; + class.write_packet(data).await.unwrap(); } }; // Run everything concurrently. // If we had made everything `'static` above instead, we could do this using separate tasks instead. - join3(fut1, fut2, fut3).await; + join(usb_fut, echo_fut).await; } -- cgit From 5ee7a85b33f83131fd42ce229d3aadaf2054f44a Mon Sep 17 00:00:00 2001 From: alexmoon Date: Tue, 29 Mar 2022 15:18:43 -0400 Subject: Async USB HID class --- examples/nrf/Cargo.toml | 6 +- examples/nrf/src/bin/usb_hid.rs | 131 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 examples/nrf/src/bin/usb_hid.rs (limited to 'examples') diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index aa30f3fa9..e944c171a 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml @@ -6,13 +6,14 @@ version = "0.1.0" [features] default = ["nightly"] -nightly = ["embassy-nrf/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embassy-usb-serial"] +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", "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" @@ -23,4 +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" - +usbd-hid = "0.5.2" +serde = { version = "1.0.136", default-features = false } diff --git a/examples/nrf/src/bin/usb_hid.rs b/examples/nrf/src/bin/usb_hid.rs new file mode 100644 index 000000000..1fd056d00 --- /dev/null +++ b/examples/nrf/src/bin/usb_hid.rs @@ -0,0 +1,131 @@ +#![no_std] +#![no_main] +#![feature(generic_associated_types)] +#![feature(type_alias_impl_trait)] + +#[path = "../example_common.rs"] +mod example_common; + +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::{Config, UsbDeviceBuilder}; +use embassy_usb_hid::{HidClass, ReportId, RequestHandler, State}; +use futures::future::join; +use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; + +#[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 state = State::<5, 0, 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 class = CdcAcmClass::new(&mut builder, &mut state, 64); + let mut hid = HidClass::new( + &mut builder, + &mut state, + MouseReport::desc(), + Some(&request_handler), + 60, + ); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let hid_fut = async { + loop { + Timer::after(Duration::from_millis(500)).await; + hid.input() + .serialize(&MouseReport { + buttons: 0, + x: 0, + y: 4, + wheel: 0, + pan: 0, + }) + .await + .unwrap(); + + Timer::after(Duration::from_millis(500)).await; + hid.input() + .serialize(&MouseReport { + buttons: 0, + x: 0, + y: -4, + wheel: 0, + pan: 0, + }) + .await + .unwrap(); + } + }; + + // 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 { + info!("Get report for {:?}", id); + None + } + + fn set_idle(&self, id: Option, dur: Duration) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle(&self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +} -- cgit From a51de5a39a4bc246d6b3696ac94a200e93d918ea Mon Sep 17 00:00:00 2001 From: alexmoon Date: Thu, 31 Mar 2022 11:25:01 -0400 Subject: Remove the feature report reader --- examples/nrf/src/bin/usb_hid.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb_hid.rs b/examples/nrf/src/bin/usb_hid.rs index 1fd056d00..11c2d71ad 100644 --- a/examples/nrf/src/bin/usb_hid.rs +++ b/examples/nrf/src/bin/usb_hid.rs @@ -14,6 +14,7 @@ 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; @@ -51,7 +52,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut control_buf = [0; 16]; let request_handler = MyRequestHandler {}; - let mut state = State::<5, 0, 0>::new(); + let mut state = State::<5, 0>::new(); let mut builder = UsbDeviceBuilder::new( driver, @@ -63,8 +64,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { ); // Create classes on the builder. - // let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); - let mut hid = HidClass::new( + let mut hid = HidClass::new_ep_in( &mut builder, &mut state, MouseReport::desc(), @@ -120,6 +120,11 @@ impl RequestHandler for MyRequestHandler { 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, dur: Duration) { info!("Set idle rate for {:?} to {:?}", id, dur); } -- cgit From c309531874052bc96ef9ce39dd8698c120cee824 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Fri, 1 Apr 2022 10:57:37 -0400 Subject: Remove output() and split() methods from HidClass when there is no out endpoint, and route set_report requests for output reports to RequestHandler::set_report in that case. --- examples/nrf/src/bin/usb_hid.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb_hid.rs b/examples/nrf/src/bin/usb_hid.rs index 11c2d71ad..5253f225d 100644 --- a/examples/nrf/src/bin/usb_hid.rs +++ b/examples/nrf/src/bin/usb_hid.rs @@ -64,12 +64,13 @@ async fn main(_spawner: Spawner, p: Peripherals) { ); // Create classes on the builder. - let mut hid = HidClass::new_ep_in( + let mut hid = HidClass::new( &mut builder, &mut state, MouseReport::desc(), Some(&request_handler), 60, + 8, ); // Build the builder. -- cgit From 99f95a33c30b08359fcd72123fea01f4de0903ec Mon Sep 17 00:00:00 2001 From: alexmoon Date: Sat, 2 Apr 2022 11:58:01 -0400 Subject: Simplify hid output report handling --- examples/nrf/src/bin/usb_hid.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb_hid.rs b/examples/nrf/src/bin/usb_hid.rs index 5253f225d..6ffb1fd40 100644 --- a/examples/nrf/src/bin/usb_hid.rs +++ b/examples/nrf/src/bin/usb_hid.rs @@ -52,7 +52,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut control_buf = [0; 16]; let request_handler = MyRequestHandler {}; - let mut state = State::<5, 0>::new(); + let mut control = State::<5, 0>::new(); let mut builder = UsbDeviceBuilder::new( driver, @@ -66,7 +66,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { // Create classes on the builder. let mut hid = HidClass::new( &mut builder, - &mut state, + &mut control, MouseReport::desc(), Some(&request_handler), 60, -- cgit From 2ce435dc341c0238392df5dab5db9b80db167117 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Sat, 2 Apr 2022 16:35:03 -0400 Subject: Add basic device state handling for endpoints. --- examples/nrf/src/bin/usb_serial.rs | 46 ++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb_serial.rs b/examples/nrf/src/bin/usb_serial.rs index cd681c5ce..9437e835f 100644 --- a/examples/nrf/src/bin/usb_serial.rs +++ b/examples/nrf/src/bin/usb_serial.rs @@ -4,12 +4,13 @@ #![feature(type_alias_impl_trait)] use core::mem; -use defmt::*; +use defmt::{info, panic}; use embassy::executor::Spawner; use embassy_nrf::interrupt; use embassy_nrf::pac; -use embassy_nrf::usb::Driver; +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; @@ -66,12 +67,11 @@ async fn main(_spawner: Spawner, p: Peripherals) { // Do stuff with the class! let echo_fut = async { - let mut buf = [0; 64]; loop { - let n = class.read_packet(&mut buf).await.unwrap(); - let data = &buf[..n]; - info!("data: {:x}", data); - class.write_packet(data).await.unwrap(); + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); } }; @@ -79,3 +79,35 @@ async fn main(_spawner: Spawner, p: Peripherals) { // 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 for Disconnected { + fn from(val: ReadError) -> Self { + match val { + ReadError::BufferOverflow => panic!("Buffer overflow"), + ReadError::Disabled => Disconnected {}, + } + } +} + +impl From 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?; + } +} -- cgit From 6d514a0b31a6e480d00a36132ccd41bebfd246cc Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 6 Apr 2022 02:18:39 +0200 Subject: usb/hid: update for endpoint state changes. --- examples/nrf/src/bin/usb_hid.rs | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb_hid.rs b/examples/nrf/src/bin/usb_hid.rs index 6ffb1fd40..741e234b6 100644 --- a/examples/nrf/src/bin/usb_hid.rs +++ b/examples/nrf/src/bin/usb_hid.rs @@ -3,9 +3,6 @@ #![feature(generic_associated_types)] #![feature(type_alias_impl_trait)] -#[path = "../example_common.rs"] -mod example_common; - use core::mem; use defmt::*; use embassy::executor::Spawner; @@ -20,6 +17,9 @@ 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(()) }; @@ -81,30 +81,22 @@ async fn main(_spawner: Spawner, p: Peripherals) { // Do stuff with the class! let hid_fut = async { + let mut y: i8 = 5; loop { Timer::after(Duration::from_millis(500)).await; - hid.input() - .serialize(&MouseReport { - buttons: 0, - x: 0, - y: 4, - wheel: 0, - pan: 0, - }) - .await - .unwrap(); - Timer::after(Duration::from_millis(500)).await; - hid.input() - .serialize(&MouseReport { - buttons: 0, - x: 0, - y: -4, - wheel: 0, - pan: 0, - }) - .await - .unwrap(); + 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), + } } }; -- cgit From 3dbb7c9e159aa456c1d85cb4d5c8d1299013d0cc Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 6 Apr 2022 02:24:55 +0200 Subject: usb/hid: add keyboard example. --- examples/nrf/src/bin/usb_hid.rs | 129 --------------------------- examples/nrf/src/bin/usb_hid_keyboard.rs | 148 +++++++++++++++++++++++++++++++ examples/nrf/src/bin/usb_hid_mouse.rs | 129 +++++++++++++++++++++++++++ 3 files changed, 277 insertions(+), 129 deletions(-) delete mode 100644 examples/nrf/src/bin/usb_hid.rs create mode 100644 examples/nrf/src/bin/usb_hid_keyboard.rs create mode 100644 examples/nrf/src/bin/usb_hid_mouse.rs (limited to 'examples') diff --git a/examples/nrf/src/bin/usb_hid.rs b/examples/nrf/src/bin/usb_hid.rs deleted file mode 100644 index 741e234b6..000000000 --- a/examples/nrf/src/bin/usb_hid.rs +++ /dev/null @@ -1,129 +0,0 @@ -#![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(); - - // 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 { - 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, dur: Duration) { - info!("Set idle rate for {:?} to {:?}", id, dur); - } - - fn get_idle(&self, id: Option) -> Option { - info!("Get idle rate for {:?}", id); - None - } -} diff --git a/examples/nrf/src/bin/usb_hid_keyboard.rs b/examples/nrf/src/bin/usb_hid_keyboard.rs new file mode 100644 index 000000000..af70a9a60 --- /dev/null +++ b/examples/nrf/src/bin/usb_hid_keyboard.rs @@ -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::<64, 64>::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(); + + // 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(&MyRequestHandler {}).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 { + 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, dur: Duration) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle(&self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +} diff --git a/examples/nrf/src/bin/usb_hid_mouse.rs b/examples/nrf/src/bin/usb_hid_mouse.rs new file mode 100644 index 000000000..741e234b6 --- /dev/null +++ b/examples/nrf/src/bin/usb_hid_mouse.rs @@ -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(); + + // 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 { + 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, dur: Duration) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle(&self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +} -- cgit From b2e517bb2860b1ec35bb744b8a28efae50cb2d59 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 6 Apr 2022 02:37:17 +0200 Subject: usb/serial: add multitask example. --- examples/nrf/src/bin/usb_serial_multitask.rs | 122 +++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 examples/nrf/src/bin/usb_serial_multitask.rs (limited to 'examples') diff --git a/examples/nrf/src/bin/usb_serial_multitask.rs b/examples/nrf/src/bin/usb_serial_multitask.rs new file mode 100644 index 000000000..bef704417 --- /dev/null +++ b/examples/nrf/src/bin/usb_serial_multitask.rs @@ -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 = 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(); + + unwrap!(spawner.spawn(usb_task(usb))); + unwrap!(spawner.spawn(echo_task(class))); +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: ReadError) -> Self { + match val { + ReadError::BufferOverflow => panic!("Buffer overflow"), + ReadError::Disabled => Disconnected {}, + } + } +} + +impl From 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?; + } +} -- cgit From a1754ac8a820d9cae97cf214969faf3090b37c76 Mon Sep 17 00:00:00 2001 From: alexmoon Date: Tue, 5 Apr 2022 17:23:46 -0400 Subject: embassy-usb-hid bug fixes --- examples/nrf/src/bin/usb_hid_keyboard.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb_hid_keyboard.rs b/examples/nrf/src/bin/usb_hid_keyboard.rs index af70a9a60..51136292f 100644 --- a/examples/nrf/src/bin/usb_hid_keyboard.rs +++ b/examples/nrf/src/bin/usb_hid_keyboard.rs @@ -54,7 +54,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut control_buf = [0; 16]; let request_handler = MyRequestHandler {}; - let mut state = State::<64, 64>::new(); + let mut state = State::<8, 1>::new(); let mut builder = UsbDeviceBuilder::new( driver, @@ -117,7 +117,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { }; let out_fut = async { - hid_out.run(&MyRequestHandler {}).await; + 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. -- cgit From 6abbfa9a92ec9feb03e30846bccb66272020601d Mon Sep 17 00:00:00 2001 From: alexmoon Date: Wed, 6 Apr 2022 22:10:18 -0400 Subject: Async-ify Driver::enable and UsbDeviceBuilder::build --- examples/nrf/src/bin/usb_hid_keyboard.rs | 2 +- examples/nrf/src/bin/usb_hid_mouse.rs | 2 +- examples/nrf/src/bin/usb_serial.rs | 2 +- examples/nrf/src/bin/usb_serial_multitask.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'examples') diff --git a/examples/nrf/src/bin/usb_hid_keyboard.rs b/examples/nrf/src/bin/usb_hid_keyboard.rs index 51136292f..0812697e4 100644 --- a/examples/nrf/src/bin/usb_hid_keyboard.rs +++ b/examples/nrf/src/bin/usb_hid_keyboard.rs @@ -76,7 +76,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { ); // Build the builder. - let mut usb = builder.build(); + let mut usb = builder.build().await; // Run the USB device. let usb_fut = usb.run(); diff --git a/examples/nrf/src/bin/usb_hid_mouse.rs b/examples/nrf/src/bin/usb_hid_mouse.rs index 741e234b6..ca9383827 100644 --- a/examples/nrf/src/bin/usb_hid_mouse.rs +++ b/examples/nrf/src/bin/usb_hid_mouse.rs @@ -74,7 +74,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { ); // Build the builder. - let mut usb = builder.build(); + let mut usb = builder.build().await; // Run the USB device. let usb_fut = usb.run(); diff --git a/examples/nrf/src/bin/usb_serial.rs b/examples/nrf/src/bin/usb_serial.rs index 9437e835f..500be2ce8 100644 --- a/examples/nrf/src/bin/usb_serial.rs +++ b/examples/nrf/src/bin/usb_serial.rs @@ -60,7 +60,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); // Build the builder. - let mut usb = builder.build(); + let mut usb = builder.build().await; // Run the USB device. let usb_fut = usb.run(); diff --git a/examples/nrf/src/bin/usb_serial_multitask.rs b/examples/nrf/src/bin/usb_serial_multitask.rs index bef704417..1258bc53d 100644 --- a/examples/nrf/src/bin/usb_serial_multitask.rs +++ b/examples/nrf/src/bin/usb_serial_multitask.rs @@ -85,7 +85,7 @@ async fn main(spawner: Spawner, p: Peripherals) { let class = CdcAcmClass::new(&mut builder, &mut res.serial_state, 64); // Build the builder. - let usb = builder.build(); + let usb = builder.build().await; unwrap!(spawner.spawn(usb_task(usb))); unwrap!(spawner.spawn(echo_task(class))); -- cgit