aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxoviat <[email protected]>2021-03-27 21:31:49 -0500
committerxoviat <[email protected]>2021-03-27 21:31:49 -0500
commit9e687ade6483c0541aa082c7a1922ba9271d2f46 (patch)
tree28ce03ef3b4f8c25cc644139d93e4dcd3469c209
parent6ee9e012fcfc9a0a901f78885d19ae3c3d10570c (diff)
parent4bc77e3d0ebd12d9e435be44804dc453a64619ea (diff)
Merge branch 'master' of https://github.com/akiles/embassy into proc-macro
-rw-r--r--embassy-extras/Cargo.toml1
-rw-r--r--embassy-extras/src/lib.rs1
-rw-r--r--embassy-extras/src/ring_buffer.rs6
-rw-r--r--embassy-extras/src/usb/cdc_acm.rs338
-rw-r--r--embassy-extras/src/usb/mod.rs254
-rw-r--r--embassy-extras/src/usb/usb_serial.rs310
-rw-r--r--embassy-stm32/Cargo.toml1
-rw-r--r--embassy-stm32/src/lib.rs3
-rw-r--r--embassy-stm32f4-examples/Cargo.toml6
-rw-r--r--embassy-stm32f4-examples/src/bin/usb_serial.rs146
-rw-r--r--embassy-stm32f4/Cargo.toml1
11 files changed, 1065 insertions, 2 deletions
diff --git a/embassy-extras/Cargo.toml b/embassy-extras/Cargo.toml
index 3c42b5c2f..5d07901a9 100644
--- a/embassy-extras/Cargo.toml
+++ b/embassy-extras/Cargo.toml
@@ -17,3 +17,4 @@ embassy = { version = "0.1.0", path = "../embassy" }
17defmt = { version = "0.2.0", optional = true } 17defmt = { version = "0.2.0", optional = true }
18log = { version = "0.4.11", optional = true } 18log = { version = "0.4.11", optional = true }
19cortex-m = "0.7.1" 19cortex-m = "0.7.1"
20usb-device = "0.2.7"
diff --git a/embassy-extras/src/lib.rs b/embassy-extras/src/lib.rs
index 4a95173cf..536e86c61 100644
--- a/embassy-extras/src/lib.rs
+++ b/embassy-extras/src/lib.rs
@@ -5,6 +5,7 @@ pub(crate) mod fmt;
5 5
6pub mod peripheral; 6pub mod peripheral;
7pub mod ring_buffer; 7pub mod ring_buffer;
8pub mod usb;
8 9
9/// Low power blocking wait loop using WFE/SEV. 10/// Low power blocking wait loop using WFE/SEV.
10pub fn low_power_wait_until(mut condition: impl FnMut() -> bool) { 11pub fn low_power_wait_until(mut condition: impl FnMut() -> bool) {
diff --git a/embassy-extras/src/ring_buffer.rs b/embassy-extras/src/ring_buffer.rs
index f2b9f7359..0ef66f00a 100644
--- a/embassy-extras/src/ring_buffer.rs
+++ b/embassy-extras/src/ring_buffer.rs
@@ -69,6 +69,12 @@ impl<'a> RingBuffer<'a> {
69 self.empty = self.start == self.end; 69 self.empty = self.start == self.end;
70 } 70 }
71 71
72 pub fn clear(&mut self) {
73 self.start = 0;
74 self.end = 0;
75 self.empty = true;
76 }
77
72 fn wrap(&self, n: usize) -> usize { 78 fn wrap(&self, n: usize) -> usize {
73 assert!(n <= self.buf.len()); 79 assert!(n <= self.buf.len());
74 if n == self.buf.len() { 80 if n == self.buf.len() {
diff --git a/embassy-extras/src/usb/cdc_acm.rs b/embassy-extras/src/usb/cdc_acm.rs
new file mode 100644
index 000000000..5a85b3846
--- /dev/null
+++ b/embassy-extras/src/usb/cdc_acm.rs
@@ -0,0 +1,338 @@
1// Copied from https://github.com/mvirkkunen/usbd-serial
2#![allow(dead_code)]
3
4use core::convert::TryInto;
5use core::mem;
6use usb_device::class_prelude::*;
7use usb_device::Result;
8
9/// This should be used as `device_class` when building the `UsbDevice`.
10pub const USB_CLASS_CDC: u8 = 0x02;
11
12const USB_CLASS_CDC_DATA: u8 = 0x0a;
13const CDC_SUBCLASS_ACM: u8 = 0x02;
14const CDC_PROTOCOL_NONE: u8 = 0x00;
15
16const CS_INTERFACE: u8 = 0x24;
17const CDC_TYPE_HEADER: u8 = 0x00;
18const CDC_TYPE_CALL_MANAGEMENT: u8 = 0x01;
19const CDC_TYPE_ACM: u8 = 0x02;
20const CDC_TYPE_UNION: u8 = 0x06;
21
22const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00;
23#[allow(unused)]
24const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01;
25const REQ_SET_LINE_CODING: u8 = 0x20;
26const REQ_GET_LINE_CODING: u8 = 0x21;
27const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22;
28
29/// Packet level implementation of a CDC-ACM serial port.
30///
31/// This class can be used directly and it has the least overhead due to directly reading and
32/// writing USB packets with no intermediate buffers, but it will not act like a stream-like serial
33/// port. The following constraints must be followed if you use this class directly:
34///
35/// - `read_packet` must be called with a buffer large enough to hold max_packet_size bytes, and the
36/// method will return a `WouldBlock` error if there is no packet to be read.
37/// - `write_packet` must not be called with a buffer larger than max_packet_size bytes, and the
38/// method will return a `WouldBlock` error if the previous packet has not been sent yet.
39/// - If you write a packet that is exactly max_packet_size bytes long, it won't be processed by the
40/// host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP)
41/// can be sent if there is no other data to send. This is because USB bulk transactions must be
42/// terminated with a short packet, even if the bulk endpoint is used for stream-like data.
43pub struct CdcAcmClass<'a, B: UsbBus> {
44 comm_if: InterfaceNumber,
45 comm_ep: EndpointIn<'a, B>,
46 data_if: InterfaceNumber,
47 read_ep: EndpointOut<'a, B>,
48 write_ep: EndpointIn<'a, B>,
49 line_coding: LineCoding,
50 dtr: bool,
51 rts: bool,
52}
53
54impl<B: UsbBus> CdcAcmClass<'_, B> {
55 /// Creates a new CdcAcmClass with the provided UsbBus and max_packet_size in bytes. For
56 /// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64.
57 pub fn new(alloc: &UsbBusAllocator<B>, max_packet_size: u16) -> CdcAcmClass<'_, B> {
58 CdcAcmClass {
59 comm_if: alloc.interface(),
60 comm_ep: alloc.interrupt(8, 255),
61 data_if: alloc.interface(),
62 read_ep: alloc.bulk(max_packet_size),
63 write_ep: alloc.bulk(max_packet_size),
64 line_coding: LineCoding {
65 stop_bits: StopBits::One,
66 data_bits: 8,
67 parity_type: ParityType::None,
68 data_rate: 8_000,
69 },
70 dtr: false,
71 rts: false,
72 }
73 }
74
75 /// Gets the maximum packet size in bytes.
76 pub fn max_packet_size(&self) -> u16 {
77 // The size is the same for both endpoints.
78 self.read_ep.max_packet_size()
79 }
80
81 /// Gets the current line coding. The line coding contains information that's mainly relevant
82 /// for USB to UART serial port emulators, and can be ignored if not relevant.
83 pub fn line_coding(&self) -> &LineCoding {
84 &self.line_coding
85 }
86
87 /// Gets the DTR (data terminal ready) state
88 pub fn dtr(&self) -> bool {
89 self.dtr
90 }
91
92 /// Gets the RTS (request to send) state
93 pub fn rts(&self) -> bool {
94 self.rts
95 }
96
97 /// Writes a single packet into the IN endpoint.
98 pub fn write_packet(&mut self, data: &[u8]) -> Result<usize> {
99 self.write_ep.write(data)
100 }
101
102 /// Reads a single packet from the OUT endpoint.
103 pub fn read_packet(&mut self, data: &mut [u8]) -> Result<usize> {
104 self.read_ep.read(data)
105 }
106
107 /// Gets the address of the IN endpoint.
108 pub fn write_ep_address(&self) -> EndpointAddress {
109 self.write_ep.address()
110 }
111
112 /// Gets the address of the OUT endpoint.
113 pub fn read_ep_address(&self) -> EndpointAddress {
114 self.read_ep.address()
115 }
116}
117
118impl<B: UsbBus> UsbClass<B> for CdcAcmClass<'_, B> {
119 fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<()> {
120 writer.iad(
121 self.comm_if,
122 2,
123 USB_CLASS_CDC,
124 CDC_SUBCLASS_ACM,
125 CDC_PROTOCOL_NONE,
126 )?;
127
128 writer.interface(
129 self.comm_if,
130 USB_CLASS_CDC,
131 CDC_SUBCLASS_ACM,
132 CDC_PROTOCOL_NONE,
133 )?;
134
135 writer.write(
136 CS_INTERFACE,
137 &[
138 CDC_TYPE_HEADER, // bDescriptorSubtype
139 0x10,
140 0x01, // bcdCDC (1.10)
141 ],
142 )?;
143
144 writer.write(
145 CS_INTERFACE,
146 &[
147 CDC_TYPE_ACM, // bDescriptorSubtype
148 0x00, // bmCapabilities
149 ],
150 )?;
151
152 writer.write(
153 CS_INTERFACE,
154 &[
155 CDC_TYPE_UNION, // bDescriptorSubtype
156 self.comm_if.into(), // bControlInterface
157 self.data_if.into(), // bSubordinateInterface
158 ],
159 )?;
160
161 writer.write(
162 CS_INTERFACE,
163 &[
164 CDC_TYPE_CALL_MANAGEMENT, // bDescriptorSubtype
165 0x00, // bmCapabilities
166 self.data_if.into(), // bDataInterface
167 ],
168 )?;
169
170 writer.endpoint(&self.comm_ep)?;
171
172 writer.interface(self.data_if, USB_CLASS_CDC_DATA, 0x00, 0x00)?;
173
174 writer.endpoint(&self.write_ep)?;
175 writer.endpoint(&self.read_ep)?;
176
177 Ok(())
178 }
179
180 fn reset(&mut self) {
181 self.line_coding = LineCoding::default();
182 self.dtr = false;
183 self.rts = false;
184 }
185
186 fn control_in(&mut self, xfer: ControlIn<B>) {
187 let req = xfer.request();
188
189 if !(req.request_type == control::RequestType::Class
190 && req.recipient == control::Recipient::Interface
191 && req.index == u8::from(self.comm_if) as u16)
192 {
193 return;
194 }
195
196 match req.request {
197 // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below.
198 REQ_GET_LINE_CODING if req.length == 7 => {
199 xfer.accept(|data| {
200 data[0..4].copy_from_slice(&self.line_coding.data_rate.to_le_bytes());
201 data[4] = self.line_coding.stop_bits as u8;
202 data[5] = self.line_coding.parity_type as u8;
203 data[6] = self.line_coding.data_bits;
204
205 Ok(7)
206 })
207 .ok();
208 }
209 _ => {
210 xfer.reject().ok();
211 }
212 }
213 }
214
215 fn control_out(&mut self, xfer: ControlOut<B>) {
216 let req = xfer.request();
217
218 if !(req.request_type == control::RequestType::Class
219 && req.recipient == control::Recipient::Interface
220 && req.index == u8::from(self.comm_if) as u16)
221 {
222 return;
223 }
224
225 match req.request {
226 REQ_SEND_ENCAPSULATED_COMMAND => {
227 // We don't actually support encapsulated commands but pretend we do for standards
228 // compatibility.
229 xfer.accept().ok();
230 }
231 REQ_SET_LINE_CODING if xfer.data().len() >= 7 => {
232 self.line_coding.data_rate =
233 u32::from_le_bytes(xfer.data()[0..4].try_into().unwrap());
234 self.line_coding.stop_bits = xfer.data()[4].into();
235 self.line_coding.parity_type = xfer.data()[5].into();
236 self.line_coding.data_bits = xfer.data()[6];
237
238 xfer.accept().ok();
239 }
240 REQ_SET_CONTROL_LINE_STATE => {
241 self.dtr = (req.value & 0x0001) != 0;
242 self.rts = (req.value & 0x0002) != 0;
243
244 xfer.accept().ok();
245 }
246 _ => {
247 xfer.reject().ok();
248 }
249 };
250 }
251}
252
253/// Number of stop bits for LineCoding
254#[derive(Copy, Clone, PartialEq, Eq)]
255pub enum StopBits {
256 /// 1 stop bit
257 One = 0,
258
259 /// 1.5 stop bits
260 OnePointFive = 1,
261
262 /// 2 stop bits
263 Two = 2,
264}
265
266impl From<u8> for StopBits {
267 fn from(value: u8) -> Self {
268 if value <= 2 {
269 unsafe { mem::transmute(value) }
270 } else {
271 StopBits::One
272 }
273 }
274}
275
276/// Parity for LineCoding
277#[derive(Copy, Clone, PartialEq, Eq)]
278pub enum ParityType {
279 None = 0,
280 Odd = 1,
281 Event = 2,
282 Mark = 3,
283 Space = 4,
284}
285
286impl From<u8> for ParityType {
287 fn from(value: u8) -> Self {
288 if value <= 4 {
289 unsafe { mem::transmute(value) }
290 } else {
291 ParityType::None
292 }
293 }
294}
295
296/// Line coding parameters
297///
298/// This is provided by the host for specifying the standard UART parameters such as baud rate. Can
299/// be ignored if you don't plan to interface with a physical UART.
300pub struct LineCoding {
301 stop_bits: StopBits,
302 data_bits: u8,
303 parity_type: ParityType,
304 data_rate: u32,
305}
306
307impl LineCoding {
308 /// Gets the number of stop bits for UART communication.
309 pub fn stop_bits(&self) -> StopBits {
310 self.stop_bits
311 }
312
313 /// Gets the number of data bits for UART communication.
314 pub fn data_bits(&self) -> u8 {
315 self.data_bits
316 }
317
318 /// Gets the parity type for UART communication.
319 pub fn parity_type(&self) -> ParityType {
320 self.parity_type
321 }
322
323 /// Gets the data rate in bits per second for UART communication.
324 pub fn data_rate(&self) -> u32 {
325 self.data_rate
326 }
327}
328
329impl Default for LineCoding {
330 fn default() -> Self {
331 LineCoding {
332 stop_bits: StopBits::One,
333 data_bits: 8,
334 parity_type: ParityType::None,
335 data_rate: 8_000,
336 }
337 }
338}
diff --git a/embassy-extras/src/usb/mod.rs b/embassy-extras/src/usb/mod.rs
new file mode 100644
index 000000000..182cd87d0
--- /dev/null
+++ b/embassy-extras/src/usb/mod.rs
@@ -0,0 +1,254 @@
1use core::cell::RefCell;
2use core::marker::PhantomData;
3use core::pin::Pin;
4
5use usb_device::bus::UsbBus;
6use usb_device::class::UsbClass;
7use usb_device::device::UsbDevice;
8
9mod cdc_acm;
10pub mod usb_serial;
11
12use crate::peripheral::{PeripheralMutex, PeripheralState};
13use embassy::interrupt::Interrupt;
14use usb_serial::{ReadInterface, UsbSerial, WriteInterface};
15
16/// Marker trait to mark an interrupt to be used with the [`Usb`] abstraction.
17pub unsafe trait USBInterrupt: Interrupt {}
18
19pub(crate) struct State<'bus, B, T, I>
20where
21 B: UsbBus,
22 T: ClassSet<B>,
23 I: USBInterrupt,
24{
25 device: UsbDevice<'bus, B>,
26 pub(crate) classes: T,
27 _interrupt: PhantomData<I>,
28}
29
30pub struct Usb<'bus, B, T, I>
31where
32 B: UsbBus,
33 T: ClassSet<B>,
34 I: USBInterrupt,
35{
36 // Don't you dare moving out `PeripheralMutex`
37 inner: RefCell<PeripheralMutex<State<'bus, B, T, I>>>,
38}
39
40impl<'bus, B, T, I> Usb<'bus, B, T, I>
41where
42 B: UsbBus,
43 T: ClassSet<B>,
44 I: USBInterrupt,
45{
46 pub fn new<S: IntoClassSet<B, T>>(device: UsbDevice<'bus, B>, class_set: S, irq: I) -> Self {
47 let state = State {
48 device,
49 classes: class_set.into_class_set(),
50 _interrupt: PhantomData,
51 };
52 let mutex = PeripheralMutex::new(state, irq);
53 Self {
54 inner: RefCell::new(mutex),
55 }
56 }
57
58 pub fn start(self: Pin<&mut Self>) {
59 let this = unsafe { self.get_unchecked_mut() };
60 let mut mutex = this.inner.borrow_mut();
61 let mutex = unsafe { Pin::new_unchecked(&mut *mutex) };
62
63 // Use inner to register the irq
64 mutex.register_interrupt();
65 }
66}
67
68impl<'bus, 'c, B, T, I> Usb<'bus, B, T, I>
69where
70 B: UsbBus,
71 T: ClassSet<B> + SerialState<'bus, 'c, B, Index0>,
72 I: USBInterrupt,
73{
74 /// Take a serial class that was passed as the first class in a tuple
75 pub fn take_serial_0<'a>(
76 self: Pin<&'a Self>,
77 ) -> (
78 ReadInterface<'a, 'bus, 'c, Index0, B, T, I>,
79 WriteInterface<'a, 'bus, 'c, Index0, B, T, I>,
80 ) {
81 let this = self.get_ref();
82
83 let r = ReadInterface {
84 inner: &this.inner,
85 _buf_lifetime: PhantomData,
86 _index: PhantomData,
87 };
88
89 let w = WriteInterface {
90 inner: &this.inner,
91 _buf_lifetime: PhantomData,
92 _index: PhantomData,
93 };
94 (r, w)
95 }
96}
97
98impl<'bus, 'c, B, T, I> Usb<'bus, B, T, I>
99where
100 B: UsbBus,
101 T: ClassSet<B> + SerialState<'bus, 'c, B, Index1>,
102 I: USBInterrupt,
103{
104 /// Take a serial class that was passed as the second class in a tuple
105 pub fn take_serial_1<'a>(
106 self: Pin<&'a Self>,
107 ) -> (
108 ReadInterface<'a, 'bus, 'c, Index1, B, T, I>,
109 WriteInterface<'a, 'bus, 'c, Index1, B, T, I>,
110 ) {
111 let this = self.get_ref();
112
113 let r = ReadInterface {
114 inner: &this.inner,
115 _buf_lifetime: PhantomData,
116 _index: PhantomData,
117 };
118
119 let w = WriteInterface {
120 inner: &this.inner,
121 _buf_lifetime: PhantomData,
122 _index: PhantomData,
123 };
124 (r, w)
125 }
126}
127
128impl<'bus, B, T, I> PeripheralState for State<'bus, B, T, I>
129where
130 B: UsbBus,
131 T: ClassSet<B>,
132 I: USBInterrupt,
133{
134 type Interrupt = I;
135 fn on_interrupt(&mut self) {
136 self.classes.poll_all(&mut self.device);
137 }
138}
139
140pub trait ClassSet<B: UsbBus> {
141 fn poll_all(&mut self, device: &mut UsbDevice<'_, B>) -> bool;
142}
143
144pub trait IntoClassSet<B: UsbBus, C: ClassSet<B>> {
145 fn into_class_set(self) -> C;
146}
147
148pub struct ClassSet1<B, C1>
149where
150 B: UsbBus,
151 C1: UsbClass<B>,
152{
153 class: C1,
154 _bus: PhantomData<B>,
155}
156
157pub struct ClassSet2<B, C1, C2>
158where
159 B: UsbBus,
160 C1: UsbClass<B>,
161 C2: UsbClass<B>,
162{
163 class1: C1,
164 class2: C2,
165 _bus: PhantomData<B>,
166}
167
168/// The first class into a [`ClassSet`]
169pub struct Index0;
170
171/// The second class into a [`ClassSet`]
172pub struct Index1;
173
174impl<B, C1> ClassSet<B> for ClassSet1<B, C1>
175where
176 B: UsbBus,
177 C1: UsbClass<B>,
178{
179 fn poll_all(&mut self, device: &mut UsbDevice<'_, B>) -> bool {
180 device.poll(&mut [&mut self.class])
181 }
182}
183
184impl<B, C1, C2> ClassSet<B> for ClassSet2<B, C1, C2>
185where
186 B: UsbBus,
187 C1: UsbClass<B>,
188 C2: UsbClass<B>,
189{
190 fn poll_all(&mut self, device: &mut UsbDevice<'_, B>) -> bool {
191 device.poll(&mut [&mut self.class1, &mut self.class2])
192 }
193}
194
195impl<B, C1> IntoClassSet<B, ClassSet1<B, C1>> for C1
196where
197 B: UsbBus,
198 C1: UsbClass<B>,
199{
200 fn into_class_set(self) -> ClassSet1<B, C1> {
201 ClassSet1 {
202 class: self,
203 _bus: PhantomData,
204 }
205 }
206}
207
208impl<B, C1, C2> IntoClassSet<B, ClassSet2<B, C1, C2>> for (C1, C2)
209where
210 B: UsbBus,
211 C1: UsbClass<B>,
212 C2: UsbClass<B>,
213{
214 fn into_class_set(self) -> ClassSet2<B, C1, C2> {
215 ClassSet2 {
216 class1: self.0,
217 class2: self.1,
218 _bus: PhantomData,
219 }
220 }
221}
222
223/// Trait for a USB State that has a serial class inside
224pub trait SerialState<'bus, 'a, B: UsbBus, I> {
225 fn get_serial(&mut self) -> &mut UsbSerial<'bus, 'a, B>;
226}
227
228impl<'bus, 'a, B: UsbBus> SerialState<'bus, 'a, B, Index0>
229 for ClassSet1<B, UsbSerial<'bus, 'a, B>>
230{
231 fn get_serial(&mut self) -> &mut UsbSerial<'bus, 'a, B> {
232 &mut self.class
233 }
234}
235
236impl<'bus, 'a, B, C2> SerialState<'bus, 'a, B, Index0> for ClassSet2<B, UsbSerial<'bus, 'a, B>, C2>
237where
238 B: UsbBus,
239 C2: UsbClass<B>,
240{
241 fn get_serial(&mut self) -> &mut UsbSerial<'bus, 'a, B> {
242 &mut self.class1
243 }
244}
245
246impl<'bus, 'a, B, C1> SerialState<'bus, 'a, B, Index1> for ClassSet2<B, C1, UsbSerial<'bus, 'a, B>>
247where
248 B: UsbBus,
249 C1: UsbClass<B>,
250{
251 fn get_serial(&mut self) -> &mut UsbSerial<'bus, 'a, B> {
252 &mut self.class2
253 }
254}
diff --git a/embassy-extras/src/usb/usb_serial.rs b/embassy-extras/src/usb/usb_serial.rs
new file mode 100644
index 000000000..9cbfb2da4
--- /dev/null
+++ b/embassy-extras/src/usb/usb_serial.rs
@@ -0,0 +1,310 @@
1use core::cell::RefCell;
2use core::marker::{PhantomData, Unpin};
3use core::pin::Pin;
4use core::task::{Context, Poll};
5
6use embassy::io::{self, AsyncBufRead, AsyncWrite};
7use embassy::util::WakerRegistration;
8use usb_device::bus::UsbBus;
9use usb_device::class_prelude::*;
10use usb_device::UsbError;
11
12use super::cdc_acm::CdcAcmClass;
13use crate::peripheral::PeripheralMutex;
14use crate::ring_buffer::RingBuffer;
15use crate::usb::{ClassSet, SerialState, State, USBInterrupt};
16
17pub struct ReadInterface<'a, 'bus, 'c, I, B, T, INT>
18where
19 I: Unpin,
20 B: UsbBus,
21 T: SerialState<'bus, 'c, B, I> + ClassSet<B>,
22 INT: USBInterrupt,
23{
24 // Don't you dare moving out `PeripheralMutex`
25 pub(crate) inner: &'a RefCell<PeripheralMutex<State<'bus, B, T, INT>>>,
26 pub(crate) _buf_lifetime: PhantomData<&'c T>,
27 pub(crate) _index: PhantomData<I>,
28}
29
30/// Write interface for USB CDC_ACM
31///
32/// This interface is buffered, meaning that after the write returns the bytes might not be fully
33/// on the wire just yet
34pub struct WriteInterface<'a, 'bus, 'c, I, B, T, INT>
35where
36 I: Unpin,
37 B: UsbBus,
38 T: SerialState<'bus, 'c, B, I> + ClassSet<B>,
39 INT: USBInterrupt,
40{
41 // Don't you dare moving out `PeripheralMutex`
42 pub(crate) inner: &'a RefCell<PeripheralMutex<State<'bus, B, T, INT>>>,
43 pub(crate) _buf_lifetime: PhantomData<&'c T>,
44 pub(crate) _index: PhantomData<I>,
45}
46
47impl<'a, 'bus, 'c, I, B, T, INT> AsyncBufRead for ReadInterface<'a, 'bus, 'c, I, B, T, INT>
48where
49 I: Unpin,
50 B: UsbBus,
51 T: SerialState<'bus, 'c, B, I> + ClassSet<B>,
52 INT: USBInterrupt,
53{
54 fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
55 let this = self.get_mut();
56 let mut mutex = this.inner.borrow_mut();
57 let mutex = unsafe { Pin::new_unchecked(&mut *mutex) };
58 mutex.with(|state, _irq| {
59 let serial = state.classes.get_serial();
60 let serial = Pin::new(serial);
61
62 match serial.poll_fill_buf(cx) {
63 Poll::Ready(Ok(buf)) => {
64 let buf: &[u8] = buf;
65 // NOTE(unsafe) This part of the buffer won't be modified until the user calls
66 // consume, which will invalidate this ref
67 let buf: &[u8] = unsafe { core::mem::transmute(buf) };
68 Poll::Ready(Ok(buf))
69 }
70 Poll::Ready(Err(_)) => Poll::Ready(Err(io::Error::Other)),
71 Poll::Pending => Poll::Pending,
72 }
73 })
74 }
75
76 fn consume(self: Pin<&mut Self>, amt: usize) {
77 let this = self.get_mut();
78 let mut mutex = this.inner.borrow_mut();
79 let mutex = unsafe { Pin::new_unchecked(&mut *mutex) };
80 mutex.with(|state, _irq| {
81 let serial = state.classes.get_serial();
82 let serial = Pin::new(serial);
83
84 serial.consume(amt);
85 })
86 }
87}
88
89impl<'a, 'bus, 'c, I, B, T, INT> AsyncWrite for WriteInterface<'a, 'bus, 'c, I, B, T, INT>
90where
91 I: Unpin,
92 B: UsbBus,
93 T: SerialState<'bus, 'c, B, I> + ClassSet<B>,
94 INT: USBInterrupt,
95{
96 fn poll_write(
97 self: Pin<&mut Self>,
98 cx: &mut Context<'_>,
99 buf: &[u8],
100 ) -> Poll<io::Result<usize>> {
101 let this = self.get_mut();
102 let mut mutex = this.inner.borrow_mut();
103 let mutex = unsafe { Pin::new_unchecked(&mut *mutex) };
104 mutex.with(|state, _irq| {
105 let serial = state.classes.get_serial();
106 let serial = Pin::new(serial);
107
108 serial.poll_write(cx, buf)
109 })
110 }
111}
112
113pub struct UsbSerial<'bus, 'a, B: UsbBus> {
114 inner: CdcAcmClass<'bus, B>,
115 read_buf: RingBuffer<'a>,
116 write_buf: RingBuffer<'a>,
117 read_waker: WakerRegistration,
118 write_waker: WakerRegistration,
119 write_state: WriteState,
120 read_error: bool,
121 write_error: bool,
122}
123
124impl<'bus, 'a, B: UsbBus> AsyncBufRead for UsbSerial<'bus, 'a, B> {
125 fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
126 let this = self.get_mut();
127
128 if this.read_error {
129 this.read_error = false;
130 return Poll::Ready(Err(io::Error::Other));
131 }
132
133 let buf = this.read_buf.pop_buf();
134 if buf.is_empty() {
135 this.read_waker.register(cx.waker());
136 return Poll::Pending;
137 }
138 Poll::Ready(Ok(buf))
139 }
140
141 fn consume(self: Pin<&mut Self>, amt: usize) {
142 self.get_mut().read_buf.pop(amt);
143 }
144}
145
146impl<'bus, 'a, B: UsbBus> AsyncWrite for UsbSerial<'bus, 'a, B> {
147 fn poll_write(
148 self: Pin<&mut Self>,
149 cx: &mut Context<'_>,
150 buf: &[u8],
151 ) -> Poll<io::Result<usize>> {
152 let this = self.get_mut();
153
154 if this.write_error {
155 this.write_error = false;
156 return Poll::Ready(Err(io::Error::Other));
157 }
158
159 let write_buf = this.write_buf.push_buf();
160 if write_buf.is_empty() {
161 this.write_waker.register(cx.waker());
162 return Poll::Pending;
163 }
164
165 let count = write_buf.len().min(buf.len());
166 write_buf[..count].copy_from_slice(&buf[..count]);
167 this.write_buf.push(count);
168
169 this.flush_write();
170 Poll::Ready(Ok(count))
171 }
172}
173
174/// Keeps track of the type of the last written packet.
175enum WriteState {
176 /// No packets in-flight
177 Idle,
178
179 /// Short packet currently in-flight
180 Short,
181
182 /// Full packet current in-flight. A full packet must be followed by a short packet for the host
183 /// OS to see the transaction. The data is the number of subsequent full packets sent so far. A
184 /// short packet is forced every SHORT_PACKET_INTERVAL packets so that the OS sees data in a
185 /// timely manner.
186 Full(usize),
187}
188
189impl<'bus, 'a, B: UsbBus> UsbSerial<'bus, 'a, B> {
190 pub fn new(
191 alloc: &'bus UsbBusAllocator<B>,
192 read_buf: &'a mut [u8],
193 write_buf: &'a mut [u8],
194 ) -> Self {
195 Self {
196 inner: CdcAcmClass::new(alloc, 64),
197 read_buf: RingBuffer::new(read_buf),
198 write_buf: RingBuffer::new(write_buf),
199 read_waker: WakerRegistration::new(),
200 write_waker: WakerRegistration::new(),
201 write_state: WriteState::Idle,
202 read_error: false,
203 write_error: false,
204 }
205 }
206
207 fn flush_write(&mut self) {
208 /// If this many full size packets have been sent in a row, a short packet will be sent so that the
209 /// host sees the data in a timely manner.
210 const SHORT_PACKET_INTERVAL: usize = 10;
211
212 let full_size_packets = match self.write_state {
213 WriteState::Full(c) => c,
214 _ => 0,
215 };
216
217 let ep_size = self.inner.max_packet_size() as usize;
218 let max_size = if full_size_packets > SHORT_PACKET_INTERVAL {
219 ep_size - 1
220 } else {
221 ep_size
222 };
223
224 let buf = {
225 let buf = self.write_buf.pop_buf();
226 if buf.len() > max_size {
227 &buf[..max_size]
228 } else {
229 buf
230 }
231 };
232
233 if !buf.is_empty() {
234 let count = match self.inner.write_packet(buf) {
235 Ok(c) => c,
236 Err(UsbError::WouldBlock) => 0,
237 Err(_) => {
238 self.write_error = true;
239 return;
240 }
241 };
242
243 if buf.len() == ep_size {
244 self.write_state = WriteState::Full(full_size_packets + 1);
245 } else {
246 self.write_state = WriteState::Short;
247 }
248 self.write_buf.pop(count);
249 } else if full_size_packets > 0 {
250 if let Err(e) = self.inner.write_packet(&[]) {
251 if !matches!(e, UsbError::WouldBlock) {
252 self.write_error = true;
253 }
254 return;
255 }
256 self.write_state = WriteState::Idle;
257 }
258 }
259}
260
261impl<B> UsbClass<B> for UsbSerial<'_, '_, B>
262where
263 B: UsbBus,
264{
265 fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<(), UsbError> {
266 self.inner.get_configuration_descriptors(writer)
267 }
268
269 fn reset(&mut self) {
270 self.inner.reset();
271 self.read_buf.clear();
272 self.write_buf.clear();
273 self.write_state = WriteState::Idle;
274 }
275
276 fn endpoint_in_complete(&mut self, addr: EndpointAddress) {
277 if addr == self.inner.write_ep_address() {
278 self.write_waker.wake();
279
280 self.flush_write();
281 }
282 }
283
284 fn endpoint_out(&mut self, addr: EndpointAddress) {
285 if addr == self.inner.read_ep_address() {
286 let buf = self.read_buf.push_buf();
287 let count = match self.inner.read_packet(buf) {
288 Ok(c) => c,
289 Err(UsbError::WouldBlock) => 0,
290 Err(_) => {
291 self.read_error = true;
292 return;
293 }
294 };
295
296 if count > 0 {
297 self.read_buf.push(count);
298 self.read_waker.wake();
299 }
300 }
301 }
302
303 fn control_in(&mut self, xfer: ControlIn<B>) {
304 self.inner.control_in(xfer);
305 }
306
307 fn control_out(&mut self, xfer: ControlOut<B>) {
308 self.inner.control_out(xfer);
309 }
310}
diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml
index 437cacb27..28d9e7fe3 100644
--- a/embassy-stm32/Cargo.toml
+++ b/embassy-stm32/Cargo.toml
@@ -36,6 +36,7 @@ stm32l0x3 = ["stm32l0xx-hal/stm32l0x3"]
36[dependencies] 36[dependencies]
37embassy = { version = "0.1.0", path = "../embassy" } 37embassy = { version = "0.1.0", path = "../embassy" }
38embassy-macros = { version = "0.1.0", path = "../embassy-macros", features = ["stm32"]} 38embassy-macros = { version = "0.1.0", path = "../embassy-macros", features = ["stm32"]}
39embassy-extras = {version = "0.1.0", path = "../embassy-extras" }
39 40
40defmt = { version = "0.2.0", optional = true } 41defmt = { version = "0.2.0", optional = true }
41log = { version = "0.4.11", optional = true } 42log = { version = "0.4.11", optional = true }
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs
index 122761c1e..078e1e5a6 100644
--- a/embassy-stm32/src/lib.rs
+++ b/embassy-stm32/src/lib.rs
@@ -36,6 +36,7 @@ pub mod exti;
36pub mod interrupt; 36pub mod interrupt;
37 37
38#[cfg(any( 38#[cfg(any(
39 feature = "stm32f401",
39 feature = "stm32f405", 40 feature = "stm32f405",
40 feature = "stm32f407", 41 feature = "stm32f407",
41 feature = "stm32f412", 42 feature = "stm32f412",
@@ -74,6 +75,8 @@ pub mod can;
74))] 75))]
75pub mod rtc; 76pub mod rtc;
76 77
78unsafe impl embassy_extras::usb::USBInterrupt for interrupt::OTG_FS {}
79
77use core::option::Option; 80use core::option::Option;
78use hal::prelude::*; 81use hal::prelude::*;
79use hal::rcc::Clocks; 82use hal::rcc::Clocks;
diff --git a/embassy-stm32f4-examples/Cargo.toml b/embassy-stm32f4-examples/Cargo.toml
index fbdc6d794..c6ef98973 100644
--- a/embassy-stm32f4-examples/Cargo.toml
+++ b/embassy-stm32f4-examples/Cargo.toml
@@ -39,6 +39,7 @@ embassy = { version = "0.1.0", path = "../embassy", features = ["defmt", "defmt-
39embassy-traits = { version = "0.1.0", path = "../embassy-traits", features = ["defmt"] } 39embassy-traits = { version = "0.1.0", path = "../embassy-traits", features = ["defmt"] }
40embassy-stm32f4 = { version = "*", path = "../embassy-stm32f4" } 40embassy-stm32f4 = { version = "*", path = "../embassy-stm32f4" }
41embassy-stm32 = { version = "*", path = "../embassy-stm32" } 41embassy-stm32 = { version = "*", path = "../embassy-stm32" }
42embassy-extras = {version = "0.1.0", path = "../embassy-extras" }
42 43
43defmt = "0.2.0" 44defmt = "0.2.0"
44defmt-rtt = "0.2.0" 45defmt-rtt = "0.2.0"
@@ -47,7 +48,8 @@ cortex-m = "0.7.1"
47cortex-m-rt = "0.6.13" 48cortex-m-rt = "0.6.13"
48embedded-hal = { version = "0.2.4" } 49embedded-hal = { version = "0.2.4" }
49panic-probe = "0.1.0" 50panic-probe = "0.1.0"
50stm32f4xx-hal = { version = "0.8.3", features = ["rt"], git = "https://github.com/stm32-rs/stm32f4xx-hal.git"} 51stm32f4xx-hal = { version = "0.8.3", features = ["rt", "usb_fs"], git = "https://github.com/stm32-rs/stm32f4xx-hal.git"}
51futures = { version = "0.3.8", default-features = false, features = ["async-await"] } 52futures = { version = "0.3.8", default-features = false, features = ["async-await"] }
52rtt-target = { version = "0.3", features = ["cortex-m"] } 53rtt-target = { version = "0.3", features = ["cortex-m"] }
53bxcan = "0.5.0" \ No newline at end of file 54bxcan = "0.5.0"
55usb-device = "0.2.7"
diff --git a/embassy-stm32f4-examples/src/bin/usb_serial.rs b/embassy-stm32f4-examples/src/bin/usb_serial.rs
new file mode 100644
index 000000000..f1c4631d7
--- /dev/null
+++ b/embassy-stm32f4-examples/src/bin/usb_serial.rs
@@ -0,0 +1,146 @@
1#![no_std]
2#![no_main]
3#![feature(type_alias_impl_trait)]
4#![feature(min_type_alias_impl_trait)]
5#![feature(impl_trait_in_bindings)]
6
7#[path = "../example_common.rs"]
8mod example_common;
9use example_common::*;
10
11use cortex_m_rt::entry;
12use defmt::panic;
13use embassy::executor::{task, Executor};
14use embassy::interrupt::InterruptExt;
15use embassy::io::{AsyncBufReadExt, AsyncWriteExt};
16use embassy::time::{Duration, Timer};
17use embassy::util::Forever;
18use embassy_extras::usb::usb_serial::UsbSerial;
19use embassy_extras::usb::Usb;
20use embassy_stm32f4::{interrupt, pac, rtc};
21use futures::future::{select, Either};
22use futures::pin_mut;
23use stm32f4xx_hal::otg_fs::{UsbBus, USB};
24use stm32f4xx_hal::prelude::*;
25use usb_device::bus::UsbBusAllocator;
26use usb_device::prelude::*;
27
28#[task]
29async fn run1(bus: &'static mut UsbBusAllocator<UsbBus<USB>>) {
30 info!("Async task");
31
32 let mut read_buf = [0u8; 128];
33 let mut write_buf = [0u8; 128];
34 let serial = UsbSerial::new(bus, &mut read_buf, &mut write_buf);
35
36 let device = UsbDeviceBuilder::new(bus, UsbVidPid(0x16c0, 0x27dd))
37 .manufacturer("Fake company")
38 .product("Serial port")
39 .serial_number("TEST")
40 .device_class(0x02)
41 .build();
42
43 let irq = interrupt::take!(OTG_FS);
44 irq.set_priority(interrupt::Priority::Level3);
45
46 let usb = Usb::new(device, serial, irq);
47 pin_mut!(usb);
48 usb.as_mut().start();
49
50 let (mut read_interface, mut write_interface) = usb.as_ref().take_serial_0();
51
52 let mut buf = [0u8; 64];
53 loop {
54 let mut n = 0;
55 let left = {
56 let recv_fut = async {
57 loop {
58 let byte = unwrap!(read_interface.read_byte().await);
59 unwrap!(write_interface.write_byte(byte).await);
60 buf[n] = byte;
61
62 n += 1;
63 if byte == b'\n' || byte == b'\r' || n == buf.len() {
64 break;
65 }
66 }
67 };
68 pin_mut!(recv_fut);
69
70 let timeout = Timer::after(Duration::from_ticks(32768 * 10));
71
72 match select(recv_fut, timeout).await {
73 Either::Left(_) => true,
74 Either::Right(_) => false,
75 }
76 };
77
78 if left {
79 for c in buf[..n].iter_mut() {
80 if 0x61 <= *c && *c <= 0x7a {
81 *c &= !0x20;
82 }
83 }
84 unwrap!(write_interface.write_byte(b'\n').await);
85 unwrap!(write_interface.write_all(&buf[..n]).await);
86 unwrap!(write_interface.write_byte(b'\n').await);
87 } else {
88 unwrap!(write_interface.write_all(b"\r\nSend something\r\n").await);
89 }
90 }
91}
92
93static RTC: Forever<rtc::RTC<pac::TIM2>> = Forever::new();
94static ALARM: Forever<rtc::Alarm<pac::TIM2>> = Forever::new();
95static EXECUTOR: Forever<Executor> = Forever::new();
96static USB_BUS: Forever<UsbBusAllocator<UsbBus<USB>>> = Forever::new();
97
98#[entry]
99fn main() -> ! {
100 static mut EP_MEMORY: [u32; 1024] = [0; 1024];
101
102 info!("Hello World!");
103
104 let p = unwrap!(pac::Peripherals::take());
105
106 p.RCC.ahb1enr.modify(|_, w| w.dma1en().enabled());
107 let rcc = p.RCC.constrain();
108 let clocks = rcc
109 .cfgr
110 .use_hse(25.mhz())
111 .sysclk(48.mhz())
112 .require_pll48clk()
113 .freeze();
114
115 p.DBGMCU.cr.modify(|_, w| {
116 w.dbg_sleep().set_bit();
117 w.dbg_standby().set_bit();
118 w.dbg_stop().set_bit()
119 });
120
121 let rtc = RTC.put(rtc::RTC::new(p.TIM2, interrupt::take!(TIM2), clocks));
122 rtc.start();
123
124 unsafe { embassy::time::set_clock(rtc) };
125
126 let alarm = ALARM.put(rtc.alarm1());
127 let executor = EXECUTOR.put(Executor::new());
128 executor.set_alarm(alarm);
129
130 let gpioa = p.GPIOA.split();
131 let usb = USB {
132 usb_global: p.OTG_FS_GLOBAL,
133 usb_device: p.OTG_FS_DEVICE,
134 usb_pwrclk: p.OTG_FS_PWRCLK,
135 pin_dm: gpioa.pa11.into_alternate_af10(),
136 pin_dp: gpioa.pa12.into_alternate_af10(),
137 hclk: clocks.hclk(),
138 };
139 // Rust analyzer isn't recognizing the static ref magic `cortex-m` does
140 #[allow(unused_unsafe)]
141 let usb_bus = USB_BUS.put(UsbBus::new(usb, unsafe { EP_MEMORY }));
142
143 executor.run(move |spawner| {
144 unwrap!(spawner.spawn(run1(usb_bus)));
145 });
146}
diff --git a/embassy-stm32f4/Cargo.toml b/embassy-stm32f4/Cargo.toml
index c132d7c99..8e9b14f03 100644
--- a/embassy-stm32f4/Cargo.toml
+++ b/embassy-stm32f4/Cargo.toml
@@ -32,6 +32,7 @@ stm32f479 = ["stm32f4xx-hal/stm32f469", "embassy-stm32/stm32f479"]
32[dependencies] 32[dependencies]
33embassy = { version = "0.1.0", path = "../embassy" } 33embassy = { version = "0.1.0", path = "../embassy" }
34embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32" } 34embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32" }
35
35defmt = { version = "0.2.0", optional = true } 36defmt = { version = "0.2.0", optional = true }
36log = { version = "0.4.11", optional = true } 37log = { version = "0.4.11", optional = true }
37cortex-m-rt = "0.6.13" 38cortex-m-rt = "0.6.13"