diff options
| author | Dario Nieuwenhuis <[email protected]> | 2022-03-30 01:18:37 +0200 |
|---|---|---|
| committer | Dario Nieuwenhuis <[email protected]> | 2022-04-06 05:38:11 +0200 |
| commit | d1e4b3d7d5a931cd6e04b7e2fe467945ef862477 (patch) | |
| tree | efe3199292133306c20b27da7442cadf8592f3b4 /examples | |
| parent | c06488eb2978d9eaa8dc6a2669a6ce279638f4b2 (diff) | |
usb: add -usb-serial crate, fix warnings and stable build.
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/nrf/Cargo.toml | 1 | ||||
| -rw-r--r-- | examples/nrf/src/bin/usb/cdc_acm.rs | 387 | ||||
| -rw-r--r-- | examples/nrf/src/bin/usb_serial.rs (renamed from examples/nrf/src/bin/usb/main.rs) | 9 |
3 files changed, 4 insertions, 393 deletions
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"] | |||
| 12 | embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] } | 12 | embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] } |
| 13 | embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } | 13 | embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } |
| 14 | embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } | 14 | embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } |
| 15 | embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] } | ||
| 15 | 16 | ||
| 16 | defmt = "0.3" | 17 | defmt = "0.3" |
| 17 | defmt-rtt = "0.3" | 18 | 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 @@ | |||
| 1 | use core::cell::Cell; | ||
| 2 | use core::mem::{self, MaybeUninit}; | ||
| 3 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 4 | use defmt::info; | ||
| 5 | use embassy::blocking_mutex::CriticalSectionMutex; | ||
| 6 | use embassy_usb::control::{self, ControlHandler, InResponse, OutResponse, Request}; | ||
| 7 | use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError}; | ||
| 8 | use embassy_usb::{driver::Driver, types::*, UsbDeviceBuilder}; | ||
| 9 | |||
| 10 | /// This should be used as `device_class` when building the `UsbDevice`. | ||
| 11 | pub const USB_CLASS_CDC: u8 = 0x02; | ||
| 12 | |||
| 13 | const USB_CLASS_CDC_DATA: u8 = 0x0a; | ||
| 14 | const CDC_SUBCLASS_ACM: u8 = 0x02; | ||
| 15 | const CDC_PROTOCOL_NONE: u8 = 0x00; | ||
| 16 | |||
| 17 | const CS_INTERFACE: u8 = 0x24; | ||
| 18 | const CDC_TYPE_HEADER: u8 = 0x00; | ||
| 19 | const CDC_TYPE_CALL_MANAGEMENT: u8 = 0x01; | ||
| 20 | const CDC_TYPE_ACM: u8 = 0x02; | ||
| 21 | const CDC_TYPE_UNION: u8 = 0x06; | ||
| 22 | |||
| 23 | const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00; | ||
| 24 | #[allow(unused)] | ||
| 25 | const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01; | ||
| 26 | const REQ_SET_LINE_CODING: u8 = 0x20; | ||
| 27 | const REQ_GET_LINE_CODING: u8 = 0x21; | ||
| 28 | const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; | ||
| 29 | |||
| 30 | pub struct State<'a> { | ||
| 31 | control: MaybeUninit<Control<'a>>, | ||
| 32 | shared: ControlShared, | ||
| 33 | } | ||
| 34 | |||
| 35 | impl<'a> State<'a> { | ||
| 36 | pub fn new() -> Self { | ||
| 37 | Self { | ||
| 38 | control: MaybeUninit::uninit(), | ||
| 39 | shared: Default::default(), | ||
| 40 | } | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | /// Packet level implementation of a CDC-ACM serial port. | ||
| 45 | /// | ||
| 46 | /// This class can be used directly and it has the least overhead due to directly reading and | ||
| 47 | /// writing USB packets with no intermediate buffers, but it will not act like a stream-like serial | ||
| 48 | /// port. The following constraints must be followed if you use this class directly: | ||
| 49 | /// | ||
| 50 | /// - `read_packet` must be called with a buffer large enough to hold max_packet_size bytes, and the | ||
| 51 | /// method will return a `WouldBlock` error if there is no packet to be read. | ||
| 52 | /// - `write_packet` must not be called with a buffer larger than max_packet_size bytes, and the | ||
| 53 | /// method will return a `WouldBlock` error if the previous packet has not been sent yet. | ||
| 54 | /// - If you write a packet that is exactly max_packet_size bytes long, it won't be processed by the | ||
| 55 | /// host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP) | ||
| 56 | /// can be sent if there is no other data to send. This is because USB bulk transactions must be | ||
| 57 | /// terminated with a short packet, even if the bulk endpoint is used for stream-like data. | ||
| 58 | pub struct CdcAcmClass<'d, D: Driver<'d>> { | ||
| 59 | // TODO not pub | ||
| 60 | pub comm_ep: D::EndpointIn, | ||
| 61 | pub data_if: InterfaceNumber, | ||
| 62 | pub read_ep: D::EndpointOut, | ||
| 63 | pub write_ep: D::EndpointIn, | ||
| 64 | control: &'d ControlShared, | ||
| 65 | } | ||
| 66 | |||
| 67 | struct Control<'a> { | ||
| 68 | shared: &'a ControlShared, | ||
| 69 | } | ||
| 70 | |||
| 71 | /// Shared data between Control and CdcAcmClass | ||
| 72 | struct ControlShared { | ||
| 73 | line_coding: CriticalSectionMutex<Cell<LineCoding>>, | ||
| 74 | dtr: AtomicBool, | ||
| 75 | rts: AtomicBool, | ||
| 76 | } | ||
| 77 | |||
| 78 | impl Default for ControlShared { | ||
| 79 | fn default() -> Self { | ||
| 80 | ControlShared { | ||
| 81 | dtr: AtomicBool::new(false), | ||
| 82 | rts: AtomicBool::new(false), | ||
| 83 | line_coding: CriticalSectionMutex::new(Cell::new(LineCoding { | ||
| 84 | stop_bits: StopBits::One, | ||
| 85 | data_bits: 8, | ||
| 86 | parity_type: ParityType::None, | ||
| 87 | data_rate: 8_000, | ||
| 88 | })), | ||
| 89 | } | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | impl<'a> Control<'a> { | ||
| 94 | fn shared(&mut self) -> &'a ControlShared { | ||
| 95 | self.shared | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | impl<'d> ControlHandler for Control<'d> { | ||
| 100 | fn reset(&mut self) { | ||
| 101 | let shared = self.shared(); | ||
| 102 | shared.line_coding.lock(|x| x.set(LineCoding::default())); | ||
| 103 | shared.dtr.store(false, Ordering::Relaxed); | ||
| 104 | shared.rts.store(false, Ordering::Relaxed); | ||
| 105 | } | ||
| 106 | |||
| 107 | fn control_out(&mut self, req: control::Request, data: &[u8]) -> OutResponse { | ||
| 108 | match req.request { | ||
| 109 | REQ_SEND_ENCAPSULATED_COMMAND => { | ||
| 110 | // We don't actually support encapsulated commands but pretend we do for standards | ||
| 111 | // compatibility. | ||
| 112 | OutResponse::Accepted | ||
| 113 | } | ||
| 114 | REQ_SET_LINE_CODING if data.len() >= 7 => { | ||
| 115 | let coding = LineCoding { | ||
| 116 | data_rate: u32::from_le_bytes(data[0..4].try_into().unwrap()), | ||
| 117 | stop_bits: data[4].into(), | ||
| 118 | parity_type: data[5].into(), | ||
| 119 | data_bits: data[6], | ||
| 120 | }; | ||
| 121 | self.shared().line_coding.lock(|x| x.set(coding)); | ||
| 122 | info!("Set line coding to: {:?}", coding); | ||
| 123 | |||
| 124 | OutResponse::Accepted | ||
| 125 | } | ||
| 126 | REQ_SET_CONTROL_LINE_STATE => { | ||
| 127 | let dtr = (req.value & 0x0001) != 0; | ||
| 128 | let rts = (req.value & 0x0002) != 0; | ||
| 129 | |||
| 130 | let shared = self.shared(); | ||
| 131 | shared.dtr.store(dtr, Ordering::Relaxed); | ||
| 132 | shared.rts.store(rts, Ordering::Relaxed); | ||
| 133 | info!("Set dtr {}, rts {}", dtr, rts); | ||
| 134 | |||
| 135 | OutResponse::Accepted | ||
| 136 | } | ||
| 137 | _ => OutResponse::Rejected, | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { | ||
| 142 | match req.request { | ||
| 143 | // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. | ||
| 144 | REQ_GET_LINE_CODING if req.length == 7 => { | ||
| 145 | info!("Sending line coding"); | ||
| 146 | let coding = self.shared().line_coding.lock(|x| x.get()); | ||
| 147 | assert!(buf.len() >= 7); | ||
| 148 | buf[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); | ||
| 149 | buf[4] = coding.stop_bits as u8; | ||
| 150 | buf[5] = coding.parity_type as u8; | ||
| 151 | buf[6] = coding.data_bits; | ||
| 152 | InResponse::Accepted(&buf[0..7]) | ||
| 153 | } | ||
| 154 | _ => InResponse::Rejected, | ||
| 155 | } | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { | ||
| 160 | /// Creates a new CdcAcmClass with the provided UsbBus and max_packet_size in bytes. For | ||
| 161 | /// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64. | ||
| 162 | pub fn new( | ||
| 163 | builder: &mut UsbDeviceBuilder<'d, D>, | ||
| 164 | state: &'d mut State<'d>, | ||
| 165 | max_packet_size: u16, | ||
| 166 | ) -> Self { | ||
| 167 | let control = state.control.write(Control { | ||
| 168 | shared: &state.shared, | ||
| 169 | }); | ||
| 170 | |||
| 171 | let control_shared = &state.shared; | ||
| 172 | |||
| 173 | assert!(builder.control_buf_len() >= 7); | ||
| 174 | |||
| 175 | let comm_if = builder.alloc_interface_with_handler(control); | ||
| 176 | let comm_ep = builder.alloc_interrupt_endpoint_in(8, 255); | ||
| 177 | let data_if = builder.alloc_interface(); | ||
| 178 | let read_ep = builder.alloc_bulk_endpoint_out(max_packet_size); | ||
| 179 | let write_ep = builder.alloc_bulk_endpoint_in(max_packet_size); | ||
| 180 | |||
| 181 | builder | ||
| 182 | .config_descriptor | ||
| 183 | .iad( | ||
| 184 | comm_if, | ||
| 185 | 2, | ||
| 186 | USB_CLASS_CDC, | ||
| 187 | CDC_SUBCLASS_ACM, | ||
| 188 | CDC_PROTOCOL_NONE, | ||
| 189 | ) | ||
| 190 | .unwrap(); | ||
| 191 | |||
| 192 | builder | ||
| 193 | .config_descriptor | ||
| 194 | .interface(comm_if, USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE) | ||
| 195 | .unwrap(); | ||
| 196 | |||
| 197 | builder | ||
| 198 | .config_descriptor | ||
| 199 | .write( | ||
| 200 | CS_INTERFACE, | ||
| 201 | &[ | ||
| 202 | CDC_TYPE_HEADER, // bDescriptorSubtype | ||
| 203 | 0x10, | ||
| 204 | 0x01, // bcdCDC (1.10) | ||
| 205 | ], | ||
| 206 | ) | ||
| 207 | .unwrap(); | ||
| 208 | |||
| 209 | builder | ||
| 210 | .config_descriptor | ||
| 211 | .write( | ||
| 212 | CS_INTERFACE, | ||
| 213 | &[ | ||
| 214 | CDC_TYPE_ACM, // bDescriptorSubtype | ||
| 215 | 0x00, // bmCapabilities | ||
| 216 | ], | ||
| 217 | ) | ||
| 218 | .unwrap(); | ||
| 219 | |||
| 220 | builder | ||
| 221 | .config_descriptor | ||
| 222 | .write( | ||
| 223 | CS_INTERFACE, | ||
| 224 | &[ | ||
| 225 | CDC_TYPE_UNION, // bDescriptorSubtype | ||
| 226 | comm_if.into(), // bControlInterface | ||
| 227 | data_if.into(), // bSubordinateInterface | ||
| 228 | ], | ||
| 229 | ) | ||
| 230 | .unwrap(); | ||
| 231 | |||
| 232 | builder | ||
| 233 | .config_descriptor | ||
| 234 | .write( | ||
| 235 | CS_INTERFACE, | ||
| 236 | &[ | ||
| 237 | CDC_TYPE_CALL_MANAGEMENT, // bDescriptorSubtype | ||
| 238 | 0x00, // bmCapabilities | ||
| 239 | data_if.into(), // bDataInterface | ||
| 240 | ], | ||
| 241 | ) | ||
| 242 | .unwrap(); | ||
| 243 | |||
| 244 | builder.config_descriptor.endpoint(comm_ep.info()).unwrap(); | ||
| 245 | |||
| 246 | builder | ||
| 247 | .config_descriptor | ||
| 248 | .interface(data_if, USB_CLASS_CDC_DATA, 0x00, 0x00) | ||
| 249 | .unwrap(); | ||
| 250 | |||
| 251 | builder.config_descriptor.endpoint(write_ep.info()).unwrap(); | ||
| 252 | builder.config_descriptor.endpoint(read_ep.info()).unwrap(); | ||
| 253 | |||
| 254 | CdcAcmClass { | ||
| 255 | comm_ep, | ||
| 256 | data_if, | ||
| 257 | read_ep, | ||
| 258 | write_ep, | ||
| 259 | control: control_shared, | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | /// Gets the maximum packet size in bytes. | ||
| 264 | pub fn max_packet_size(&self) -> u16 { | ||
| 265 | // The size is the same for both endpoints. | ||
| 266 | self.read_ep.info().max_packet_size | ||
| 267 | } | ||
| 268 | |||
| 269 | /// Gets the current line coding. The line coding contains information that's mainly relevant | ||
| 270 | /// for USB to UART serial port emulators, and can be ignored if not relevant. | ||
| 271 | pub fn line_coding(&self) -> LineCoding { | ||
| 272 | self.control.line_coding.lock(|x| x.get()) | ||
| 273 | } | ||
| 274 | |||
| 275 | /// Gets the DTR (data terminal ready) state | ||
| 276 | pub fn dtr(&self) -> bool { | ||
| 277 | self.control.dtr.load(Ordering::Relaxed) | ||
| 278 | } | ||
| 279 | |||
| 280 | /// Gets the RTS (request to send) state | ||
| 281 | pub fn rts(&self) -> bool { | ||
| 282 | self.control.rts.load(Ordering::Relaxed) | ||
| 283 | } | ||
| 284 | |||
| 285 | /// Writes a single packet into the IN endpoint. | ||
| 286 | pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), WriteError> { | ||
| 287 | self.write_ep.write(data).await | ||
| 288 | } | ||
| 289 | |||
| 290 | /// Reads a single packet from the OUT endpoint. | ||
| 291 | pub async fn read_packet(&mut self, data: &mut [u8]) -> Result<usize, ReadError> { | ||
| 292 | self.read_ep.read(data).await | ||
| 293 | } | ||
| 294 | |||
| 295 | /// Gets the address of the IN endpoint. | ||
| 296 | pub(crate) fn write_ep_address(&self) -> EndpointAddress { | ||
| 297 | self.write_ep.info().addr | ||
| 298 | } | ||
| 299 | } | ||
| 300 | |||
| 301 | /// Number of stop bits for LineCoding | ||
| 302 | #[derive(Copy, Clone, PartialEq, Eq, defmt::Format)] | ||
| 303 | pub enum StopBits { | ||
| 304 | /// 1 stop bit | ||
| 305 | One = 0, | ||
| 306 | |||
| 307 | /// 1.5 stop bits | ||
| 308 | OnePointFive = 1, | ||
| 309 | |||
| 310 | /// 2 stop bits | ||
| 311 | Two = 2, | ||
| 312 | } | ||
| 313 | |||
| 314 | impl From<u8> for StopBits { | ||
| 315 | fn from(value: u8) -> Self { | ||
| 316 | if value <= 2 { | ||
| 317 | unsafe { mem::transmute(value) } | ||
| 318 | } else { | ||
| 319 | StopBits::One | ||
| 320 | } | ||
| 321 | } | ||
| 322 | } | ||
| 323 | |||
| 324 | /// Parity for LineCoding | ||
| 325 | #[derive(Copy, Clone, PartialEq, Eq, defmt::Format)] | ||
| 326 | pub enum ParityType { | ||
| 327 | None = 0, | ||
| 328 | Odd = 1, | ||
| 329 | Event = 2, | ||
| 330 | Mark = 3, | ||
| 331 | Space = 4, | ||
| 332 | } | ||
| 333 | |||
| 334 | impl From<u8> for ParityType { | ||
| 335 | fn from(value: u8) -> Self { | ||
| 336 | if value <= 4 { | ||
| 337 | unsafe { mem::transmute(value) } | ||
| 338 | } else { | ||
| 339 | ParityType::None | ||
| 340 | } | ||
| 341 | } | ||
| 342 | } | ||
| 343 | |||
| 344 | /// Line coding parameters | ||
| 345 | /// | ||
| 346 | /// This is provided by the host for specifying the standard UART parameters such as baud rate. Can | ||
| 347 | /// be ignored if you don't plan to interface with a physical UART. | ||
| 348 | #[derive(Clone, Copy, defmt::Format)] | ||
| 349 | pub struct LineCoding { | ||
| 350 | stop_bits: StopBits, | ||
| 351 | data_bits: u8, | ||
| 352 | parity_type: ParityType, | ||
| 353 | data_rate: u32, | ||
| 354 | } | ||
| 355 | |||
| 356 | impl LineCoding { | ||
| 357 | /// Gets the number of stop bits for UART communication. | ||
| 358 | pub fn stop_bits(&self) -> StopBits { | ||
| 359 | self.stop_bits | ||
| 360 | } | ||
| 361 | |||
| 362 | /// Gets the number of data bits for UART communication. | ||
| 363 | pub fn data_bits(&self) -> u8 { | ||
| 364 | self.data_bits | ||
| 365 | } | ||
| 366 | |||
| 367 | /// Gets the parity type for UART communication. | ||
| 368 | pub fn parity_type(&self) -> ParityType { | ||
| 369 | self.parity_type | ||
| 370 | } | ||
| 371 | |||
| 372 | /// Gets the data rate in bits per second for UART communication. | ||
| 373 | pub fn data_rate(&self) -> u32 { | ||
| 374 | self.data_rate | ||
| 375 | } | ||
| 376 | } | ||
| 377 | |||
| 378 | impl Default for LineCoding { | ||
| 379 | fn default() -> Self { | ||
| 380 | LineCoding { | ||
| 381 | stop_bits: StopBits::One, | ||
| 382 | data_bits: 8, | ||
| 383 | parity_type: ParityType::None, | ||
| 384 | data_rate: 8_000, | ||
| 385 | } | ||
| 386 | } | ||
| 387 | } | ||
diff --git a/examples/nrf/src/bin/usb/main.rs b/examples/nrf/src/bin/usb_serial.rs index c4b9c0176..0a4ff9489 100644 --- a/examples/nrf/src/bin/usb/main.rs +++ b/examples/nrf/src/bin/usb_serial.rs | |||
| @@ -3,11 +3,6 @@ | |||
| 3 | #![feature(generic_associated_types)] | 3 | #![feature(generic_associated_types)] |
| 4 | #![feature(type_alias_impl_trait)] | 4 | #![feature(type_alias_impl_trait)] |
| 5 | 5 | ||
| 6 | #[path = "../../example_common.rs"] | ||
| 7 | mod example_common; | ||
| 8 | |||
| 9 | mod cdc_acm; | ||
| 10 | |||
| 11 | use core::mem; | 6 | use core::mem; |
| 12 | use defmt::*; | 7 | use defmt::*; |
| 13 | use embassy::executor::Spawner; | 8 | use embassy::executor::Spawner; |
| @@ -18,9 +13,11 @@ use embassy_nrf::usb::Driver; | |||
| 18 | use embassy_nrf::Peripherals; | 13 | use embassy_nrf::Peripherals; |
| 19 | use embassy_usb::driver::{EndpointIn, EndpointOut}; | 14 | use embassy_usb::driver::{EndpointIn, EndpointOut}; |
| 20 | use embassy_usb::{Config, UsbDeviceBuilder}; | 15 | use embassy_usb::{Config, UsbDeviceBuilder}; |
| 16 | use embassy_usb_serial::{CdcAcmClass, State}; | ||
| 21 | use futures::future::join3; | 17 | use futures::future::join3; |
| 22 | 18 | ||
| 23 | use crate::cdc_acm::{CdcAcmClass, State}; | 19 | use defmt_rtt as _; // global logger |
| 20 | use panic_probe as _; | ||
| 24 | 21 | ||
| 25 | #[embassy::main] | 22 | #[embassy::main] |
| 26 | async fn main(_spawner: Spawner, p: Peripherals) { | 23 | async fn main(_spawner: Spawner, p: Peripherals) { |
