stm_32/peripherals/vcp.rs
1// ******************************************************************************
2// * File : platforms/stm_32/src/peripherals/vcp.rs
3// * Date : June 28, 2026
4// ******************************************************************************
5// *
6// * Copyright (c) 2023, AeroVironment, Inc.
7// * All rights reserved.
8// *
9// * Redistribution and use in source and binary forms, with or without
10// * modification, are permitted provided that the following conditions are met:
11// *
12// * 1.Redistributions of source code must retain the above copyright notice, this
13// * list of conditions and the following disclaimer.
14// *
15// * 2.Redistributions in binary form must reproduce the above copyright notice,
16// * this list of conditions and the following disclaimer in the documentation
17// * and/or other materials provided with the distribution.
18// *
19// * 3.Neither the name of the copyright holder nor the names of its
20// * contributors may be used to endorse or promote products derived from
21// * this software without specific prior written permission.
22// *
23// * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24// * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25// * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26// * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
27// * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28// * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29// * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30// * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31// * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32// * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33// *
34// ******************************************************************************
35
36use 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
45pub const VCP_TX_BUFF_SIZE: usize = 2048;
46pub const VCP_RX_BUFF_SIZE: usize = 2048;
47const USB_CDC_FS_PACKET_SIZE: usize = 64;
48
49pub static VCP_TX: Pipe<CriticalSectionRawMutex, VCP_TX_BUFF_SIZE> = Pipe::new();
50pub static VCP_RX: Pipe<CriticalSectionRawMutex, VCP_RX_BUFF_SIZE> = Pipe::new();
51
52pub struct BasicProcessor;
53
54impl EmbeddedComInterface for BasicProcessor {
55 async fn process_bytes(&mut self, buf: &[u8], num_bytes: usize) {
56 VCP_RX.write_all(&buf[0..num_bytes]).await;
57 }
58}
59
60pub struct Vcp<ECI: EmbeddedComInterface> {
61 pub driver: Driver<'static, USB_OTG_FS>,
62 pub byte_processor: ECI,
63}
64
65impl<ECI: EmbeddedComInterface> Vcp<ECI> {
66 pub async fn run(self) {
67 // Adapted from Embassy STM32H7 examples
68
69 let driver = self.driver;
70 let mut byte_processor = self.byte_processor;
71
72 // Create embassy-usb Config
73 let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
74 config.manufacturer = Some("Embassy");
75 config.product = Some("USB-serial example");
76 config.serial_number = Some("12345678");
77
78 // Create embassy-usb DeviceBuilder using the driver and config.
79 // It needs some buffers for building the descriptors.
80 let mut config_descriptor = [0; 256];
81 let mut bos_descriptor = [0; 256];
82 let mut control_buf = [0; 64];
83
84 let mut state = State::new();
85
86 let mut builder = Builder::new(
87 driver,
88 config,
89 &mut config_descriptor,
90 &mut bos_descriptor,
91 &mut [], // no msos descriptors
92 &mut control_buf,
93 );
94
95 // Create classes on the builder.
96 let class = CdcAcmClass::new(&mut builder, &mut state, 64);
97 // Build the builder.
98 let mut usb = builder.build();
99 // Run the USB device.
100 let usb_fut = usb.run();
101
102 // Keep both USB endpoints armed independently. A single alternating RX/TX
103 // loop can block RX indefinitely while it waits for outbound pipe data.
104 let (mut sender, mut receiver) = class.split();
105 let vcp_fut = async {
106 join(
107 Self::run_rx(&mut byte_processor, &mut receiver),
108 Self::run_tx(&mut sender),
109 )
110 .await;
111 };
112
113 join(usb_fut, vcp_fut).await;
114 }
115
116 async fn run_rx<'d, T: Instance + 'd>(
117 byte_processor: &mut ECI,
118 receiver: &mut Receiver<'d, Driver<'d, T>>,
119 ) {
120 let mut rx_buf = [0u8; VCP_RX_BUFF_SIZE];
121
122 loop {
123 receiver.wait_connection().await;
124 loop {
125 match receiver.read_packet(&mut rx_buf).await {
126 Ok(n) if n > 0 => {
127 byte_processor.process_bytes(&rx_buf[..n], n).await;
128 }
129 Ok(_) => {}
130 Err(_) => break,
131 }
132 }
133 }
134 }
135
136 async fn run_tx<'d, T: Instance + 'd>(sender: &mut Sender<'d, Driver<'d, T>>) {
137 let mut tx_buf = [0u8; VCP_TX_BUFF_SIZE];
138
139 loop {
140 sender.wait_connection().await;
141 'connected: loop {
142 let n = VCP_TX.read(&mut tx_buf).await;
143 for packet in tx_buf[..n].chunks(USB_CDC_FS_PACKET_SIZE) {
144 if sender.write_packet(packet).await.is_err() {
145 break 'connected;
146 }
147 }
148 }
149 }
150 }
151}
152
153#[embassy_executor::task]
154pub async fn task(vcp: Vcp<BasicProcessor>) {
155 vcp.run().await;
156}