1#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
2 defined(USE_ESP32_VARIANT_ESP32S31) || defined(USE_ESP32_VARIANT_ESP32H4)
10#include "freertos/FreeRTOS.h"
11#include "freertos/ringbuf.h"
12#include "freertos/task.h"
16#include "tinyusb_cdc_acm.h"
20static const char *
const TAG =
"usb_cdc_acm";
23static constexpr size_t USB_CDC_MAX_LOG_BYTES = 168;
25static constexpr size_t USB_TX_TASK_STACK_SIZE = 4096;
26static constexpr size_t USB_TX_TASK_STACK_SIZE_VV = 8192;
30static constexpr uint32_t FLUSH_TIMEOUT_MS = 100;
33static constexpr uint32_t LOG_THROTTLE_MS = 1000;
35static USBCDCACMInstance *get_instance_by_itf(
int itf) {
42static void tinyusb_cdc_rx_callback(
int itf, cdcacm_event_t *event) {
43 USBCDCACMInstance *instance = get_instance_by_itf(itf);
44 if (instance ==
nullptr) {
45 ESP_LOGE(TAG,
"RX callback: invalid interface %d", itf);
50 static uint8_t rx_buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE] = {0};
54 tinyusb_cdcacm_read(
static_cast<tinyusb_cdcacm_itf_t
>(itf), rx_buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size);
55 ESP_LOGV(TAG,
"tinyusb_cdc_rx_callback itf=%d (size: %u)", itf, rx_size);
56#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
61 if (
ret == ESP_OK && rx_size > 0) {
62 RingbufHandle_t rx_ringbuf = instance->get_rx_ringbuf();
63 if (rx_ringbuf !=
nullptr) {
64 BaseType_t send_res = xRingbufferSend(rx_ringbuf, rx_buf, rx_size, 0);
65 if (send_res != pdTRUE) {
66 ESP_LOGE(TAG,
"USB RX itf=%d: buffer full, %u bytes lost", itf, rx_size);
68 ESP_LOGV(TAG,
"USB RX itf=%d: queued %u bytes", itf, rx_size);
74static void tinyusb_cdc_line_state_changed_callback(
int itf, cdcacm_event_t *event) {
75 USBCDCACMInstance *instance = get_instance_by_itf(itf);
76 if (instance ==
nullptr) {
77 ESP_LOGE(TAG,
"Line state callback: invalid interface %d", itf);
81 int dtr =
event->line_state_changed_data.dtr;
82 int rts =
event->line_state_changed_data.rts;
83 ESP_LOGV(TAG,
"Line state itf=%d: DTR=%d, RTS=%d", itf, dtr, rts);
86 instance->queue_line_state_event(dtr != 0, rts != 0);
89static void tinyusb_cdc_line_coding_changed_callback(
int itf, cdcacm_event_t *event) {
90 USBCDCACMInstance *instance = get_instance_by_itf(itf);
91 if (instance ==
nullptr) {
92 ESP_LOGE(TAG,
"Line coding callback: invalid interface %d", itf);
96 uint32_t bit_rate =
event->line_coding_changed_data.p_line_coding->bit_rate;
97 uint8_t stop_bits =
event->line_coding_changed_data.p_line_coding->stop_bits;
98 uint8_t parity =
event->line_coding_changed_data.p_line_coding->parity;
99 uint8_t data_bits =
event->line_coding_changed_data.p_line_coding->data_bits;
100 ESP_LOGV(TAG,
"Line coding itf=%d: bit_rate=%" PRIu32
" stop_bits=%u parity=%u data_bits=%u", itf, bit_rate,
101 stop_bits, parity, data_bits);
104 instance->queue_line_coding_event(bit_rate, stop_bits, parity, data_bits);
107static esp_err_t ringbuf_read_bytes(RingbufHandle_t ring_buf, uint8_t *out_buf,
size_t out_buf_sz,
size_t *rx_data_size,
108 TickType_t x_ticks_to_wait) {
110 uint8_t *buf =
static_cast<uint8_t *
>(xRingbufferReceiveUpTo(ring_buf, &read_sz, x_ticks_to_wait, out_buf_sz));
112 if (buf ==
nullptr) {
116 memcpy(out_buf, buf, read_sz);
117 vRingbufferReturnItem(ring_buf, (
void *) buf);
118 *rx_data_size = read_sz;
121 buf =
static_cast<uint8_t *
>(xRingbufferReceiveUpTo(ring_buf, &read_sz, 0, out_buf_sz - *rx_data_size));
122 if (buf !=
nullptr) {
123 memcpy(out_buf + *rx_data_size, buf, read_sz);
124 vRingbufferReturnItem(ring_buf, (
void *) buf);
125 *rx_data_size += read_sz;
136 this->
usb_tx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_TX_BUFSIZE, RINGBUF_TYPE_BYTEBUF);
138 ESP_LOGE(TAG,
"USB TX buffer creation error for itf %d", this->
itf_);
143 this->
usb_rx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_RX_BUFSIZE, RINGBUF_TYPE_BYTEBUF);
145 ESP_LOGE(TAG,
"USB RX buffer creation error for itf %d", this->
itf_);
151 const tinyusb_config_cdcacm_t acm_cfg = {
152 .cdc_port =
static_cast<tinyusb_cdcacm_itf_t
>(this->
itf_),
153 .callback_rx = &tinyusb_cdc_rx_callback,
154 .callback_rx_wanted_char = NULL,
155 .callback_line_state_changed = &tinyusb_cdc_line_state_changed_callback,
156 .callback_line_coding_changed = &tinyusb_cdc_line_coding_changed_callback,
159 esp_err_t result = tinyusb_cdcacm_init(&acm_cfg);
160 if (result != ESP_OK) {
161 ESP_LOGE(TAG,
"tinyusb_cdcacm_init failed: %d", result);
167 constexpr size_t stack_size =
168 ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE ? USB_TX_TASK_STACK_SIZE_VV : USB_TX_TASK_STACK_SIZE;
171 char task_name[] =
"usb_tx_0";
176 ESP_LOGE(TAG,
"Failed to create USB TX task for itf %d", this->
itf_);
195 uint8_t data[CONFIG_TINYUSB_CDC_TX_BUFSIZE] = {0};
196 size_t tx_data_size = 0;
206 ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
213 esp_err_t
ret = ringbuf_read_bytes(this->
usb_tx_ringbuf_, data, CONFIG_TINYUSB_CDC_TX_BUFSIZE, &tx_data_size, 0);
216 ESP_LOGE(TAG,
"USB TX itf=%d: RingBuf read failed", this->
itf_);
218 }
else if (tx_data_size == 0) {
219 ESP_LOGD(TAG,
"USB TX itf=%d: RingBuf empty, skipping", this->
itf_);
223 ESP_LOGV(TAG,
"USB TX itf=%d: Read %d bytes from buffer", this->
itf_, tx_data_size);
224#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
231 uint8_t *data_head = &data[0];
233 while (tx_data_size > 0) {
235 tinyusb_cdcacm_write_queue(
static_cast<tinyusb_cdcacm_itf_t
>(this->
itf_), data_head, tx_data_size);
236 ESP_LOGV(TAG,
"USB TX itf=%d: enqueued: size=%d, queued=%u", this->
itf_, tx_data_size, queued);
238 tx_data_size -= queued;
241 ESP_LOGV(TAG,
"USB TX itf=%d: waiting 10ms for flush", this->
itf_);
242 esp_err_t flush_ret =
243 tinyusb_cdcacm_write_flush(
static_cast<tinyusb_cdcacm_itf_t
>(this->
itf_), pdMS_TO_TICKS(10));
245 if (flush_ret == ESP_OK) {
252 const size_t pending = tx_data_size + (CFG_TUD_CDC_TX_BUFSIZE - tud_cdc_n_write_available(this->
itf_));
268 if (flush_ret == ESP_ERR_TIMEOUT && tud_cdc_n_connected(this->
itf_)) {
270 if ((now - stall_log_ms) >= LOG_THROTTLE_MS) {
272 ESP_LOGW(TAG,
"USB TX itf=%d: host not reading; %zu bytes pending", this->
itf_, pending);
277 if (flush_ret == ESP_ERR_TIMEOUT) {
282 ESP_LOGW(TAG,
"USB TX itf=%d: not connected; dropping %zu bytes", this->
itf_, pending);
284 ESP_LOGE(TAG,
"USB TX itf=%d: flush failed (%s); dropping %zu bytes", this->
itf_, esp_err_to_name(flush_ret),
287 tud_cdc_n_write_clear(this->
itf_);
304 if (send_res != pdTRUE) {
316 ESP_LOGW(TAG,
"USB TX itf=%d: buffer full, %" PRIu32
" bytes dropped total", this->
itf_, this->
tx_dropped_bytes_);
347 size_t original_len =
len;
348 size_t bytes_read = 0;
363 uint8_t *buf =
static_cast<uint8_t *
>(xRingbufferReceiveUpTo(this->
usb_rx_ringbuf_, &rx_size, 0,
len));
364 if (buf ==
nullptr) {
368 memcpy(data, buf, rx_size);
370 bytes_read += rx_size;
378 buf =
static_cast<uint8_t *
>(xRingbufferReceiveUpTo(this->
usb_rx_ringbuf_, &rx_size, 0,
len));
379 if (buf ==
nullptr) {
383 memcpy(data, buf, rx_size);
385 bytes_read += rx_size;
387 return bytes_read == original_len;
391 UBaseType_t waiting = 0;
393 vRingbufferGetInfo(this->
usb_rx_ringbuf_,
nullptr,
nullptr,
nullptr,
nullptr, &waiting);
395 return waiting + (this->
has_peek_ ? 1 : 0);
403 UBaseType_t waiting = 0;
404 vRingbufferGetInfo(this->
usb_tx_ringbuf_,
nullptr,
nullptr,
nullptr,
nullptr, &waiting);
418 TickType_t now = xTaskGetTickCount();
419 const TickType_t deadline = now + pdMS_TO_TICKS(FLUSH_TIMEOUT_MS);
421 if (
static_cast<int32_t
>(now - deadline) >= 0) {
424 vTaskDelay(pdMS_TO_TICKS(1));
425 now = xTaskGetTickCount();
433 const int32_t remaining =
static_cast<int32_t
>(deadline - now);
434 const TickType_t flush_ticks = remaining > 0 ?
static_cast<TickType_t
>(remaining) : 1;
435 switch (tinyusb_cdcacm_write_flush(
static_cast<tinyusb_cdcacm_itf_t
>(this->
itf_), flush_ticks)) {
438 case ESP_ERR_TIMEOUT:
441 case ESP_ERR_NOT_FINISHED:
USBCDCACMComponent * parent_
bool read_byte(uint8_t *data)
USBCDCACMInstance * get_interface_by_number(uint8_t itf)
Represents a single CDC ACM interface instance.
TaskHandle_t usb_tx_task_handle_
size_t available() override
std::atomic< uint8_t > usb_tx_busy_
bool read_array(uint8_t *data, size_t len) override
bool peek_byte(uint8_t *data) override
RingbufHandle_t usb_tx_ringbuf_
uint32_t tx_dropped_log_ms_
void check_logger_conflict() override
uint32_t tx_dropped_bytes_
uart::UARTFlushResult flush() override
static void usb_tx_task_fn(void *arg)
void write_array(const uint8_t *data, size_t len) override
RingbufHandle_t usb_rx_ringbuf_
UARTFlushResult
Result of a flush() call.
@ UART_FLUSH_RESULT_ASSUMED_SUCCESS
Platform cannot report result; success is assumed.
@ UART_FLUSH_RESULT_SUCCESS
Confirmed: all bytes left the TX FIFO.
@ UART_FLUSH_RESULT_FAILED
Confirmed: driver or hardware error.
@ UART_FLUSH_RESULT_TIMEOUT
Confirmed: timed out before TX completed.
USBCDCACMComponent * global_usb_cdc_component
ESPHOME_ALWAYS_INLINE char format_hex_char(uint8_t v, char base)
Convert a nibble (0-15) to hex char with specified base ('a' for lowercase, 'A' for uppercase)
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
uint32_t IRAM_ATTR HOT millis()