ESPHome 2026.9.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 <atomic>
11#include <functional>
12#include "freertos/ringbuf.h"
13#include "tinyusb_cdc_acm.h"
14
15namespace esphome::usb_cdc_acm {
16
17static const uint8_t EVENT_QUEUE_SIZE = 12;
18
19// Callback types for line coding and line state changes
20using LineCodingCallback = std::function<void(uint32_t bit_rate, uint8_t stop_bits, uint8_t parity, uint8_t data_bits)>;
21using LineStateCallback = std::function<void(bool dtr, bool rts)>;
22
23// Event types
28
29// Event structure for the queue
30struct CDCEvent {
32 union {
33 struct {
34 bool dtr;
35 bool rts;
37 struct {
39 uint8_t stop_bits;
40 uint8_t parity;
41 uint8_t data_bits;
44
45 // Required by EventPool - called before returning to pool
46 void release() {
47 // No dynamic memory to clean up, data is stored inline
48 }
49};
50
51// Forward declaration
52class USBCDCACMComponent;
53
55class USBCDCACMInstance final : public uart::UARTComponent, public Parented<USBCDCACMComponent> {
56 public:
57 void setup();
58 void loop();
59 void dump_config();
60
61 void set_interface_number(uint8_t itf) { this->itf_ = itf; }
62 // Get the CDC port number for this instance
63 uint8_t get_itf() const { return this->itf_; }
64 // Ring buffer accessors for bridge components
65 RingbufHandle_t get_tx_ringbuf() const { return this->usb_tx_ringbuf_; }
66 RingbufHandle_t get_rx_ringbuf() const { return this->usb_rx_ringbuf_; }
67
68 // Task handle accessor for notifying TX task
69 TaskHandle_t get_tx_task_handle() const { return this->usb_tx_task_handle_; }
70
71 // Callback registration for line coding and line state changes
72 void set_line_coding_callback(LineCodingCallback callback) { this->line_coding_callback_ = std::move(callback); }
73 void set_line_state_callback(LineStateCallback callback) { this->line_state_callback_ = std::move(callback); }
74
75 // Called from USB core task context queues event for processing in main loop
76 void queue_line_coding_event(uint32_t bit_rate, uint8_t stop_bits, uint8_t parity, uint8_t data_bits);
77 void queue_line_state_event(bool dtr, bool rts);
78
79 static void usb_tx_task_fn(void *arg);
80 void usb_tx_task();
81
82 // UARTComponent interface implementation
83 void write_array(const uint8_t *data, size_t len) override;
84 bool peek_byte(uint8_t *data) override;
85 bool read_array(uint8_t *data, size_t len) override;
86 size_t available() override;
88#if defined(USE_ESP8266) || defined(USE_ESP32)
89 // No-op: in CDC ACM device mode the host dictates the line coding, so there are no
90 // local UART settings to (re)apply.
91 void load_settings(bool dump_config) override {}
92 using UARTComponent::load_settings; // also bring in the no-arg overload for convenience
93#endif
94
95 protected:
96 void check_logger_conflict() override;
97
98 // Process queued events and invoke callbacks (called from main loop)
99 void process_events_();
100 // True while TX bytes are still in the ring buffer or held by the TX task
101 bool tx_pending_();
102 TaskHandle_t usb_tx_task_handle_{nullptr};
103
104 RingbufHandle_t usb_tx_ringbuf_{nullptr};
105 RingbufHandle_t usb_rx_ringbuf_{nullptr};
106 // Non-zero while the TX task holds bytes it has pulled from the ring buffer but not
107 // yet handed to TinyUSB; lets flush() account for data that is in neither the ring
108 // buffer nor TinyUSB's FIFO.
109 // Threading: written only by usb_tx_task(); read by flush()'s bounded wait on the
110 // caller's task (typically the main loop).
111 // std::atomic<uint8_t> rather than std::atomic<bool> because GCC on Xtensa
112 // generates an indirect function call for atomic<bool> ops instead of inlining
113 // them; atomic<uint8_t> inlines correctly on all platforms.
114 std::atomic<uint8_t> usb_tx_busy_{0};
115 // Running total of bytes dropped by write_array() (never reset), and the timestamp
116 // of the last "buffer full" log line (throttled so a sustained host stall doesn't
117 // flood the log).
120 // RX buffer for peek functionality
121 uint8_t peek_buffer_{0};
122 bool has_peek_{false};
123 uint8_t itf_{0};
124 // User-registered callbacks (called from main loop)
127
128 // Lock-free queue and event pool for cross-task event passing
129 // Pool sized to queue capacity (SIZE-1) because LockFreeQueue<T,N> is a ring
130 // buffer that holds N-1 elements. This guarantees allocate() returns nullptr
131 // before push() can fail, preventing both a pool slot leak and an SPSC
132 // violation on the pool's internal free list.
133 EventPool<CDCEvent, EVENT_QUEUE_SIZE - 1> event_pool_;
135};
136
138class USBCDCACMComponent final : public Component {
139 public:
141
142 void setup() override;
143 void loop() override;
144 void dump_config() override;
145 float get_setup_priority() const override { return setup_priority::IO; }
146
147 // Interface management
148 void add_interface(USBCDCACMInstance *interface);
150
151 protected:
152 std::array<USBCDCACMInstance *, ESPHOME_MAX_USB_CDC_INSTANCES> interfaces_{};
153};
154
155extern USBCDCACMComponent *global_usb_cdc_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
156
157} // namespace esphome::usb_cdc_acm
158#endif
Helper class to easily give an object a parent of type T.
Definition helpers.h:1910
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:55
RingbufHandle_t get_rx_ringbuf() const
Definition usb_cdc_acm.h:66
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:65
void set_line_state_callback(LineStateCallback callback)
Definition usb_cdc_acm.h:73
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)
TaskHandle_t get_tx_task_handle() const
Definition usb_cdc_acm.h:69
void set_line_coding_callback(LineCodingCallback callback)
Definition usb_cdc_acm.h:72
void load_settings(bool dump_config) override
Definition usb_cdc_acm.h:91
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:20
USBCDCACMComponent * global_usb_cdc_component
std::function< void(bool dtr, bool rts)> LineStateCallback
Definition usb_cdc_acm.h:21
const void size_t len
Definition hal.h:64
static void uint32_t
union esphome::usb_cdc_acm::CDCEvent::@177 data
struct esphome::usb_cdc_acm::CDCEvent::@177::@179 line_coding
struct esphome::usb_cdc_acm::CDCEvent::@177::@178 line_state