Skip to main content

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}