diff options
| author | xoviat <[email protected]> | 2021-03-27 21:31:49 -0500 |
|---|---|---|
| committer | xoviat <[email protected]> | 2021-03-27 21:31:49 -0500 |
| commit | 9e687ade6483c0541aa082c7a1922ba9271d2f46 (patch) | |
| tree | 28ce03ef3b4f8c25cc644139d93e4dcd3469c209 | |
| parent | 6ee9e012fcfc9a0a901f78885d19ae3c3d10570c (diff) | |
| parent | 4bc77e3d0ebd12d9e435be44804dc453a64619ea (diff) | |
Merge branch 'master' of https://github.com/akiles/embassy into proc-macro
| -rw-r--r-- | embassy-extras/Cargo.toml | 1 | ||||
| -rw-r--r-- | embassy-extras/src/lib.rs | 1 | ||||
| -rw-r--r-- | embassy-extras/src/ring_buffer.rs | 6 | ||||
| -rw-r--r-- | embassy-extras/src/usb/cdc_acm.rs | 338 | ||||
| -rw-r--r-- | embassy-extras/src/usb/mod.rs | 254 | ||||
| -rw-r--r-- | embassy-extras/src/usb/usb_serial.rs | 310 | ||||
| -rw-r--r-- | embassy-stm32/Cargo.toml | 1 | ||||
| -rw-r--r-- | embassy-stm32/src/lib.rs | 3 | ||||
| -rw-r--r-- | embassy-stm32f4-examples/Cargo.toml | 6 | ||||
| -rw-r--r-- | embassy-stm32f4-examples/src/bin/usb_serial.rs | 146 | ||||
| -rw-r--r-- | embassy-stm32f4/Cargo.toml | 1 |
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" } | |||
| 17 | defmt = { version = "0.2.0", optional = true } | 17 | defmt = { version = "0.2.0", optional = true } |
| 18 | log = { version = "0.4.11", optional = true } | 18 | log = { version = "0.4.11", optional = true } |
| 19 | cortex-m = "0.7.1" | 19 | cortex-m = "0.7.1" |
| 20 | usb-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 | ||
| 6 | pub mod peripheral; | 6 | pub mod peripheral; |
| 7 | pub mod ring_buffer; | 7 | pub mod ring_buffer; |
| 8 | pub mod usb; | ||
| 8 | 9 | ||
| 9 | /// Low power blocking wait loop using WFE/SEV. | 10 | /// Low power blocking wait loop using WFE/SEV. |
| 10 | pub fn low_power_wait_until(mut condition: impl FnMut() -> bool) { | 11 | pub 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 | |||
| 4 | use core::convert::TryInto; | ||
| 5 | use core::mem; | ||
| 6 | use usb_device::class_prelude::*; | ||
| 7 | use usb_device::Result; | ||
| 8 | |||
| 9 | /// This should be used as `device_class` when building the `UsbDevice`. | ||
| 10 | pub const USB_CLASS_CDC: u8 = 0x02; | ||
| 11 | |||
| 12 | const USB_CLASS_CDC_DATA: u8 = 0x0a; | ||
| 13 | const CDC_SUBCLASS_ACM: u8 = 0x02; | ||
| 14 | const CDC_PROTOCOL_NONE: u8 = 0x00; | ||
| 15 | |||
| 16 | const CS_INTERFACE: u8 = 0x24; | ||
| 17 | const CDC_TYPE_HEADER: u8 = 0x00; | ||
| 18 | const CDC_TYPE_CALL_MANAGEMENT: u8 = 0x01; | ||
| 19 | const CDC_TYPE_ACM: u8 = 0x02; | ||
| 20 | const CDC_TYPE_UNION: u8 = 0x06; | ||
| 21 | |||
| 22 | const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00; | ||
| 23 | #[allow(unused)] | ||
| 24 | const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01; | ||
| 25 | const REQ_SET_LINE_CODING: u8 = 0x20; | ||
| 26 | const REQ_GET_LINE_CODING: u8 = 0x21; | ||
| 27 | const 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. | ||
| 43 | pub 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 | |||
| 54 | impl<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 | |||
| 118 | impl<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)] | ||
| 255 | pub 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 | |||
| 266 | impl 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)] | ||
| 278 | pub enum ParityType { | ||
| 279 | None = 0, | ||
| 280 | Odd = 1, | ||
| 281 | Event = 2, | ||
| 282 | Mark = 3, | ||
| 283 | Space = 4, | ||
| 284 | } | ||
| 285 | |||
| 286 | impl 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. | ||
| 300 | pub struct LineCoding { | ||
| 301 | stop_bits: StopBits, | ||
| 302 | data_bits: u8, | ||
| 303 | parity_type: ParityType, | ||
| 304 | data_rate: u32, | ||
| 305 | } | ||
| 306 | |||
| 307 | impl 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 | |||
| 329 | impl 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 @@ | |||
| 1 | use core::cell::RefCell; | ||
| 2 | use core::marker::PhantomData; | ||
| 3 | use core::pin::Pin; | ||
| 4 | |||
| 5 | use usb_device::bus::UsbBus; | ||
| 6 | use usb_device::class::UsbClass; | ||
| 7 | use usb_device::device::UsbDevice; | ||
| 8 | |||
| 9 | mod cdc_acm; | ||
| 10 | pub mod usb_serial; | ||
| 11 | |||
| 12 | use crate::peripheral::{PeripheralMutex, PeripheralState}; | ||
| 13 | use embassy::interrupt::Interrupt; | ||
| 14 | use usb_serial::{ReadInterface, UsbSerial, WriteInterface}; | ||
| 15 | |||
| 16 | /// Marker trait to mark an interrupt to be used with the [`Usb`] abstraction. | ||
| 17 | pub unsafe trait USBInterrupt: Interrupt {} | ||
| 18 | |||
| 19 | pub(crate) struct State<'bus, B, T, I> | ||
| 20 | where | ||
| 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 | |||
| 30 | pub struct Usb<'bus, B, T, I> | ||
| 31 | where | ||
| 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 | |||
| 40 | impl<'bus, B, T, I> Usb<'bus, B, T, I> | ||
| 41 | where | ||
| 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 | |||
| 68 | impl<'bus, 'c, B, T, I> Usb<'bus, B, T, I> | ||
| 69 | where | ||
| 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 | |||
| 98 | impl<'bus, 'c, B, T, I> Usb<'bus, B, T, I> | ||
| 99 | where | ||
| 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 | |||
| 128 | impl<'bus, B, T, I> PeripheralState for State<'bus, B, T, I> | ||
| 129 | where | ||
| 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 | |||
| 140 | pub trait ClassSet<B: UsbBus> { | ||
| 141 | fn poll_all(&mut self, device: &mut UsbDevice<'_, B>) -> bool; | ||
| 142 | } | ||
| 143 | |||
| 144 | pub trait IntoClassSet<B: UsbBus, C: ClassSet<B>> { | ||
| 145 | fn into_class_set(self) -> C; | ||
| 146 | } | ||
| 147 | |||
| 148 | pub struct ClassSet1<B, C1> | ||
| 149 | where | ||
| 150 | B: UsbBus, | ||
| 151 | C1: UsbClass<B>, | ||
| 152 | { | ||
| 153 | class: C1, | ||
| 154 | _bus: PhantomData<B>, | ||
| 155 | } | ||
| 156 | |||
| 157 | pub struct ClassSet2<B, C1, C2> | ||
| 158 | where | ||
| 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`] | ||
| 169 | pub struct Index0; | ||
| 170 | |||
| 171 | /// The second class into a [`ClassSet`] | ||
| 172 | pub struct Index1; | ||
| 173 | |||
| 174 | impl<B, C1> ClassSet<B> for ClassSet1<B, C1> | ||
| 175 | where | ||
| 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 | |||
| 184 | impl<B, C1, C2> ClassSet<B> for ClassSet2<B, C1, C2> | ||
| 185 | where | ||
| 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 | |||
| 195 | impl<B, C1> IntoClassSet<B, ClassSet1<B, C1>> for C1 | ||
| 196 | where | ||
| 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 | |||
| 208 | impl<B, C1, C2> IntoClassSet<B, ClassSet2<B, C1, C2>> for (C1, C2) | ||
| 209 | where | ||
| 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 | ||
| 224 | pub trait SerialState<'bus, 'a, B: UsbBus, I> { | ||
| 225 | fn get_serial(&mut self) -> &mut UsbSerial<'bus, 'a, B>; | ||
| 226 | } | ||
| 227 | |||
| 228 | impl<'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 | |||
| 236 | impl<'bus, 'a, B, C2> SerialState<'bus, 'a, B, Index0> for ClassSet2<B, UsbSerial<'bus, 'a, B>, C2> | ||
| 237 | where | ||
| 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 | |||
| 246 | impl<'bus, 'a, B, C1> SerialState<'bus, 'a, B, Index1> for ClassSet2<B, C1, UsbSerial<'bus, 'a, B>> | ||
| 247 | where | ||
| 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 @@ | |||
| 1 | use core::cell::RefCell; | ||
| 2 | use core::marker::{PhantomData, Unpin}; | ||
| 3 | use core::pin::Pin; | ||
| 4 | use core::task::{Context, Poll}; | ||
| 5 | |||
| 6 | use embassy::io::{self, AsyncBufRead, AsyncWrite}; | ||
| 7 | use embassy::util::WakerRegistration; | ||
| 8 | use usb_device::bus::UsbBus; | ||
| 9 | use usb_device::class_prelude::*; | ||
| 10 | use usb_device::UsbError; | ||
| 11 | |||
| 12 | use super::cdc_acm::CdcAcmClass; | ||
| 13 | use crate::peripheral::PeripheralMutex; | ||
| 14 | use crate::ring_buffer::RingBuffer; | ||
| 15 | use crate::usb::{ClassSet, SerialState, State, USBInterrupt}; | ||
| 16 | |||
| 17 | pub struct ReadInterface<'a, 'bus, 'c, I, B, T, INT> | ||
| 18 | where | ||
| 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 | ||
| 34 | pub struct WriteInterface<'a, 'bus, 'c, I, B, T, INT> | ||
| 35 | where | ||
| 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 | |||
| 47 | impl<'a, 'bus, 'c, I, B, T, INT> AsyncBufRead for ReadInterface<'a, 'bus, 'c, I, B, T, INT> | ||
| 48 | where | ||
| 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 | |||
| 89 | impl<'a, 'bus, 'c, I, B, T, INT> AsyncWrite for WriteInterface<'a, 'bus, 'c, I, B, T, INT> | ||
| 90 | where | ||
| 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 | |||
| 113 | pub 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 | |||
| 124 | impl<'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 | |||
| 146 | impl<'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. | ||
| 175 | enum 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 | |||
| 189 | impl<'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 | |||
| 261 | impl<B> UsbClass<B> for UsbSerial<'_, '_, B> | ||
| 262 | where | ||
| 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] |
| 37 | embassy = { version = "0.1.0", path = "../embassy" } | 37 | embassy = { version = "0.1.0", path = "../embassy" } |
| 38 | embassy-macros = { version = "0.1.0", path = "../embassy-macros", features = ["stm32"]} | 38 | embassy-macros = { version = "0.1.0", path = "../embassy-macros", features = ["stm32"]} |
| 39 | embassy-extras = {version = "0.1.0", path = "../embassy-extras" } | ||
| 39 | 40 | ||
| 40 | defmt = { version = "0.2.0", optional = true } | 41 | defmt = { version = "0.2.0", optional = true } |
| 41 | log = { version = "0.4.11", optional = true } | 42 | log = { 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; | |||
| 36 | pub mod interrupt; | 36 | pub 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 | ))] |
| 75 | pub mod rtc; | 76 | pub mod rtc; |
| 76 | 77 | ||
| 78 | unsafe impl embassy_extras::usb::USBInterrupt for interrupt::OTG_FS {} | ||
| 79 | |||
| 77 | use core::option::Option; | 80 | use core::option::Option; |
| 78 | use hal::prelude::*; | 81 | use hal::prelude::*; |
| 79 | use hal::rcc::Clocks; | 82 | use 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- | |||
| 39 | embassy-traits = { version = "0.1.0", path = "../embassy-traits", features = ["defmt"] } | 39 | embassy-traits = { version = "0.1.0", path = "../embassy-traits", features = ["defmt"] } |
| 40 | embassy-stm32f4 = { version = "*", path = "../embassy-stm32f4" } | 40 | embassy-stm32f4 = { version = "*", path = "../embassy-stm32f4" } |
| 41 | embassy-stm32 = { version = "*", path = "../embassy-stm32" } | 41 | embassy-stm32 = { version = "*", path = "../embassy-stm32" } |
| 42 | embassy-extras = {version = "0.1.0", path = "../embassy-extras" } | ||
| 42 | 43 | ||
| 43 | defmt = "0.2.0" | 44 | defmt = "0.2.0" |
| 44 | defmt-rtt = "0.2.0" | 45 | defmt-rtt = "0.2.0" |
| @@ -47,7 +48,8 @@ cortex-m = "0.7.1" | |||
| 47 | cortex-m-rt = "0.6.13" | 48 | cortex-m-rt = "0.6.13" |
| 48 | embedded-hal = { version = "0.2.4" } | 49 | embedded-hal = { version = "0.2.4" } |
| 49 | panic-probe = "0.1.0" | 50 | panic-probe = "0.1.0" |
| 50 | stm32f4xx-hal = { version = "0.8.3", features = ["rt"], git = "https://github.com/stm32-rs/stm32f4xx-hal.git"} | 51 | stm32f4xx-hal = { version = "0.8.3", features = ["rt", "usb_fs"], git = "https://github.com/stm32-rs/stm32f4xx-hal.git"} |
| 51 | futures = { version = "0.3.8", default-features = false, features = ["async-await"] } | 52 | futures = { version = "0.3.8", default-features = false, features = ["async-await"] } |
| 52 | rtt-target = { version = "0.3", features = ["cortex-m"] } | 53 | rtt-target = { version = "0.3", features = ["cortex-m"] } |
| 53 | bxcan = "0.5.0" \ No newline at end of file | 54 | bxcan = "0.5.0" |
| 55 | usb-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"] | ||
| 8 | mod example_common; | ||
| 9 | use example_common::*; | ||
| 10 | |||
| 11 | use cortex_m_rt::entry; | ||
| 12 | use defmt::panic; | ||
| 13 | use embassy::executor::{task, Executor}; | ||
| 14 | use embassy::interrupt::InterruptExt; | ||
| 15 | use embassy::io::{AsyncBufReadExt, AsyncWriteExt}; | ||
| 16 | use embassy::time::{Duration, Timer}; | ||
| 17 | use embassy::util::Forever; | ||
| 18 | use embassy_extras::usb::usb_serial::UsbSerial; | ||
| 19 | use embassy_extras::usb::Usb; | ||
| 20 | use embassy_stm32f4::{interrupt, pac, rtc}; | ||
| 21 | use futures::future::{select, Either}; | ||
| 22 | use futures::pin_mut; | ||
| 23 | use stm32f4xx_hal::otg_fs::{UsbBus, USB}; | ||
| 24 | use stm32f4xx_hal::prelude::*; | ||
| 25 | use usb_device::bus::UsbBusAllocator; | ||
| 26 | use usb_device::prelude::*; | ||
| 27 | |||
| 28 | #[task] | ||
| 29 | async 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 | |||
| 93 | static RTC: Forever<rtc::RTC<pac::TIM2>> = Forever::new(); | ||
| 94 | static ALARM: Forever<rtc::Alarm<pac::TIM2>> = Forever::new(); | ||
| 95 | static EXECUTOR: Forever<Executor> = Forever::new(); | ||
| 96 | static USB_BUS: Forever<UsbBusAllocator<UsbBus<USB>>> = Forever::new(); | ||
| 97 | |||
| 98 | #[entry] | ||
| 99 | fn 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] |
| 33 | embassy = { version = "0.1.0", path = "../embassy" } | 33 | embassy = { version = "0.1.0", path = "../embassy" } |
| 34 | embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32" } | 34 | embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32" } |
| 35 | |||
| 35 | defmt = { version = "0.2.0", optional = true } | 36 | defmt = { version = "0.2.0", optional = true } |
| 36 | log = { version = "0.4.11", optional = true } | 37 | log = { version = "0.4.11", optional = true } |
| 37 | cortex-m-rt = "0.6.13" | 38 | cortex-m-rt = "0.6.13" |
