29
29
#include " hal/usb_serial_jtag_ll.h"
30
30
#pragma GCC diagnostic warning "-Wvolatile"
31
31
#include " rom/ets_sys.h"
32
- #include " driver/usb_serial_jtag.h"
33
32
34
33
ESP_EVENT_DEFINE_BASE (ARDUINO_HW_CDC_EVENTS);
35
34
@@ -40,8 +39,11 @@ static intr_handle_t intr_handle = NULL;
40
39
static SemaphoreHandle_t tx_lock = NULL ;
41
40
static volatile bool connected = false ;
42
41
42
+ static volatile unsigned long lastSOF_ms;
43
+ static volatile uint8_t SOF_TIMEOUT;
44
+
43
45
// 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 ;
45
47
46
48
static esp_event_loop_handle_t arduino_hw_cdc_event_loop_handle = NULL ;
47
49
@@ -77,7 +79,7 @@ static void hw_cdc_isr_handler(void *arg) {
77
79
78
80
if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY) {
79
81
// Interrupt tells us the host picked up the data we sent.
80
- if (!usb_serial_jtag_is_connected ()) {
82
+ if (!HWCDC::isPlugged ()) {
81
83
connected = false ;
82
84
usb_serial_jtag_ll_clr_intsts_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
83
85
// USB is unplugged, nothing to be done here
@@ -132,19 +134,32 @@ static void hw_cdc_isr_handler(void *arg) {
132
134
connected = false ;
133
135
}
134
136
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
+
135
142
if (xTaskWoken == pdTRUE) {
136
143
portYIELD_FROM_ISR ();
137
144
}
138
145
}
139
146
147
+ inline bool HWCDC::isPlugged (void )
148
+ {
149
+ return (lastSOF_ms + SOF_TIMEOUT) >= millis ();
150
+ }
151
+
140
152
bool HWCDC::isCDC_Connected () {
141
153
static bool running = false ;
142
154
143
155
// USB may be unplugged
144
- if (usb_serial_jtag_is_connected () == false ) {
156
+ if (! isPlugged () ) {
145
157
connected = false ;
146
158
running = false ;
159
+ SOF_TIMEOUT = 5 ; // SOF timeout when unplugged
147
160
return false ;
161
+ } else {
162
+ SOF_TIMEOUT = 50 ; // SOF timeout when plugged
148
163
}
149
164
150
165
if (connected) {
@@ -155,21 +170,71 @@ bool HWCDC::isCDC_Connected() {
155
170
if (running == false && !connected) { // enables it only once!
156
171
usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
157
172
}
173
+
158
174
// 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));
161
175
usb_serial_jtag_ll_txfifo_flush ();
162
176
running = true ;
163
177
return false ;
164
178
}
165
179
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
+
166
230
static void ARDUINO_ISR_ATTR cdc0_write_char (char c) {
167
231
if (tx_ring_buf == NULL ) {
168
232
return ;
169
233
}
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 ;
173
238
}
174
239
if (xPortInIsrContext ()) {
175
240
xRingbufferSendFromISR (tx_ring_buf, (void *)(&c), 1 , NULL );
@@ -182,6 +247,8 @@ static void ARDUINO_ISR_ATTR cdc0_write_char(char c) {
182
247
HWCDC::HWCDC () {
183
248
perimanSetBusDeinit (ESP32_BUS_TYPE_USB_DM, HWCDC::deinit);
184
249
perimanSetBusDeinit (ESP32_BUS_TYPE_USB_DP, HWCDC::deinit);
250
+ lastSOF_ms = 0 ;
251
+ SOF_TIMEOUT = 5 ;
185
252
}
186
253
187
254
HWCDC::~HWCDC () {
@@ -234,9 +301,9 @@ void HWCDC::begin(unsigned long baud) {
234
301
log_e (" HW CDC RX Buffer error" );
235
302
}
236
303
}
237
- // TX Buffer default has 16 bytes if not preset
304
+ // TX Buffer default has 256 bytes if not preset
238
305
if (tx_ring_buf == NULL ) {
239
- if (!setTxBufferSize (16 )) {
306
+ if (!setTxBufferSize (256 )) {
240
307
log_e (" HW CDC TX Buffer error" );
241
308
}
242
309
}
@@ -265,7 +332,7 @@ void HWCDC::begin(unsigned long baud) {
265
332
// Enable USB pad function
266
333
USB_SERIAL_JTAG.conf0 .usb_pad_enable = 1 ;
267
334
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 );
269
336
if (!intr_handle && esp_intr_alloc (ETS_USB_SERIAL_JTAG_INTR_SOURCE, 0 , hw_cdc_isr_handler, NULL , &intr_handle) != ESP_OK) {
270
337
isr_log_e (" HW USB CDC failed to init interrupts" );
271
338
end ();
@@ -300,7 +367,7 @@ void HWCDC::end() {
300
367
}
301
368
302
369
void HWCDC::setTxTimeoutMs (uint32_t timeout) {
303
- requested_tx_timeout_ms = timeout;
370
+ tx_timeout_ms = timeout;
304
371
}
305
372
306
373
/*
@@ -323,13 +390,9 @@ size_t HWCDC::setTxBufferSize(size_t tx_queue_len) {
323
390
}
324
391
325
392
int HWCDC::availableForWrite (void ) {
326
- uint32_t tx_timeout_ms = 0 ;
327
393
if (tx_ring_buf == NULL || tx_lock == NULL ) {
328
394
return 0 ;
329
395
}
330
- if (HWCDC::isCDC_Connected ()) {
331
- tx_timeout_ms = requested_tx_timeout_ms;
332
- }
333
396
if (xSemaphoreTake (tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS) {
334
397
return 0 ;
335
398
}
@@ -355,59 +418,74 @@ static void flushTXBuffer() {
355
418
}
356
419
357
420
size_t HWCDC::write (const uint8_t *buffer, size_t size) {
358
- uint32_t tx_timeout_ms = 0 ;
359
421
if (buffer == NULL || size == 0 || tx_ring_buf == NULL || tx_lock == NULL ) {
360
422
return 0 ;
361
423
}
362
- if (HWCDC::isCDC_Connected ()) {
363
- tx_timeout_ms = requested_tx_timeout_ms;
364
- } else {
365
- connected = false ;
366
- }
367
424
if (xSemaphoreTake (tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS) {
368
425
return 0 ;
369
426
}
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);
379
430
} 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 ;
387
433
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 {
399
441
to_send -= space;
442
+ so_far += space;
400
443
// Now trigger the ISR to read data from the ring buffer.
401
444
usb_serial_jtag_ll_txfifo_flush ();
402
445
if (connected) {
403
446
usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
404
447
}
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);
405
488
}
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 ();
411
489
}
412
490
xSemaphoreGive (tx_lock);
413
491
return size;
@@ -418,39 +496,40 @@ size_t HWCDC::write(uint8_t c) {
418
496
}
419
497
420
498
void HWCDC::flush (void ) {
421
- uint32_t tx_timeout_ms = 0 ;
422
499
if (tx_ring_buf == NULL || tx_lock == NULL ) {
423
500
return ;
424
501
}
425
- if (HWCDC::isCDC_Connected ()) {
426
- tx_timeout_ms = requested_tx_timeout_ms;
427
- } else {
428
- connected = false ;
429
- }
430
502
if (xSemaphoreTake (tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS) {
431
503
return ;
432
504
}
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 ;
446
509
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
449
532
}
450
- }
451
- if (tries == 0 ) { // CDC isn't connected anymore...
452
- connected = false ;
453
- flushTXBuffer ();
454
533
}
455
534
xSemaphoreGive (tx_lock);
456
535
}
0 commit comments