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)
9#include "freertos/FreeRTOS.h"
10#include "freertos/ringbuf.h"
11#include "freertos/task.h"
15#include "tinyusb_cdc_acm.h"
19static const char *
const TAG =
"usb_cdc_acm";
22static constexpr size_t USB_CDC_MAX_LOG_BYTES = 168;
24static constexpr size_t USB_TX_TASK_STACK_SIZE = 4096;
25static constexpr size_t USB_TX_TASK_STACK_SIZE_VV = 8192;
27static USBCDCACMInstance *get_instance_by_itf(
int itf) {
34static void tinyusb_cdc_rx_callback(
int itf, cdcacm_event_t *event) {
35 USBCDCACMInstance *instance = get_instance_by_itf(itf);
36 if (instance ==
nullptr) {
37 ESP_LOGE(TAG,
"RX callback: invalid interface %d", itf);
42 static uint8_t rx_buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE] = {0};
46 tinyusb_cdcacm_read(
static_cast<tinyusb_cdcacm_itf_t
>(itf), rx_buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size);
47 ESP_LOGV(TAG,
"tinyusb_cdc_rx_callback itf=%d (size: %u)", itf, rx_size);
48#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
53 if (ret == ESP_OK && rx_size > 0) {
54 RingbufHandle_t rx_ringbuf = instance->get_rx_ringbuf();
55 if (rx_ringbuf !=
nullptr) {
56 BaseType_t send_res = xRingbufferSend(rx_ringbuf, rx_buf, rx_size, 0);
57 if (send_res != pdTRUE) {
58 ESP_LOGE(TAG,
"USB RX itf=%d: buffer full, %u bytes lost", itf, rx_size);
60 ESP_LOGV(TAG,
"USB RX itf=%d: queued %u bytes", itf, rx_size);
66static void tinyusb_cdc_line_state_changed_callback(
int itf, cdcacm_event_t *event) {
67 USBCDCACMInstance *instance = get_instance_by_itf(itf);
68 if (instance ==
nullptr) {
69 ESP_LOGE(TAG,
"Line state callback: invalid interface %d", itf);
73 int dtr =
event->line_state_changed_data.dtr;
74 int rts =
event->line_state_changed_data.rts;
75 ESP_LOGV(TAG,
"Line state itf=%d: DTR=%d, RTS=%d", itf, dtr, rts);
78 instance->queue_line_state_event(dtr != 0, rts != 0);
81static void tinyusb_cdc_line_coding_changed_callback(
int itf, cdcacm_event_t *event) {
82 USBCDCACMInstance *instance = get_instance_by_itf(itf);
83 if (instance ==
nullptr) {
84 ESP_LOGE(TAG,
"Line coding callback: invalid interface %d", itf);
88 uint32_t bit_rate =
event->line_coding_changed_data.p_line_coding->bit_rate;
89 uint8_t stop_bits =
event->line_coding_changed_data.p_line_coding->stop_bits;
90 uint8_t parity =
event->line_coding_changed_data.p_line_coding->parity;
91 uint8_t data_bits =
event->line_coding_changed_data.p_line_coding->data_bits;
92 ESP_LOGV(TAG,
"Line coding itf=%d: bit_rate=%" PRIu32
" stop_bits=%u parity=%u data_bits=%u", itf, bit_rate,
93 stop_bits, parity, data_bits);
96 instance->queue_line_coding_event(bit_rate, stop_bits, parity, data_bits);
99static esp_err_t ringbuf_read_bytes(RingbufHandle_t ring_buf, uint8_t *out_buf,
size_t out_buf_sz,
size_t *rx_data_size,
100 TickType_t x_ticks_to_wait) {
102 uint8_t *buf =
static_cast<uint8_t *
>(xRingbufferReceiveUpTo(ring_buf, &read_sz, x_ticks_to_wait, out_buf_sz));
104 if (buf ==
nullptr) {
108 memcpy(out_buf, buf, read_sz);
109 vRingbufferReturnItem(ring_buf, (
void *) buf);
110 *rx_data_size = read_sz;
113 buf =
static_cast<uint8_t *
>(xRingbufferReceiveUpTo(ring_buf, &read_sz, 0, out_buf_sz - *rx_data_size));
114 if (buf !=
nullptr) {
115 memcpy(out_buf + *rx_data_size, buf, read_sz);
116 vRingbufferReturnItem(ring_buf, (
void *) buf);
117 *rx_data_size += read_sz;
128 this->
usb_tx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_TX_BUFSIZE, RINGBUF_TYPE_BYTEBUF);
130 ESP_LOGE(TAG,
"USB TX buffer creation error for itf %d", this->
itf_);
135 this->
usb_rx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_RX_BUFSIZE, RINGBUF_TYPE_BYTEBUF);
137 ESP_LOGE(TAG,
"USB RX buffer creation error for itf %d", this->
itf_);
143 const tinyusb_config_cdcacm_t acm_cfg = {
144 .cdc_port =
static_cast<tinyusb_cdcacm_itf_t
>(this->
itf_),
145 .callback_rx = &tinyusb_cdc_rx_callback,
146 .callback_rx_wanted_char = NULL,
147 .callback_line_state_changed = &tinyusb_cdc_line_state_changed_callback,
148 .callback_line_coding_changed = &tinyusb_cdc_line_coding_changed_callback,
151 esp_err_t result = tinyusb_cdcacm_init(&acm_cfg);
152 if (result != ESP_OK) {
153 ESP_LOGE(TAG,
"tinyusb_cdcacm_init failed: %d", result);
159 constexpr size_t stack_size =
160 ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE ? USB_TX_TASK_STACK_SIZE_VV : USB_TX_TASK_STACK_SIZE;
163 char task_name[] =
"usb_tx_0";
168 ESP_LOGE(TAG,
"Failed to create USB TX task for itf %d", this->
itf_);
187 uint8_t data[CONFIG_TINYUSB_CDC_TX_BUFSIZE] = {0};
188 size_t tx_data_size = 0;
192 ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
195 esp_err_t ret = ringbuf_read_bytes(this->
usb_tx_ringbuf_, data, CONFIG_TINYUSB_CDC_TX_BUFSIZE, &tx_data_size, 0);
198 ESP_LOGE(TAG,
"USB TX itf=%d: RingBuf read failed", this->
itf_);
200 }
else if (tx_data_size == 0) {
201 ESP_LOGD(TAG,
"USB TX itf=%d: RingBuf empty, skipping", this->
itf_);
205 ESP_LOGV(TAG,
"USB TX itf=%d: Read %d bytes from buffer", this->
itf_, tx_data_size);
206#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
213 uint8_t *data_head = &data[0];
215 while (tx_data_size > 0) {
217 tinyusb_cdcacm_write_queue(
static_cast<tinyusb_cdcacm_itf_t
>(this->
itf_), data_head, tx_data_size);
218 ESP_LOGV(TAG,
"USB TX itf=%d: enqueued: size=%d, queued=%u", this->
itf_, tx_data_size, queued);
220 tx_data_size -= queued;
223 ESP_LOGV(TAG,
"USB TX itf=%d: waiting 10ms for flush", this->
itf_);
224 esp_err_t flush_ret =
225 tinyusb_cdcacm_write_flush(
static_cast<tinyusb_cdcacm_itf_t
>(this->
itf_), pdMS_TO_TICKS(10));
227 if (flush_ret != ESP_OK) {
228 ESP_LOGE(TAG,
"USB TX itf=%d: flush failed", this->
itf_);
229 tud_cdc_n_write_clear(this->
itf_);
247 if (send_res != pdTRUE) {
248 ESP_LOGW(TAG,
"USB TX itf=%d: buffer full, %u bytes dropped", this->
itf_, len);
278 size_t original_len =
len;
279 size_t bytes_read = 0;
294 uint8_t *buf =
static_cast<uint8_t *
>(xRingbufferReceiveUpTo(this->
usb_rx_ringbuf_, &rx_size, 0,
len));
295 if (buf ==
nullptr) {
299 memcpy(data, buf, rx_size);
301 bytes_read += rx_size;
309 buf =
static_cast<uint8_t *
>(xRingbufferReceiveUpTo(this->
usb_rx_ringbuf_, &rx_size, 0,
len));
310 if (buf ==
nullptr) {
314 memcpy(data, buf, rx_size);
316 bytes_read += rx_size;
318 return bytes_read == original_len;
322 UBaseType_t waiting = 0;
324 vRingbufferGetInfo(this->
usb_rx_ringbuf_,
nullptr,
nullptr,
nullptr,
nullptr, &waiting);
326 return waiting + (this->
has_peek_ ? 1 : 0);
335 UBaseType_t waiting = 1;
336 while (waiting > 0) {
337 vRingbufferGetInfo(this->
usb_tx_ringbuf_,
nullptr,
nullptr,
nullptr,
nullptr, &waiting);
339 vTaskDelay(pdMS_TO_TICKS(1));
344 esp_err_t err = tinyusb_cdcacm_write_flush(
static_cast<tinyusb_cdcacm_itf_t
>(this->
itf_), pdMS_TO_TICKS(100));
347 if (err == ESP_ERR_TIMEOUT)
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
bool read_array(uint8_t *data, size_t len) override
bool peek_byte(uint8_t *data) override
RingbufHandle_t usb_tx_ringbuf_
void check_logger_conflict() override
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".