Merge branch 'feat/add_cherryusb_demo' into 'master'

feat(usb): add CherryUSB serial device and host example

See merge request espressif/esp-idf!39467
This commit is contained in:
Li Peng
2025-09-03 10:14:46 +08:00
18 changed files with 2228 additions and 18 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.22)
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,38 @@
menu "Example Configuration"
choice EXAMPLE_USB_DEVICE_RHPORT
prompt "USB Device Peripheral"
default EXAMPLE_USB_DEVICE_RHPORT_HS if IDF_TARGET_ESP32P4
default EXAMPLE_USB_DEVICE_RHPORT_FS
help
Allows set the USB Peripheral Controller for USB device.
- High-speed (USB OTG2.0 Peripheral for High-, Full- and Low-speed)
- Full-speed (USB OTG1.1 Peripheral for Full- and Low-speed)
config EXAMPLE_USB_DEVICE_RHPORT_HS
bool "OTG2.0"
depends on IDF_TARGET_ESP32P4
config EXAMPLE_USB_DEVICE_RHPORT_FS
bool "OTG1.1"
endchoice
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,410 @@
/*
* 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
#if CDC_ACM_CHANNEL_NUM >= 2
#define CDC_IN_EP1 0x82
#define CDC_OUT_EP1 0x02
#define CDC_INT_EP1 0x84
#endif
#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 * CDC_ACM_CHANNEL_NUM)
#define CDC_FS_MAX_MPS 64
#define CDC_HS_MAX_MPS 512
#ifdef CONFIG_USB_HS
#define CDC_MAX_MPS CDC_HS_MAX_MPS
#else
#define CDC_MAX_MPS CDC_FS_MAX_MPS
#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
};
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
#define WRITE_BUFFER_SIZE ALIGN_UP(ALIGN_UP(2048, CDC_MAX_MPS), CONFIG_USB_ALIGN_SIZE)
#if CONFIG_EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS
#define READ_BUFFER_SIZE ALIGN_UP(CDC_MAX_MPS, CONFIG_USB_ALIGN_SIZE)
#else
#define READ_BUFFER_SIZE WRITE_BUFFER_SIZE
#endif
static uint32_t s_mps = CDC_MAX_MPS;
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][WRITE_BUFFER_SIZE];
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_fs[] = {
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_FS_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_FS_MAX_MPS, 0x02),
#endif
};
#ifdef CONFIG_USB_HS
static const uint8_t config_descriptor_hs[] = {
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_HS_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_HS_MAX_MPS, 0x02),
#endif
};
#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)
{
#ifdef CONFIG_USB_HS
if (speed >= USB_SPEED_HIGH) {
s_mps = CDC_HS_MAX_MPS;
return config_descriptor_hs;
}
s_mps = CDC_FS_MAX_MPS;
#endif
return config_descriptor_fs;
}
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
};
//Note: This callback is in the interrupt context
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;
}
}
//Note: This callback is in the interrupt context
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;
#if CONFIG_EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS
usbd_ep_start_read(0, ep, read_buffer[i], s_mps);
#else
usbd_ep_start_read(0, ep, read_buffer[i], sizeof(read_buffer[0]));
#endif
} else {
ep_status->read_len = nbytes;
ep_status->rx_busy_flag = false;
}
xTaskNotifyFromISR(s_task_handle, NOTIFY_EP_BIT, eSetBits, &HPTaskAwoken);
} else {
#if CONFIG_EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS
usbd_ep_start_read(0, ep, read_buffer[i], s_mps);
#else
usbd_ep_start_read(0, ep, read_buffer[i], sizeof(read_buffer[i]));
#endif
}
break;
}
}
if (HPTaskAwoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
//Note: This callback is in the interrupt context
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 % s_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)
{
usbd_desc_register(0, &cdc_descriptor);
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
#if CONFIG_EXAMPLE_USB_DEVICE_RHPORT_HS
usbd_initialize(0, ESP_USB_HS0_BASE, usbd_event_handler);
#else
usbd_initialize(0, ESP_USB_FS0_BASE, usbd_event_handler);
#endif
}
//Note: This callback is in the interrupt context
void usbd_cdc_acm_set_dtr(uint8_t busid, uint8_t intf, bool dtr)
{
ESP_EARLY_LOGI(TAG, "intf:%u, dtr:%d", intf, dtr);
}
//Note: This callback is in the interrupt context
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;
#if CONFIG_EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS
usbd_ep_start_read(0, ep_status->rx_addr, read_buffer[i], s_mps);
#else
usbd_ep_start_read(0, ep_status->rx_addr, read_buffer[i], sizeof(read_buffer[0]));
#endif
}
}
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;
#if CONFIG_EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS
usbd_ep_start_read(0, ep_status->rx_addr, read_buffer[i], s_mps);
#else
usbd_ep_start_read(0, ep_status->rx_addr, read_buffer[i], sizeof(read_buffer[i]));
#endif
}
}
}
}
usbd_deinitialize(0);
ESP_LOGW(TAG, "task exit");
s_task_handle = NULL;
vTaskDelete(NULL);
}
void app_main(void)
{
#if CONFIG_EXAMPLE_CHERRYUSB_INIT_DEINIT_LOOP
while (1)
#endif
{
xTaskCreatePinnedToCore(&cdc_acm_task, "cdc_acm_task", 1024 * 3, NULL, 10, &s_task_handle, 0);
#if CONFIG_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);
}
#endif
}
}

View File

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

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

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.22)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
idf_build_set_property(MINIMAL_BUILD ON)
project(cherryusb_host)

View File

@@ -0,0 +1,73 @@
| Supported Targets | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- | -------- |
# CherryUSB Host Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates how to use the CherryUSB host driver. Currently, this example supports communication with HID devices (such as Keyboard and Mouse), serial port devices (such as CDC_ACM, CH34x, CP210x, PL2303, FTDI FT23x/FT423x devices) and MSC (Mass Storage Class).
## How to use example
By default, all drivers supported by the routine are enabled. If you need to trim or disable unnecessary drivers, please disable the corresponding driver configuration.
Run `idf.py menuconfig` and in `Component config → CherryUSB Configuration → Enable usb host mode`, Uncheck the drivers you dont need.
### Hardware Required
* Development board with USB-OTG support
* A USB cable for Power supply and programming
* USB OTG Cable
#### Pin Assignment
Follow instruction in [examples/usb/README.md](../../../README.md) for specific hardware setup.
### 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 (264) main_task: Started on CPU0
I (304) main_task: Calling app_main()
[I/USB] cherryusb, version: v1.5.0
[I/USB] ========== dwc2 hcd params ==========
[I/USB] CID:00000000
[I/USB] GSNPSID:4f54400a
[I/USB] GHWCFG1:00000000
[I/USB] GHWCFG2:215fffd0
[I/USB] GHWCFG3:03805eb5
[I/USB] GHWCFG4:dff1a030
[I/USB] dwc2 has 16 channels and dfifo depth(32-bit words) is 896
I (334) HOST: Init usb
I (334) main_task: Returned from app_main()
[I/usbh_hub] New low-speed device on Bus 0, Hub 1, Port 1 connected
[I/usbh_core] New device found,idVendor:413c,idProduct:2113,bcdDevice:0110
[I/usbh_core] The device has 1 bNumConfigurations
[I/usbh_core] The device has 2 interfaces
[I/usbh_core] Enumeration success, start loading class driver
[I/usbh_core] Loading hid class driver
[I/usbh_hid] Ep=81 Attr=03 Mps=8 Interval=10 Mult=00
[I/usbh_hid] Register HID Class:/dev/input0
I (1054) HID: intf 0, SubClass 1, Protocol 1
[I/usbh_core] Loading hid class driver
[W/usbh_hid] Do not support set idle
[I/usbh_hid] Ep=82 Attr=03 Mps=3 Interval=10 Mult=00
[I/usbh_hid] Register HID Class:/dev/input1
I (1074) HID: intf 1, SubClass 0, Protocol 0
Keyboard
xiaodou
```

View File

@@ -0,0 +1,35 @@
set(srcs "main.c")
if(CONFIG_CHERRYUSB_HOST_HID)
list(APPEND srcs "hid.c")
endif()
if(CONFIG_CHERRYUSB_HOST_CDC_ACM OR CONFIG_CHERRYUSB_HOST_FTDI OR CONFIG_CHERRYUSB_HOST_CH34X OR
CONFIG_CHERRYUSB_HOST_CP210X OR CONFIG_CHERRYUSB_HOST_PL2303)
list(APPEND srcs "cdc_acm.c")
set(CHERRYUSB_HOST_CDC_ACM_INCLUDE TRUE)
endif()
if(CONFIG_CHERRYUSB_HOST_MSC)
list(APPEND srcs "msc.c")
endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "."
PRIV_REQUIRES esp_timer fatfs esp_ringbuf
)
if(CONFIG_CHERRYUSB_HOST_HID)
# Make sure the definitions in hid.c are linked correctly
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u ld_include_hid")
endif()
if(CHERRYUSB_HOST_CDC_ACM_INCLUDE)
# Make sure the definitions in cdc_acm.c are linked correctly
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u ld_include_cdc_acm")
endif()
if(CONFIG_CHERRYUSB_HOST_MSC)
# Make sure the definitions in msc.c are linked correctly
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u ld_include_msc")
endif()

View File

@@ -0,0 +1,38 @@
menu "Example Configuration"
choice EXAMPLE_USB_HOST_RHPORT
prompt "USB Host Peripheral"
default EXAMPLE_USB_HOST_RHPORT_HS if IDF_TARGET_ESP32P4
default EXAMPLE_USB_HOST_RHPORT_FS
help
Allows set the USB Peripheral Controller for USB host.
- High-speed (USB OTG2.0 Peripheral for High-, Full- and Low-speed)
- Full-speed (USB OTG1.1 Peripheral for Full- and Low-speed)
config EXAMPLE_USB_HOST_RHPORT_HS
bool "OTG2.0"
depends on IDF_TARGET_ESP32P4
config EXAMPLE_USB_HOST_RHPORT_FS
bool "OTG1.1"
endchoice
config EXAMPLE_HAL_USE_ESP32_S3_USB_OTG
bool "Use dev kit ESP32-S3-USB-OTG"
depends on IDF_TARGET_ESP32S3
default y
config EXAMPLE_FORMAT_IF_MOUNT_FAILED
bool "Format the card if mount failed"
depends on CHERRYUSB_HOST_MSC
default n
help
If this config item is set, format_if_mount_failed will be set to true and the card will be formatted if
the mount has failed.
config EXAMPLE_CHERRYUSB_INIT_DEINIT_LOOP
bool "Perform init deinit of cherryusb stack in a loop"
default n
endmenu

View File

@@ -0,0 +1,341 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/ringbuf.h"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "usbh_core.h"
#if CONFIG_CHERRYUSB_HOST_CDC_ACM
#include "usbh_cdc_acm.h"
#endif
#if CONFIG_CHERRYUSB_HOST_FTDI
#include "usbh_ftdi.h"
#endif
#if CONFIG_CHERRYUSB_HOST_CH34X
#include "usbh_ch34x.h"
#endif
#if CONFIG_CHERRYUSB_HOST_CP210X
#include "usbh_cp210x.h"
#endif
#if CONFIG_CHERRYUSB_HOST_PL2303
#include "usbh_pl2303.h"
#endif
static char *TAG = "CDC_ACM";
#define RINGBUF_SIZE 1024
static RingbufHandle_t s_recv_ringbuf = NULL;
static TaskHandle_t s_printf_task_handle = NULL;
typedef enum {
SERIAL_TYPE_CDC_ACM = 0,
SERIAL_TYPE_FTDI,
SERIAL_TYPE_CH34X,
SERIAL_TYPE_CP210X,
SERIAL_TYPE_PL2303,
} serial_type_t;
typedef struct {
serial_type_t type;
struct usbh_hubport *hport;
struct usbh_urb *bulkout_urb;
struct usb_endpoint_descriptor *bulkout;
uint8_t *out_buffer;
struct usbh_urb *bulkin_urb;
struct usb_endpoint_descriptor *bulkin;
uint8_t *in_buffer;
} serial_t;
static void free_serial_buffer(serial_t *serial);
static esp_err_t serial_start_in(serial_t *serial);
void ld_include_cdc_acm(void)
{
}
#define alloc_serial_buffer(serial_class, serial_type, out_serial) \
{ \
out_serial = heap_caps_calloc(1, sizeof(serial_t), MALLOC_CAP_DEFAULT|MALLOC_CAP_INTERNAL); \
if (out_serial) { \
out_serial->type = serial_type; \
out_serial->hport = serial_class->hport; \
out_serial->bulkout_urb = &serial_class->bulkout_urb; \
out_serial->bulkout = serial_class->bulkout; \
out_serial->out_buffer = heap_caps_aligned_alloc(CONFIG_USB_ALIGN_SIZE, out_serial->bulkout->wMaxPacketSize, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); \
out_serial->bulkin_urb = &serial_class->bulkin_urb; \
out_serial->bulkin = serial_class->bulkin; \
out_serial->in_buffer = heap_caps_aligned_alloc(CONFIG_USB_ALIGN_SIZE, out_serial->bulkin->wMaxPacketSize, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); \
if(out_serial->out_buffer == NULL || out_serial->in_buffer == NULL) { \
free_serial_buffer(out_serial); \
out_serial = NULL; \
} \
} \
}
static void free_serial_buffer(serial_t *serial)
{
if (serial == NULL) {
return;
}
if (serial->out_buffer) {
heap_caps_free(serial->out_buffer);
}
if (serial->in_buffer) {
heap_caps_free(serial->in_buffer);
}
heap_caps_free(serial);
}
//Note: This callback is in the interrupt context
static void serial_in_cb(void *arg, int nbytes)
{
BaseType_t xTaskWoken = pdFALSE;
serial_t *serial = (serial_t *)arg;
uint8_t *data = serial->in_buffer;
if (nbytes < 0) {
return;
}
if (serial->type == SERIAL_TYPE_FTDI) {
if (nbytes <= 2) {
serial_start_in(serial);
return;
}
//FTDI Skip the first two bytes (header)
data += 2;
nbytes -= 2;
}
if (s_recv_ringbuf) {
if (xRingbufferSendFromISR(s_recv_ringbuf, data, nbytes, &xTaskWoken) != pdTRUE) {
ESP_LOGD(TAG, "Ringbuffer send failed");
}
}
if (xTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}
serial_start_in(serial);
}
static esp_err_t serial_start_in(serial_t *serial)
{
int ret;
usbh_bulk_urb_fill(serial->bulkin_urb, serial->hport, serial->bulkin, serial->in_buffer, serial->bulkin->wMaxPacketSize, 0, serial_in_cb, serial);
ret = usbh_submit_urb(serial->bulkin_urb);
if (ret != 0) {
return ESP_FAIL;
}
return ESP_OK;
}
static esp_err_t serial_out(serial_t *serial, uint8_t *data, size_t len, uint32_t timeout)
{
int ret;
if (len > serial->bulkout->wMaxPacketSize) {
return ESP_ERR_INVALID_SIZE;
}
memcpy(serial->out_buffer, data, len);
usbh_bulk_urb_fill(serial->bulkout_urb, serial->hport, serial->bulkout, serial->out_buffer, len, timeout, NULL, NULL);
ret = usbh_submit_urb(serial->bulkout_urb);
if (ret != 0) {
return ESP_FAIL;
}
return ESP_OK;
}
static void usbh_cdc_acm_printf_task(void *arg)
{
while (1) {
size_t length = 0;
char *data = (char *)xRingbufferReceive(s_recv_ringbuf, &length, portMAX_DELAY);
if (data == NULL) {
continue;
}
ESP_LOGI(TAG, "Data received");
ESP_LOG_BUFFER_HEXDUMP(TAG, data, length, ESP_LOG_INFO);
vRingbufferReturnItem(s_recv_ringbuf, (void *)data);
fflush(stdout);
}
vTaskDelete(NULL);
}
static void creat_printf_task(void)
{
if (s_recv_ringbuf == NULL) {
s_recv_ringbuf = xRingbufferCreate(RINGBUF_SIZE, RINGBUF_TYPE_BYTEBUF);
if (s_recv_ringbuf == NULL) {
ESP_LOGE(TAG, "ringbuf create failed");
return;
}
}
if (s_printf_task_handle == NULL) {
xTaskCreate(usbh_cdc_acm_printf_task, "usbh_cdc_acm_printf_task", 4096, NULL, 5, &s_printf_task_handle);
}
}
#if CONFIG_CHERRYUSB_HOST_CDC_ACM
void usbh_cdc_acm_run(struct usbh_cdc_acm *cdc_acm_class)
{
serial_t *serial;
alloc_serial_buffer(cdc_acm_class, SERIAL_TYPE_CDC_ACM, serial);
if (serial == NULL) {
ESP_LOGW(TAG, "Malloc failed");
return;
}
creat_printf_task();
cdc_acm_class->user_data = serial;
struct cdc_line_coding linecoding = {
.dwDTERate = 115200,
.bDataBits = 8,
.bParityType = 0,
.bCharFormat = 0,
};
usbh_cdc_acm_set_line_coding(cdc_acm_class, &linecoding);
serial_start_in(serial);
const char data[] = "CDC: Hello, world!\r\n";
serial_out(serial, (uint8_t *)data, sizeof(data), 1000);
}
void usbh_cdc_acm_stop(struct usbh_cdc_acm *cdc_acm_class)
{
free_serial_buffer(cdc_acm_class->user_data);
}
#endif
#if CONFIG_CHERRYUSB_HOST_FTDI
void usbh_ftdi_run(struct usbh_ftdi *ftdi_class)
{
serial_t *serial;
alloc_serial_buffer(ftdi_class, SERIAL_TYPE_FTDI, serial);
if (serial == NULL) {
ESP_LOGW(TAG, "Malloc failed");
return;
}
creat_printf_task();
ftdi_class->user_data = serial;
struct cdc_line_coding linecoding = {
.dwDTERate = 115200,
.bDataBits = 8,
.bParityType = 0,
.bCharFormat = 0,
};
usbh_ftdi_set_line_coding(ftdi_class, &linecoding);
serial_start_in(serial);
const char data[] = "FTDI: Hello, world!\r\n";
serial_out(serial, (uint8_t *)data, sizeof(data), 1000);
}
void usbh_ftdi_stop(struct usbh_ftdi *ftdi_class)
{
free_serial_buffer(ftdi_class->user_data);
}
#endif
#if CONFIG_CHERRYUSB_HOST_CH34X
void usbh_ch34x_run(struct usbh_ch34x *ch34x_class)
{
serial_t *serial;
alloc_serial_buffer(ch34x_class, SERIAL_TYPE_CH34X, serial);
if (serial == NULL) {
ESP_LOGW(TAG, "Malloc failed");
return;
}
creat_printf_task();
ch34x_class->user_data = serial;
struct cdc_line_coding linecoding = {
.dwDTERate = 115200,
.bDataBits = 8,
.bParityType = 0,
.bCharFormat = 0,
};
usbh_ch34x_set_line_coding(ch34x_class, &linecoding);
serial_start_in(serial);
const char data[] = "CH34x: Hello, world!\r\n";
serial_out(serial, (uint8_t *)data, sizeof(data), 1000);
}
void usbh_ch34x_stop(struct usbh_ch34x *ch34x_class)
{
free_serial_buffer(ch34x_class->user_data);
}
#endif
#if CONFIG_CHERRYUSB_HOST_CP210X
void usbh_cp210x_run(struct usbh_cp210x *cp210x_class)
{
serial_t *serial;
alloc_serial_buffer(cp210x_class, SERIAL_TYPE_CP210X, serial);
if (serial == NULL) {
ESP_LOGW(TAG, "Malloc failed");
return;
}
creat_printf_task();
cp210x_class->user_data = serial;
struct cdc_line_coding linecoding = {
.dwDTERate = 115200,
.bDataBits = 8,
.bParityType = 0,
.bCharFormat = 0,
};
usbh_cp210x_set_line_coding(cp210x_class, &linecoding);
serial_start_in(serial);
const char data[] = "CP201x: Hello, world!\r\n";
serial_out(serial, (uint8_t *)data, sizeof(data), 1000);
}
void usbh_cp210x_stop(struct usbh_cp210x *cp210x_class)
{
free_serial_buffer(cp210x_class->user_data);
}
#endif
#if CONFIG_CHERRYUSB_HOST_PL2303
void usbh_pl2303_run(struct usbh_pl2303 *pl2303_class)
{
serial_t *serial;
alloc_serial_buffer(pl2303_class, SERIAL_TYPE_PL2303, serial);
if (serial == NULL) {
ESP_LOGW(TAG, "Malloc failed");
return;
}
creat_printf_task();
pl2303_class->user_data = serial;
struct cdc_line_coding linecoding = {
.dwDTERate = 115200,
.bDataBits = 8,
.bParityType = 0,
.bCharFormat = 0,
};
usbh_pl2303_set_line_coding(pl2303_class, &linecoding);
serial_start_in(serial);
const char data[] = "PL2303: Hello, world!\r\n";
serial_out(serial, (uint8_t *)data, sizeof(data), 1000);
}
void usbh_pl2303_stop(struct usbh_pl2303 *pl2303_class)
{
free_serial_buffer(pl2303_class->user_data);
}
#endif

View File

@@ -0,0 +1,580 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_timer.h"
#include "esp_log.h"
#include "usbh_core.h"
#include "usbh_hid.h"
static char *TAG = "HID";
typedef struct {
bool is_active;
esp_timer_handle_t timer;
uint8_t *buffer;
} hid_int_in_t;
/**
* @brief Key event
*/
typedef struct {
enum key_state {
KEY_STATE_PRESSED = 0x00,
KEY_STATE_RELEASED = 0x01
} state;
uint8_t modifier;
uint8_t key_code;
} key_event_t;
/**
* @brief hid msg
*/
typedef struct {
uint8_t protocol;
uint16_t len;
uint8_t data[64];
} hid_msg_t;
#define QUEUE_LEN 10
static QueueHandle_t s_msg_queue = NULL;
static TaskHandle_t s_msg_task_handle = NULL;
/* Main char symbol for ENTER key */
#define KEYBOARD_ENTER_MAIN_CHAR '\r'
/* When set to 1 pressing ENTER will be extending with LineFeed during serial debug output */
#define KEYBOARD_ENTER_LF_EXTEND 1
/* When set to 1, numbers entered from the numeric keypad while ALT is pressed will be escaped */
#define KEYBOARD_ENTER_ALT_ESCAPE 1
#if KEYBOARD_ENTER_ALT_ESCAPE
static bool is_ansi = false;
static unsigned int alt_code = 0;
#endif
/**
* @brief Scancode to ascii table
*/
const uint8_t keycode2ascii [57][2] = {
{0, 0}, /* HID_KEY_NO_PRESS */
{0, 0}, /* HID_KEY_ROLLOVER */
{0, 0}, /* HID_KEY_POST_FAIL */
{0, 0}, /* HID_KBD_USAGE_ERRUNDEF */
{'a', 'A'}, /* HID_KEY_A */
{'b', 'B'}, /* HID_KEY_B */
{'c', 'C'}, /* HID_KEY_C */
{'d', 'D'}, /* HID_KEY_D */
{'e', 'E'}, /* HID_KEY_E */
{'f', 'F'}, /* HID_KEY_F */
{'g', 'G'}, /* HID_KEY_G */
{'h', 'H'}, /* HID_KEY_H */
{'i', 'I'}, /* HID_KEY_I */
{'j', 'J'}, /* HID_KEY_J */
{'k', 'K'}, /* HID_KEY_K */
{'l', 'L'}, /* HID_KEY_L */
{'m', 'M'}, /* HID_KEY_M */
{'n', 'N'}, /* HID_KEY_N */
{'o', 'O'}, /* HID_KEY_O */
{'p', 'P'}, /* HID_KEY_P */
{'q', 'Q'}, /* HID_KEY_Q */
{'r', 'R'}, /* HID_KEY_R */
{'s', 'S'}, /* HID_KEY_S */
{'t', 'T'}, /* HID_KEY_T */
{'u', 'U'}, /* HID_KEY_U */
{'v', 'V'}, /* HID_KEY_V */
{'w', 'W'}, /* HID_KEY_W */
{'x', 'X'}, /* HID_KEY_X */
{'y', 'Y'}, /* HID_KEY_Y */
{'z', 'Z'}, /* HID_KEY_Z */
{'1', '!'}, /* HID_KEY_1 */
{'2', '@'}, /* HID_KEY_2 */
{'3', '#'}, /* HID_KEY_3 */
{'4', '$'}, /* HID_KEY_4 */
{'5', '%'}, /* HID_KEY_5 */
{'6', '^'}, /* HID_KEY_6 */
{'7', '&'}, /* HID_KEY_7 */
{'8', '*'}, /* HID_KEY_8 */
{'9', '('}, /* HID_KEY_9 */
{'0', ')'}, /* HID_KEY_0 */
{KEYBOARD_ENTER_MAIN_CHAR, KEYBOARD_ENTER_MAIN_CHAR}, /* HID_KEY_ENTER */
{0, 0}, /* HID_KEY_ESC */
{'\b', 0}, /* HID_KEY_DEL */
{0, 0}, /* HID_KEY_TAB */
{' ', ' '}, /* HID_KEY_SPACE */
{'-', '_'}, /* HID_KEY_MINUS */
{'=', '+'}, /* HID_KEY_EQUAL */
{'[', '{'}, /* HID_KEY_OPEN_BRACKET */
{']', '}'}, /* HID_KEY_CLOSE_BRACKET */
{'\\', '|'}, /* HID_KEY_BACK_SLASH */
{'\\', '|'}, /* HID_KEY_SHARP */ // HOTFIX: for NonUS Keyboards repeat HID_KEY_BACK_SLASH
{';', ':'}, /* HID_KEY_COLON */
{'\'', '"'}, /* HID_KEY_QUOTE */
{'`', '~'}, /* HID_KEY_TILDE */
{',', '<'}, /* HID_KEY_LESS */
{'.', '>'}, /* HID_KEY_GREATER */
{'/', '?'} /* HID_KBD_USAGE_QUESTION */
};
void ld_include_hid(void)
{
}
/**
* @brief HID Keyboard print char symbol
*
* @param[in] key_char Keyboard char to stdout
*/
static inline void hid_keyboard_print_char(unsigned int key_char)
{
if (!!key_char) {
putchar(key_char);
#if (KEYBOARD_ENTER_LF_EXTEND)
if (KEYBOARD_ENTER_MAIN_CHAR == key_char) {
putchar('\n');
}
#endif // KEYBOARD_ENTER_LF_EXTEND
fflush(stdout);
}
}
/**
* @brief Makes new line depending on report output protocol type
*
* @param[in] proto Current protocol to output
*/
static void hid_print_new_device_report_header(int proto)
{
static int prev_proto_output = -1;
if (prev_proto_output != proto) {
prev_proto_output = proto;
printf("\r\n");
if (proto == HID_PROTOCOL_MOUSE) {
printf("Mouse\r\n");
} else if (proto == HID_PROTOCOL_KEYBOARD) {
printf("Keyboard\r\n");
} else {
printf("Generic\r\n");
}
fflush(stdout);
}
}
/**
* @brief HID Keyboard modifier verification for capitalization application (right or left shift)
*
* @param[in] modifier
* @return true Modifier was pressed (left or right shift)
* @return false Modifier was not pressed (left or right shift)
*
*/
static inline bool hid_keyboard_is_modifier_shift(uint8_t modifier)
{
if (((modifier & HID_MODIFIER_LSHIFT) == HID_MODIFIER_LSHIFT) ||
((modifier & HID_MODIFIER_RSHIFT) == HID_MODIFIER_RSHIFT)) {
return true;
}
return false;
}
#if KEYBOARD_ENTER_ALT_ESCAPE
/**
* @brief HID Keyboard modifier verification for capitalization application (right or left alt)
*
* @param[in] modifier
* @return true Modifier was pressed (left or right alt)
* @return false Modifier was not pressed (left or right alt)
*
*/
static inline bool hid_keyboard_is_modifier_alt(uint8_t modifier)
{
if (((modifier & HID_MODIFIER_LALT) == HID_MODIFIER_LALT) ||
((modifier & HID_MODIFIER_RALT) == HID_MODIFIER_RALT)) {
return true;
}
return false;
}
/**
* @brief HID Keyboard alt code process(Called when ALT is pressed)
*
* @param[in] key_code Entered key value
* @return true Key values that qualify for ALT escape processing
* @return false Key values that do not comply with ALT escape processing
*
*/
static inline bool hid_keyboard_alt_code_processing(uint8_t key_code)
{
if ((key_code < HID_KBD_USAGE_KPD1) || (key_code > HID_KBD_USAGE_KPD0)) {
return false;
}
if (key_code == HID_KBD_USAGE_KPD0) {
if (alt_code == 0) {
is_ansi = true;
return true;
}
/* Note: Since the keyboard code 0 of the numeric keypad is not keyboard code 1 minus 1, the
* conversion is performed here to facilitate subsequent calculations of the input numbers.
*/
key_code = HID_KBD_USAGE_KPD1 - 1;
}
alt_code = alt_code * 10 + (key_code - (HID_KBD_USAGE_KPD1 - 1));
return true;
}
/**
* @brief HID Keyboard alt code process complete(Called when ALT is not pressed)
*/
static inline void hid_keyboard_alt_code_process_complete(void)
{
if (alt_code > 0) {
alt_code = alt_code & 0xff;
if (is_ansi || alt_code == 0) {
char utf8_buffer[8] = { 0 };
if (alt_code == 0) {
alt_code = 0x100;
}
//ANSI is processed as UTF8
if (alt_code <= 0x7F) {
utf8_buffer[0] = (char)alt_code;
} else {
utf8_buffer[0] = 0xC0 | ((alt_code >> 6) & 0x1F);
utf8_buffer[1] = 0x80 | (alt_code & 0x3F);
}
printf("%s", utf8_buffer);
fflush(stdout);
} else {
hid_keyboard_print_char(alt_code);
}
alt_code = 0;
}
is_ansi = false;
}
#endif
/**
* @brief HID Keyboard get char symbol from key code
*
* @param[in] modifier Keyboard modifier data
* @param[in] key_code Keyboard key code
* @param[in] key_char Pointer to key char data
*
* @return true Key scancode converted successfully
* @return false Key scancode unknown
*/
static inline bool hid_keyboard_get_char(uint8_t modifier,
uint8_t key_code,
unsigned char *key_char)
{
uint8_t mod = (hid_keyboard_is_modifier_shift(modifier)) ? 1 : 0;
#if KEYBOARD_ENTER_ALT_ESCAPE
if (hid_keyboard_is_modifier_alt(modifier)) {
if (hid_keyboard_alt_code_processing(key_code)) {
return false;
}
}
#endif
if ((key_code >= HID_KBD_USAGE_A) && (key_code <= HID_KBD_USAGE_QUESTION)) {
*key_char = keycode2ascii[key_code][mod];
} else {
// All other key pressed
return false;
}
return true;
}
/**
* @brief Key Event. Key event with the key code, state and modifier.
*
* @param[in] key_event Pointer to Key Event structure
*
*/
static void key_event_callback(key_event_t *key_event)
{
unsigned char key_char;
hid_print_new_device_report_header(HID_PROTOCOL_KEYBOARD);
if (KEY_STATE_PRESSED == key_event->state) {
if (hid_keyboard_get_char(key_event->modifier,
key_event->key_code, &key_char)) {
hid_keyboard_print_char(key_char);
}
}
}
/**
* @brief Key buffer scan code search.
*
* @param[in] src Pointer to source buffer where to search
* @param[in] key Key scancode to search
* @param[in] length Size of the source buffer
*/
static inline bool key_found(const uint8_t *const src,
uint8_t key,
unsigned int length)
{
for (unsigned int i = 0; i < length; i++) {
if (src[i] == key) {
return true;
}
}
return false;
}
static void usbh_hid_keyboard_report_callback(void *arg, int nbytes)
{
struct usb_hid_kbd_report *kb_report = (struct usb_hid_kbd_report *)arg;
if (nbytes < sizeof(struct usb_hid_kbd_report)) {
return;
}
static uint8_t prev_keys[sizeof(kb_report->key)] = { 0 };
key_event_t key_event;
#if KEYBOARD_ENTER_ALT_ESCAPE
if (!hid_keyboard_is_modifier_alt(kb_report->modifier)) {
hid_keyboard_alt_code_process_complete();
}
#endif
for (int i = 0; i < sizeof(kb_report->key); i++) {
// key has been released verification
if (prev_keys[i] > HID_KBD_USAGE_ERRUNDEF &&
!key_found(kb_report->key, prev_keys[i], sizeof(kb_report->key))) {
key_event.key_code = prev_keys[i];
key_event.modifier = 0;
key_event.state = KEY_STATE_RELEASED;
key_event_callback(&key_event);
}
// key has been pressed verification
if (kb_report->key[i] > HID_KBD_USAGE_ERRUNDEF &&
!key_found(prev_keys, kb_report->key[i], sizeof(kb_report->key))) {
key_event.key_code = kb_report->key[i];
key_event.modifier = kb_report->modifier;
key_event.state = KEY_STATE_PRESSED;
key_event_callback(&key_event);
}
}
memcpy(prev_keys, &kb_report->key, sizeof(kb_report->key));
}
static void usbh_hid_mouse_report_callback(void *arg, int nbytes)
{
struct usb_hid_mouse_report *mouse_report = (struct usb_hid_mouse_report *)arg;
// At least 3 bytes are reported, followed by optional data
if (nbytes < 3) {
return;
}
static int x_pos = 0;
static int y_pos = 0;
// Calculate absolute position from displacement
x_pos += (int8_t)mouse_report->xdisp;
y_pos += (int8_t)mouse_report->ydisp;
hid_print_new_device_report_header(HID_PROTOCOL_MOUSE);
printf("X: %06d\tY: %06d\t|%c|%c|\n",
x_pos, y_pos,
((mouse_report->buttons & HID_MOUSE_INPUT_BUTTON_LEFT) ? 'o' : ' '),
((mouse_report->buttons & HID_MOUSE_INPUT_BUTTON_RIGHT) ? 'o' : ' '));
fflush(stdout);
}
static void usbh_hid_generic_report_callback(void *arg, int nbytes)
{
char *data = arg;
hid_print_new_device_report_header(HID_PROTOCOL_NONE);
for (int i = 0; i < nbytes; i++) {
printf("%02X", data[i]);
}
putchar('\r');
}
//Note: This callback is in the interrupt context
static void usbh_hid_callback(void *arg, int nbytes)
{
BaseType_t xTaskWoken = pdFALSE;
struct usbh_hid *hid_class = (struct usbh_hid *)arg;
hid_int_in_t *hid_intin = (hid_int_in_t *)hid_class->user_data;
if (nbytes <= 0) {
hid_intin->is_active = false;
return;
}
uint8_t sub_class = hid_class->hport->config.intf[hid_class->intf].altsetting[0].intf_desc.bInterfaceSubClass;
uint8_t protocol = hid_class->hport->config.intf[hid_class->intf].altsetting[0].intf_desc.bInterfaceProtocol;
if (s_msg_queue) {
hid_msg_t msg;
if (nbytes <= sizeof(msg.data)) {
msg.protocol = HID_PROTOCOL_NONE;
if (sub_class == HID_SUBCLASS_BOOTIF) {
if (protocol == HID_PROTOCOL_KEYBOARD) {
msg.protocol = HID_PROTOCOL_KEYBOARD;
} else if (protocol == HID_PROTOCOL_MOUSE) {
msg.protocol = HID_PROTOCOL_MOUSE;
}
}
msg.len = nbytes;
memcpy(msg.data, hid_intin->buffer, nbytes);
if (xQueueSendFromISR(s_msg_queue, &msg, &xTaskWoken) != pdTRUE) {
ESP_EARLY_LOGD(TAG, "msg queue full");
}
} else {
ESP_EARLY_LOGD(TAG, "nbytes(%d) > sizeof(msg.data)", nbytes);
}
}
hid_intin->is_active = false;
if (xTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
//Note: If the dispatch_method of esp_timer is ESP_TIMER_ISR, the callback is in the interrupt context.
static void intin_timer_cb(void *arg)
{
int ret;
struct usbh_hid *hid_class = (struct usbh_hid *)arg;
hid_int_in_t *hid_intin = (hid_int_in_t *)hid_class->user_data;
if (hid_intin->is_active) {
return;
}
usbh_int_urb_fill(&hid_class->intin_urb, hid_class->hport, hid_class->intin, hid_intin->buffer, hid_class->intin->wMaxPacketSize, 0,
usbh_hid_callback, hid_class);
hid_intin->is_active = true;
ret = usbh_submit_urb(&hid_class->intin_urb);
if (ret != 0) {
if (ret == -USB_ERR_NOTCONN) {
esp_timer_stop(hid_intin->timer);
return;
}
hid_intin->is_active = false;
ESP_EARLY_LOGE(TAG, "usbh_submit_urb failed");
}
}
static void usbh_hid_msg_task(void *arg)
{
hid_msg_t msg;
while (1) {
BaseType_t err = xQueueReceive(s_msg_queue, &msg, portMAX_DELAY);
if (err != pdTRUE) {
continue;
}
if (msg.protocol == HID_PROTOCOL_KEYBOARD) {
usbh_hid_keyboard_report_callback(msg.data, msg.len);
} else if (msg.protocol == HID_PROTOCOL_MOUSE) {
usbh_hid_mouse_report_callback(msg.data, msg.len);
} else {
usbh_hid_generic_report_callback(msg.data, msg.len);
}
}
vTaskDelete(NULL);
}
static void creat_msg_task(void)
{
if (s_msg_queue == NULL) {
s_msg_queue = xQueueCreate(QUEUE_LEN, sizeof(hid_msg_t));
if (s_msg_queue == NULL) {
ESP_LOGE(TAG, "ringbuf create failed");
return;
}
}
if (s_msg_task_handle == NULL) {
xTaskCreate(usbh_hid_msg_task, "usbh_hid_msg_task", 4096, NULL, 5, &s_msg_task_handle);
}
}
void usbh_hid_run(struct usbh_hid *hid_class)
{
int ret;
esp_err_t err;
uint8_t sub_class = hid_class->hport->config.intf[hid_class->intf].altsetting[0].intf_desc.bInterfaceSubClass;
uint8_t protocol = hid_class->hport->config.intf[hid_class->intf].altsetting[0].intf_desc.bInterfaceProtocol;
ESP_LOGI(TAG, "intf %u, SubClass %u, Protocol %u", hid_class->intf, sub_class, protocol);
if (sub_class == HID_SUBCLASS_BOOTIF) {
ret = usbh_hid_set_protocol(hid_class, HID_PROTOCOL_BOOT);
if (ret < 0) {
return;
}
}
creat_msg_task();
if (hid_class->intin == NULL) {
ESP_LOGW(TAG, "no intin ep desc");
return;
}
hid_int_in_t *hid_intin = heap_caps_calloc(1, sizeof(hid_int_in_t), MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL);
if (hid_intin == NULL) {
ESP_LOGW(TAG, "Malloc failed");
return;
}
hid_intin->buffer = heap_caps_aligned_alloc(CONFIG_USB_ALIGN_SIZE, hid_class->intin->wMaxPacketSize, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
if (hid_intin->buffer == NULL) {
ESP_LOGW(TAG, "Malloc failed");
goto error;
}
hid_intin->is_active = false;
esp_timer_create_args_t timer_cfg = {
.callback = intin_timer_cb,
.arg = hid_class,
#if CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD
.dispatch_method = ESP_TIMER_ISR,
#else
.dispatch_method = ESP_TIMER_TASK,
#endif
.name = "intin timer",
.skip_unhandled_events = true,
};
err = esp_timer_create(&timer_cfg, &hid_intin->timer);
if (err != ESP_OK) {
ESP_LOGE(TAG, "timer create failed");
goto error;
}
hid_class->user_data = hid_intin;
esp_timer_start_periodic(hid_intin->timer, USBH_GET_URB_INTERVAL(hid_class->intin->bInterval, hid_class->hport->speed));
return;
error:
if (hid_intin->buffer) {
heap_caps_free(hid_intin->buffer);
}
heap_caps_free(hid_intin);
}
void usbh_hid_stop(struct usbh_hid *hid_class)
{
hid_int_in_t *hid_intin = (hid_int_in_t *)hid_class->user_data;
if (hid_intin) {
esp_timer_stop(hid_intin->timer);
esp_timer_delete(hid_intin->timer);
heap_caps_free(hid_intin->buffer);
heap_caps_free(hid_intin);
}
ESP_LOGW(TAG, "hid stop");
}

View File

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

View File

@@ -0,0 +1,65 @@
/*
* 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"
#if CONFIG_EXAMPLE_HAL_USE_ESP32_S3_USB_OTG
#include "driver/gpio.h"
#define BOOST_EN 13
#define DEV_VBUS_EN 12
#define LIMIT_EN 17
#define USB_SEL 18
#endif
#include "usbh_core.h"
#include "usbh_hid.h"
static char *TAG = "HOST";
void app_main(void)
{
#if CONFIG_EXAMPLE_HAL_USE_ESP32_S3_USB_OTG
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pull_down_en = 0,
.pull_up_en = 0,
.pin_bit_mask = (1ULL << BOOST_EN) | (1ULL << DEV_VBUS_EN) | (1ULL << LIMIT_EN) | (1ULL << USB_SEL),
};
gpio_config(&io_conf);
gpio_set_level(BOOST_EN, 0);
gpio_set_level(DEV_VBUS_EN, 1);
gpio_set_level(LIMIT_EN, 1);
gpio_set_level(USB_SEL, 1);
#endif
#if CONFIG_EXAMPLE_CHERRYUSB_INIT_DEINIT_LOOP
while (1)
#endif
{
#if CONFIG_EXAMPLE_USB_HOST_RHPORT_HS
usbh_initialize(0, ESP_USB_HS0_BASE);
#else
usbh_initialize(0, ESP_USB_FS0_BASE);
#endif
ESP_LOGI(TAG, "Init usb");
#if CONFIG_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, "Deinit usb");
usbh_deinitialize(0);
#endif
}
}

View File

@@ -0,0 +1,404 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_check.h"
#include "diskio_impl.h"
#include "ffconf.h"
#include "ff.h"
#include "esp_vfs_fat.h"
#include "usbh_core.h"
#include "usbh_msc.h"
static char *TAG = "MSC";
#define DRIVE_STR_LEN 3
typedef struct msc_host_vfs {
uint8_t pdrv;
FATFS *fs;
char base_path[0];
} msc_host_vfs_t;
static struct usbh_msc *s_mscs[FF_VOLUMES] = { NULL };
#define WAIT_BUFFER_TIMEOUT_MS 8000
static SemaphoreHandle_t s_buff_mux = NULL;
static size_t s_buff_size = 0;
static uint8_t *s_buff = NULL;
void ld_include_msc(void)
{
}
static DSTATUS usb_disk_initialize(BYTE pdrv)
{
return RES_OK;
}
static DSTATUS usb_disk_status(BYTE pdrv)
{
return RES_OK;
}
static uint8_t *get_buffer(size_t size)
{
if (xSemaphoreTake(s_buff_mux, WAIT_BUFFER_TIMEOUT_MS / portTICK_PERIOD_MS) != pdTRUE) {
ESP_LOGW(TAG, "wait buffer timeout");
return NULL;
}
if (s_buff_size < size) {
if (s_buff) {
heap_caps_free(s_buff);
}
s_buff = heap_caps_aligned_alloc(CONFIG_USB_ALIGN_SIZE, size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
if (s_buff == NULL) {
s_buff_size = 0;
ESP_LOGW(TAG, "no mem");
xSemaphoreGive(s_buff_mux);
return NULL;
}
s_buff_size = size;
}
return s_buff;
}
static void free_buffer(void)
{
xSemaphoreGive(s_buff_mux);
}
static void check_free_buffer(void)
{
uint8_t *buff;
if (s_buff == NULL || xSemaphoreTake(s_buff_mux, 0) != pdTRUE) {
return;
}
for (size_t i = 0; i < sizeof(s_mscs) / sizeof(s_mscs[0]); i++) {
if (s_mscs[i] != NULL) {
xSemaphoreGive(s_buff_mux);
return;
}
}
buff = s_buff;
s_buff = NULL;
s_buff_size = 0;
xSemaphoreGive(s_buff_mux);
if (buff) {
heap_caps_free(buff);
ESP_LOGI(TAG, "free msc buffer");
}
}
static DRESULT usb_disk_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
{
struct usbh_msc *msc_class;
assert(pdrv < FF_VOLUMES);
msc_class = s_mscs[pdrv];
assert(msc_class);
if (sector >= msc_class->blocknum - count) {
ESP_LOGW(TAG, "%s: sector 0x%"PRIX32" out of range", __FUNCTION__, (uint32_t)sector);
return RES_PARERR;
}
uint8_t *dma_buff = buff;
size_t len = msc_class->blocksize * count;
if (((uint32_t)dma_buff & (CONFIG_USB_ALIGN_SIZE - 1)) || (len & (CONFIG_USB_ALIGN_SIZE - 1))) {
dma_buff = get_buffer(len);
if (dma_buff == NULL) {
return RES_ERROR;
}
}
int ret = usbh_msc_scsi_read10(msc_class, sector, dma_buff, count);
if (dma_buff != buff) {
if (ret == 0) {
memcpy(buff, dma_buff, len);
}
free_buffer();
}
if (ret != 0) {
ESP_LOGE(TAG, "usbh_msc_scsi_read10 failed (%d)", ret);
return RES_ERROR;
}
return RES_OK;
}
static DRESULT usb_disk_write(BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
{
struct usbh_msc *msc_class;
assert(pdrv < FF_VOLUMES);
msc_class = s_mscs[pdrv];
assert(msc_class);
if (sector >= msc_class->blocknum - count) {
ESP_LOGW(TAG, "%s: sector 0x%"PRIX32" out of range", __FUNCTION__, (uint32_t)sector);
return RES_PARERR;
}
const uint8_t *dma_buff = buff;
size_t len = msc_class->blocksize * count;
if (((uint32_t)dma_buff & (CONFIG_USB_ALIGN_SIZE - 1)) || (len & (CONFIG_USB_ALIGN_SIZE - 1))) {
dma_buff = get_buffer(len);
if (dma_buff == NULL) {
return RES_ERROR;
}
memcpy((uint8_t *)dma_buff, buff, len);
}
int ret = usbh_msc_scsi_write10(msc_class, sector, dma_buff, count);
if (dma_buff != buff) {
free_buffer();
}
if (ret != ESP_OK) {
ESP_LOGE(TAG, "usbh_msc_scsi_write10 failed (%d)", ret);
return RES_ERROR;
}
return RES_OK;
}
static DRESULT usb_disk_ioctl(BYTE pdrv, BYTE cmd, void *buff)
{
struct usbh_msc *msc_class;
assert(pdrv < FF_VOLUMES);
msc_class = s_mscs[pdrv];
assert(msc_class);
switch (cmd) {
case CTRL_SYNC:
return RES_OK;
case GET_SECTOR_COUNT:
*((DWORD *) buff) = msc_class->blocknum;
return RES_OK;
case GET_SECTOR_SIZE:
*((WORD *) buff) = msc_class->blocksize;
return RES_OK;
case GET_BLOCK_SIZE:
return RES_ERROR;
}
return RES_ERROR;
}
void ff_diskio_register_msc(BYTE pdrv, struct usbh_msc *msc_class)
{
assert(pdrv < FF_VOLUMES);
static const ff_diskio_impl_t usb_disk_impl = {
.init = &usb_disk_initialize,
.status = &usb_disk_status,
.read = &usb_disk_read,
.write = &usb_disk_write,
.ioctl = &usb_disk_ioctl
};
s_mscs[pdrv] = msc_class;
ff_diskio_register(pdrv, &usb_disk_impl);
}
BYTE ff_diskio_get_pdrv_disk(const struct usbh_msc *msc_class)
{
for (int i = 0; i < FF_VOLUMES; i++) {
if (msc_class == s_mscs[i]) {
return i;
}
}
return 0xff;
}
static esp_err_t msc_host_format(struct usbh_msc *msc_class, size_t allocation_size)
{
ESP_RETURN_ON_FALSE((msc_class != NULL && msc_class->user_data != NULL), ESP_ERR_INVALID_ARG, TAG, "");
void *workbuf = NULL;
const size_t workbuf_size = 4096;
msc_host_vfs_t *vfs = (msc_host_vfs_t *)msc_class->user_data;
char drive[DRIVE_STR_LEN] = {(char)('0' + vfs->pdrv), ':', 0};
ESP_RETURN_ON_FALSE((workbuf = ff_memalloc(workbuf_size)), ESP_ERR_NO_MEM, TAG, "");
// Valid value of cluster size is between sector_size and 128 * sector_size.
size_t cluster_size = MIN(MAX(allocation_size, msc_class->blocksize), 128 * msc_class->blocksize);
ESP_LOGW(TAG, "Formatting card, allocation unit size=%d", cluster_size);
f_mount(0, drive, 0);
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
FRESULT err = f_mkfs(drive, FM_ANY | FM_SFD, cluster_size, workbuf, workbuf_size);
#else
const MKFS_PARM opt = {(BYTE)(FM_ANY | FM_SFD), 0, 0, 0, cluster_size};
FRESULT err = f_mkfs(drive, &opt, workbuf, workbuf_size);
#endif
free(workbuf);
if (err != FR_OK || (err = f_mount(vfs->fs, drive, 0)) != FR_OK) {
ESP_LOGE(TAG, "Formatting failed with error: %d", err);
return ESP_FAIL;
}
return ESP_OK;
}
esp_err_t msc_host_vfs_register(struct usbh_msc *msc_class,
const char *base_path,
const esp_vfs_fat_mount_config_t *mount_config)
{
ESP_RETURN_ON_FALSE((msc_class != NULL && msc_class->user_data == NULL && base_path != NULL && mount_config != NULL), ESP_ERR_INVALID_ARG, TAG, "");
FATFS *fs = NULL;
BYTE pdrv;
if (ff_diskio_get_drive(&pdrv) != ESP_OK) {
ESP_LOGW(TAG, "the maximum count of volumes is already mounted");
return ESP_ERR_NO_MEM;
}
esp_err_t ret;
msc_host_vfs_t *vfs = malloc(sizeof(msc_host_vfs_t) + strlen(base_path) + 1);
ESP_RETURN_ON_FALSE(vfs != NULL, ESP_ERR_NO_MEM, TAG, "");
ff_diskio_register_msc(pdrv, msc_class);
char drive[DRIVE_STR_LEN] = {(char)('0' + pdrv), ':', 0};
strcpy(vfs->base_path, base_path);
vfs->pdrv = pdrv;
ret = esp_vfs_fat_register(base_path, drive, mount_config->max_files, &fs);
ESP_GOTO_ON_ERROR(ret, fail, TAG, "Failed to register filesystem, error=%s", esp_err_to_name(ret));
vfs->fs = fs;
msc_class->user_data = vfs;
if (f_mount(fs, drive, 1) != FR_OK) {
if ((!mount_config->format_if_mount_failed) || msc_host_format(msc_class, mount_config->allocation_unit_size) != ESP_OK) {
ret = ESP_FAIL;
goto fail;
}
}
return ESP_OK;
fail:
msc_class->user_data = NULL;
if (fs) {
f_mount(NULL, drive, 0);
}
esp_vfs_fat_unregister_path(base_path);
ff_diskio_unregister(pdrv);
s_mscs[pdrv] = NULL;
return ret;
}
esp_err_t msc_host_vfs_unregister(struct usbh_msc *msc_class)
{
ESP_RETURN_ON_FALSE((msc_class != NULL && ff_diskio_get_pdrv_disk(msc_class) != 0XFF), ESP_ERR_INVALID_ARG, TAG, "");
msc_host_vfs_t *vfs = (msc_host_vfs_t *)msc_class->user_data;
msc_class->user_data = NULL;
char drive[DRIVE_STR_LEN] = {(char)('0' + vfs->pdrv), ':', 0};
f_mount(NULL, drive, 0);
ff_diskio_unregister(vfs->pdrv);
s_mscs[vfs->pdrv] = NULL;
esp_vfs_fat_unregister_path(vfs->base_path);
heap_caps_free(vfs);
check_free_buffer();
return ESP_OK;
}
static esp_err_t s_example_write_file(const char *path, const char *data)
{
ESP_LOGI(TAG, "Opening file %s", path);
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC);
if (fd < 0) {
ESP_LOGE(TAG, "Failed to open file for writing");
return ESP_FAIL;
}
write(fd, data, strlen(data));
close(fd);
ESP_LOGI(TAG, "File written");
return ESP_OK;
}
static esp_err_t s_example_read_file(const char *path)
{
ESP_LOGI(TAG, "Reading file %s", path);
int fd = open(path, O_RDONLY);
if (fd < 0) {
ESP_LOGE(TAG, "Failed to open file for reading");
return ESP_FAIL;
}
char line[64];
size_t len;
ESP_LOGI(TAG, "Read from file:");
do {
len = read(fd, line, sizeof(line));
ESP_LOG_BUFFER_HEXDUMP(TAG, line, len, ESP_LOG_WARN);
} while (len == sizeof(line));
close(fd);
return ESP_OK;
}
void usbh_msc_run(struct usbh_msc *msc_class)
{
int ret;
if (s_buff_mux == NULL) {
s_buff_mux = xSemaphoreCreateMutex();
if (s_buff_mux == NULL) {
ESP_LOGE(TAG, "create mutex fail");
return;
}
}
ret = usbh_msc_scsi_init(msc_class);
if (ret < 0) {
ESP_LOGE(TAG, "scsi_init error,ret:%d", ret);
return;
}
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
#ifdef CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED
.format_if_mount_failed = true,
#else
.format_if_mount_failed = false,
#endif // EXAMPLE_FORMAT_IF_MOUNT_FAILED
.max_files = 5,
.allocation_unit_size = 4 * 1024
};
ESP_LOGI(TAG, "Mounting filesystem");
if (msc_host_vfs_register(msc_class, "/usb", &mount_config) != ESP_OK) {
ESP_LOGE(TAG, "msc_host_vfs_register fail");
return;
}
ESP_LOGI(TAG, "Filesystem mounted");
const char *file_hello = "/usb/hello.txt";
const char data[] = "Hello, world!\n";
ret = s_example_write_file(file_hello, data);
if (ret != ESP_OK) {
return;
}
ret = s_example_read_file(file_hello);
if (ret != ESP_OK) {
return;
}
return;
}
void usbh_msc_stop(struct usbh_msc *msc_class)
{
msc_host_vfs_unregister(msc_class);
}

View File

@@ -0,0 +1,16 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) 6.0.0 Project Minimal Configuration
#
CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD=y
CONFIG_FATFS_LFN_HEAP=y
CONFIG_FATFS_VFS_FSTAT_BLKSIZE=4096
CONFIG_CHERRYUSB=y
CONFIG_CHERRYUSB_HOST=y
CONFIG_CHERRYUSB_HOST_DWC2_ESP=y
CONFIG_CHERRYUSB_HOST_CDC_ACM=y
CONFIG_CHERRYUSB_HOST_HID=y
CONFIG_CHERRYUSB_HOST_MSC=y
CONFIG_CHERRYUSB_HOST_FTDI=y
CONFIG_CHERRYUSB_HOST_CH34X=y
CONFIG_CHERRYUSB_HOST_CP210X=y
CONFIG_CHERRYUSB_HOST_PL2303=y

View File

@@ -83,6 +83,13 @@ typedef struct {
#define KEYBOARD_ENTER_MAIN_CHAR '\r' #define KEYBOARD_ENTER_MAIN_CHAR '\r'
/* When set to 1 pressing ENTER will be extending with LineFeed during serial debug output */ /* When set to 1 pressing ENTER will be extending with LineFeed during serial debug output */
#define KEYBOARD_ENTER_LF_EXTEND 1 #define KEYBOARD_ENTER_LF_EXTEND 1
/* When set to 1, numbers entered from the numeric keypad while ALT is pressed will be escaped */
#define KEYBOARD_ENTER_ALT_ESCAPE 1
#if KEYBOARD_ENTER_ALT_ESCAPE
static bool is_ansi = false;
static unsigned int alt_code = 0;
#endif
/** /**
* @brief Scancode to ascii table * @brief Scancode to ascii table
@@ -147,6 +154,24 @@ const uint8_t keycode2ascii [57][2] = {
{'/', '?'} /* HID_KEY_SLASH */ {'/', '?'} /* HID_KEY_SLASH */
}; };
/**
* @brief HID Keyboard print char symbol
*
* @param[in] key_char Keyboard char to stdout
*/
static inline void hid_keyboard_print_char(unsigned int key_char)
{
if (!!key_char) {
putchar(key_char);
#if (KEYBOARD_ENTER_LF_EXTEND)
if (KEYBOARD_ENTER_MAIN_CHAR == key_char) {
putchar('\n');
}
#endif // KEYBOARD_ENTER_LF_EXTEND
fflush(stdout);
}
}
/** /**
* @brief Makes new line depending on report output protocol type * @brief Makes new line depending on report output protocol type
* *
@@ -187,6 +212,81 @@ static inline bool hid_keyboard_is_modifier_shift(uint8_t modifier)
return false; return false;
} }
#if KEYBOARD_ENTER_ALT_ESCAPE
/**
* @brief HID Keyboard modifier verification for capitalization application (right or left alt)
*
* @param[in] modifier
* @return true Modifier was pressed (left or right alt)
* @return false Modifier was not pressed (left or right alt)
*
*/
static inline bool hid_keyboard_is_modifier_alt(uint8_t modifier)
{
if (((modifier & HID_LEFT_ALT) == HID_LEFT_ALT) ||
((modifier & HID_RIGHT_ALT) == HID_RIGHT_ALT)) {
return true;
}
return false;
}
/**
* @brief HID Keyboard alt code process(Called when ALT is pressed)
*
* @param[in] key_code Entered key value
* @return true Key values that qualify for ALT escape processing
* @return false Key values that do not comply with ALT escape processing
*
*/
static inline bool hid_keyboard_alt_code_processing(uint8_t key_code)
{
if ((key_code < HID_KEY_KEYPAD_1) || (key_code > HID_KEY_KEYPAD_0)) {
return false;
}
if (key_code == HID_KEY_KEYPAD_0) {
if (alt_code == 0) {
is_ansi = true;
return true;
}
/* Note: Since the keyboard code 0 of the numeric keypad is not keyboard code 1 minus 1, the
* conversion is performed here to facilitate subsequent calculations of the input numbers.
*/
key_code = HID_KEY_KEYPAD_1 - 1;
}
alt_code = alt_code * 10 + (key_code - (HID_KEY_KEYPAD_1 - 1));
return true;
}
/**
* @brief HID Keyboard alt code process complete(Called when ALT is not pressed)
*/
static inline void hid_keyboard_alt_code_process_complete(void)
{
if (alt_code > 0) {
alt_code = alt_code & 0xff;
if (is_ansi || alt_code == 0) {
char utf8_buffer[8] = { 0 };
if (alt_code == 0) {
alt_code = 0x100;
}
//ANSI is processed as UTF8
if (alt_code <= 0x7F) {
utf8_buffer[0] = (char)alt_code;
} else {
utf8_buffer[0] = 0xC0 | ((alt_code >> 6) & 0x1F);
utf8_buffer[1] = 0x80 | (alt_code & 0x3F);
}
printf("%s", utf8_buffer);
fflush(stdout);
} else {
hid_keyboard_print_char(alt_code);
}
alt_code = 0;
}
is_ansi = false;
}
#endif
/** /**
* @brief HID Keyboard get char symbol from key code * @brief HID Keyboard get char symbol from key code
* *
@@ -203,6 +303,16 @@ static inline bool hid_keyboard_get_char(uint8_t modifier,
{ {
uint8_t mod = (hid_keyboard_is_modifier_shift(modifier)) ? 1 : 0; uint8_t mod = (hid_keyboard_is_modifier_shift(modifier)) ? 1 : 0;
#if KEYBOARD_ENTER_ALT_ESCAPE
if (hid_keyboard_is_modifier_alt(modifier)) {
// ALT modifier is still pressed
if (hid_keyboard_alt_code_processing(key_code)) {
// ALT code processed, no need to go further
return false;
}
}
#endif
if ((key_code >= HID_KEY_A) && (key_code <= HID_KEY_SLASH)) { if ((key_code >= HID_KEY_A) && (key_code <= HID_KEY_SLASH)) {
*key_char = keycode2ascii[key_code][mod]; *key_char = keycode2ascii[key_code][mod];
} else { } else {
@@ -213,24 +323,6 @@ static inline bool hid_keyboard_get_char(uint8_t modifier,
return true; return true;
} }
/**
* @brief HID Keyboard print char symbol
*
* @param[in] key_char Keyboard char to stdout
*/
static inline void hid_keyboard_print_char(unsigned int key_char)
{
if (!!key_char) {
putchar(key_char);
#if (KEYBOARD_ENTER_LF_EXTEND)
if (KEYBOARD_ENTER_MAIN_CHAR == key_char) {
putchar('\n');
}
#endif // KEYBOARD_ENTER_LF_EXTEND
fflush(stdout);
}
}
/** /**
* @brief Key Event. Key event with the key code, state and modifier. * @brief Key Event. Key event with the key code, state and modifier.
* *
@@ -289,6 +381,12 @@ static void hid_host_keyboard_report_callback(const uint8_t *const data, const i
static uint8_t prev_keys[HID_KEYBOARD_KEY_MAX] = { 0 }; static uint8_t prev_keys[HID_KEYBOARD_KEY_MAX] = { 0 };
key_event_t key_event; key_event_t key_event;
#if KEYBOARD_ENTER_ALT_ESCAPE
if (!hid_keyboard_is_modifier_alt(kb_report->modifier.val)) {
hid_keyboard_alt_code_process_complete();
}
#endif
for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) { for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) {
// key has been released verification // key has been released verification