stm_32/peripherals/
vcp.rs1use embassy_futures::join::join;
37use embassy_stm32::peripherals::USB_OTG_FS;
38use embassy_stm32::usb::{Driver, Instance};
39use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
40use embassy_sync::pipe::Pipe;
41use embassy_usb::Builder;
42use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State};
43use veloxity_core::comm::interface::EmbeddedComInterface;
44
45#[cfg(feature = "runtime-diagnostics")]
46use embassy_time::Instant;
47
48#[cfg(feature = "runtime-diagnostics")]
49use core::sync::atomic::{AtomicU32, Ordering};
50
51pub const VCP_TX_BUFF_SIZE: usize = 2048;
52pub const VCP_RX_BUFF_SIZE: usize = 2048;
53const USB_CDC_FS_PACKET_SIZE: usize = 64;
54
55pub static VCP_TX: Pipe<CriticalSectionRawMutex, VCP_TX_BUFF_SIZE> = Pipe::new();
56pub static VCP_RX: Pipe<CriticalSectionRawMutex, VCP_RX_BUFF_SIZE> = Pipe::new();
57
58#[cfg(feature = "runtime-diagnostics")]
59#[unsafe(no_mangle)]
60pub static VELOXITY_DIAG_VCP_FRAME_ATTEMPT: AtomicU32 = AtomicU32::new(0);
61#[cfg(feature = "runtime-diagnostics")]
62#[unsafe(no_mangle)]
63pub static VELOXITY_DIAG_VCP_FRAME_ENQUEUED: AtomicU32 = AtomicU32::new(0);
64#[cfg(feature = "runtime-diagnostics")]
65#[unsafe(no_mangle)]
66pub static VELOXITY_DIAG_VCP_FRAME_REJECTED: AtomicU32 = AtomicU32::new(0);
67#[cfg(feature = "runtime-diagnostics")]
68#[unsafe(no_mangle)]
69pub static VELOXITY_DIAG_VCP_PARTIAL_FRAME_FAILURE: AtomicU32 = AtomicU32::new(0);
70#[cfg(feature = "runtime-diagnostics")]
71#[unsafe(no_mangle)]
72pub static VELOXITY_DIAG_VCP_TX_MIN_FREE: AtomicU32 = AtomicU32::new(VCP_TX_BUFF_SIZE as u32);
73#[cfg(feature = "runtime-diagnostics")]
74#[unsafe(no_mangle)]
75pub static VELOXITY_DIAG_VCP_IMU_ATTEMPT: AtomicU32 = AtomicU32::new(0);
76#[cfg(feature = "runtime-diagnostics")]
77#[unsafe(no_mangle)]
78pub static VELOXITY_DIAG_VCP_IMU_ENQUEUED: AtomicU32 = AtomicU32::new(0);
79#[cfg(feature = "runtime-diagnostics")]
80#[unsafe(no_mangle)]
81pub static VELOXITY_DIAG_VCP_IMU_REJECTED: AtomicU32 = AtomicU32::new(0);
82#[cfg(feature = "runtime-diagnostics")]
83#[unsafe(no_mangle)]
84pub static VELOXITY_DIAG_VCP_DEQUEUE_CALLS: AtomicU32 = AtomicU32::new(0);
85#[cfg(feature = "runtime-diagnostics")]
86#[unsafe(no_mangle)]
87pub static VELOXITY_DIAG_VCP_DEQUEUE_BYTES: AtomicU32 = AtomicU32::new(0);
88#[cfg(feature = "runtime-diagnostics")]
89#[unsafe(no_mangle)]
90pub static VELOXITY_DIAG_VCP_USB_PACKETS: AtomicU32 = AtomicU32::new(0);
91#[cfg(feature = "runtime-diagnostics")]
92#[unsafe(no_mangle)]
93pub static VELOXITY_DIAG_VCP_USB_BYTES: AtomicU32 = AtomicU32::new(0);
94#[cfg(feature = "runtime-diagnostics")]
95#[unsafe(no_mangle)]
96pub static VELOXITY_DIAG_VCP_USB_ERRORS: AtomicU32 = AtomicU32::new(0);
97#[cfg(feature = "runtime-diagnostics")]
98#[unsafe(no_mangle)]
99pub static VELOXITY_DIAG_VCP_RX_USB_PACKETS: AtomicU32 = AtomicU32::new(0);
100#[cfg(feature = "runtime-diagnostics")]
101#[unsafe(no_mangle)]
102pub static VELOXITY_DIAG_VCP_RX_USB_BYTES: AtomicU32 = AtomicU32::new(0);
103#[cfg(feature = "runtime-diagnostics")]
104#[unsafe(no_mangle)]
105pub static VELOXITY_DIAG_VCP_RX_PIPE_MIN_FREE: AtomicU32 = AtomicU32::new(VCP_RX_BUFF_SIZE as u32);
106#[cfg(feature = "runtime-diagnostics")]
107#[unsafe(no_mangle)]
108pub static VELOXITY_DIAG_VCP_RX_WAIT_COUNT: AtomicU32 = AtomicU32::new(0);
109#[cfg(feature = "runtime-diagnostics")]
110#[unsafe(no_mangle)]
111pub static VELOXITY_DIAG_VCP_RX_WAIT_SUM_US: AtomicU32 = AtomicU32::new(0);
112#[cfg(feature = "runtime-diagnostics")]
113#[unsafe(no_mangle)]
114pub static VELOXITY_DIAG_VCP_RX_WAIT_MAX_US: AtomicU32 = AtomicU32::new(0);
115
116#[cfg(feature = "runtime-diagnostics")]
117pub fn record_frame_attempt(message_id: Option<u8>) {
118 VELOXITY_DIAG_VCP_FRAME_ATTEMPT.fetch_add(1, Ordering::Relaxed);
119 VELOXITY_DIAG_VCP_TX_MIN_FREE.fetch_min(VCP_TX.free_capacity() as u32, Ordering::Relaxed);
120 if message_id == Some(181) {
121 VELOXITY_DIAG_VCP_IMU_ATTEMPT.fetch_add(1, Ordering::Relaxed);
122 }
123}
124
125#[cfg(feature = "runtime-diagnostics")]
126pub fn record_frame_enqueued(message_id: Option<u8>) {
127 VELOXITY_DIAG_VCP_FRAME_ENQUEUED.fetch_add(1, Ordering::Relaxed);
128 if message_id == Some(181) {
129 VELOXITY_DIAG_VCP_IMU_ENQUEUED.fetch_add(1, Ordering::Relaxed);
130 }
131}
132
133#[cfg(feature = "runtime-diagnostics")]
134pub fn record_frame_rejected(message_id: Option<u8>, partial: bool) {
135 VELOXITY_DIAG_VCP_FRAME_REJECTED.fetch_add(1, Ordering::Relaxed);
136 if partial {
137 VELOXITY_DIAG_VCP_PARTIAL_FRAME_FAILURE.fetch_add(1, Ordering::Relaxed);
138 }
139 if message_id == Some(181) {
140 VELOXITY_DIAG_VCP_IMU_REJECTED.fetch_add(1, Ordering::Relaxed);
141 }
142}
143
144pub struct BasicProcessor;
145
146impl EmbeddedComInterface for BasicProcessor {
147 async fn process_bytes(&mut self, buf: &[u8], num_bytes: usize) {
148 #[cfg(feature = "runtime-diagnostics")]
149 let (started, had_to_wait) = (Instant::now(), VCP_RX.free_capacity() < num_bytes);
150 VCP_RX.write_all(&buf[0..num_bytes]).await;
151 #[cfg(feature = "runtime-diagnostics")]
152 {
153 VELOXITY_DIAG_VCP_RX_PIPE_MIN_FREE
154 .fetch_min(VCP_RX.free_capacity() as u32, Ordering::Relaxed);
155 if had_to_wait {
156 let elapsed_us = started.elapsed().as_micros().min(u32::MAX as u64) as u32;
157 VELOXITY_DIAG_VCP_RX_WAIT_COUNT.fetch_add(1, Ordering::Relaxed);
158 VELOXITY_DIAG_VCP_RX_WAIT_SUM_US.fetch_add(elapsed_us, Ordering::Relaxed);
159 VELOXITY_DIAG_VCP_RX_WAIT_MAX_US.fetch_max(elapsed_us, Ordering::Relaxed);
160 }
161 }
162 }
163}
164
165pub struct Vcp<ECI: EmbeddedComInterface> {
166 pub driver: Driver<'static, USB_OTG_FS>,
167 pub byte_processor: ECI,
168}
169
170impl<ECI: EmbeddedComInterface> Vcp<ECI> {
171 pub async fn run(self) {
172 let driver = self.driver;
175 let mut byte_processor = self.byte_processor;
176
177 let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
179 config.manufacturer = Some("Embassy");
180 config.product = Some("USB-serial example");
181 config.serial_number = Some("12345678");
182
183 let mut config_descriptor = [0; 256];
186 let mut bos_descriptor = [0; 256];
187 let mut control_buf = [0; 64];
188
189 let mut state = State::new();
190
191 let mut builder = Builder::new(
192 driver,
193 config,
194 &mut config_descriptor,
195 &mut bos_descriptor,
196 &mut [], &mut control_buf,
198 );
199
200 let class = CdcAcmClass::new(&mut builder, &mut state, 64);
202 let mut usb = builder.build();
204 let usb_fut = usb.run();
206
207 let (mut sender, mut receiver) = class.split();
210 let vcp_fut = async {
211 join(
212 Self::run_rx(&mut byte_processor, &mut receiver),
213 Self::run_tx(&mut sender),
214 )
215 .await;
216 };
217
218 join(usb_fut, vcp_fut).await;
219 }
220
221 async fn run_rx<'d, T: Instance + 'd>(
222 byte_processor: &mut ECI,
223 receiver: &mut Receiver<'d, Driver<'d, T>>,
224 ) {
225 let mut rx_buf = [0u8; VCP_RX_BUFF_SIZE];
226
227 loop {
228 receiver.wait_connection().await;
229 loop {
230 match receiver.read_packet(&mut rx_buf).await {
231 Ok(n) if n > 0 => {
232 #[cfg(feature = "runtime-diagnostics")]
233 {
234 VELOXITY_DIAG_VCP_RX_USB_PACKETS.fetch_add(1, Ordering::Relaxed);
235 VELOXITY_DIAG_VCP_RX_USB_BYTES.fetch_add(n as u32, Ordering::Relaxed);
236 }
237 byte_processor.process_bytes(&rx_buf[..n], n).await;
238 }
239 Ok(_) => {}
240 Err(_) => break,
241 }
242 }
243 }
244 }
245
246 async fn run_tx<'d, T: Instance + 'd>(sender: &mut Sender<'d, Driver<'d, T>>) {
247 let mut tx_buf = [0u8; VCP_TX_BUFF_SIZE];
248
249 loop {
250 sender.wait_connection().await;
251 'connected: loop {
252 let n = VCP_TX.read(&mut tx_buf).await;
253 #[cfg(feature = "runtime-diagnostics")]
254 {
255 VELOXITY_DIAG_VCP_DEQUEUE_CALLS.fetch_add(1, Ordering::Relaxed);
256 VELOXITY_DIAG_VCP_DEQUEUE_BYTES.fetch_add(n as u32, Ordering::Relaxed);
257 }
258 for packet in tx_buf[..n].chunks(USB_CDC_FS_PACKET_SIZE) {
259 if sender.write_packet(packet).await.is_err() {
260 #[cfg(feature = "runtime-diagnostics")]
261 VELOXITY_DIAG_VCP_USB_ERRORS.fetch_add(1, Ordering::Relaxed);
262 break 'connected;
263 }
264 #[cfg(feature = "runtime-diagnostics")]
265 {
266 VELOXITY_DIAG_VCP_USB_PACKETS.fetch_add(1, Ordering::Relaxed);
267 VELOXITY_DIAG_VCP_USB_BYTES
268 .fetch_add(packet.len() as u32, Ordering::Relaxed);
269 }
270 }
271 }
272 }
273 }
274}
275
276#[embassy_executor::task]
277pub async fn task(vcp: Vcp<BasicProcessor>) {
278 vcp.run().await;
279}