mirror of
https://github.com/espressif/esp-idf.git
synced 2025-10-21 02:11:01 +00:00
usb: cdc support, streams redirection; ci, examples upd
tusb: cdc, tasks encapsulation, callbacks api, multiple interfaces examples: added serial interface and usb console ci: reimplemented cmake/make test in python with ignore lists IDF-578
This commit is contained in:
412
components/tinyusb/additions/src/tusb_cdc_acm.c
Normal file
412
components/tinyusb/additions/src/tusb_cdc_acm.c
Normal file
@@ -0,0 +1,412 @@
|
||||
// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "tusb.h"
|
||||
#include "tusb_cdc_acm.h"
|
||||
#include "cdc.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#define ESP_RETURN_ON_ERROR(x) do{esp_err_t r = (x); if (r != ESP_OK) return r;} while(0)
|
||||
|
||||
#define RX_UNREADBUF_SZ_DEFAULT 64 // buffer storing all unread RX data
|
||||
|
||||
|
||||
typedef struct {
|
||||
bool initialized;
|
||||
size_t rx_unread_buf_sz;
|
||||
RingbufHandle_t rx_unread_buf;
|
||||
uint8_t *rx_tfbuf;
|
||||
tusb_cdcacm_callback_t callback_rx;
|
||||
tusb_cdcacm_callback_t callback_rx_wanted_char;
|
||||
tusb_cdcacm_callback_t callback_line_state_changed;
|
||||
tusb_cdcacm_callback_t callback_line_coding_changed;
|
||||
xTimerHandle flush_timer;
|
||||
} esp_tusb_cdcacm_t; /*!< CDC_AMC object */
|
||||
|
||||
static const char *TAG = "tusb_cdc_acm";
|
||||
|
||||
static inline esp_tusb_cdcacm_t *get_acm(tinyusb_cdcacm_itf_t itf)
|
||||
{
|
||||
esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf);
|
||||
if (cdc_inst == NULL) {
|
||||
return (esp_tusb_cdcacm_t *)NULL;
|
||||
}
|
||||
return (esp_tusb_cdcacm_t *)(cdc_inst->subclass_obj);
|
||||
}
|
||||
|
||||
|
||||
/* TinyUSB callbacks
|
||||
********************************************************************* */
|
||||
|
||||
/* Invoked when cdc when line state changed e.g connected/disconnected */
|
||||
void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts)
|
||||
{
|
||||
esp_tusb_cdcacm_t *acm = get_acm(itf);
|
||||
if (dtr && rts) { // connected
|
||||
if (acm != NULL) {
|
||||
ESP_LOGV(TAG, "Host connected to CDC no.%d.", itf);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Host is connected to CDC no.%d, but it is not initialized. Initialize it using `tinyusb_cdc_init`.", itf);
|
||||
return;
|
||||
}
|
||||
} else { // disconnected
|
||||
if (acm != NULL) {
|
||||
ESP_LOGV(TAG, "Serial device is ready to connect to CDC no.%d", itf);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (acm) {
|
||||
tusb_cdcacm_callback_t cb = acm->callback_line_state_changed;
|
||||
if (cb) {
|
||||
cdcacm_event_t event = {
|
||||
.type = CDC_EVENT_LINE_STATE_CHANGED,
|
||||
.line_state_changed_data = {
|
||||
.dtr = dtr,
|
||||
.rts = rts
|
||||
}
|
||||
};
|
||||
cb(itf, &event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Invoked when CDC interface received data from host */
|
||||
void tud_cdc_rx_cb(uint8_t itf)
|
||||
{
|
||||
esp_tusb_cdcacm_t *acm = get_acm(itf);
|
||||
if (acm) {
|
||||
if (!acm->rx_unread_buf) {
|
||||
ESP_LOGE(TAG, "There is no RX buffer created");
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
tud_cdc_n_read_flush(itf); // we have no place to store data, so just drop it
|
||||
return;
|
||||
}
|
||||
while (tud_cdc_n_available(itf)) {
|
||||
int read_res = tud_cdc_n_read( itf,
|
||||
acm->rx_tfbuf,
|
||||
CONFIG_USB_CDC_RX_BUFSIZE );
|
||||
int res = xRingbufferSend(acm->rx_unread_buf,
|
||||
acm->rx_tfbuf,
|
||||
read_res, 0);
|
||||
if (res != pdTRUE) {
|
||||
ESP_LOGW(TAG, "The unread buffer is too small, the data has been lost");
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Sent %d bytes to the buffer", read_res);
|
||||
}
|
||||
}
|
||||
if (acm) {
|
||||
tusb_cdcacm_callback_t cb = acm->callback_rx;
|
||||
if (cb) {
|
||||
cdcacm_event_t event = {
|
||||
.type = CDC_EVENT_RX
|
||||
};
|
||||
cb(itf, &event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Invoked when line coding is change via SET_LINE_CODING
|
||||
void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const *p_line_coding)
|
||||
{
|
||||
esp_tusb_cdcacm_t *acm = get_acm(itf);
|
||||
if (acm) {
|
||||
tusb_cdcacm_callback_t cb = acm->callback_line_coding_changed;
|
||||
if (cb) {
|
||||
cdcacm_event_t event = {
|
||||
.type = CDC_EVENT_LINE_CODING_CHANGED,
|
||||
.line_coding_changed_data = {
|
||||
.p_line_coding = p_line_coding,
|
||||
}
|
||||
};
|
||||
cb(itf, &event);
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Invoked when received `wanted_char`
|
||||
void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char)
|
||||
{
|
||||
esp_tusb_cdcacm_t *acm = get_acm(itf);
|
||||
if (acm) {
|
||||
tusb_cdcacm_callback_t cb = acm->callback_rx_wanted_char;
|
||||
if (cb) {
|
||||
cdcacm_event_t event = {
|
||||
.type = CDC_EVENT_RX_WANTED_CHAR,
|
||||
.rx_wanted_char_data = {
|
||||
.wanted_char = wanted_char,
|
||||
}
|
||||
};
|
||||
cb(itf, &event);
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
esp_err_t tinyusb_cdcacm_register_callback(tinyusb_cdcacm_itf_t itf,
|
||||
cdcacm_event_type_t event_type,
|
||||
tusb_cdcacm_callback_t callback)
|
||||
{
|
||||
esp_tusb_cdcacm_t *acm = get_acm(itf);
|
||||
if (acm) {
|
||||
switch (event_type) {
|
||||
case CDC_EVENT_RX:
|
||||
acm->callback_rx = callback;
|
||||
return ESP_OK;
|
||||
case CDC_EVENT_RX_WANTED_CHAR:
|
||||
acm->callback_rx_wanted_char = callback;
|
||||
return ESP_OK;
|
||||
case CDC_EVENT_LINE_STATE_CHANGED:
|
||||
acm->callback_line_state_changed = callback;
|
||||
return ESP_OK;
|
||||
case CDC_EVENT_LINE_CODING_CHANGED:
|
||||
acm->callback_line_coding_changed = callback;
|
||||
return ESP_OK;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Wrong event type");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "CDC-ACM is not initialized");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
esp_err_t tinyusb_cdcacm_unregister_callback(tinyusb_cdcacm_itf_t itf,
|
||||
cdcacm_event_type_t event_type)
|
||||
{
|
||||
esp_tusb_cdcacm_t *acm = get_acm(itf);
|
||||
if (!acm) {
|
||||
ESP_LOGE(TAG, "Interface is not initialized. Use `tinyusb_cdc_init` for initialization");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
switch (event_type) {
|
||||
case CDC_EVENT_RX:
|
||||
acm->callback_rx = NULL;
|
||||
return ESP_OK;
|
||||
case CDC_EVENT_RX_WANTED_CHAR:
|
||||
acm->callback_rx_wanted_char = NULL;
|
||||
return ESP_OK;
|
||||
case CDC_EVENT_LINE_STATE_CHANGED:
|
||||
acm->callback_line_state_changed = NULL;
|
||||
return ESP_OK;
|
||||
case CDC_EVENT_LINE_CODING_CHANGED:
|
||||
acm->callback_line_coding_changed = NULL;
|
||||
return ESP_OK;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Wrong event type");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************************************** TinyUSB callbacks*/
|
||||
/* CDC-ACM
|
||||
********************************************************************* */
|
||||
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)
|
||||
{
|
||||
esp_tusb_cdcacm_t *acm = get_acm(itf);
|
||||
if (!acm) {
|
||||
ESP_LOGE(TAG, "Interface is not initialized. Use `tinyusb_cdc_init` for initialization");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
uint8_t *buf = xRingbufferReceiveUpTo(acm->rx_unread_buf, rx_data_size, 0, out_buf_sz);
|
||||
if (buf) {
|
||||
memcpy(out_buf, buf, *rx_data_size);
|
||||
vRingbufferReturnItem(acm->rx_unread_buf, (void *)buf);
|
||||
return ESP_OK;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to receive item");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
size_t tinyusb_cdcacm_write_queue_char(tinyusb_cdcacm_itf_t itf, char ch)
|
||||
{
|
||||
if (!get_acm(itf)) { // non-initialized
|
||||
return 0;
|
||||
}
|
||||
return tud_cdc_n_write_char(itf, ch);
|
||||
}
|
||||
|
||||
|
||||
size_t tinyusb_cdcacm_write_queue(tinyusb_cdcacm_itf_t itf, uint8_t *in_buf, size_t in_size)
|
||||
{
|
||||
if (!get_acm(itf)) { // non-initialized
|
||||
return 0;
|
||||
}
|
||||
return tud_cdc_n_write(itf, in_buf, in_size);
|
||||
}
|
||||
|
||||
|
||||
static inline bool timer_isactive(tinyusb_cdcacm_itf_t itf)
|
||||
{
|
||||
esp_tusb_cdcacm_t *acm = get_acm(itf);
|
||||
return xTimerIsTimerActive(acm->flush_timer);
|
||||
}
|
||||
|
||||
static inline esp_err_t timer_start(tinyusb_cdcacm_itf_t itf, uint32_t timeout_ticks)
|
||||
{
|
||||
esp_tusb_cdcacm_t *acm = get_acm(itf);
|
||||
xTimerChangePeriod(acm->flush_timer, timeout_ticks, 0); // set the timer
|
||||
if (!xTimerIsTimerActive(acm->flush_timer)) {
|
||||
if (xTimerStart(acm->flush_timer, 0) != pdPASS) { // start
|
||||
ESP_LOGE(TAG, "Can't start the timer");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static inline void timer_stop(tinyusb_cdcacm_itf_t itf)
|
||||
{
|
||||
if (timer_isactive(itf)) {
|
||||
xTimerStop(get_acm(itf)->flush_timer, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
esp_err_t tinyusb_cdcacm_write_flush(tinyusb_cdcacm_itf_t itf, uint32_t timeout_ticks)
|
||||
{
|
||||
if (!get_acm(itf)) { // non-initialized
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (!timeout_ticks) { // if no timeout - nonblocking mode
|
||||
int res = tud_cdc_n_write_flush(itf);
|
||||
if (!res) {
|
||||
return ESP_FAIL;
|
||||
} else {
|
||||
if (tud_cdc_n_write_available(itf)) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
return ESP_ERR_TIMEOUT;
|
||||
} else { // if timeout use timer
|
||||
ESP_RETURN_ON_ERROR(timer_start(itf, timeout_ticks));
|
||||
while (1) { // loop until success or until the time runs out
|
||||
if (!tud_cdc_n_write_available(itf)) { // if nothing to write - nothing to flush
|
||||
break;
|
||||
}
|
||||
if (tud_cdc_n_write_flush(itf)) { // Success
|
||||
break;
|
||||
}
|
||||
if (!timer_isactive(itf)) { // Time is up
|
||||
ESP_LOGW(TAG, "Flush failed");
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
vTaskDelay(1);
|
||||
}
|
||||
timer_stop(itf);
|
||||
return ESP_OK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void flush_timer_cb(xTimerHandle pxTimer)
|
||||
{
|
||||
ESP_LOGV(TAG, "flush_timer stopped");
|
||||
xTimerStop(pxTimer, 0);
|
||||
}
|
||||
|
||||
static esp_err_t alloc_obj(tinyusb_cdcacm_itf_t itf)
|
||||
{
|
||||
esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf);
|
||||
cdc_inst->subclass_obj = calloc(1, sizeof(esp_tusb_cdcacm_t));
|
||||
if (!cdc_inst->subclass_obj) {
|
||||
return ESP_FAIL;
|
||||
} else {
|
||||
return ESP_OK;
|
||||
}
|
||||
}
|
||||
|
||||
static void free_obj(tinyusb_cdcacm_itf_t itf)
|
||||
{
|
||||
esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf);
|
||||
free(cdc_inst->subclass_obj);
|
||||
cdc_inst->subclass_obj = NULL;
|
||||
}
|
||||
|
||||
esp_err_t tusb_cdc_acm_init(const tinyusb_config_cdcacm_t *cfg)
|
||||
{
|
||||
int itf = (int)cfg->cdc_port;
|
||||
/* Creating a CDC object */
|
||||
const tinyusb_config_cdc_t cdc_cfg = {
|
||||
.usb_dev = cfg->usb_dev,
|
||||
.cdc_class = TUSB_CLASS_CDC,
|
||||
.cdc_subclass.comm_subclass = CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL
|
||||
};
|
||||
ESP_RETURN_ON_ERROR(tinyusb_cdc_init(itf, &cdc_cfg));
|
||||
ESP_RETURN_ON_ERROR(alloc_obj(itf));
|
||||
|
||||
esp_tusb_cdcacm_t *acm = get_acm(itf);
|
||||
/* Callbacks setting up*/
|
||||
if (cfg->callback_rx) {
|
||||
tinyusb_cdcacm_register_callback(itf, CDC_EVENT_RX, cfg->callback_rx);
|
||||
}
|
||||
if (cfg->callback_rx_wanted_char) {
|
||||
tinyusb_cdcacm_register_callback(itf, CDC_EVENT_RX_WANTED_CHAR, cfg->callback_rx_wanted_char);
|
||||
}
|
||||
if (cfg->callback_line_state_changed) {
|
||||
tinyusb_cdcacm_register_callback(itf, CDC_EVENT_LINE_STATE_CHANGED, cfg->callback_line_state_changed);
|
||||
}
|
||||
if (cfg->callback_line_coding_changed) {
|
||||
tinyusb_cdcacm_register_callback( itf, CDC_EVENT_LINE_CODING_CHANGED, cfg->callback_line_coding_changed);
|
||||
}
|
||||
|
||||
/* SW timer*/
|
||||
acm->flush_timer = xTimerCreate(
|
||||
"flush_timer", 10 / portTICK_PERIOD_MS, pdTRUE, (void *)itf, flush_timer_cb);
|
||||
|
||||
/* Buffers */
|
||||
acm->rx_tfbuf = malloc(CONFIG_USB_CDC_RX_BUFSIZE);
|
||||
if (!acm->rx_tfbuf) {
|
||||
ESP_LOGE(TAG, "Creation buffer error");
|
||||
free_obj(itf);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
acm->rx_unread_buf_sz = cfg->rx_unread_buf_sz == 0 ? RX_UNREADBUF_SZ_DEFAULT : cfg->rx_unread_buf_sz;
|
||||
acm->rx_unread_buf = xRingbufferCreate(acm->rx_unread_buf_sz, RINGBUF_TYPE_BYTEBUF);
|
||||
if (acm->rx_unread_buf == NULL) {
|
||||
ESP_LOGE(TAG, "Creation buffer error");
|
||||
free_obj(itf);
|
||||
return ESP_ERR_NO_MEM;
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Comm Initialized buff:%d bytes", cfg->rx_unread_buf_sz);
|
||||
return ESP_OK;
|
||||
}
|
||||
}
|
||||
|
||||
bool tusb_cdc_acm_initialized(tinyusb_cdcacm_itf_t itf)
|
||||
{
|
||||
esp_tusb_cdcacm_t *acm = get_acm(itf);
|
||||
if (acm) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/*********************************************************************** CDC-ACM*/
|
Reference in New Issue
Block a user