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"
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);
112 this->
usb_tx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_TX_BUFSIZE, RINGBUF_TYPE_BYTEBUF);
114 ESP_LOGE(TAG,
"USB TX buffer creation error for itf %d", this->
itf_);
119 this->
usb_rx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_RX_BUFSIZE, RINGBUF_TYPE_BYTEBUF);
121 ESP_LOGE(TAG,
"USB RX buffer creation error for itf %d", this->
itf_);
127 const tinyusb_config_cdcacm_t acm_cfg = {
128 .cdc_port =
static_cast<tinyusb_cdcacm_itf_t
>(this->
itf_),
129 .callback_rx = &tinyusb_cdc_rx_callback,
130 .callback_rx_wanted_char = NULL,
131 .callback_line_state_changed = &tinyusb_cdc_line_state_changed_callback,
132 .callback_line_coding_changed = &tinyusb_cdc_line_coding_changed_callback,
135 esp_err_t result = tinyusb_cdcacm_init(&acm_cfg);
136 if (result != ESP_OK) {
137 ESP_LOGE(TAG,
"tinyusb_cdcacm_init failed: %d", result);
143 constexpr size_t stack_size =
144 ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE ? USB_TX_TASK_STACK_SIZE_VV : USB_TX_TASK_STACK_SIZE;
147 char task_name[] =
"usb_tx_0";
152 ESP_LOGE(TAG,
"Failed to create USB TX task for itf %d", this->
itf_);
172 size_t tx_data_size = 0;
182 ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
192 ESP_LOGE(TAG,
"USB TX itf=%d: RingBuf read failed", this->
itf_);
194 }
else if (tx_data_size == 0) {
195 ESP_LOGD(TAG,
"USB TX itf=%d: RingBuf empty, skipping", this->
itf_);
199 ESP_LOGV(TAG,
"USB TX itf=%d: Read %d bytes from buffer", this->
itf_, tx_data_size);
200#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
207 uint8_t *data_head = &data[0];
209 while (tx_data_size > 0) {
211 tinyusb_cdcacm_write_queue(
static_cast<tinyusb_cdcacm_itf_t
>(this->
itf_), data_head, tx_data_size);
212 ESP_LOGV(TAG,
"USB TX itf=%d: enqueued: size=%d, queued=%u", this->
itf_, tx_data_size, queued);
214 tx_data_size -= queued;
217 ESP_LOGV(TAG,
"USB TX itf=%d: waiting 10ms for flush", this->
itf_);
218 esp_err_t flush_ret =
219 tinyusb_cdcacm_write_flush(
static_cast<tinyusb_cdcacm_itf_t
>(this->
itf_), pdMS_TO_TICKS(10));
221 if (flush_ret == ESP_OK) {
228 const size_t pending = tx_data_size + (CFG_TUD_CDC_TX_BUFSIZE - tud_cdc_n_write_available(this->
itf_));
244 if (flush_ret == ESP_ERR_TIMEOUT && tud_cdc_n_connected(this->
itf_)) {
246 if ((now - stall_log_ms) >= LOG_THROTTLE_MS) {
248 ESP_LOGW(TAG,
"USB TX itf=%d: host not reading; %zu bytes pending", this->
itf_, pending);
253 if (flush_ret == ESP_ERR_TIMEOUT) {
258 ESP_LOGW(TAG,
"USB TX itf=%d: not connected; dropping %zu bytes", this->
itf_, pending);
260 ESP_LOGE(TAG,
"USB TX itf=%d: flush failed (%s); dropping %zu bytes", this->
itf_, esp_err_to_name(flush_ret),
263 tud_cdc_n_write_clear(this->
itf_);
280 if (send_res != pdTRUE) {
292 ESP_LOGW(TAG,
"USB TX itf=%d: buffer full, %" PRIu32
" bytes dropped total", this->
itf_, this->
tx_dropped_bytes_);
323 size_t original_len =
len;
324 size_t bytes_read = 0;
339 uint8_t *buf =
static_cast<uint8_t *
>(xRingbufferReceiveUpTo(this->
usb_rx_ringbuf_, &rx_size, 0,
len));
340 if (buf ==
nullptr) {
344 memcpy(data, buf, rx_size);
346 bytes_read += rx_size;
354 buf =
static_cast<uint8_t *
>(xRingbufferReceiveUpTo(this->
usb_rx_ringbuf_, &rx_size, 0,
len));
355 if (buf ==
nullptr) {
359 memcpy(data, buf, rx_size);
361 bytes_read += rx_size;
363 return bytes_read == original_len;
367 UBaseType_t waiting = 0;
369 vRingbufferGetInfo(this->
usb_rx_ringbuf_,
nullptr,
nullptr,
nullptr,
nullptr, &waiting);
371 return waiting + (this->
has_peek_ ? 1 : 0);
379 UBaseType_t waiting = 0;
380 vRingbufferGetInfo(this->
usb_tx_ringbuf_,
nullptr,
nullptr,
nullptr,
nullptr, &waiting);
394 TickType_t now = xTaskGetTickCount();
395 const TickType_t deadline = now + pdMS_TO_TICKS(FLUSH_TIMEOUT_MS);
397 if (
static_cast<int32_t
>(now - deadline) >= 0) {
400 vTaskDelay(pdMS_TO_TICKS(1));
401 now = xTaskGetTickCount();
409 const int32_t remaining =
static_cast<int32_t
>(deadline - now);
410 const TickType_t flush_ticks = remaining > 0 ?
static_cast<TickType_t
>(remaining) : 1;
411 switch (tinyusb_cdcacm_write_flush(
static_cast<tinyusb_cdcacm_itf_t
>(this->
itf_), flush_ticks)) {
414 case ESP_ERR_TIMEOUT:
417 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::array< uint8_t, CONFIG_TINYUSB_CDC_TX_BUFSIZE > usb_tx_staging_
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.
ESPHOME_LOG_TAG(TAG, "usb_cdc_acm")
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)
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()