aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2022-03-09 01:34:35 +0100
committerDario Nieuwenhuis <[email protected]>2022-04-06 05:38:11 +0200
commit37598a5b3792ec1b763b5c16fe422c9e1347d7d6 (patch)
tree558d06652cd95998e2d7b4eebc8e85329aa17a6b /examples
parentc1b382296434e762d16a36d658d2f308358e3f87 (diff)
wip: experimental async usb stack
Diffstat (limited to 'examples')
-rw-r--r--examples/nrf/Cargo.toml5
-rw-r--r--examples/nrf/src/bin/usb/cdc_acm.rs356
-rw-r--r--examples/nrf/src/bin/usb/main.rs53
-rw-r--r--examples/nrf/src/bin/usb_uart.rs89
-rw-r--r--examples/nrf/src/bin/usb_uart_io.rs66
5 files changed, 411 insertions, 158 deletions
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"]
10 10
11[dependencies] 11[dependencies]
12embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] } 12embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] }
13embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] } 13embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] }
14embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
14 15
15defmt = "0.3" 16defmt = "0.3"
16defmt-rtt = "0.3" 17defmt-rtt = "0.3"
@@ -22,5 +23,3 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa
22rand = { version = "0.8.4", default-features = false } 23rand = { version = "0.8.4", default-features = false }
23embedded-storage = "0.3.0" 24embedded-storage = "0.3.0"
24 25
25usb-device = "0.2"
26usbd-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 @@
1use core::convert::TryInto;
2use core::mem;
3use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError};
4use embassy_usb::{driver::Driver, types::*, UsbDeviceBuilder};
5
6/// This should be used as `device_class` when building the `UsbDevice`.
7pub const USB_CLASS_CDC: u8 = 0x02;
8
9const USB_CLASS_CDC_DATA: u8 = 0x0a;
10const CDC_SUBCLASS_ACM: u8 = 0x02;
11const CDC_PROTOCOL_NONE: u8 = 0x00;
12
13const CS_INTERFACE: u8 = 0x24;
14const CDC_TYPE_HEADER: u8 = 0x00;
15const CDC_TYPE_CALL_MANAGEMENT: u8 = 0x01;
16const CDC_TYPE_ACM: u8 = 0x02;
17const CDC_TYPE_UNION: u8 = 0x06;
18
19const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00;
20#[allow(unused)]
21const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01;
22const REQ_SET_LINE_CODING: u8 = 0x20;
23const REQ_GET_LINE_CODING: u8 = 0x21;
24const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22;
25
26/// Packet level implementation of a CDC-ACM serial port.
27///
28/// This class can be used directly and it has the least overhead due to directly reading and
29/// writing USB packets with no intermediate buffers, but it will not act like a stream-like serial
30/// port. The following constraints must be followed if you use this class directly:
31///
32/// - `read_packet` must be called with a buffer large enough to hold max_packet_size bytes, and the
33/// method will return a `WouldBlock` error if there is no packet to be read.
34/// - `write_packet` must not be called with a buffer larger than max_packet_size bytes, and the
35/// method will return a `WouldBlock` error if the previous packet has not been sent yet.
36/// - If you write a packet that is exactly max_packet_size bytes long, it won't be processed by the
37/// host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP)
38/// can be sent if there is no other data to send. This is because USB bulk transactions must be
39/// terminated with a short packet, even if the bulk endpoint is used for stream-like data.
40pub struct CdcAcmClass<'d, D: Driver<'d>> {
41 comm_if: InterfaceNumber,
42 comm_ep: D::EndpointIn,
43 data_if: InterfaceNumber,
44 read_ep: D::EndpointOut,
45 write_ep: D::EndpointIn,
46 line_coding: LineCoding,
47 dtr: bool,
48 rts: bool,
49}
50
51impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> {
52 /// Creates a new CdcAcmClass with the provided UsbBus and max_packet_size in bytes. For
53 /// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64.
54 pub fn new(builder: &mut UsbDeviceBuilder<'d, D>, max_packet_size: u16) -> Self {
55 let comm_if = builder.alloc_interface();
56 let comm_ep = builder.alloc_interrupt_endpoint_in(8, 255);
57 let data_if = builder.alloc_interface();
58 let read_ep = builder.alloc_bulk_endpoint_out(max_packet_size);
59 let write_ep = builder.alloc_bulk_endpoint_in(max_packet_size);
60
61 builder
62 .config_descriptor
63 .iad(
64 comm_if,
65 2,
66 USB_CLASS_CDC,
67 CDC_SUBCLASS_ACM,
68 CDC_PROTOCOL_NONE,
69 )
70 .unwrap();
71
72 builder
73 .config_descriptor
74 .interface(comm_if, USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE)
75 .unwrap();
76
77 builder
78 .config_descriptor
79 .write(
80 CS_INTERFACE,
81 &[
82 CDC_TYPE_HEADER, // bDescriptorSubtype
83 0x10,
84 0x01, // bcdCDC (1.10)
85 ],
86 )
87 .unwrap();
88
89 builder
90 .config_descriptor
91 .write(
92 CS_INTERFACE,
93 &[
94 CDC_TYPE_ACM, // bDescriptorSubtype
95 0x00, // bmCapabilities
96 ],
97 )
98 .unwrap();
99
100 builder
101 .config_descriptor
102 .write(
103 CS_INTERFACE,
104 &[
105 CDC_TYPE_UNION, // bDescriptorSubtype
106 comm_if.into(), // bControlInterface
107 data_if.into(), // bSubordinateInterface
108 ],
109 )
110 .unwrap();
111
112 builder
113 .config_descriptor
114 .write(
115 CS_INTERFACE,
116 &[
117 CDC_TYPE_CALL_MANAGEMENT, // bDescriptorSubtype
118 0x00, // bmCapabilities
119 data_if.into(), // bDataInterface
120 ],
121 )
122 .unwrap();
123
124 builder.config_descriptor.endpoint(comm_ep.info()).unwrap();
125
126 builder
127 .config_descriptor
128 .interface(data_if, USB_CLASS_CDC_DATA, 0x00, 0x00)
129 .unwrap();
130
131 builder.config_descriptor.endpoint(write_ep.info()).unwrap();
132 builder.config_descriptor.endpoint(read_ep.info()).unwrap();
133
134 CdcAcmClass {
135 comm_if,
136 comm_ep,
137 data_if,
138 read_ep,
139 write_ep,
140 line_coding: LineCoding {
141 stop_bits: StopBits::One,
142 data_bits: 8,
143 parity_type: ParityType::None,
144 data_rate: 8_000,
145 },
146 dtr: false,
147 rts: false,
148 }
149 }
150
151 /// Gets the maximum packet size in bytes.
152 pub fn max_packet_size(&self) -> u16 {
153 // The size is the same for both endpoints.
154 self.read_ep.info().max_packet_size
155 }
156
157 /// Gets the current line coding. The line coding contains information that's mainly relevant
158 /// for USB to UART serial port emulators, and can be ignored if not relevant.
159 pub fn line_coding(&self) -> &LineCoding {
160 &self.line_coding
161 }
162
163 /// Gets the DTR (data terminal ready) state
164 pub fn dtr(&self) -> bool {
165 self.dtr
166 }
167
168 /// Gets the RTS (request to send) state
169 pub fn rts(&self) -> bool {
170 self.rts
171 }
172
173 /// Writes a single packet into the IN endpoint.
174 pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), WriteError> {
175 self.write_ep.write(data).await
176 }
177
178 /// Reads a single packet from the OUT endpoint.
179 pub async fn read_packet(&mut self, data: &mut [u8]) -> Result<usize, ReadError> {
180 self.read_ep.read(data).await
181 }
182
183 /// Gets the address of the IN endpoint.
184 pub(crate) fn write_ep_address(&self) -> EndpointAddress {
185 self.write_ep.info().addr
186 }
187}
188
189/*
190impl<B: UsbBus> UsbClass<B> for CdcAcmClass<'_, B> {
191 fn get_configuration_descriptors(&self, builder.config_descriptor: &mut Descriptorbuilder.config_descriptor) -> Result<()> {
192
193 Ok(())
194 }
195
196 fn reset(&mut self) {
197 self.line_coding = LineCoding::default();
198 self.dtr = false;
199 self.rts = false;
200 }
201
202 fn control_in(&mut self, xfer: ControlIn<B>) {
203 let req = xfer.request();
204
205 if !(req.request_type == control::RequestType::Class
206 && req.recipient == control::Recipient::Interface
207 && req.index == u8::from(self.comm_if) as u16)
208 {
209 return;
210 }
211
212 match req.request {
213 // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below.
214 REQ_GET_LINE_CODING if req.length == 7 => {
215 xfer.accept(|data| {
216 data[0..4].copy_from_slice(&self.line_coding.data_rate.to_le_bytes());
217 data[4] = self.line_coding.stop_bits as u8;
218 data[5] = self.line_coding.parity_type as u8;
219 data[6] = self.line_coding.data_bits;
220
221 Ok(7)
222 })
223 .ok();
224 }
225 _ => {
226 xfer.reject().ok();
227 }
228 }
229 }
230
231 fn control_out(&mut self, xfer: ControlOut<B>) {
232 let req = xfer.request();
233
234 if !(req.request_type == control::RequestType::Class
235 && req.recipient == control::Recipient::Interface
236 && req.index == u8::from(self.comm_if) as u16)
237 {
238 return;
239 }
240
241 match req.request {
242 REQ_SEND_ENCAPSULATED_COMMAND => {
243 // We don't actually support encapsulated commands but pretend we do for standards
244 // compatibility.
245 xfer.accept().ok();
246 }
247 REQ_SET_LINE_CODING if xfer.data().len() >= 7 => {
248 self.line_coding.data_rate =
249 u32::from_le_bytes(xfer.data()[0..4].try_into().unwrap());
250 self.line_coding.stop_bits = xfer.data()[4].into();
251 self.line_coding.parity_type = xfer.data()[5].into();
252 self.line_coding.data_bits = xfer.data()[6];
253
254 xfer.accept().ok();
255 }
256 REQ_SET_CONTROL_LINE_STATE => {
257 self.dtr = (req.value & 0x0001) != 0;
258 self.rts = (req.value & 0x0002) != 0;
259
260 xfer.accept().ok();
261 }
262 _ => {
263 xfer.reject().ok();
264 }
265 };
266 }
267}
268
269 */
270
271/// Number of stop bits for LineCoding
272#[derive(Copy, Clone, PartialEq, Eq)]
273pub enum StopBits {
274 /// 1 stop bit
275 One = 0,
276
277 /// 1.5 stop bits
278 OnePointFive = 1,
279
280 /// 2 stop bits
281 Two = 2,
282}
283
284impl From<u8> for StopBits {
285 fn from(value: u8) -> Self {
286 if value <= 2 {
287 unsafe { mem::transmute(value) }
288 } else {
289 StopBits::One
290 }
291 }
292}
293
294/// Parity for LineCoding
295#[derive(Copy, Clone, PartialEq, Eq)]
296pub enum ParityType {
297 None = 0,
298 Odd = 1,
299 Event = 2,
300 Mark = 3,
301 Space = 4,
302}
303
304impl From<u8> for ParityType {
305 fn from(value: u8) -> Self {
306 if value <= 4 {
307 unsafe { mem::transmute(value) }
308 } else {
309 ParityType::None
310 }
311 }
312}
313
314/// Line coding parameters
315///
316/// This is provided by the host for specifying the standard UART parameters such as baud rate. Can
317/// be ignored if you don't plan to interface with a physical UART.
318pub struct LineCoding {
319 stop_bits: StopBits,
320 data_bits: u8,
321 parity_type: ParityType,
322 data_rate: u32,
323}
324
325impl LineCoding {
326 /// Gets the number of stop bits for UART communication.
327 pub fn stop_bits(&self) -> StopBits {
328 self.stop_bits
329 }
330
331 /// Gets the number of data bits for UART communication.
332 pub fn data_bits(&self) -> u8 {
333 self.data_bits
334 }
335
336 /// Gets the parity type for UART communication.
337 pub fn parity_type(&self) -> ParityType {
338 self.parity_type
339 }
340
341 /// Gets the data rate in bits per second for UART communication.
342 pub fn data_rate(&self) -> u32 {
343 self.data_rate
344 }
345}
346
347impl Default for LineCoding {
348 fn default() -> Self {
349 LineCoding {
350 stop_bits: StopBits::One,
351 data_bits: 8,
352 parity_type: ParityType::None,
353 data_rate: 8_000,
354 }
355 }
356}
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 @@
1#![no_std]
2#![no_main]
3#![feature(type_alias_impl_trait)]
4
5#[path = "../../example_common.rs"]
6mod example_common;
7
8mod cdc_acm;
9
10use core::mem;
11use defmt::*;
12use embassy::executor::Spawner;
13use embassy_nrf::interrupt;
14use embassy_nrf::pac;
15use embassy_nrf::usb::{self, Driver};
16use embassy_nrf::Peripherals;
17use embassy_usb::{Config, UsbDeviceBuilder};
18
19use crate::cdc_acm::CdcAcmClass;
20
21#[embassy::main]
22async fn main(_spawner: Spawner, p: Peripherals) {
23 let clock: pac::CLOCK = unsafe { mem::transmute(()) };
24 let power: pac::POWER = unsafe { mem::transmute(()) };
25
26 info!("Enabling ext hfosc...");
27 clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) });
28 while clock.events_hfclkstarted.read().bits() != 1 {}
29
30 info!("Waiting for vbus...");
31 while !power.usbregstatus.read().vbusdetect().is_vbus_present() {}
32 info!("vbus OK");
33
34 let irq = interrupt::take!(USBD);
35 let driver = Driver::new(p.USBD, irq);
36 let config = Config::new(0xc0de, 0xcafe);
37 let mut device_descriptor = [0; 256];
38 let mut config_descriptor = [0; 256];
39 let mut bos_descriptor = [0; 256];
40
41 let mut builder = UsbDeviceBuilder::new(
42 driver,
43 config,
44 &mut device_descriptor,
45 &mut config_descriptor,
46 &mut bos_descriptor,
47 );
48
49 let mut class = CdcAcmClass::new(&mut builder, 64);
50
51 let mut usb = builder.build();
52 usb.run().await;
53}
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 @@
1#![no_std]
2#![no_main]
3#![feature(type_alias_impl_trait)]
4
5use defmt::{info, unwrap};
6use embassy::executor::Spawner;
7use embassy::interrupt::InterruptExt;
8use embassy::io::{AsyncBufReadExt, AsyncWriteExt};
9use embassy_nrf::usb::{State, Usb, UsbBus, UsbSerial};
10use embassy_nrf::{interrupt, Peripherals};
11use futures::pin_mut;
12use usb_device::device::{UsbDeviceBuilder, UsbVidPid};
13
14use defmt_rtt as _; // global logger
15use panic_probe as _; // print out panic messages
16
17#[embassy::main]
18async fn main(_spawner: Spawner, p: Peripherals) {
19 let mut rx_buffer = [0u8; 64];
20 // we send back input + cr + lf
21 let mut tx_buffer = [0u8; 66];
22
23 let usb_bus = UsbBus::new(p.USBD);
24
25 let serial = UsbSerial::new(&usb_bus, &mut rx_buffer, &mut tx_buffer);
26
27 let device = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd))
28 .manufacturer("Fake company")
29 .product("Serial port")
30 .serial_number("TEST")
31 .device_class(0x02)
32 .build();
33
34 let irq = interrupt::take!(USBD);
35 irq.set_priority(interrupt::Priority::P3);
36
37 let mut state = State::new();
38 let usb = unsafe { Usb::new(&mut state, device, serial, irq) };
39 pin_mut!(usb);
40
41 let (mut reader, mut writer) = usb.as_ref().take_serial_0();
42
43 info!("usb initialized!");
44
45 unwrap!(
46 writer
47 .write_all(b"\r\nInput returned upper cased on CR+LF\r\n")
48 .await
49 );
50
51 let mut buf = [0u8; 64];
52 loop {
53 let mut n = 0;
54
55 async {
56 loop {
57 let char = unwrap!(reader.read_byte().await);
58
59 // throw away, read more on cr, exit on lf
60 if char == b'\r' {
61 continue;
62 } else if char == b'\n' {
63 break;
64 }
65
66 buf[n] = char;
67 n += 1;
68
69 // stop if we're out of room
70 if n == buf.len() {
71 break;
72 }
73 }
74 }
75 .await;
76
77 if n > 0 {
78 for char in buf[..n].iter_mut() {
79 // upper case
80 if 0x61 <= *char && *char <= 0x7a {
81 *char &= !0x20;
82 }
83 }
84 unwrap!(writer.write_all(&buf[..n]).await);
85 unwrap!(writer.write_all(b"\r\n").await);
86 unwrap!(writer.flush().await);
87 }
88 }
89}
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 @@
1#![no_std]
2#![no_main]
3#![feature(type_alias_impl_trait)]
4
5use defmt::{info, unwrap};
6use embassy::executor::Spawner;
7use embassy::interrupt::InterruptExt;
8use embassy::io::{read_line, AsyncWriteExt};
9use embassy_nrf::usb::{State, Usb, UsbBus, UsbSerial};
10use embassy_nrf::{interrupt, Peripherals};
11use futures::pin_mut;
12use usb_device::device::{UsbDeviceBuilder, UsbVidPid};
13
14use defmt_rtt as _; // global logger
15use panic_probe as _; // print out panic messages
16
17#[embassy::main]
18async fn main(_spawner: Spawner, p: Peripherals) {
19 let mut rx_buffer = [0u8; 64];
20 // we send back input + cr + lf
21 let mut tx_buffer = [0u8; 66];
22
23 let usb_bus = UsbBus::new(p.USBD);
24
25 let serial = UsbSerial::new(&usb_bus, &mut rx_buffer, &mut tx_buffer);
26
27 let device = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd))
28 .manufacturer("Fake company")
29 .product("Serial port")
30 .serial_number("TEST")
31 .device_class(0x02)
32 .build();
33
34 let irq = interrupt::take!(USBD);
35 irq.set_priority(interrupt::Priority::P3);
36
37 let mut state = State::new();
38 let usb = unsafe { Usb::new(&mut state, device, serial, irq) };
39 pin_mut!(usb);
40
41 let (mut reader, mut writer) = usb.as_ref().take_serial_0();
42
43 info!("usb initialized!");
44
45 unwrap!(
46 writer
47 .write_all(b"\r\nInput returned upper cased on CR+LF\r\n")
48 .await
49 );
50
51 let mut buf = [0u8; 64];
52 loop {
53 let n = unwrap!(read_line(&mut reader, &mut buf).await);
54
55 for char in buf[..n].iter_mut() {
56 // upper case
57 if 0x61 <= *char && *char <= 0x7a {
58 *char &= !0x20;
59 }
60 }
61
62 unwrap!(writer.write_all(&buf[..n]).await);
63 unwrap!(writer.write_all(b"\r\n").await);
64 unwrap!(writer.flush().await);
65 }
66}