1
- // Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
1
+ // Copyright 2015-2024 Espressif Systems (Shanghai) PTE LTD
2
2
//
3
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
4
// you may not use this file except in compliance with the License.
@@ -30,12 +30,14 @@ static RingbufHandle_t tx_ring_buf = NULL;
30
30
static xQueueHandle rx_queue = NULL ;
31
31
static uint8_t rx_data_buf[64 ] = {0 };
32
32
static intr_handle_t intr_handle = NULL ;
33
- static volatile bool initial_empty = false ;
33
+ static volatile bool connected = false ;
34
34
static xSemaphoreHandle tx_lock = NULL ;
35
35
36
- // workaround for when USB CDC is not connected
37
- static uint32_t tx_timeout_ms = 0 ;
38
- static bool tx_timeout_change_request = false ;
36
+ static volatile unsigned long lastSOF_ms;
37
+ static volatile uint8_t SOF_TIMEOUT;
38
+
39
+ // timeout has no effect when USB CDC is unplugged
40
+ static uint32_t tx_timeout_ms = 100 ;
39
41
40
42
static esp_event_loop_handle_t arduino_hw_cdc_event_loop_handle = NULL ;
41
43
@@ -73,21 +75,17 @@ static void hw_cdc_isr_handler(void *arg) {
73
75
74
76
if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY) {
75
77
// Interrupt tells us the host picked up the data we sent.
76
- if (usb_serial_jtag_ll_txfifo_writable () == 1 ) {
78
+ if (!HWCDC::isPlugged ()) {
79
+ connected = false ;
80
+ usb_serial_jtag_ll_clr_intsts_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
81
+ // USB is unplugged, nothing to be done here
82
+ return ;
83
+ } else {
84
+ connected = true ;
85
+ }
86
+ if (tx_ring_buf != NULL && usb_serial_jtag_ll_txfifo_writable () == 1 ) {
77
87
// We disable the interrupt here so that the interrupt won't be triggered if there is no data to send.
78
88
usb_serial_jtag_ll_disable_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
79
- if (!initial_empty){
80
- initial_empty = true ;
81
- // First time USB is plugged and the application has not explicitly set TX Timeout, set it to default 100ms.
82
- // Otherwise, USB is still unplugged and the timeout will be kept as Zero in order to avoid any delay in the
83
- // application whenever it uses write() and the TX Queue gets full.
84
- if (!tx_timeout_change_request) {
85
- tx_timeout_ms = 100 ;
86
- }
87
- // send event?
88
- // ets_printf("CONNECTED\n");
89
- arduino_hw_cdc_event_post (ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_CONNECTED_EVENT, &event, sizeof (arduino_hw_cdc_event_data_t ), &xTaskWoken);
90
- }
91
89
size_t queued_size;
92
90
uint8_t *queued_buff = (uint8_t *)xRingbufferReceiveUpToFromISR (tx_ring_buf, &queued_size, 64 );
93
91
// If the hardware fifo is avaliable, write in it. Otherwise, do nothing.
@@ -97,7 +95,7 @@ static void hw_cdc_isr_handler(void *arg) {
97
95
usb_serial_jtag_ll_write_txfifo (queued_buff, queued_size);
98
96
usb_serial_jtag_ll_txfifo_flush ();
99
97
vRingbufferReturnItemFromISR (tx_ring_buf, queued_buff, &xTaskWoken);
100
- usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
98
+ if (connected) usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
101
99
// send event?
102
100
// ets_printf("TX:%u\n", queued_size);
103
101
event.tx .len = queued_size;
@@ -119,45 +117,138 @@ static void hw_cdc_isr_handler(void *arg) {
119
117
break ;
120
118
}
121
119
}
122
- // send event?
123
- // ets_printf("RX:%u/%u\n", i, rx_fifo_len);
124
120
event.rx .len = i;
125
121
arduino_hw_cdc_event_post (ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_RX_EVENT, &event, sizeof (arduino_hw_cdc_event_data_t ), &xTaskWoken);
122
+ connected = true ;
126
123
}
127
124
128
125
if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_BUS_RESET) {
129
126
usb_serial_jtag_ll_clr_intsts_mask (USB_SERIAL_JTAG_INTR_BUS_RESET);
130
- initial_empty = false ;
131
- usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
132
- // ets_printf("BUS_RESET\n");
133
127
arduino_hw_cdc_event_post (ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_BUS_RESET_EVENT, &event, sizeof (arduino_hw_cdc_event_data_t ), &xTaskWoken);
128
+ connected = false ;
129
+ }
130
+
131
+ if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_SOF) {
132
+ usb_serial_jtag_ll_clr_intsts_mask (USB_SERIAL_JTAG_INTR_SOF);
133
+ lastSOF_ms = millis ();
134
134
}
135
135
136
136
if (xTaskWoken == pdTRUE) {
137
137
portYIELD_FROM_ISR ();
138
138
}
139
139
}
140
140
141
+ inline bool HWCDC::isPlugged (void )
142
+ {
143
+ return (lastSOF_ms + SOF_TIMEOUT) >= millis ();
144
+ }
145
+
146
+ bool HWCDC::isCDC_Connected ()
147
+ {
148
+ static bool running = false ;
149
+
150
+ // USB may be unplugged
151
+ if (!isPlugged ()) {
152
+ connected = false ;
153
+ running = false ;
154
+ SOF_TIMEOUT = 5 ; // SOF timeout when unplugged
155
+ return false ;
156
+ } else {
157
+ SOF_TIMEOUT = 50 ; // SOF timeout when plugged
158
+ }
159
+
160
+ if (connected) {
161
+ running = false ;
162
+ return true ;
163
+ }
164
+
165
+ if (running == false && !connected) { // enables it only once!
166
+ usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
167
+ }
168
+ // this will feed CDC TX FIFO to trigger IN_EMPTY
169
+ // uint8_t c = '\0';
170
+ // usb_serial_jtag_ll_write_txfifo(&c, sizeof(c));
171
+ usb_serial_jtag_ll_txfifo_flush ();
172
+ running = true ;
173
+ return false ;
174
+ }
175
+
176
+ static void flushTXBuffer (const uint8_t *buffer, size_t size)
177
+ {
178
+ if (!tx_ring_buf) return ;
179
+ UBaseType_t uxItemsWaiting= 0 ;
180
+ vRingbufferGetInfo (tx_ring_buf, NULL , NULL , NULL , NULL , &uxItemsWaiting);
181
+ size_t freeSpace = xRingbufferGetCurFreeSize (tx_ring_buf);
182
+ size_t ringbufferLength = freeSpace + uxItemsWaiting;
183
+
184
+ if (buffer == NULL ) {
185
+ // just flush the whole ring buffer and exit - used by HWCDC::flush()
186
+ size_t queued_size = 0 ;
187
+ uint8_t *queued_buff = (uint8_t *)xRingbufferReceiveUpTo (tx_ring_buf, &queued_size, 0 , ringbufferLength);
188
+ if (queued_size && queued_buff != NULL ) {
189
+ vRingbufferReturnItem (tx_ring_buf, (void *)queued_buff);
190
+ }
191
+ return ;
192
+ }
193
+ if (size == 0 ) return ; // nothing to do
194
+ if (freeSpace >= size){
195
+ // just add the data to the ring buffer and exit
196
+ if (xRingbufferSend (tx_ring_buf, (void *)buffer, size, 0 ) != pdTRUE){
197
+ return ;
198
+ }
199
+ } else {
200
+ // how many byte should be flushed to make space for the new data
201
+ size_t to_flush = size - freeSpace;
202
+ if (to_flush > ringbufferLength) to_flush = ringbufferLength;
203
+ size_t queued_size = 0 ;
204
+ uint8_t *queued_buff = (uint8_t *)xRingbufferReceiveUpTo (tx_ring_buf, &queued_size, 0 , to_flush);
205
+ if (queued_size && queued_buff != NULL ) {
206
+ vRingbufferReturnItem (tx_ring_buf, (void *)queued_buff);
207
+ }
208
+ // now add the new data that fits to the ring buffer
209
+ uint8_t *bptr = (uint8_t *)buffer;
210
+ if (size >= ringbufferLength) {
211
+ size = ringbufferLength;
212
+ bptr = (uint8_t *)buffer + (size - ringbufferLength);
213
+ }
214
+ if (xRingbufferSend (tx_ring_buf, (void *)bptr, size, 0 ) != pdTRUE){
215
+ return ;
216
+ }
217
+ }
218
+ // flushes CDC FIFO
219
+ usb_serial_jtag_ll_txfifo_flush ();
220
+ }
221
+
141
222
static void ARDUINO_ISR_ATTR cdc0_write_char (char c) {
223
+ if (tx_ring_buf == NULL ) {
224
+ return ;
225
+ }
226
+ if (!HWCDC::isConnected ()) {
227
+ // just pop/push RingBuffer and apply FIFO policy
228
+ flushTXBuffer ((const uint8_t *)&c, 1 );
229
+ return ;
230
+ }
142
231
if (xPortInIsrContext ()){
143
232
xRingbufferSendFromISR (tx_ring_buf, (void *) (&c), 1 , NULL );
144
233
} else {
145
234
xRingbufferSend (tx_ring_buf, (void *) (&c), 1 , tx_timeout_ms / portTICK_PERIOD_MS);
146
235
}
147
- usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY );
236
+ usb_serial_jtag_ll_txfifo_flush ( );
148
237
}
149
238
150
239
HWCDC::HWCDC () {
151
-
240
+ lastSOF_ms = 0 ;
241
+ SOF_TIMEOUT = 5 ;
152
242
}
153
243
154
244
HWCDC::~HWCDC (){
155
245
end ();
156
246
}
157
247
248
+ // It should return <true> just when USB is plugged and CDC is connected.
158
249
HWCDC::operator bool () const
159
250
{
160
- return initial_empty ;
251
+ return HWCDC::isCDC_Connected () ;
161
252
}
162
253
163
254
void HWCDC::onEvent (esp_event_handler_t callback){
@@ -168,6 +259,16 @@ void HWCDC::onEvent(arduino_hw_cdc_event_t event, esp_event_handler_t callback){
168
259
arduino_hw_cdc_event_handler_register_with (ARDUINO_HW_CDC_EVENTS, event, callback, this );
169
260
}
170
261
262
+ void HWCDC::deinit ()
263
+ {
264
+ // Setting USB D+ D- pins
265
+ // Force the host to re-enumerate (BUS_RESET)
266
+ pinMode (USB_DM_GPIO_NUM, OUTPUT_OPEN_DRAIN);
267
+ pinMode (USB_DP_GPIO_NUM, OUTPUT_OPEN_DRAIN);
268
+ digitalWrite (USB_DM_GPIO_NUM, LOW);
269
+ digitalWrite (USB_DP_GPIO_NUM, LOW);
270
+ }
271
+
171
272
void HWCDC::begin (unsigned long baud)
172
273
{
173
274
if (tx_lock == NULL ) {
@@ -185,20 +286,35 @@ void HWCDC::begin(unsigned long baud)
185
286
log_e (" HW CDC TX Buffer error" );
186
287
}
187
288
}
289
+
290
+ // the HW Serial pins needs to be first deinited in order to allow `if(Serial)` to work :-(
291
+ deinit ();
292
+ delay (10 ); // USB Host has to enumerate it again
293
+
294
+ // Configure PHY
295
+ // USB_Serial_JTAG use internal PHY
296
+ USB_SERIAL_JTAG.conf0 .phy_sel = 0 ;
297
+ // Disable software control USB D+ D- pullup pulldown (Device FS: dp_pullup = 1)
298
+ USB_SERIAL_JTAG.conf0 .pad_pull_override = 0 ;
299
+ // Enable USB D+ pullup
300
+ USB_SERIAL_JTAG.conf0 .dp_pullup = 1 ;
301
+ // Enable USB pad function
302
+ USB_SERIAL_JTAG.conf0 .usb_pad_enable = 1 ;
188
303
usb_serial_jtag_ll_disable_intr_mask (USB_SERIAL_JTAG_LL_INTR_MASK);
189
- 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);
304
+ usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY | USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT
305
+ | USB_SERIAL_JTAG_INTR_BUS_RESET | USB_SERIAL_JTAG_INTR_SOF);
190
306
if (!intr_handle && esp_intr_alloc (ETS_USB_SERIAL_JTAG_INTR_SOURCE, 0 , hw_cdc_isr_handler, NULL , &intr_handle) != ESP_OK){
191
307
isr_log_e (" HW USB CDC failed to init interrupts" );
192
308
end ();
193
309
return ;
194
310
}
195
- usb_serial_jtag_ll_txfifo_flush ();
196
311
}
197
312
198
313
void HWCDC::end ()
199
314
{
200
- // Disable tx/rx interrupt.
315
+ // Disable/clear/free tx/rx interrupt.
201
316
usb_serial_jtag_ll_disable_intr_mask (USB_SERIAL_JTAG_LL_INTR_MASK);
317
+ usb_serial_jtag_ll_clr_intsts_mask (USB_SERIAL_JTAG_LL_INTR_MASK);
202
318
esp_intr_free (intr_handle);
203
319
intr_handle = NULL ;
204
320
if (tx_lock != NULL ) {
@@ -211,13 +327,13 @@ void HWCDC::end()
211
327
esp_event_loop_delete (arduino_hw_cdc_event_loop_handle);
212
328
arduino_hw_cdc_event_loop_handle = NULL ;
213
329
}
330
+ deinit ();
331
+ setDebugOutput (false );
332
+ connected = false ;
214
333
}
215
334
216
335
void HWCDC::setTxTimeoutMs (uint32_t timeout){
217
336
tx_timeout_ms = timeout;
218
- // it registers that the user has explicitly requested to use a value as TX timeout
219
- // used for the workaround with unplugged USB and TX Queue Full that causes a delay on every write()
220
- tx_timeout_change_request = true ;
221
337
}
222
338
223
339
/*
@@ -260,35 +376,63 @@ size_t HWCDC::write(const uint8_t *buffer, size_t size)
260
376
if (xSemaphoreTake (tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS){
261
377
return 0 ;
262
378
}
263
- size_t max_size = xRingbufferGetMaxItemSize (tx_ring_buf);
264
- size_t space = xRingbufferGetCurFreeSize (tx_ring_buf);
265
- size_t to_send = size, so_far = 0 ;
266
-
267
- if (space > size){
268
- space = size;
269
- }
270
- // Non-Blocking method, Sending data to ringbuffer, and handle the data in ISR.
271
- if (xRingbufferSend (tx_ring_buf, (void *) (buffer), space, 0 ) != pdTRUE){
272
- size = 0 ;
379
+ if (!isCDC_Connected ()) {
380
+ // just pop/push RingBuffer and apply FIFO policy
381
+ flushTXBuffer (buffer, size);
273
382
} else {
274
- to_send -= space;
275
- so_far += space;
276
- // Now trigger the ISR to read data from the ring buffer.
277
- usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
383
+ size_t space = xRingbufferGetCurFreeSize (tx_ring_buf);
384
+ size_t to_send = size, so_far = 0 ;
278
385
279
- while (to_send){
280
- if (max_size > to_send){
281
- max_size = to_send;
282
- }
283
- // Blocking method, Sending data to ringbuffer, and handle the data in ISR.
284
- if (xRingbufferSend (tx_ring_buf, (void *) (buffer+so_far), max_size, tx_timeout_ms / portTICK_PERIOD_MS) != pdTRUE){
285
- size = so_far;
286
- break ;
287
- }
288
- so_far += max_size;
289
- to_send -= max_size;
386
+ if (space > size){
387
+ space = size;
388
+ }
389
+ // Non-Blocking method, Sending data to ringbuffer, and handle the data in ISR.
390
+ if (space > 0 && xRingbufferSend (tx_ring_buf, (void *) (buffer), space, 0 ) != pdTRUE){
391
+ size = 0 ;
392
+ } else {
393
+ to_send -= space;
394
+ so_far += space;
290
395
// Now trigger the ISR to read data from the ring buffer.
291
- usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
396
+ usb_serial_jtag_ll_txfifo_flush ();
397
+ if (connected) usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
398
+ // tracks CDC trasmission progress to avoid hanging if CDC is unplugged while still sending data
399
+ size_t last_toSend = to_send;
400
+ uint32_t tries = tx_timeout_ms; // waits 1ms per sending data attempt, in case CDC is unplugged
401
+ while (connected && to_send){
402
+ space = xRingbufferGetCurFreeSize (tx_ring_buf);
403
+ if (space > to_send){
404
+ space = to_send;
405
+ }
406
+ // Blocking method, Sending data to ringbuffer, and handle the data in ISR.
407
+ if (xRingbufferSend (tx_ring_buf, (void *) (buffer+so_far), space, tx_timeout_ms / portTICK_PERIOD_MS) != pdTRUE){
408
+ size = so_far;
409
+ log_w (" write failed due to ring buffer full - timeout" );
410
+ break ;
411
+ }
412
+ so_far += space;
413
+ to_send -= space;
414
+ // Now trigger the ISR to read data from the ring buffer.
415
+ usb_serial_jtag_ll_txfifo_flush ();
416
+ if (connected) usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
417
+ if (last_toSend == to_send) {
418
+ // no progress in sending data... USB CDC is probably unplugged
419
+ tries--;
420
+ delay (1 );
421
+ } else {
422
+ last_toSend = to_send;
423
+ tries = tx_timeout_ms; // reset the timeout
424
+ }
425
+ if (tries == 0 ) { // CDC isn't connected anymore...
426
+ size = so_far;
427
+ log_w (" write failed due to waiting USB Host - timeout" );
428
+ connected = false ;
429
+ }
430
+ }
431
+ }
432
+ // CDC was diconnected while sending data ==> flush the TX buffer keeping the last data
433
+ if (to_send && !usb_serial_jtag_ll_txfifo_writable ()) {
434
+ connected = false ;
435
+ flushTXBuffer (buffer + so_far, to_send);
292
436
}
293
437
}
294
438
xSemaphoreGive (tx_lock);
@@ -308,15 +452,28 @@ void HWCDC::flush(void)
308
452
if (xSemaphoreTake (tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS){
309
453
return ;
310
454
}
311
- UBaseType_t uxItemsWaiting = 0 ;
312
- vRingbufferGetInfo (tx_ring_buf, NULL , NULL , NULL , NULL , &uxItemsWaiting);
313
- if (uxItemsWaiting){
314
- // Now trigger the ISR to read data from the ring buffer.
315
- usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
316
- }
317
- while (uxItemsWaiting){
318
- delay (5 );
455
+ if (!isCDC_Connected ()) {
456
+ flushTXBuffer (NULL , 0 );
457
+ } else {
458
+ UBaseType_t uxItemsWaiting = 0 ;
319
459
vRingbufferGetInfo (tx_ring_buf, NULL , NULL , NULL , NULL , &uxItemsWaiting);
460
+ if (uxItemsWaiting){
461
+ // Now trigger the ISR to read data from the ring buffer.
462
+ usb_serial_jtag_ll_txfifo_flush ();
463
+ if (connected) usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
464
+ }
465
+ uint32_t tries = tx_timeout_ms; // waits 1ms per ISR sending data attempt, in case CDC is unplugged
466
+ while (connected && tries && uxItemsWaiting){
467
+ delay (1 );
468
+ UBaseType_t lastUxItemsWaiting = uxItemsWaiting;
469
+ vRingbufferGetInfo (tx_ring_buf, NULL , NULL , NULL , NULL , &uxItemsWaiting);
470
+ if (lastUxItemsWaiting == uxItemsWaiting) tries--;
471
+ if (connected) usb_serial_jtag_ll_ena_intr_mask (USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
472
+ }
473
+ if (tries == 0 ) { // CDC isn't connected anymore...
474
+ connected = false ;
475
+ flushTXBuffer (NULL , 0 ); // flushes all TX Buffer
476
+ }
320
477
}
321
478
xSemaphoreGive (tx_lock);
322
479
}
@@ -407,4 +564,4 @@ HWCDC USBSerial;
407
564
#endif
408
565
#endif
409
566
410
- #endif /* CONFIG_TINYUSB_CDC_ENABLED */
567
+ #endif /* CONFIG_IDF_TARGET_ESP32C3 */
0 commit comments