Merge branch 'feat/add_cherryusb_demo_v5.5' into 'release/v5.5'

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

See merge request espressif/esp-idf!41700
This commit is contained in:
Jiang Jiang Jian
2025-10-21 14:00:57 +08:00
19 changed files with 2230 additions and 20 deletions
@@ -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.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
idf_build_set_property(MINIMAL_BUILD ON)
project(cherryusb_host)
@@ -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
```
@@ -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()
@@ -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
@@ -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
@@ -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");
}
@@ -0,0 +1,3 @@
## IDF Component Manager Manifest File
dependencies:
cherry-embedded/cherryusb: =1.5.2~2
@@ -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
}
}
@@ -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);
}
@@ -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
@@ -1,6 +1,6 @@
# 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)
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -83,6 +83,13 @@ typedef struct {
#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
@@ -147,6 +154,24 @@ const uint8_t keycode2ascii [57][2] = {
{'/', '?'} /* 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
*
@@ -187,6 +212,81 @@ static inline bool hid_keyboard_is_modifier_shift(uint8_t modifier)
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
*
@@ -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;
#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)) {
*key_char = keycode2ascii[key_code][mod];
} else {
@@ -213,24 +323,6 @@ static inline bool hid_keyboard_get_char(uint8_t modifier,
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.
*
@@ -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 };
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++) {
// key has been released verification