ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
usb_cdc_acm.h
Go to the documentation of this file.
1#pragma once
2#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
3 defined(USE_ESP32_VARIANT_ESP32S31) || defined(USE_ESP32_VARIANT_ESP32H4)
4
9
10#include <array>
11#include <atomic>
12#include <cstring>
13#include <functional>
14#include "freertos/ringbuf.h"
15#include "esp_err.h"
16#include "tinyusb_cdc_acm.h"
17
18namespace esphome::usb_cdc_acm {
19
20static const uint8_t EVENT_QUEUE_SIZE = 12;
21
22// Drain up to out_buf_sz bytes from a byte ring buffer, handling FreeRTOS's wrapped
23// case with a second read. Shared with the cdc_acm_uart bridge platform, whose worker
24// tasks drain the same ring buffers.
25inline esp_err_t ringbuf_read_bytes(RingbufHandle_t ring_buf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size,
26 TickType_t x_ticks_to_wait) {
27 size_t read_sz;
28 uint8_t *buf = static_cast<uint8_t *>(xRingbufferReceiveUpTo(ring_buf, &read_sz, x_ticks_to_wait, out_buf_sz));
29
30 if (buf == nullptr) {
31 return ESP_FAIL;
32 }
33
34 memcpy(out_buf, buf, read_sz);
35 vRingbufferReturnItem(ring_buf, (void *) buf);
36 *rx_data_size = read_sz;
37
38 // Buffer's data can be wrapped, in which case we should perform another read
39 if (*rx_data_size < out_buf_sz) {
40 buf = static_cast<uint8_t *>(xRingbufferReceiveUpTo(ring_buf, &read_sz, 0, out_buf_sz - *rx_data_size));
41 if (buf != nullptr) {
42 memcpy(out_buf + *rx_data_size, buf, read_sz);
43 vRingbufferReturnItem(ring_buf, (void *) buf);
44 *rx_data_size += read_sz;
45 }
46 }
47
48 return ESP_OK;
49}
50
51// Callback types for line coding and line state changes
52using LineCodingCallback = std::function<void(uint32_t bit_rate, uint8_t stop_bits, uint8_t parity, uint8_t data_bits)>;
53using LineStateCallback = std::function<void(bool dtr, bool rts)>;
54
55// Event types
60
61// Event structure for the queue
62struct CDCEvent {
64 union {
65 struct {
66 bool dtr;
67 bool rts;
69 struct {
71 uint8_t stop_bits;
72 uint8_t parity;
73 uint8_t data_bits;
76
77 // Required by EventPool - called before returning to pool
78 void release() {
79 // No dynamic memory to clean up, data is stored inline
80 }
81};
82
83// Forward declaration
84class USBCDCACMComponent;
85
87class USBCDCACMInstance final : public uart::UARTComponent, public Parented<USBCDCACMComponent> {
88 public:
89 void setup();
90 void loop();
91 void dump_config();
92
93 void set_interface_number(uint8_t itf) { this->itf_ = itf; }
94 // Get the CDC port number for this instance
95 uint8_t get_itf() const { return this->itf_; }
96 // Ring buffer accessors for bridge components
97 RingbufHandle_t get_tx_ringbuf() const { return this->usb_tx_ringbuf_; }
98 RingbufHandle_t get_rx_ringbuf() const { return this->usb_rx_ringbuf_; }
99
100 // Task handle accessor for notifying TX task
101 TaskHandle_t get_tx_task_handle() const { return this->usb_tx_task_handle_; }
102
103 // Callback registration for line coding and line state changes
104 void set_line_coding_callback(LineCodingCallback callback) { this->line_coding_callback_ = std::move(callback); }
105 void set_line_state_callback(LineStateCallback callback) { this->line_state_callback_ = std::move(callback); }
106
107 // Called from USB core task context queues event for processing in main loop
108 void queue_line_coding_event(uint32_t bit_rate, uint8_t stop_bits, uint8_t parity, uint8_t data_bits);
109 void queue_line_state_event(bool dtr, bool rts);
110
111 static void usb_tx_task_fn(void *arg);
112 void usb_tx_task();
113
114 // UARTComponent interface implementation
115 void write_array(const uint8_t *data, size_t len) override;
116 bool peek_byte(uint8_t *data) override;
117 bool read_array(uint8_t *data, size_t len) override;
118 size_t available() override;
119 uart::UARTFlushResult flush() override;
120#if defined(USE_ESP8266) || defined(USE_ESP32)
121 // No-op: in CDC ACM device mode the host dictates the line coding, so there are no
122 // local UART settings to (re)apply.
123 void load_settings(bool dump_config) override {}
124 using UARTComponent::load_settings; // also bring in the no-arg overload for convenience
125#endif
126
127 protected:
128 void check_logger_conflict() override;
129
130 // Process queued events and invoke callbacks (called from main loop)
131 void process_events_();
132 // True while TX bytes are still in the ring buffer or held by the TX task
133 bool tx_pending_();
134 TaskHandle_t usb_tx_task_handle_{nullptr};
135
136 RingbufHandle_t usb_tx_ringbuf_{nullptr};
137 RingbufHandle_t usb_rx_ringbuf_{nullptr};
138 // TX task staging; a member rather than a stack array so it does not size the task stack.
139 std::array<uint8_t, CONFIG_TINYUSB_CDC_TX_BUFSIZE> usb_tx_staging_{};
140 // Non-zero while the TX task holds bytes it has pulled from the ring buffer but not
141 // yet handed to TinyUSB; lets flush() account for data that is in neither the ring
142 // buffer nor TinyUSB's FIFO.
143 // Threading: written only by usb_tx_task(); read by flush()'s bounded wait on the
144 // caller's task (typically the main loop).
145 // std::atomic<uint8_t> rather than std::atomic<bool> because GCC on Xtensa
146 // generates an indirect function call for atomic<bool> ops instead of inlining
147 // them; atomic<uint8_t> inlines correctly on all platforms.
148 std::atomic<uint8_t> usb_tx_busy_{0};
149 // Running total of bytes dropped by write_array() (never reset), and the timestamp
150 // of the last "buffer full" log line (throttled so a sustained host stall doesn't
151 // flood the log).
154 // RX buffer for peek functionality
155 uint8_t peek_buffer_{0};
156 bool has_peek_{false};
157 uint8_t itf_{0};
158 // User-registered callbacks (called from main loop)
161
162 // Lock-free queue and event pool for cross-task event passing
163 // Pool sized to queue capacity (SIZE-1) because LockFreeQueue<T,N> is a ring
164 // buffer that holds N-1 elements. This guarantees allocate() returns nullptr
165 // before push() can fail, preventing both a pool slot leak and an SPSC
166 // violation on the pool's internal free list.
167 EventPool<CDCEvent, EVENT_QUEUE_SIZE - 1> event_pool_;
169};
170
172class USBCDCACMComponent final : public Component {
173 public:
175
176 void setup() override;
177 void loop() override;
178 void dump_config() override;
179 float get_setup_priority() const override { return setup_priority::IO; }
180
181 // Interface management
182 void add_interface(USBCDCACMInstance *interface);
184
185 protected:
186 std::array<USBCDCACMInstance *, ESPHOME_MAX_USB_CDC_INSTANCES> interfaces_{};
187};
188
189extern USBCDCACMComponent *global_usb_cdc_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
190
191} // namespace esphome::usb_cdc_acm
192#endif
Helper class to easily give an object a parent of type T.
Definition helpers.h:1997
Main USB CDC ACM component that manages the USB device and all CDC interfaces.
void add_interface(USBCDCACMInstance *interface)
std::array< USBCDCACMInstance *, ESPHOME_MAX_USB_CDC_INSTANCES > interfaces_
USBCDCACMInstance * get_interface_by_number(uint8_t itf)
float get_setup_priority() const override
Represents a single CDC ACM interface instance.
Definition usb_cdc_acm.h:87
RingbufHandle_t get_rx_ringbuf() const
Definition usb_cdc_acm.h:98
std::array< uint8_t, CONFIG_TINYUSB_CDC_TX_BUFSIZE > usb_tx_staging_
EventPool< CDCEvent, EVENT_QUEUE_SIZE - 1 > event_pool_
bool read_array(uint8_t *data, size_t len) override
RingbufHandle_t get_tx_ringbuf() const
Definition usb_cdc_acm.h:97
void set_line_state_callback(LineStateCallback callback)
uart::UARTFlushResult flush() override
LockFreeQueue< CDCEvent, EVENT_QUEUE_SIZE > event_queue_
void queue_line_coding_event(uint32_t bit_rate, uint8_t stop_bits, uint8_t parity, uint8_t data_bits)
void set_line_coding_callback(LineCodingCallback callback)
void load_settings(bool dump_config) override
void write_array(const uint8_t *data, size_t len) override
void queue_line_state_event(bool dtr, bool rts)
constexpr float IO
For components that represent GPIO pins like PCF8573.
Definition component.h:41
UARTFlushResult
Result of a flush() call.
std::function< void(uint32_t bit_rate, uint8_t stop_bits, uint8_t parity, uint8_t data_bits)> LineCodingCallback
Definition usb_cdc_acm.h:52
esp_err_t ringbuf_read_bytes(RingbufHandle_t ring_buf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size, TickType_t x_ticks_to_wait)
Definition usb_cdc_acm.h:25
USBCDCACMComponent * global_usb_cdc_component
std::function< void(bool dtr, bool rts)> LineStateCallback
Definition usb_cdc_acm.h:53
const void size_t len
Definition hal.h:68
static void uint32_t
struct esphome::usb_cdc_acm::CDCEvent::@181::@183 line_coding
union esphome::usb_cdc_acm::CDCEvent::@181 data
struct esphome::usb_cdc_acm::CDCEvent::@181::@182 line_state