feat(examples/peripherals/usb/cherryusb/device/cherryusb_serial_device): add CherryUSB serial device example

This commit is contained in:
LiPeng
2025-06-26 21:08:47 +08:00
committed by Li Peng
parent b191c34647
commit f866fbd9bc
7 changed files with 566 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
idf_build_set_property(MINIMAL_BUILD ON)
project(cherryusb_serial_device)

View File

@@ -0,0 +1,85 @@
| Supported Targets | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- | -------- |
# CherryUSB Serial Device Example
Starts a CDC Device, and sends back any received data from the host.
(See the README.md file in the upper level 'examples' directory for more information about examples.)
## How to use example
This example can also be configured to act as double serial device.
Run `idf.py menuconfig` and in `Example Configuration → Enable usb cdc acm two channel` enable.
### Hardware Required
Any ESP board that have USB-OTG supported.
#### Pin Assignment
_Note:_ In case your board doesn't have micro-USB connector connected to USB-OTG peripheral, you may have to DIY a cable and connect **D+** and **D-** to the pins listed below.
See common pin assignments for USB Device examples from [upper level](../../README.md#common-pin-assignments).
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```bash
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example output
When running, the example will print the following output:
```
I (271) main_task: Started on CPU0
I (291) main_task: Calling app_main()
[I/USB] cherryusb, version: v1.4.3
[I/USB] ========== dwc2 udc params ==========
[I/USB] CID:00000000
[I/USB] GSNPSID:4f54400a
[I/USB] GHWCFG1:00000000
[I/USB] GHWCFG2:224dd930
[I/USB] GHWCFG3:00c804b5
[I/USB] GHWCFG4:d3f0a030
[I/USB] dwc2 fsphy type:1, hsphy type:0, dma support:2
[I/USB] dwc2 has 7 endpoints and dfifo depth(32-bit words) is 200, default config: 7 endpoints
[I/USB] =================================
[I/USB] fifo0 size:0010, offset:0050
[I/USB] fifo1 size:0010, offset:0060
[I/USB] fifo2 size:0010, offset:0070
[I/USB] fifo3 size:0010, offset:0080
[I/USB] fifo4 size:0010, offset:0090
[I/USB] fifo5 size:0010, offset:00a0
[I/USB] fifo6 size:0010, offset:00b0
I (391) device_cdc_main: usb cdc acm init done
I (721) device_cdc_main: intf:0, dtr:0
I (721) device_cdc_main: intf:0, rts:0
I (2981) device_cdc_main: intf:0, dtr:0
I (2981) device_cdc_main: intf:0, rts:0
I (2981) device_cdc_main: intf:0, dtr:0
I (2981) device_cdc_main: intf:0, rts:0
I (2981) device_cdc_main: intf:0, dtr:0
I (2981) device_cdc_main: intf:0, rts:0
I (2991) device_cdc_main: intf:0, dtr:0
I (2991) device_cdc_main: intf:0, rts:0
I (2991) device_cdc_main: intf:0, dtr:0
I (2991) device_cdc_main: intf:0, rts:0
I (3001) device_cdc_main: intf:0, dtr:0
I (3001) device_cdc_main: intf:0, rts:0
I (6011) device_cdc_main: actual out len:13
I (6021) device_cdc_main: actual in len:13
I (10411) device_cdc_main: actual out len:13
I (10421) device_cdc_main: actual in len:13
I (11111) device_cdc_main: intf:0, dtr:0
I (11111) device_cdc_main: intf:0, rts:0
```

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "device_cdc_main.c"
INCLUDE_DIRS "."
)

View File

@@ -0,0 +1,20 @@
menu "Example Configuration"
config EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL
bool "Enable usb cdc acm two channel"
default n
config EXAMPLE_CHERRYUSB_INIT_DEINIT_LOOP
bool "Perform init deinit of cherryusb stack in a loop"
default n
config EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS
bool "set usb read buffer size to MPS"
default y
help
If this option is disabled, a larger receive buffer will be configured for DMA at a time,
which may improve efficiency a little. However, when the host sends data with a length that
is an integer multiple of MPS, the host may not send ZLP, resulting in failure to report
the receipt of data in a timely manner.
endmenu

View File

@@ -0,0 +1,444 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "usbd_core.h"
#include "usbd_cdc_acm.h"
static char *TAG = "device_cdc_main";
#ifndef CONFIG_EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL
#define CDC_ACM_CHANNEL_NUM 1
#else
#define CDC_ACM_CHANNEL_NUM 2
#endif
#define INTERFACES_NUM (2 * CDC_ACM_CHANNEL_NUM)
#define NOTIFY_USB_CONFIGURED_BIT BIT0
#define NOTIFY_EXIT_BIT BIT1
#define NOTIFY_EP_BIT BIT2
static TaskHandle_t s_task_handle = NULL;
/*!< endpoint address */
#define CDC_IN_EP 0x81
#define CDC_OUT_EP 0x01
#define CDC_INT_EP 0x83
#define CDC_IN_EP1 0x82
#define CDC_OUT_EP1 0x02
#define CDC_INT_EP1 0x84
#define USBD_VID 0xFFFF
#define USBD_PID 0xFFFF
#define USBD_MAX_POWER 100
#define USBD_LANGID_STRING 1033
/*!< config descriptor size */
#define USB_CONFIG_SIZE (9 + CDC_ACM_DESCRIPTOR_LEN * 4)
#ifdef CONFIG_USB_HS
#define CDC_MAX_MPS 512
#else
#define CDC_MAX_MPS 64
#endif
typedef struct {
uint8_t rx_addr;
uint8_t tx_addr;
bool rx_busy_flag;
bool tx_busy_flag;
uint32_t read_len;
uint32_t write_len;
} ep_status_t;
static ep_status_t s_ep_status[CDC_ACM_CHANNEL_NUM] = {
{ CDC_OUT_EP, CDC_IN_EP, false, false, 0, 0 },
#ifdef CONFIG_EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL
{ CDC_OUT_EP1, CDC_IN_EP1, false, false, 0, 0 },
#endif
};
#if CONFIG_EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS
#define READ_BUFFER_SIZE CDC_MAX_MPS
#else
#define READ_BUFFER_SIZE 2048
#endif
static DRAM_DMA_ALIGNED_ATTR uint8_t read_buffer[CDC_ACM_CHANNEL_NUM][READ_BUFFER_SIZE];
static DRAM_DMA_ALIGNED_ATTR uint8_t write_buffer[CDC_ACM_CHANNEL_NUM][2048];
#ifdef CONFIG_USBDEV_ADVANCE_DESC
static const uint8_t device_descriptor[] = {
USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0xEF, 0x02, 0x01, USBD_VID, USBD_PID, 0x0100, 0x01)
};
static const uint8_t config_descriptor[] = {
USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, INTERFACES_NUM, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, CDC_MAX_MPS, 0x02),
#ifdef CONFIG_EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL
CDC_ACM_DESCRIPTOR_INIT(0x02, CDC_INT_EP1, CDC_OUT_EP1, CDC_IN_EP1, CDC_MAX_MPS, 0x02),
#endif
};
static const uint8_t device_quality_descriptor[] = {
///////////////////////////////////////
/// device qualifier descriptor
///////////////////////////////////////
0x0a,
USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER,
0x00,
0x02,
0x00,
0x00,
0x00,
0x40,
0x00,
0x00,
};
static const char *string_descriptors[] = {
(const char[])
{
0x09, 0x04
}, /* Langid */
"CherryUSB", /* Manufacturer */
"CherryUSB CDC MULTI DEMO", /* Product */
"2025060518", /* Serial Number */
};
static const uint8_t *device_descriptor_callback(uint8_t speed)
{
return device_descriptor;
}
static const uint8_t *config_descriptor_callback(uint8_t speed)
{
return config_descriptor;
}
static const uint8_t *device_quality_descriptor_callback(uint8_t speed)
{
return device_quality_descriptor;
}
static const char *string_descriptor_callback(uint8_t speed, uint8_t index)
{
if (index > 3) {
return NULL;
}
return string_descriptors[index];
}
const struct usb_descriptor cdc_descriptor = {
.device_descriptor_callback = device_descriptor_callback,
.config_descriptor_callback = config_descriptor_callback,
.device_quality_descriptor_callback = device_quality_descriptor_callback,
.string_descriptor_callback = string_descriptor_callback
};
#else
/*!< global descriptor */
static const uint8_t cdc_descriptor[] = {
USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0xEF, 0x02, 0x01, USBD_VID, USBD_PID, 0x0100, 0x01),
USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, INTERFACES_NUM, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, CDC_MAX_MPS, 0x02),
#ifdef CONFIG_EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL
CDC_ACM_DESCRIPTOR_INIT(0x02, CDC_INT_EP1, CDC_OUT_EP1, CDC_IN_EP1, CDC_MAX_MPS, 0x02),
#endif
///////////////////////////////////////
/// string0 descriptor
///////////////////////////////////////
USB_LANGID_INIT(USBD_LANGID_STRING),
///////////////////////////////////////
/// string1 descriptor
///////////////////////////////////////
0x14, /* bLength */
USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */
'C', 0x00, /* wcChar0 */
'h', 0x00, /* wcChar1 */
'e', 0x00, /* wcChar2 */
'r', 0x00, /* wcChar3 */
'r', 0x00, /* wcChar4 */
'y', 0x00, /* wcChar5 */
'U', 0x00, /* wcChar6 */
'S', 0x00, /* wcChar7 */
'B', 0x00, /* wcChar8 */
///////////////////////////////////////
/// string2 descriptor
///////////////////////////////////////
0x26, /* bLength */
USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */
'C', 0x00, /* wcChar0 */
'h', 0x00, /* wcChar1 */
'e', 0x00, /* wcChar2 */
'r', 0x00, /* wcChar3 */
'r', 0x00, /* wcChar4 */
'y', 0x00, /* wcChar5 */
'U', 0x00, /* wcChar6 */
'S', 0x00, /* wcChar7 */
'B', 0x00, /* wcChar8 */
' ', 0x00, /* wcChar9 */
'C', 0x00, /* wcChar10 */
'D', 0x00, /* wcChar11 */
'C', 0x00, /* wcChar12 */
' ', 0x00, /* wcChar13 */
'D', 0x00, /* wcChar14 */
'E', 0x00, /* wcChar15 */
'M', 0x00, /* wcChar16 */
'O', 0x00, /* wcChar17 */
///////////////////////////////////////
/// string3 descriptor
///////////////////////////////////////
0x16, /* bLength */
USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */
'2', 0x00, /* wcChar0 */
'0', 0x00, /* wcChar1 */
'2', 0x00, /* wcChar2 */
'5', 0x00, /* wcChar3 */
'0', 0x00, /* wcChar4 */
'6', 0x00, /* wcChar5 */
'0', 0x00, /* wcChar6 */
'5', 0x00, /* wcChar7 */
'1', 0x00, /* wcChar8 */
'8', 0x00, /* wcChar9 */
#ifdef CONFIG_USB_HS
///////////////////////////////////////
/// device qualifier descriptor
///////////////////////////////////////
0x0a,
USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER,
0x00,
0x02,
0x00,
0x00,
0x00,
0x40,
0x00,
0x00,
#endif
0x00
};
#endif
void usbd_event_handler(uint8_t busid, uint8_t event)
{
switch (event) {
case USBD_EVENT_RESET:
break;
case USBD_EVENT_CONNECTED:
break;
case USBD_EVENT_DISCONNECTED:
break;
case USBD_EVENT_RESUME:
break;
case USBD_EVENT_SUSPEND:
break;
case USBD_EVENT_CONFIGURED: {
BaseType_t HPTaskAwoken = pdFALSE;
xTaskNotifyFromISR(s_task_handle, NOTIFY_USB_CONFIGURED_BIT, eSetBits, &HPTaskAwoken);
if (HPTaskAwoken == pdTRUE) {
portYIELD_FROM_ISR();
}
break;
}
case USBD_EVENT_SET_REMOTE_WAKEUP:
break;
case USBD_EVENT_CLR_REMOTE_WAKEUP:
break;
default:
break;
}
}
static void usbd_cdc_acm_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
ESP_EARLY_LOGI(TAG, "ep 0x%02X actual out len:%d", ep, nbytes);
BaseType_t HPTaskAwoken = pdFALSE;
for (size_t i = 0; i < CDC_ACM_CHANNEL_NUM; i++) {
ep_status_t *ep_status = &s_ep_status[i];
if (ep_status->rx_addr == ep) {
if (nbytes > 0) {
if (ep_status->write_len == 0) {
memcpy(write_buffer[i], read_buffer[i], nbytes);
ep_status->write_len = nbytes;
usbd_ep_start_read(0, ep, read_buffer[i], sizeof(read_buffer[0]));
} else {
ep_status->read_len = nbytes;
ep_status->rx_busy_flag = false;
}
xTaskNotifyFromISR(s_task_handle, NOTIFY_EP_BIT, eSetBits, &HPTaskAwoken);
} else {
usbd_ep_start_read(0, ep_status->rx_addr, read_buffer[i], sizeof(read_buffer[i]));
}
break;
}
}
if (HPTaskAwoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
static void usbd_cdc_acm_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
BaseType_t HPTaskAwoken = pdFALSE;
ESP_EARLY_LOGI(TAG, "ep 0x%02X actual in len:%d", ep, nbytes);
if ((nbytes % CDC_MAX_MPS) == 0 && nbytes) {
usbd_ep_start_write(0, ep, NULL, 0);
} else {
for (size_t i = 0; i < CDC_ACM_CHANNEL_NUM; i++) {
ep_status_t *ep_status = &s_ep_status[i];
if (ep_status->tx_addr == ep) {
ep_status->write_len = 0;
ep_status->tx_busy_flag = false;
xTaskNotifyFromISR(s_task_handle, NOTIFY_EP_BIT, eSetBits, &HPTaskAwoken);
break;
}
}
}
if (HPTaskAwoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
struct usbd_endpoint cdc_out_ep = {
.ep_addr = CDC_OUT_EP,
.ep_cb = usbd_cdc_acm_bulk_out
};
struct usbd_endpoint cdc_in_ep = {
.ep_addr = CDC_IN_EP,
.ep_cb = usbd_cdc_acm_bulk_in
};
struct usbd_interface intf0;
struct usbd_interface intf1;
#ifdef CONFIG_EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL
struct usbd_endpoint cdc_out_ep1 = {
.ep_addr = CDC_OUT_EP1,
.ep_cb = usbd_cdc_acm_bulk_out
};
struct usbd_endpoint cdc_in_ep1 = {
.ep_addr = CDC_IN_EP1,
.ep_cb = usbd_cdc_acm_bulk_in
};
struct usbd_interface intf2;
struct usbd_interface intf3;
#endif
static void cdc_acm_init(void)
{
#ifdef CONFIG_USBDEV_ADVANCE_DESC
usbd_desc_register(0, &cdc_descriptor);
#else
usbd_desc_register(0, cdc_descriptor);
#endif
usbd_add_interface(0, usbd_cdc_acm_init_intf(0, &intf0));
usbd_add_interface(0, usbd_cdc_acm_init_intf(0, &intf1));
usbd_add_endpoint(0, &cdc_out_ep);
usbd_add_endpoint(0, &cdc_in_ep);
#ifdef CONFIG_EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL
usbd_add_interface(0, usbd_cdc_acm_init_intf(0, &intf2));
usbd_add_interface(0, usbd_cdc_acm_init_intf(0, &intf3));
usbd_add_endpoint(0, &cdc_out_ep1);
usbd_add_endpoint(0, &cdc_in_ep1);
#endif
usbd_initialize(0, ESP_USBD_BASE, usbd_event_handler);
}
void usbd_cdc_acm_set_dtr(uint8_t busid, uint8_t intf, bool dtr)
{
ESP_EARLY_LOGI(TAG, "intf:%u, dtr:%d", intf, dtr);
}
void usbd_cdc_acm_set_rts(uint8_t busid, uint8_t intf, bool rts)
{
ESP_EARLY_LOGI(TAG, "intf:%u, rts:%d", intf, rts);
}
void cdc_acm_task(void *arg)
{
uint32_t notify_value = 0;
ESP_LOGI(TAG, "usb cdc acm task start");
// Initialize the USB driver and CDC interface
cdc_acm_init();
ESP_LOGI(TAG, "usb cdc acm init done");
while (1) {
xTaskNotifyWait(0, 0xFFFFFFFF, &notify_value, portMAX_DELAY);
if (notify_value & NOTIFY_EXIT_BIT) {
break;
}
if (notify_value & NOTIFY_USB_CONFIGURED_BIT) {
/* setup first out ep read transfer */
for (size_t i = 0; i < sizeof(s_ep_status) / sizeof(s_ep_status[0]); i++) {
ep_status_t *ep_status = &s_ep_status[i];
ep_status->write_len = 0;
ep_status->tx_busy_flag = false;
ep_status->read_len = 0;
ep_status->rx_busy_flag = true;
usbd_ep_start_read(0, ep_status->rx_addr, read_buffer[i], sizeof(read_buffer[0]));
}
}
for (size_t i = 0; i < sizeof(s_ep_status) / sizeof(s_ep_status[0]); i++) {
ep_status_t *ep_status = &s_ep_status[i];
if (ep_status->tx_busy_flag == false) {
if (ep_status->write_len == 0 && ep_status->read_len > 0) {
memcpy(&write_buffer[i][0], &read_buffer[i][0], ep_status->read_len);
ep_status->write_len = ep_status->read_len;
ep_status->read_len = 0;
}
if (ep_status->write_len > 0) {
ep_status->tx_busy_flag = true;
usbd_ep_start_write(0, ep_status->tx_addr, write_buffer[i], ep_status->write_len);
}
}
if (ep_status->rx_busy_flag == false) {
if (ep_status->read_len == 0) {
ep_status->rx_busy_flag = true;
usbd_ep_start_read(0, ep_status->rx_addr, read_buffer[i], sizeof(read_buffer[i]));
}
}
}
}
usbd_deinitialize(0);
ESP_LOGW(TAG, "task exit");
s_task_handle = NULL;
vTaskDelete(NULL);
}
void app_main(void)
{
#if EXAMPLE_CHERRYUSB_INIT_DEINIT_LOOP
reinit :
#endif
xTaskCreatePinnedToCore(&cdc_acm_task, "cdc_acm_task", 1024 * 3, NULL, 10, &s_task_handle, 0);
#if EXAMPLE_CHERRYUSB_INIT_DEINIT_LOOP
for (int i = 10; i >= 0; i--) {
ESP_LOGW(TAG, "Deinit usb after %d seconds...", i);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
ESP_LOGW(TAG, "Notify deinit usb");
xTaskNotify(s_task_handle, NOTIFY_EXIT_BIT, eSetBits);
while (s_task_handle) {
vTaskDelay(100 / portTICK_PERIOD_MS);
}
goto reinit;
#endif
}

View File

@@ -0,0 +1,3 @@
## IDF Component Manager Manifest File
dependencies:
cherry-embedded/cherryusb: ~=1.5.0~4

View File

@@ -0,0 +1,4 @@
CONFIG_CHERRYUSB=y
CONFIG_CHERRYUSB_DEVICE=y
CONFIG_CHERRYUSB_DEVICE_DWC2_ESP=y
CONFIG_CHERRYUSB_DEVICE_CDC_ACM=y