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
3433ESP_EVENT_DEFINE_BASE (ARDUINO_HW_CDC_EVENTS);
3534
@@ -40,8 +39,11 @@ static intr_handle_t intr_handle = NULL;
4039static SemaphoreHandle_t tx_lock = NULL ;
4140static 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
4648static 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,31 @@ 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+ return (lastSOF_ms + SOF_TIMEOUT) >= millis ();
149+ }
150+
140151bool HWCDC::isCDC_Connected () {
141152 static bool running = false ;
142153
143154 // USB may be unplugged
144- if (usb_serial_jtag_is_connected () == false ) {
155+ if (! isPlugged () ) {
145156 connected = false ;
146157 running = false ;
158+ SOF_TIMEOUT = 5 ; // SOF timeout when unplugged
147159 return false ;
160+ } else {
161+ SOF_TIMEOUT = 50 ; // SOF timeout when plugged
148162 }
149163
150164 if (connected) {
@@ -155,21 +169,72 @@ bool HWCDC::isCDC_Connected() {
155169 if (running == false && !connected) { // enables it only once!
156170 usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
157171 }
172+
158173 // 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));
161174 usb_serial_jtag_ll_txfifo_flush ();
162175 running = true ;
163176 return false ;
164177}
165178
179+ static void flushTXBuffer (const uint8_t *buffer, size_t size) {
180+ if (!tx_ring_buf) {
181+ return ;
182+ }
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 into 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+
166230static 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) {
182247HWCDC::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
187254HWCDC::~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,9 @@ 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 (
336+ 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
337+ );
269338 if (!intr_handle && esp_intr_alloc (ETS_USB_SERIAL_JTAG_INTR_SOURCE, 0 , hw_cdc_isr_handler, NULL , &intr_handle) != ESP_OK) {
270339 isr_log_e (" HW USB CDC failed to init interrupts" );
271340 end ();
@@ -300,7 +369,7 @@ void HWCDC::end() {
300369}
301370
302371void HWCDC::setTxTimeoutMs (uint32_t timeout) {
303- requested_tx_timeout_ms = timeout;
372+ tx_timeout_ms = timeout;
304373}
305374
306375/*
@@ -323,13 +392,9 @@ size_t HWCDC::setTxBufferSize(size_t tx_queue_len) {
323392}
324393
325394int HWCDC::availableForWrite (void ) {
326- uint32_t tx_timeout_ms = 0 ;
327395 if (tx_ring_buf == NULL || tx_lock == NULL ) {
328396 return 0 ;
329397 }
330- if (HWCDC::isCDC_Connected ()) {
331- tx_timeout_ms = requested_tx_timeout_ms;
332- }
333398 if (xSemaphoreTake (tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS) {
334399 return 0 ;
335400 }
@@ -338,76 +403,75 @@ int HWCDC::availableForWrite(void) {
338403 return a;
339404}
340405
341- static void flushTXBuffer () {
342- if (!tx_ring_buf) {
343- return ;
344- }
345- UBaseType_t uxItemsWaiting = 0 ;
346- vRingbufferGetInfo (tx_ring_buf, NULL , NULL , NULL , NULL , &uxItemsWaiting);
347-
348- size_t queued_size = 0 ;
349- uint8_t *queued_buff = (uint8_t *)xRingbufferReceiveUpTo (tx_ring_buf, &queued_size, 0 , uxItemsWaiting);
350- if (queued_size && queued_buff != NULL ) {
351- vRingbufferReturnItem (tx_ring_buf, (void *)queued_buff);
352- }
353- // flushes CDC FIFO
354- usb_serial_jtag_ll_txfifo_flush ();
355- }
356-
357406size_t HWCDC::write (const uint8_t *buffer, size_t size) {
358- uint32_t tx_timeout_ms = 0 ;
359407 if (buffer == NULL || size == 0 || tx_ring_buf == NULL || tx_lock == NULL ) {
360408 return 0 ;
361409 }
362- if (HWCDC::isCDC_Connected ()) {
363- tx_timeout_ms = requested_tx_timeout_ms;
364- } else {
365- connected = false ;
366- }
367410 if (xSemaphoreTake (tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS) {
368411 return 0 ;
369412 }
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 ;
413+ if (!isCDC_Connected ()) {
414+ // just pop/push RingBuffer and apply FIFO policy
415+ flushTXBuffer (buffer, size);
379416 } 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- }
417+ size_t space = xRingbufferGetCurFreeSize (tx_ring_buf);
418+ size_t to_send = size, so_far = 0 ;
387419
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;
420+ if (space > size) {
421+ space = size;
422+ }
423+ // Non-Blocking method, Sending data to ringbuffer, and handle the data in ISR.
424+ if (space > 0 && xRingbufferSend (tx_ring_buf, (void *)(buffer), space, 0 ) != pdTRUE) {
425+ size = 0 ;
426+ } else {
399427 to_send -= space;
428+ so_far += space;
400429 // Now trigger the ISR to read data from the ring buffer.
401430 usb_serial_jtag_ll_txfifo_flush ();
402431 if (connected) {
403432 usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
404433 }
434+ // tracks CDC trasmission progress to avoid hanging if CDC is unplugged while still sending data
435+ size_t last_toSend = to_send;
436+ uint32_t tries = tx_timeout_ms; // waits 1ms per sending data attempt, in case CDC is unplugged
437+ while (connected && to_send) {
438+ space = xRingbufferGetCurFreeSize (tx_ring_buf);
439+ if (space > to_send) {
440+ space = to_send;
441+ }
442+ // Blocking method, Sending data to ringbuffer, and handle the data in ISR.
443+ if (xRingbufferSend (tx_ring_buf, (void *)(buffer + so_far), space, tx_timeout_ms / portTICK_PERIOD_MS) != pdTRUE) {
444+ size = so_far;
445+ log_w (" write failed due to ring buffer full - timeout" );
446+ break ;
447+ }
448+ so_far += space;
449+ to_send -= space;
450+ // Now trigger the ISR to read data from the ring buffer.
451+ usb_serial_jtag_ll_txfifo_flush ();
452+ if (connected) {
453+ usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
454+ }
455+ if (last_toSend == to_send) {
456+ // no progress in sending data... USB CDC is probably unplugged
457+ tries--;
458+ delay (1 );
459+ } else {
460+ last_toSend = to_send;
461+ tries = tx_timeout_ms; // reset the timeout
462+ }
463+ if (tries == 0 ) { // CDC isn't connected anymore...
464+ size = so_far;
465+ log_w (" write failed due to waiting USB Host - timeout" );
466+ connected = false ;
467+ }
468+ }
469+ }
470+ // CDC was diconnected while sending data ==> flush the TX buffer keeping the last data
471+ if (to_send && !usb_serial_jtag_ll_txfifo_writable ()) {
472+ connected = false ;
473+ flushTXBuffer (buffer + so_far, to_send);
405474 }
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 ();
411475 }
412476 xSemaphoreGive (tx_lock);
413477 return size;
@@ -418,39 +482,40 @@ size_t HWCDC::write(uint8_t c) {
418482}
419483
420484void HWCDC::flush (void ) {
421- uint32_t tx_timeout_ms = 0 ;
422485 if (tx_ring_buf == NULL || tx_lock == NULL ) {
423486 return ;
424487 }
425- if (HWCDC::isCDC_Connected ()) {
426- tx_timeout_ms = requested_tx_timeout_ms;
427- } else {
428- connected = false ;
429- }
430488 if (xSemaphoreTake (tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS) {
431489 return ;
432490 }
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;
491+ if (!isCDC_Connected ()) {
492+ flushTXBuffer (NULL , 0 );
493+ } else {
494+ UBaseType_t uxItemsWaiting = 0 ;
446495 vRingbufferGetInfo (tx_ring_buf, NULL , NULL , NULL , NULL , &uxItemsWaiting);
447- if (lastUxItemsWaiting == uxItemsWaiting) {
448- tries--;
496+ if (uxItemsWaiting) {
497+ // Now trigger the ISR to read data from the ring buffer.
498+ usb_serial_jtag_ll_txfifo_flush ();
499+ if (connected) {
500+ usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
501+ }
502+ }
503+ uint32_t tries = tx_timeout_ms; // waits 1ms per ISR sending data attempt, in case CDC is unplugged
504+ while (connected && tries && uxItemsWaiting) {
505+ delay (1 );
506+ UBaseType_t lastUxItemsWaiting = uxItemsWaiting;
507+ vRingbufferGetInfo (tx_ring_buf, NULL , NULL , NULL , NULL , &uxItemsWaiting);
508+ if (lastUxItemsWaiting == uxItemsWaiting) {
509+ tries--;
510+ }
511+ if (connected) {
512+ usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
513+ }
514+ }
515+ if (tries == 0 ) { // CDC isn't connected anymore...
516+ connected = false ;
517+ flushTXBuffer (NULL , 0 ); // flushes all TX Buffer
449518 }
450- }
451- if (tries == 0 ) { // CDC isn't connected anymore...
452- connected = false ;
453- flushTXBuffer ();
454519 }
455520 xSemaphoreGive (tx_lock);
456521}
0 commit comments