aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/nrf/Cargo.toml1
-rw-r--r--examples/nrf/src/bin/usb/cdc_acm.rs387
-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"]
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", "unstable-pac"] } 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"] } 14embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] }
15embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] }
15 16
16defmt = "0.3" 17defmt = "0.3"
17defmt-rtt = "0.3" 18defmt-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 @@
1use core::cell::Cell;
2use core::mem::{self, MaybeUninit};
3use core::sync::atomic::{AtomicBool, Ordering};
4use defmt::info;
5use embassy::blocking_mutex::CriticalSectionMutex;
6use embassy_usb::control::{self, ControlHandler, InResponse, OutResponse, Request};
7use embassy_usb::driver::{Endpoint, EndpointIn, EndpointOut, ReadError, WriteError};
8use embassy_usb::{driver::Driver, types::*, UsbDeviceBuilder};
9
10/// This should be used as `device_class` when building the `UsbDevice`.
11pub const USB_CLASS_CDC: u8 = 0x02;
12
13const USB_CLASS_CDC_DATA: u8 = 0x0a;
14const CDC_SUBCLASS_ACM: u8 = 0x02;
15const CDC_PROTOCOL_NONE: u8 = 0x00;
16
17const CS_INTERFACE: u8 = 0x24;
18const CDC_TYPE_HEADER: u8 = 0x00;
19const CDC_TYPE_CALL_MANAGEMENT: u8 = 0x01;
20const CDC_TYPE_ACM: u8 = 0x02;
21const CDC_TYPE_UNION: u8 = 0x06;
22
23const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00;
24#[allow(unused)]
25const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01;
26const REQ_SET_LINE_CODING: u8 = 0x20;
27const REQ_GET_LINE_CODING: u8 = 0x21;
28const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22;
29
30pub struct State<'a> {
31 control: MaybeUninit<Control<'a>>,
32 shared: ControlShared,
33}
34
35impl<'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.
58pub 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
67struct Control<'a> {
68 shared: &'a ControlShared,
69}
70
71/// Shared data between Control and CdcAcmClass
72struct ControlShared {
73 line_coding: CriticalSectionMutex<Cell<LineCoding>>,
74 dtr: AtomicBool,
75 rts: AtomicBool,
76}
77
78impl 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
93impl<'a> Control<'a> {
94 fn shared(&mut self) -> &'a ControlShared {
95 self.shared
96 }
97}
98
99impl<'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
159impl<'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)]
303pub 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
314impl 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)]
326pub enum ParityType {
327 None = 0,
328 Odd = 1,
329 Event = 2,
330 Mark = 3,
331 Space = 4,
332}
333
334impl 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)]
349pub struct LineCoding {
350 stop_bits: StopBits,
351 data_bits: u8,
352 parity_type: ParityType,
353 data_rate: u32,
354}
355
356impl 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
378impl 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"]
7mod example_common;
8
9mod cdc_acm;
10
11use core::mem; 6use core::mem;
12use defmt::*; 7use defmt::*;
13use embassy::executor::Spawner; 8use embassy::executor::Spawner;
@@ -18,9 +13,11 @@ use embassy_nrf::usb::Driver;
18use embassy_nrf::Peripherals; 13use embassy_nrf::Peripherals;
19use embassy_usb::driver::{EndpointIn, EndpointOut}; 14use embassy_usb::driver::{EndpointIn, EndpointOut};
20use embassy_usb::{Config, UsbDeviceBuilder}; 15use embassy_usb::{Config, UsbDeviceBuilder};
16use embassy_usb_serial::{CdcAcmClass, State};
21use futures::future::join3; 17use futures::future::join3;
22 18
23use crate::cdc_acm::{CdcAcmClass, State}; 19use defmt_rtt as _; // global logger
20use panic_probe as _;
24 21
25#[embassy::main] 22#[embassy::main]
26async fn main(_spawner: Spawner, p: Peripherals) { 23async fn main(_spawner: Spawner, p: Peripherals) {