espressif / arduino-esp32

Arduino core for the ESP32
GNU Lesser General Public License v2.1
13.44k stars 7.38k forks source link

HWCDC.h needs a clearTxBuffer() function for ESP32C3 USBCDC to work properly #7779

Closed specternecter closed 7 months ago

specternecter commented 1 year ago

Related area

Board Support

Hardware specification

Support for ESP32C3

Is your feature request related to a problem?

https://github.com/espressif/arduino-esp32/issues/6089

Describe the solution you'd like

It needs a function to clear the TX Buffer or anything in there will be printed when port is opened, It would seem that data should be held and printed when the port is opened, but that's actually a significant problem as the sketch below demonstrates.

Describe alternatives you've considered

I tried toggling the setTxBufferSize() but it only slightly helps. I tried many others that didn't help at all and mostly only created more problems. As a temporary solution, I added the waitForCDC variable to mask the issue, but it's designed so that the board will only run if the port is open when the variable is set to true.

Additional context

The following sketch demonstrates why this function is necessary. The USBSerial example in the IDE is absolutely wrong for the ESP32C3. I fixed it, along with fixing many other issues I've seen people post about or personally experienced. If you run this proposed example sketch, leave the port closed for at least 15 seconds and then open it. The longer it's closed, the more noticeable the need for a clearTxBuffer() function is. It will print the bootloader start message and the first 13 counts, then start printing what it's supposed to print.

#if !ARDUINO_USB_CDC_ON_BOOT
  #error USB CDC Ob Boot must be enabled to use this sketch
#endif

volatile bool usbActive; // when port is open, this is true. When closed, it's false.
bool waitForCDC = false; /* device resets every time port is CLOSED. This waits for
                           port to be open before running. */
unsigned long timer; // for non-blocking delay
unsigned long secondsActive = 0; // counts how many seconds since last start

static void usbEventCallback(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data){
    arduino_hw_cdc_event_data_t * data = (arduino_hw_cdc_event_data_t*)event_data;
    switch (event_id){
      case ARDUINO_HW_CDC_CONNECTED_EVENT:

        // ***** clearTxBuffer() should go here *****

        Serial.println("\nCDC CONNECTED\n");
        usbActive = true;
        break;
      case ARDUINO_HW_CDC_BUS_RESET_EVENT:
        Serial.println("CDC BUS RESET");
        break;
      case ARDUINO_HW_CDC_RX_EVENT:
        Serial.printf("CDC RX EVENT [%u]: ", data->rx.len);
        {
          uint8_t buf[data->rx.len];
          size_t len = Serial.read(buf, data->rx.len);
          Serial.write(buf, len);
        }
        Serial.println();
        break;
      case ARDUINO_HW_CDC_TX_EVENT:
        // No example provided
        break;
      case ARDUINO_HW_CDC_MAX_EVENT:
        // No example provided
        break;
      default:
        break;
  }
}

void setup() {
  Serial.setTxBufferSize(256); // already the default, but setting for example purposes
  Serial.setRxBufferSize(256); // already the default, but setting for example purposes
  Serial.onEvent(usbEventCallback); // monitors port for status changes
  Serial.begin(); // start the CDC port

  if(waitForCDC){ // stays here until port open if waitForCDC == true
    if(usbActive == false){
      while(usbActive == false){} // wait here on start until CDC port is opened
      delay(1000);
    }
  }
  timer = millis() + 1000; // set timer to 1 second from now
}

void loop() {
  if(usbActive){
    Serial.setTxTimeoutMs(100); // recommended value when port is open.
  } else {
    //The following is ABSOLUTELY NECESSARY when the port is closed!
    //Otherwise, Serial writes will hang and sketch may not run properly.
    Serial.setTxTimeoutMs(0); // NECESSARY value when port is closed.
  }
  if(millis() > timer){ // if 1 second has passed
    secondsActive++; // increment seconds count
    Serial.print("Seconds Active: "); Serial.println(secondsActive);
    timer = millis() + 1000; // reset timer to 1 second from now
  }
}
SuGlider commented 1 year ago

@specternecter - Please try PR #7221 and let me know.

List of issues that may be solved with PR #7221

6089

6983

7554

7779

specternecter commented 1 year ago

As far as I can tell, that only fixes blocking to the led, but the core issue remains and will likely case other unforseen blocks. There's actually a few issues in how the usbcdc functions for the ESP32C3. I just went ahead and modified the HWCDC.cpp file to eliminate the issues (including clearing the TX Buffer). Most of the issues appear to be fixed now (LED works perfectly as well now, without PR #7221), but I need to run some more tests to make sure the changes I made don't break anything else. I should be done in a few hours but I don't know how to submit changes to the repo.

specternecter commented 1 year ago

All appears to ne working perfectly after the changes. Basically, all that I had to do was add 2 global variables (user_tx_timeout_ms and user_txBufferSize) and change

This: if(!initial_empty){ initial_empty = true; //send event? //ets_printf("CONNECTED\n"); arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_CONNECTED_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken); }

To This: if(!initial_empty){ tx_timeout_ms = user_tx_timeout_ms; // sets timeout to a useful level tx_ring_buf = xRingbufferCreate(user_txBufferSize, RINGBUF_TYPE_BYTEBUF); // clear TX buffer when port is opened initial_empty = true; //send event? //ets_printf("CONNECTED\n"); arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_CONNECTED_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken); } else { tx_timeout_ms = 0; // prevents hangs on cdc writes (essentially voiding those writes) when port is closed }

Now there's no more blocking, hanging or saving the filled up TX Buffer from the beginning of runtime and printing it whenever you open the port, before current data.

specternecter commented 1 year ago

And because I changed the HWCDC.cpp file, it allowed me to make the USBSerial Example from earlier, MUCH simpler:

`#if !ARDUINO_USB_CDC_ON_BOOT

error USB CDC Ob Boot must be enabled to use this sketch

endif

unsigned long timer; // for non-blocking delay unsigned long secondsActive = 0; // counts how many seconds since last start

void setup() { //Serial.setTxTimeoutMs(100); // already the default //Serial.setTxBufferSize(256); // already the default //Serial.setRxBufferSize(256); // already the default //Serial.onEvent(usbEventCallback); // handle CDC status changes

Serial.begin(); // start the CDC port

timer = millis() + 1000; // set timer to 1 second from now }

void loop() { if(millis() > timer){ // if 1 second has passed secondsActive++; // increment seconds count Serial.print("Seconds Active: "); Serial.println(secondsActive); timer = millis() + 1000; // reset timer to 1 second from now } }

static void usbEventCallback(void arg, esp_event_base_t event_base, int32_t event_id, void event_data){ arduino_hw_cdc_event_data_t data = (arduino_hw_cdc_event_data_t)event_data; switch (event_id){ case ARDUINO_HW_CDC_CONNECTED_EVENT: Serial.println("CDC CONNECTED"); break; case ARDUINO_HW_CDC_BUS_RESET_EVENT: Serial.println("CDC BUS RESET"); break; case ARDUINO_HW_CDC_RX_EVENT: Serial.printf("CDC RX EVENT [%u]: ", data->rx.len); { uint8_t buf[data->rx.len]; size_t len = Serial.read(buf, data->rx.len); Serial.write(buf, len); } Serial.println(); break; case ARDUINO_HW_CDC_TX_EVENT: // No example provided break; case ARDUINO_HW_CDC_MAX_EVENT: // No example provided break;

  default:
    break;

} } `

specternecter commented 1 year ago

The default USBSerial example has the device using ARDUINO_USB_CDC_CONNECTED_EVENT, but the ESP32C3 actually uses ARDUINO_HW_CDC_CONNECTED_EVENT as well as other things that also don't work, or just don't make sense.. Anyway, the full edited HWCDC.cpp file is

`// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License.

include "USB.h"

if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3

include "esp32-hal.h"

include "HWCDC.h"

include "freertos/FreeRTOS.h"

include "freertos/semphr.h"

include "freertos/queue.h"

include "freertos/ringbuf.h"

include "esp_intr_alloc.h"

include "soc/periph_defs.h"

include "hal/usb_serial_jtag_ll.h"

ESP_EVENT_DEFINE_BASE(ARDUINO_HW_CDC_EVENTS);

volatile uint16_t user_tx_timeout_ms = 100; volatile uint16_t user_txBufferSize = 256;

static RingbufHandle_t tx_ring_buf = NULL; static xQueueHandle rx_queue = NULL; static uint8_t rx_data_buf[64]; static intr_handle_t intr_handle = NULL; static volatile bool initial_empty = false; static xSemaphoreHandle tx_lock = NULL; static uint32_t tx_timeout_ms = 200; static esp_event_loop_handle_t arduino_hw_cdc_event_loop_handle = NULL;

static esp_err_t arduino_hw_cdc_event_post(esp_event_base_t event_base, int32_t event_id, void event_data, size_t event_data_size, BaseType_t task_unblocked){ if(arduino_hw_cdc_event_loop_handle == NULL){ return ESP_FAIL; } return esp_event_isr_post_to(arduino_hw_cdc_event_loop_handle, event_base, event_id, event_data, event_data_size, task_unblocked); }

static esp_err_t arduino_hw_cdc_event_handler_register_with(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler, void *event_handler_arg){ if (!arduino_hw_cdc_event_loop_handle) { esp_event_loop_args_t event_task_args = { .queue_size = 5, .task_name = "arduino_hw_cdc_events", .task_priority = 5, .task_stack_size = 2048, .task_core_id = tskNO_AFFINITY }; if (esp_event_loop_create(&event_task_args, &arduino_hw_cdc_event_loop_handle) != ESP_OK) { log_e("esp_event_loop_create failed"); } } if(arduino_hw_cdc_event_loop_handle == NULL){ return ESP_FAIL; } return esp_event_handler_register_with(arduino_hw_cdc_event_loop_handle, event_base, event_id, event_handler, event_handler_arg); }

static void hw_cdc_isr_handler(void *arg) { portBASE_TYPE xTaskWoken = 0; uint32_t usbjtag_intr_status = 0; arduino_hw_cdc_event_data_t event = {0}; usbjtag_intr_status = usb_serial_jtag_ll_get_intsts_mask();

if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY) {
    // Interrupt tells us the host picked up the data we sent.
    if (usb_serial_jtag_ll_txfifo_writable() == 1) {
        // We disable the interrupt here so that the interrupt won't be triggered if there is no data to send.
        usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);

        if(!initial_empty){
            tx_timeout_ms = user_tx_timeout_ms; // sets timeout to a useful level
            tx_ring_buf = xRingbufferCreate(user_txBufferSize, RINGBUF_TYPE_BYTEBUF); // clear TX buffer when port is opened
            initial_empty = true;
            //send event?
            //ets_printf("CONNECTED\n");
            arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_CONNECTED_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);
        } else {
          tx_timeout_ms = 0; // prevents hangs on cdc writes (essentially voiding those writes) when port is closed
        }
        size_t queued_size;
        uint8_t *queued_buff = (uint8_t *)xRingbufferReceiveUpToFromISR(tx_ring_buf, &queued_size, 64);
        // If the hardware fifo is avaliable, write in it. Otherwise, do nothing.
        if (queued_buff != NULL) {  //Although tx_queued_bytes may be larger than 0. We may have interrupt before xRingbufferSend() was called.
            //Copy the queued buffer into the TX FIFO
            usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
            usb_serial_jtag_ll_write_txfifo(queued_buff, queued_size);
            usb_serial_jtag_ll_txfifo_flush();
            vRingbufferReturnItemFromISR(tx_ring_buf, queued_buff, &xTaskWoken);
            usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
            //send event?
            //ets_printf("TX:%u\n", queued_size);
            event.tx.len = queued_size;
            arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_TX_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);
        }
    } else {
        usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
    }
}

if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT) {
    // read rx buffer(max length is 64), and send avaliable data to ringbuffer.
    // Ensure the rx buffer size is larger than RX_MAX_SIZE.
    usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT);
    uint32_t rx_fifo_len = usb_serial_jtag_ll_read_rxfifo(rx_data_buf, 64);
    uint32_t i=0;
    for(i=0; i<rx_fifo_len; i++){
        if(rx_queue == NULL || !xQueueSendFromISR(rx_queue, rx_data_buf+i, &xTaskWoken)){
            break;
        }
    }
    //send event?
    //ets_printf("RX:%u/%u\n", i, rx_fifo_len);
    event.rx.len = i;
    arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_RX_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);
}

if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_BUS_RESET) {
    usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_BUS_RESET);
    initial_empty = false;
    usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
    //ets_printf("BUS_RESET\n");
    arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_BUS_RESET_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);
}

if (xTaskWoken == pdTRUE) {
    portYIELD_FROM_ISR();
}

}

static void ARDUINO_ISR_ATTR cdc0_write_char(char c) { if(xPortInIsrContext()){ xRingbufferSendFromISR(tx_ring_buf, (void) (&c), 1, NULL); } else { xRingbufferSend(tx_ring_buf, (void) (&c), 1, tx_timeout_ms / portTICK_PERIOD_MS); } usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY); }

HWCDC::HWCDC() {

}

HWCDC::~HWCDC(){ end(); }

HWCDC::operator bool() const { return initial_empty; }

void HWCDC::onEvent(esp_event_handler_t callback){ onEvent(ARDUINO_HW_CDC_ANY_EVENT, callback); }

void HWCDC::onEvent(arduino_hw_cdc_event_t event, esp_event_handler_t callback){ arduino_hw_cdc_event_handler_register_with(ARDUINO_HW_CDC_EVENTS, event, callback, this); }

void HWCDC::begin(unsigned long baud) { if(tx_lock == NULL) { tx_lock = xSemaphoreCreateMutex(); } setRxBufferSize(256);//default if not preset setTxBufferSize(256);//default if not preset

usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_LL_INTR_MASK);
usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_LL_INTR_MASK);
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY | USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT | USB_SERIAL_JTAG_INTR_BUS_RESET);
if(!intr_handle && esp_intr_alloc(ETS_USB_SERIAL_JTAG_INTR_SOURCE, 0, hw_cdc_isr_handler, NULL, &intr_handle) != ESP_OK){
    isr_log_e("HW USB CDC failed to init interrupts");
    end();
    return;
}
usb_serial_jtag_ll_txfifo_flush();

}

void HWCDC::end() { //Disable tx/rx interrupt. usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_LL_INTR_MASK); esp_intr_free(intr_handle); intr_handle = NULL; if(tx_lock != NULL) { vSemaphoreDelete(tx_lock); } setRxBufferSize(0); setTxBufferSize(0); if (arduino_hw_cdc_event_loop_handle) { esp_event_loop_delete(arduino_hw_cdc_event_loop_handle); arduino_hw_cdc_event_loop_handle = NULL; } }

void HWCDC::setTxTimeoutMs(uint32_t timeout){ user_tx_timeout_ms = timeout; tx_timeout_ms = timeout; }

/*

size_t HWCDC::setTxBufferSize(size_t tx_queue_len){ user_txBufferSize = tx_queue_len; if(tx_ring_buf){ if(!tx_queue_len){ vRingbufferDelete(tx_ring_buf); tx_ring_buf = NULL; } return 0; } tx_ring_buf = xRingbufferCreate(tx_queue_len, RINGBUF_TYPE_BYTEBUF); if(!tx_ring_buf){ return 0; } return tx_queue_len; }

int HWCDC::availableForWrite(void) { if(tx_ring_buf == NULL || tx_lock == NULL){ return 0; } if(xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS){ return 0; } size_t a = xRingbufferGetCurFreeSize(tx_ring_buf); xSemaphoreGive(tx_lock); return a; }

size_t HWCDC::write(const uint8_t *buffer, size_t size) { if(buffer == NULL || size == 0 || tx_ring_buf == NULL || tx_lock == NULL){ return 0; } if(xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS){ return 0; } size_t max_size = xRingbufferGetMaxItemSize(tx_ring_buf); size_t space = xRingbufferGetCurFreeSize(tx_ring_buf); size_t to_send = size, so_far = 0;

if(space > size){
    space = size;
}
// Non-Blocking method, Sending data to ringbuffer, and handle the data in ISR.
if(xRingbufferSend(tx_ring_buf, (void*) (buffer), space, 0) != pdTRUE){
    size = 0;
} else {
    to_send -= space;
    so_far += space;
    // Now trigger the ISR to read data from the ring buffer.
    usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);

    while(to_send){
        if(max_size > to_send){
            max_size = to_send;
        }
        // Blocking method, Sending data to ringbuffer, and handle the data in ISR.
        if(xRingbufferSend(tx_ring_buf, (void*) (buffer+so_far), max_size, tx_timeout_ms / portTICK_PERIOD_MS) != pdTRUE){
            size = so_far;
            break;
        }
        so_far += max_size;
        to_send -= max_size;
        // Now trigger the ISR to read data from the ring buffer.
        usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
    }
}
xSemaphoreGive(tx_lock);
return size;

}

size_t HWCDC::write(uint8_t c) { return write(&c, 1); }

void HWCDC::flush(void) { if(tx_ring_buf == NULL || tx_lock == NULL){ return; } if(xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS){ return; } UBaseType_t uxItemsWaiting = 0; vRingbufferGetInfo(tx_ring_buf, NULL, NULL, NULL, NULL, &uxItemsWaiting); if(uxItemsWaiting){ // Now trigger the ISR to read data from the ring buffer. usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY); } while(uxItemsWaiting){ delay(5); vRingbufferGetInfo(tx_ring_buf, NULL, NULL, NULL, NULL, &uxItemsWaiting); } xSemaphoreGive(tx_lock); }

/*

size_t HWCDC::setRxBufferSize(size_t rx_queue_len){ if(rx_queue){ if(!rx_queue_len){ vQueueDelete(rx_queue); rx_queue = NULL; } return 0; } rx_queue = xQueueCreate(rx_queue_len, sizeof(uint8_t)); if(!rx_queue){ return 0; } if(!tx_ring_buf){ tx_ring_buf = xRingbufferCreate(rx_queue_len, RINGBUF_TYPE_BYTEBUF); } return rx_queue_len; }

int HWCDC::available(void) { if(rx_queue == NULL){ return -1; } return uxQueueMessagesWaiting(rx_queue); }

int HWCDC::peek(void) { if(rx_queue == NULL){ return -1; } uint8_t c; if(xQueuePeek(rx_queue, &c, 0)) { return c; } return -1; }

int HWCDC::read(void) { if(rx_queue == NULL){ return -1; } uint8_t c = 0; if(xQueueReceive(rx_queue, &c, 0)) { return c; } return -1; }

size_t HWCDC::read(uint8_t *buffer, size_t size) { if(rx_queue == NULL){ return -1; } uint8_t c = 0; size_t count = 0; while(count < size && xQueueReceive(rx_queue, &c, 0)){ buffer[count++] = c; } return count; }

/*

void HWCDC::setDebugOutput(bool en) { if(en) { uartSetDebug(NULL); ets_install_putc1((void (*)(char)) &cdc0_write_char); } else { ets_install_putc1(NULL); } }

if ARDUINO_USB_MODE

if ARDUINO_USB_CDC_ON_BOOT//Serial used for USB CDC

HWCDC Serial;

else

HWCDC USBSerial;

endif

endif

endif / CONFIG_TINYUSB_CDC_ENABLED /`

specternecter commented 1 year ago

Actually, while the above HWCDC.cpp works on my work computer and when the device isn't plugged into a data port, it doesn't work properly on my home computer. Some more work is needed.

imwhocodes commented 1 year ago

@specternecter sorry but your comments have bad formatting ad it is not very clear Are you sure you tried the code from the pull request https://github.com/espressif/arduino-esp32/pull/7721 ?

The new code will skip completely the write if the usb is not connected or opened, not even written to the internal buffer

Nothing of what is issued with write or print should be buffered if usb is not connected

specternecter commented 1 year ago

Okay, I found the issue with my home computer. I wasn't running the example above but running LoRa communication. 1/2 mile from my home it worked fine, but in my home there was blocking happening. I assume because when close things are happening much faster. It was easily solved by just defaulting TxTimeoutMs to 0. It apparently isn't needed. But no, I'm not really interested in running a workaround. I want the core to work properly.

specternecter commented 1 year ago

And sorry, I try to use 'add code' and copying and pasting directly from the same formatting as arduino's source code but this is how it comes. I don't know how to fix it to make it look good

specternecter commented 1 year ago

This may be a better way to show the file. This fixes the core issues. tx_timeout_ms apparently isn't necessary and as long as it's set to 0, I can't find any problems. I assume the issue with tx_timout_ms is that it's always called with portTICK_PERIOD_MS like "tx_timeout_ms / portTICK_PERIOD_MS", except if you trace portTICK_PERIOD_MS through the core, you'll find that it always equals 1. It seems pretty nonsensical to me. But then again so does setting tx_timeout_ms when it seems that leaving it set to 0 eliminates all the blocking/hanging. Tested on 3 different computers and a combined 17 completely different sketches that all showed problems before I modified the HWCDC.cpp file as attached. I have yet to find any issues whatsoever with the USB after these final fixes. Between fixing the hanging and fixing the serious txBuffer issue, I am confident it's a great improvement over the ridiculously buggy USBCDC of before. It still prints "ESP-ROM:esp32c3-api1-20210207" whenever I open the port but that appears to be either the bootloader or an Arduino menuConfig issue. I'm not really concerned about it because I no longer get the very first chunk of outdated Serial prints before current prints when I open a port. Now when the port is opened I get the "ESP-ROM:esp32c3-api1-20210207" followed only by the current data that it actually should be printing. The only thing I would consider changing beyond this point is removing the setTxTimeoutMs() function altogether and just defining it as 0. I can almost guarantee that if you try the sketch that inspired the pull request example you wanted me to try, it'll work perfectly if the HWCDC.cpp file is replaced with the one attached.

HWCDC.zip

SuGlider commented 7 months ago

@specternecter @imwhocodes

The original issue is related to the JTAG/HW CDC Serial peripheral from ESP32-S3, ESP32-C3, ESP32-C6 and ESP32-H2.

One issue is that when USB cable is unplugged, flush() will block and write() will also block as soon as the HWCDC Ringbuffer gets full.

The second issue is that It won't work properly when the sketch waits for the CDC to be connected using a Serial Monitor, using a code like while(!Serial) delay(100);

These and some other issues were fixed by the PR #9275

When USB is unplugged, nothing will block any HW CDC writing or flushing. When the buffer gets full, its content will be discarted and it will show the latest written data. By this way, when the sketch connects to a serial monitor, it will display the latest data flow.

bool HWCDC will return true only when CDC is connected using some Serial Monitor Application. It is possible plug, unplug, open and close the terminal that it will work correctly. Closing the terminal while using Windows 10/11 will reset the board because for some reason Windows raises USB ACM RTS that makes the board consider that it shall reset. But it is possible to unplug the cable and the software will detect it and adjust its status to indicate that the CDC connection has been terminated. It is possbile to plug back the USB cable and reconnect automatically by opening the serial monitor again.

It has been fixed for arduino core 3.0.0-RC1 and it is available in the master branch.

Check the PR examples.