ESP-IDF Firmware
Firmware architecture and call graph
Loading...
Searching...
No Matches
tusb_cdc_acm.c
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7#include <stdint.h>
8#include "esp_check.h"
9#include "esp_err.h"
10#include "esp_log.h"
11#include "freertos/FreeRTOS.h"
12#include "freertos/task.h"
13#include "freertos/ringbuf.h"
14#include "tusb.h"
15#include "tusb_cdc_acm.h"
16#include "cdc.h"
17#include "sdkconfig.h"
18
19#define RX_UNREADBUF_SZ_DEFAULT 64 // buffer storing all unread RX data
20#define MIN(x, y) (((x) < (y)) ? (x) : (y))
21
22
33
34static const char *TAG = "tusb_cdc_acm";
35
37{
38 esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf);
39 if (cdc_inst == NULL) {
40 return (esp_tusb_cdcacm_t *)NULL;
41 }
42 return (esp_tusb_cdcacm_t *)(cdc_inst->subclass_obj);
43}
44
45
46/* TinyUSB callbacks
47 ********************************************************************* */
48
49/* Invoked by cdc interface when line state changed e.g connected/disconnected */
50void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts)
51{
52 esp_tusb_cdcacm_t *acm = get_acm(itf);
53 if (dtr && rts) { // connected
54 if (acm != NULL) {
55 ESP_LOGV(TAG, "Host connected to CDC no.%d.", itf);
56 } else {
57 ESP_LOGW(TAG, "Host is connected to CDC no.%d, but it is not initialized. Initialize it using `tinyusb_cdc_init`.", itf);
58 return;
59 }
60 } else { // disconnected
61 if (acm != NULL) {
62 ESP_LOGV(TAG, "Serial device is ready to connect to CDC no.%d", itf);
63 } else {
64 return;
65 }
66 }
67 if (acm) {
69 if (cb) {
70 cdcacm_event_t event = {
72 .line_state_changed_data = {
73 .dtr = dtr,
74 .rts = rts
75 }
76 };
77 cb(itf, &event);
78 }
79 }
80}
81
82/* Invoked when CDC interface received data from host */
83void tud_cdc_rx_cb(uint8_t itf)
84{
85 esp_tusb_cdcacm_t *acm = get_acm(itf);
86 if (acm) {
87 if (!acm->rx_unread_buf) {
88 ESP_LOGE(TAG, "There is no RX buffer created");
89 abort();
90 }
91 } else {
92 tud_cdc_n_read_flush(itf); // we have no place to store data, so just drop it
93 return;
94 }
95 while (tud_cdc_n_available(itf)) {
96 int read_res = tud_cdc_n_read( itf,
97 acm->rx_tfbuf,
98 CONFIG_TINYUSB_CDC_RX_BUFSIZE );
99 int res = xRingbufferSend(acm->rx_unread_buf,
100 acm->rx_tfbuf,
101 read_res, 0);
102 if (res != pdTRUE) {
103 ESP_LOGW(TAG, "The unread buffer is too small, the data has been lost");
104 } else {
105 ESP_LOGV(TAG, "Sent %d bytes to the buffer", read_res);
106 }
107 }
108 if (acm) {
110 if (cb) {
111 cdcacm_event_t event = {
112 .type = CDC_EVENT_RX
113 };
114 cb(itf, &event);
115 }
116 }
117}
118
119// Invoked when line coding is change via SET_LINE_CODING
120void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const *p_line_coding)
121{
122 esp_tusb_cdcacm_t *acm = get_acm(itf);
123 if (acm) {
125 if (cb) {
126 cdcacm_event_t event = {
128 .line_coding_changed_data = {
129 .p_line_coding = p_line_coding,
130 }
131 };
132 cb(itf, &event);
133 }
134 } else {
135 return;
136 }
137}
138
139// Invoked when received `wanted_char`
140void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char)
141{
142 esp_tusb_cdcacm_t *acm = get_acm(itf);
143 if (acm) {
145 if (cb) {
146 cdcacm_event_t event = {
148 .rx_wanted_char_data = {
149 .wanted_char = wanted_char,
150 }
151 };
152 cb(itf, &event);
153 }
154 } else {
155 return;
156 }
157}
158
160 cdcacm_event_type_t event_type,
161 tusb_cdcacm_callback_t callback)
162{
163 esp_tusb_cdcacm_t *acm = get_acm(itf);
164 if (acm) {
165 switch (event_type) {
166 case CDC_EVENT_RX:
167 acm->callback_rx = callback;
168 return ESP_OK;
170 acm->callback_rx_wanted_char = callback;
171 return ESP_OK;
173 acm->callback_line_state_changed = callback;
174 return ESP_OK;
176 acm->callback_line_coding_changed = callback;
177 return ESP_OK;
178 default:
179 ESP_LOGE(TAG, "Wrong event type");
180 return ESP_ERR_INVALID_ARG;
181 }
182 } else {
183 ESP_LOGE(TAG, "CDC-ACM is not initialized");
184 return ESP_ERR_INVALID_STATE;
185 }
186}
187
189 cdcacm_event_type_t event_type)
190{
191 esp_tusb_cdcacm_t *acm = get_acm(itf);
192 if (!acm) {
193 ESP_LOGE(TAG, "Interface is not initialized. Use `tinyusb_cdc_init` for initialization");
194 return ESP_ERR_INVALID_STATE;
195 }
196 switch (event_type) {
197 case CDC_EVENT_RX:
198 acm->callback_rx = NULL;
199 return ESP_OK;
201 acm->callback_rx_wanted_char = NULL;
202 return ESP_OK;
204 acm->callback_line_state_changed = NULL;
205 return ESP_OK;
208 return ESP_OK;
209 default:
210 ESP_LOGE(TAG, "Wrong event type");
211 return ESP_ERR_INVALID_ARG;
212 }
213}
214
215/*********************************************************************** TinyUSB callbacks*/
216/* CDC-ACM
217 ********************************************************************* */
218
219static esp_err_t read_from_rx_unread_to_buffer(esp_tusb_cdcacm_t *acm, uint8_t *out_buf, size_t req_bytes, size_t *read_bytes)
220{
221 uint8_t *buf = xRingbufferReceiveUpTo(acm->rx_unread_buf, read_bytes, 0, req_bytes);
222 if (buf) {
223 memcpy(out_buf, buf, *read_bytes);
224 vRingbufferReturnItem(acm->rx_unread_buf, (void *)(buf));
225 return ESP_OK;
226 } else {
227 return ESP_ERR_NO_MEM;
228 }
229}
230
232{
233 if (xSemaphoreTake(acm->ringbuf_read_mux, 0) != pdTRUE) {
234 ESP_LOGW(TAG, "Read error: ACM is busy");
235 return ESP_ERR_INVALID_STATE;
236 }
237 return ESP_OK;
238}
239
241{
242 BaseType_t ret = xSemaphoreGive(acm->ringbuf_read_mux);
243 assert(ret == pdTRUE);
244 return ESP_OK;
245}
246
247esp_err_t tinyusb_cdcacm_read(tinyusb_cdcacm_itf_t itf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size)
248{
249 esp_tusb_cdcacm_t *acm = get_acm(itf);
250 ESP_RETURN_ON_FALSE(acm, ESP_ERR_INVALID_STATE, TAG, "Interface is not initialized. Use `tinyusb_cdc_init` for initialization");
251 size_t read_sz;
252
253 /* Take a mutex to proceed two uninterrupted read operations */
254 ESP_RETURN_ON_ERROR(ringbuf_mux_take(acm), TAG, "ringbuf_mux_take failed");
255
256 esp_err_t res = read_from_rx_unread_to_buffer(acm, out_buf, out_buf_sz, &read_sz);
257 if (res != ESP_OK) {
258 ESP_RETURN_ON_ERROR(ringbuf_mux_give(acm), TAG, "ringbuf_mux_give failed");
259 return res;
260 }
261
262 *rx_data_size = read_sz;
263 /* Buffer's data can be wrapped, at that situations we should make another retrievement */
264 if (read_from_rx_unread_to_buffer(acm, out_buf + read_sz, out_buf_sz - read_sz, &read_sz) == ESP_OK) {
265 *rx_data_size += read_sz;
266 }
267
268 ESP_RETURN_ON_ERROR(ringbuf_mux_give(acm), TAG, "ringbuf_mux_give failed");
269 return ESP_OK;
270}
271
273{
274 if (!get_acm(itf)) { // non-initialized
275 return 0;
276 }
277 return tud_cdc_n_write_char(itf, ch);
278}
279
280size_t tinyusb_cdcacm_write_queue(tinyusb_cdcacm_itf_t itf, const uint8_t *in_buf, size_t in_size)
281{
282 if (!get_acm(itf)) { // non-initialized
283 return 0;
284 }
285 const uint32_t size_available = tud_cdc_n_write_available(itf);
286 return tud_cdc_n_write(itf, in_buf, MIN(in_size, size_available));
287}
288
290{
291 return CFG_TUD_CDC_TX_BUFSIZE - tud_cdc_n_write_available(itf);
292}
293
295{
296 if (!get_acm(itf)) { // non-initialized
297 return ESP_FAIL;
298 }
299
300 if (!timeout_ticks) { // if no timeout - nonblocking mode
301 int res = tud_cdc_n_write_flush(itf);
302 if (!res) {
303 ESP_LOGW(TAG, "flush failed (res: %d)", res);
304 return ESP_FAIL;
305 } else {
306 if (tud_cdc_n_write_occupied(itf)) {
307 ESP_LOGW(TAG, "remained data to flush!");
308 return ESP_FAIL;
309 } else {
310 return ESP_OK;
311 }
312 }
313 } else { // trying during the timeout
314 uint32_t ticks_start = xTaskGetTickCount();
315 uint32_t ticks_now = ticks_start;
316 while (1) { // loop until success or until the time runs out
317 ticks_now = xTaskGetTickCount();
318 if (!tud_cdc_n_write_occupied(itf)) { // if nothing to write - nothing to flush
319 break;
320 }
321 if (tud_cdc_n_write_flush(itf)) { // Success
322 break;
323 }
324 if ( (ticks_now - ticks_start) > timeout_ticks ) { // Time is up
325 ESP_LOGW(TAG, "Flush failed");
326 return ESP_ERR_TIMEOUT;
327 }
328 vTaskDelay(1);
329 }
330 return ESP_OK;
331 }
332}
333
335{
336 esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf);
337 cdc_inst->subclass_obj = calloc(1, sizeof(esp_tusb_cdcacm_t));
338 if (!cdc_inst->subclass_obj) {
339 return ESP_FAIL;
340 } else {
341 return ESP_OK;
342 }
343}
344
346{
347 esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf);
348 free(cdc_inst->subclass_obj);
349 cdc_inst->subclass_obj = NULL;
350}
351
353{
354 esp_err_t ret = ESP_OK;
355 int itf = (int)cfg->cdc_port;
356 /* Creating a CDC object */
357 const tinyusb_config_cdc_t cdc_cfg = {
358 .usb_dev = cfg->usb_dev,
359 .cdc_class = TUSB_CLASS_CDC,
360 .cdc_subclass.comm_subclass = CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL
361 };
362
363 ESP_RETURN_ON_ERROR(tinyusb_cdc_init(itf, &cdc_cfg), TAG, "tinyusb_cdc_init failed");
364 ESP_GOTO_ON_FALSE(!alloc_obj(itf), ESP_FAIL, out4, TAG, "alloc_obj failed");
365
366 /* Callbacks setting up*/
367 if (cfg->callback_rx) {
369 }
370 if (cfg->callback_rx_wanted_char) {
372 }
375 }
378 }
379
380 /* Buffers */
381 esp_tusb_cdcacm_t *acm = get_acm(itf);
382
383 acm->ringbuf_read_mux = xSemaphoreCreateMutex();
384 ESP_GOTO_ON_FALSE(acm->ringbuf_read_mux, ESP_ERR_NO_MEM, out3, TAG, "Creation of a ringbuf mutex failed");
385
386 acm->rx_tfbuf = malloc(CONFIG_TINYUSB_CDC_RX_BUFSIZE);
387 ESP_GOTO_ON_FALSE(acm->rx_tfbuf, ESP_ERR_NO_MEM, out2, TAG, "Creation buffer error");
388
390 acm->rx_unread_buf = xRingbufferCreate(acm->rx_unread_buf_sz, RINGBUF_TYPE_BYTEBUF);
391 ESP_GOTO_ON_FALSE(acm->rx_unread_buf, ESP_ERR_NO_MEM, out1, TAG, "Creation buffer error");
392
393 ESP_LOGD(TAG, "Comm Initialized buff:%d bytes", cfg->rx_unread_buf_sz);
394 return ESP_OK;
395
396out1:
397 free(acm->rx_tfbuf);
398out2:
399 vSemaphoreDelete(acm->ringbuf_read_mux);
400out3:
401 free_obj(itf);
402out4:
404 return ret;
405}
406
408{
409 esp_tusb_cdcacm_t *acm = get_acm(itf);
410 if (acm) {
411 return true;
412 } else {
413 return false;
414 }
415}
416/*********************************************************************** CDC-ACM*/
#define ESP_RETURN_ON_ERROR(x, log_tag, format,...)
esp_err_t tinyusb_cdc_deinit(int itf)
De-initializing CDC. Clean its objects.
Definition cdc.c:96
esp_tusb_cdc_t * tinyusb_cdc_get_intf(int itf_num)
Return interface of a CDC device.
Definition cdc.c:18
esp_err_t tinyusb_cdc_init(int itf, const tinyusb_config_cdc_t *cfg)
Initializing CDC basic object.
Definition cdc.c:80
int esp_err_t
Definition esp_err.h:21
#define ESP_OK
Definition esp_err.h:23
#define ESP_LOGD
Definition esp_log.h:22
static const char * TAG
Definition main/main.c:31
Describes an event passing to the input of a callbacks.
void * subclass_obj
Definition cdc.h:47
tusb_cdcacm_callback_t callback_line_coding_changed
tusb_cdcacm_callback_t callback_line_state_changed
SemaphoreHandle_t ringbuf_read_mux
tusb_cdcacm_callback_t callback_rx
RingbufHandle_t rx_unread_buf
tusb_cdcacm_callback_t callback_rx_wanted_char
Configuration structure for CDC-ACM.
tusb_cdcacm_callback_t callback_rx_wanted_char
tusb_cdcacm_callback_t callback_line_coding_changed
tinyusb_usbdev_t usb_dev
tusb_cdcacm_callback_t callback_rx
tusb_cdcacm_callback_t callback_line_state_changed
tinyusb_cdcacm_itf_t cdc_port
static esp_err_t ringbuf_mux_give(esp_tusb_cdcacm_t *acm)
static esp_err_t ringbuf_mux_take(esp_tusb_cdcacm_t *acm)
size_t tinyusb_cdcacm_write_queue(tinyusb_cdcacm_itf_t itf, const uint8_t *in_buf, size_t in_size)
Write data to write buffer from a byte array.
void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char)
esp_err_t tinyusb_cdcacm_register_callback(tinyusb_cdcacm_itf_t itf, cdcacm_event_type_t event_type, tusb_cdcacm_callback_t callback)
Register a callback invoking on CDC event. If the callback had been already registered,...
static esp_err_t read_from_rx_unread_to_buffer(esp_tusb_cdcacm_t *acm, uint8_t *out_buf, size_t req_bytes, size_t *read_bytes)
void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts)
bool tusb_cdc_acm_initialized(tinyusb_cdcacm_itf_t itf)
Check if the ACM initialized.
static esp_err_t alloc_obj(tinyusb_cdcacm_itf_t itf)
#define MIN(x, y)
static void free_obj(tinyusb_cdcacm_itf_t itf)
void tud_cdc_rx_cb(uint8_t itf)
void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const *p_line_coding)
esp_err_t tinyusb_cdcacm_read(tinyusb_cdcacm_itf_t itf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size)
Read a content to the array, and defines it's size to the sz_store.
esp_err_t tusb_cdc_acm_init(const tinyusb_config_cdcacm_t *cfg)
Initialize CDC ACM. Initialization will be finished with the tud_cdc_line_state_cb callback.
static esp_tusb_cdcacm_t * get_acm(tinyusb_cdcacm_itf_t itf)
esp_err_t tinyusb_cdcacm_unregister_callback(tinyusb_cdcacm_itf_t itf, cdcacm_event_type_t event_type)
Unregister a callback invoking on CDC event.
size_t tinyusb_cdcacm_write_queue_char(tinyusb_cdcacm_itf_t itf, char ch)
Sent one character to a write buffer.
#define RX_UNREADBUF_SZ_DEFAULT
esp_err_t tinyusb_cdcacm_write_flush(tinyusb_cdcacm_itf_t itf, uint32_t timeout_ticks)
Send all data from a write buffer. Use tinyusb_cdcacm_write_queue to add data to the buffer.
static uint32_t tud_cdc_n_write_occupied(tinyusb_cdcacm_itf_t itf)
cdcacm_event_type_t
Types of CDC ACM events.
@ CDC_EVENT_LINE_STATE_CHANGED
@ CDC_EVENT_RX_WANTED_CHAR
@ CDC_EVENT_LINE_CODING_CHANGED
@ CDC_EVENT_RX
void(* tusb_cdcacm_callback_t)(int itf, cdcacm_event_t *event)
CDC-ACM callback type.
tinyusb_cdcacm_itf_t
CDC ports available to setup.
#define CFG_TUD_CDC_TX_BUFSIZE
Definition tusb_config.h:91