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