Skip to content

Commit a565f39

Browse files
authored
feat (hwcdc): ports changes made in 2.0.15
Ports many changes, fixes and improvements made in 2.0.15: - correct use of timeout - avoids problems with CDC ISR not reading data - fixes problems with transmitting many bytes to USB Host - changes how USB SOF and CDC connection is detected
1 parent cf44890 commit a565f39

File tree

1 file changed

+159
-80
lines changed

1 file changed

+159
-80
lines changed

cores/esp32/HWCDC.cpp

+159-80
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
#include "hal/usb_serial_jtag_ll.h"
3030
#pragma GCC diagnostic warning "-Wvolatile"
3131
#include "rom/ets_sys.h"
32-
#include "driver/usb_serial_jtag.h"
3332

3433
ESP_EVENT_DEFINE_BASE(ARDUINO_HW_CDC_EVENTS);
3534

@@ -40,8 +39,11 @@ static intr_handle_t intr_handle = NULL;
4039
static SemaphoreHandle_t tx_lock = NULL;
4140
static volatile bool connected = false;
4241

42+
static volatile unsigned long lastSOF_ms;
43+
static volatile uint8_t SOF_TIMEOUT;
44+
4345
// timeout has no effect when USB CDC is unplugged
44-
static uint32_t requested_tx_timeout_ms = 100;
46+
static uint32_t tx_timeout_ms = 100;
4547

4648
static esp_event_loop_handle_t arduino_hw_cdc_event_loop_handle = NULL;
4749

@@ -77,7 +79,7 @@ static void hw_cdc_isr_handler(void *arg) {
7779

7880
if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY) {
7981
// Interrupt tells us the host picked up the data we sent.
80-
if (!usb_serial_jtag_is_connected()) {
82+
if (!HWCDC::isPlugged()) {
8183
connected = false;
8284
usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
8385
// USB is unplugged, nothing to be done here
@@ -132,19 +134,32 @@ static void hw_cdc_isr_handler(void *arg) {
132134
connected = false;
133135
}
134136

137+
if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_SOF) {
138+
usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SOF);
139+
lastSOF_ms = millis();
140+
}
141+
135142
if (xTaskWoken == pdTRUE) {
136143
portYIELD_FROM_ISR();
137144
}
138145
}
139146

147+
inline bool HWCDC::isPlugged(void)
148+
{
149+
return (lastSOF_ms + SOF_TIMEOUT) >= millis();
150+
}
151+
140152
bool HWCDC::isCDC_Connected() {
141153
static bool running = false;
142154

143155
// USB may be unplugged
144-
if (usb_serial_jtag_is_connected() == false) {
156+
if (!isPlugged()) {
145157
connected = false;
146158
running = false;
159+
SOF_TIMEOUT = 5; // SOF timeout when unplugged
147160
return false;
161+
} else {
162+
SOF_TIMEOUT = 50; // SOF timeout when plugged
148163
}
149164

150165
if (connected) {
@@ -155,21 +170,71 @@ bool HWCDC::isCDC_Connected() {
155170
if (running == false && !connected) { // enables it only once!
156171
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
157172
}
173+
158174
// this will feed CDC TX FIFO to trigger IN_EMPTY
159-
uint8_t c = '\0';
160-
usb_serial_jtag_ll_write_txfifo(&c, sizeof(c));
161175
usb_serial_jtag_ll_txfifo_flush();
162176
running = true;
163177
return false;
164178
}
165179

180+
static void flushTXBuffer(const uint8_t *buffer, size_t size)
181+
{
182+
if (!tx_ring_buf) return;
183+
UBaseType_t uxItemsWaiting= 0;
184+
vRingbufferGetInfo(tx_ring_buf, NULL, NULL, NULL, NULL, &uxItemsWaiting);
185+
size_t freeSpace = xRingbufferGetCurFreeSize(tx_ring_buf);
186+
size_t ringbufferLength = freeSpace + uxItemsWaiting;
187+
188+
if(buffer == NULL) {
189+
// just flush the whole ring buffer and exit - used by HWCDC::flush()
190+
size_t queued_size = 0;
191+
uint8_t *queued_buff = (uint8_t *)xRingbufferReceiveUpTo(tx_ring_buf, &queued_size, 0, ringbufferLength);
192+
if (queued_size && queued_buff != NULL) {
193+
vRingbufferReturnItem(tx_ring_buf, (void *)queued_buff);
194+
}
195+
return;
196+
}
197+
if(size == 0) {
198+
return; // nothing to do
199+
}
200+
if(freeSpace >= size){
201+
// there is enough space, just add the data to the ring buffer
202+
if(xRingbufferSend(tx_ring_buf, (void*)buffer, size, 0) != pdTRUE){
203+
return;
204+
}
205+
} else {
206+
// how many byte should be flushed to make space for the new data
207+
size_t to_flush = size - freeSpace;
208+
if(to_flush > ringbufferLength) {
209+
to_flush = ringbufferLength;
210+
}
211+
size_t queued_size = 0;
212+
uint8_t *queued_buff = (uint8_t *)xRingbufferReceiveUpTo(tx_ring_buf, &queued_size, 0, to_flush);
213+
if (queued_size && queued_buff != NULL) {
214+
vRingbufferReturnItem(tx_ring_buf, (void *)queued_buff);
215+
}
216+
// now add the new data that fits to the ring buffer
217+
uint8_t *bptr = (uint8_t *)buffer;
218+
if (size >= ringbufferLength) {
219+
size = ringbufferLength;
220+
bptr = (uint8_t *)buffer + (size - ringbufferLength);
221+
}
222+
if(xRingbufferSend(tx_ring_buf, (void *)bptr, size, 0) != pdTRUE){
223+
return;
224+
}
225+
}
226+
// flushes CDC FIFO
227+
usb_serial_jtag_ll_txfifo_flush();
228+
}
229+
166230
static void ARDUINO_ISR_ATTR cdc0_write_char(char c) {
167231
if (tx_ring_buf == NULL) {
168232
return;
169233
}
170-
uint32_t tx_timeout_ms = 0;
171-
if (HWCDC::isConnected()) {
172-
tx_timeout_ms = requested_tx_timeout_ms;
234+
if (!HWCDC::isConnected()) {
235+
// just pop/push RingBuffer and apply FIFO policy
236+
flushTXBuffer((const uint8_t*)&c, 1);
237+
return;
173238
}
174239
if (xPortInIsrContext()) {
175240
xRingbufferSendFromISR(tx_ring_buf, (void *)(&c), 1, NULL);
@@ -182,6 +247,8 @@ static void ARDUINO_ISR_ATTR cdc0_write_char(char c) {
182247
HWCDC::HWCDC() {
183248
perimanSetBusDeinit(ESP32_BUS_TYPE_USB_DM, HWCDC::deinit);
184249
perimanSetBusDeinit(ESP32_BUS_TYPE_USB_DP, HWCDC::deinit);
250+
lastSOF_ms = 0;
251+
SOF_TIMEOUT = 5;
185252
}
186253

187254
HWCDC::~HWCDC() {
@@ -234,9 +301,9 @@ void HWCDC::begin(unsigned long baud) {
234301
log_e("HW CDC RX Buffer error");
235302
}
236303
}
237-
//TX Buffer default has 16 bytes if not preset
304+
//TX Buffer default has 256 bytes if not preset
238305
if (tx_ring_buf == NULL) {
239-
if (!setTxBufferSize(16)) {
306+
if (!setTxBufferSize(256)) {
240307
log_e("HW CDC TX Buffer error");
241308
}
242309
}
@@ -265,7 +332,7 @@ void HWCDC::begin(unsigned long baud) {
265332
// Enable USB pad function
266333
USB_SERIAL_JTAG.conf0.usb_pad_enable = 1;
267334
usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_LL_INTR_MASK);
268-
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);
335+
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 | USB_SERIAL_JTAG_INTR_SOF);
269336
if (!intr_handle && esp_intr_alloc(ETS_USB_SERIAL_JTAG_INTR_SOURCE, 0, hw_cdc_isr_handler, NULL, &intr_handle) != ESP_OK) {
270337
isr_log_e("HW USB CDC failed to init interrupts");
271338
end();
@@ -300,7 +367,7 @@ void HWCDC::end() {
300367
}
301368

302369
void HWCDC::setTxTimeoutMs(uint32_t timeout) {
303-
requested_tx_timeout_ms = timeout;
370+
tx_timeout_ms = timeout;
304371
}
305372

306373
/*
@@ -323,13 +390,9 @@ size_t HWCDC::setTxBufferSize(size_t tx_queue_len) {
323390
}
324391

325392
int HWCDC::availableForWrite(void) {
326-
uint32_t tx_timeout_ms = 0;
327393
if (tx_ring_buf == NULL || tx_lock == NULL) {
328394
return 0;
329395
}
330-
if (HWCDC::isCDC_Connected()) {
331-
tx_timeout_ms = requested_tx_timeout_ms;
332-
}
333396
if (xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS) {
334397
return 0;
335398
}
@@ -355,59 +418,74 @@ static void flushTXBuffer() {
355418
}
356419

357420
size_t HWCDC::write(const uint8_t *buffer, size_t size) {
358-
uint32_t tx_timeout_ms = 0;
359421
if (buffer == NULL || size == 0 || tx_ring_buf == NULL || tx_lock == NULL) {
360422
return 0;
361423
}
362-
if (HWCDC::isCDC_Connected()) {
363-
tx_timeout_ms = requested_tx_timeout_ms;
364-
} else {
365-
connected = false;
366-
}
367424
if (xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS) {
368425
return 0;
369426
}
370-
size_t space = xRingbufferGetCurFreeSize(tx_ring_buf);
371-
size_t to_send = size, so_far = 0;
372-
373-
if (space > size) {
374-
space = size;
375-
}
376-
// Non-Blocking method, Sending data to ringbuffer, and handle the data in ISR.
377-
if (space > 0 && xRingbufferSend(tx_ring_buf, (void *)(buffer), space, 0) != pdTRUE) {
378-
size = 0;
427+
if (!isCDC_Connected()) {
428+
// just pop/push RingBuffer and apply FIFO policy
429+
flushTXBuffer(buffer, size);
379430
} else {
380-
to_send -= space;
381-
so_far += space;
382-
// Now trigger the ISR to read data from the ring buffer.
383-
usb_serial_jtag_ll_txfifo_flush();
384-
if (connected) {
385-
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
386-
}
431+
size_t space = xRingbufferGetCurFreeSize(tx_ring_buf);
432+
size_t to_send = size, so_far = 0;
387433

388-
while (to_send) {
389-
space = xRingbufferGetCurFreeSize(tx_ring_buf);
390-
if (space > to_send) {
391-
space = to_send;
392-
}
393-
// Blocking method, Sending data to ringbuffer, and handle the data in ISR.
394-
if (xRingbufferSend(tx_ring_buf, (void *)(buffer + so_far), space, tx_timeout_ms / portTICK_PERIOD_MS) != pdTRUE) {
395-
size = so_far;
396-
break;
397-
}
398-
so_far += space;
434+
if (space > size){
435+
space = size;
436+
}
437+
// Non-Blocking method, Sending data to ringbuffer, and handle the data in ISR.
438+
if (space > 0 && xRingbufferSend(tx_ring_buf, (void*) (buffer), space, 0) != pdTRUE){
439+
size = 0;
440+
} else {
399441
to_send -= space;
442+
so_far += space;
400443
// Now trigger the ISR to read data from the ring buffer.
401444
usb_serial_jtag_ll_txfifo_flush();
402445
if (connected) {
403446
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
404447
}
448+
// tracks CDC trasmission progress to avoid hanging if CDC is unplugged while still sending data
449+
size_t last_toSend = to_send;
450+
uint32_t tries = tx_timeout_ms; // waits 1ms per sending data attempt, in case CDC is unplugged
451+
while (connected && to_send) {
452+
space = xRingbufferGetCurFreeSize(tx_ring_buf);
453+
if (space > to_send){
454+
space = to_send;
455+
}
456+
// Blocking method, Sending data to ringbuffer, and handle the data in ISR.
457+
if (xRingbufferSend(tx_ring_buf, (void*) (buffer+so_far), space, tx_timeout_ms / portTICK_PERIOD_MS) != pdTRUE) {
458+
size = so_far;
459+
log_w("write failed due to ring buffer full - timeout");
460+
break;
461+
}
462+
so_far += space;
463+
to_send -= space;
464+
// Now trigger the ISR to read data from the ring buffer.
465+
usb_serial_jtag_ll_txfifo_flush();
466+
if (connected) {
467+
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
468+
}
469+
if (last_toSend == to_send) {
470+
// no progress in sending data... USB CDC is probably unplugged
471+
tries--;
472+
delay(1);
473+
} else {
474+
last_toSend = to_send;
475+
tries = tx_timeout_ms; // reset the timeout
476+
}
477+
if (tries == 0) { // CDC isn't connected anymore...
478+
size = so_far;
479+
log_w("write failed due to waiting USB Host - timeout");
480+
connected = false;
481+
}
482+
}
483+
}
484+
// CDC was diconnected while sending data ==> flush the TX buffer keeping the last data
485+
if(to_send && !usb_serial_jtag_ll_txfifo_writable()) {
486+
connected = false;
487+
flushTXBuffer(buffer + so_far, to_send);
405488
}
406-
}
407-
// CDC is disconnected ==> flush all data from TX buffer
408-
if (to_send && !usb_serial_jtag_ll_txfifo_writable()) {
409-
connected = false;
410-
flushTXBuffer();
411489
}
412490
xSemaphoreGive(tx_lock);
413491
return size;
@@ -418,39 +496,40 @@ size_t HWCDC::write(uint8_t c) {
418496
}
419497

420498
void HWCDC::flush(void) {
421-
uint32_t tx_timeout_ms = 0;
422499
if (tx_ring_buf == NULL || tx_lock == NULL) {
423500
return;
424501
}
425-
if (HWCDC::isCDC_Connected()) {
426-
tx_timeout_ms = requested_tx_timeout_ms;
427-
} else {
428-
connected = false;
429-
}
430502
if (xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS) {
431503
return;
432504
}
433-
UBaseType_t uxItemsWaiting = 0;
434-
vRingbufferGetInfo(tx_ring_buf, NULL, NULL, NULL, NULL, &uxItemsWaiting);
435-
if (uxItemsWaiting) {
436-
// Now trigger the ISR to read data from the ring buffer.
437-
usb_serial_jtag_ll_txfifo_flush();
438-
if (connected) {
439-
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
440-
}
441-
}
442-
uint8_t tries = 3;
443-
while (tries && uxItemsWaiting) {
444-
delay(5);
445-
UBaseType_t lastUxItemsWaiting = uxItemsWaiting;
505+
if(!isCDC_Connected()) {
506+
flushTXBuffer(NULL, 0);
507+
} else {
508+
UBaseType_t uxItemsWaiting = 0;
446509
vRingbufferGetInfo(tx_ring_buf, NULL, NULL, NULL, NULL, &uxItemsWaiting);
447-
if (lastUxItemsWaiting == uxItemsWaiting) {
448-
tries--;
510+
if(uxItemsWaiting){
511+
// Now trigger the ISR to read data from the ring buffer.
512+
usb_serial_jtag_ll_txfifo_flush();
513+
if(connected) {
514+
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
515+
}
516+
}
517+
uint32_t tries = tx_timeout_ms; // waits 1ms per ISR sending data attempt, in case CDC is unplugged
518+
while(connected && tries && uxItemsWaiting){
519+
delay(1);
520+
UBaseType_t lastUxItemsWaiting = uxItemsWaiting;
521+
vRingbufferGetInfo(tx_ring_buf, NULL, NULL, NULL, NULL, &uxItemsWaiting);
522+
if (lastUxItemsWaiting == uxItemsWaiting) {
523+
tries--;
524+
}
525+
if(connected) {
526+
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
527+
}
528+
}
529+
if (tries == 0) { // CDC isn't connected anymore...
530+
connected = false;
531+
flushTXBuffer(NULL, 0); // flushes all TX Buffer
449532
}
450-
}
451-
if (tries == 0) { // CDC isn't connected anymore...
452-
connected = false;
453-
flushTXBuffer();
454533
}
455534
xSemaphoreGive(tx_lock);
456535
}

0 commit comments

Comments
 (0)