feat(examples/peripherals/usb/cherryusb/host/cherryusb_host): add CherryUSB host example

This commit is contained in:
LiPeng
2025-06-30 18:25:59 +08:00
committed by Li Peng
parent f866fbd9bc
commit 6069bdcdca
9 changed files with 931 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
idf_build_set_property(MINIMAL_BUILD ON)
project(cherryusb_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) and serial port devices (such as CDC_ACM, CH34x, CP210x, PL2303, FTDI FT23x/FT423x devices).
## How to use example
If you need to enable support for FTDI, please enable the following configuration
Run `idf.py menuconfig` and in `Component config → CherryUSB Configuration → Enable usb host mode → Enable usb ftdi driver` enable.
### 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,44 @@
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")
endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "."
)
if(CONFIG_CHERRYUSB_HOST_HID)
# Make sure the definitions in hid.c are linked correctly
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u usbh_hid_run_real")
endif()
if(CONFIG_CHERRYUSB_HOST_CDC_ACM)
# Make sure the definitions in hid.c are linked correctly
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u usbh_cdc_acm_run_real")
endif()
if(CONFIG_CHERRYUSB_HOST_FTDI)
# Make sure the definitions in hid.c are linked correctly
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u usbh_ftdi_run_real")
endif()
if(CONFIG_CHERRYUSB_HOST_CH34X)
# Make sure the definitions in hid.c are linked correctly
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u usbh_ch34x_run_real")
endif()
if(CONFIG_CHERRYUSB_HOST_CP210X)
# Make sure the definitions in hid.c are linked correctly
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u usbh_cp210x_run_real")
endif()
if(CONFIG_CHERRYUSB_HOST_PL2303)
# Make sure the definitions in hid.c are linked correctly
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u usbh_pl2303_run_real")
endif()

View File

@@ -0,0 +1,12 @@
menu "Example Configuration"
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_CHERRYUSB_INIT_DEINIT_LOOP
bool "Perform init deinit of cherryusb stack in a loop"
default n
endmenu

View File

@@ -0,0 +1,308 @@
/*
* 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 "esp_timer.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";
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);
#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);
}
static void serial_in_cb(void *arg, int nbytes)
{
// ESP_EARLY_LOGE(TAG, "in_cb: %d bytes", nbytes);
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;
}
for (size_t i = 0; i < nbytes; i++) {
if (data[i] >= 0x20 && data[i] <= 0x7e) {
esp_rom_printf("%c", data[i]);
} else {
esp_rom_printf("[%02x]", data[i]);
}
}
esp_rom_printf("\r");
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;
}
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;
}
#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;
}
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_run_real(struct usbh_cdc_acm *cdc_acm_class) __attribute__((alias("usbh_cdc_acm_run")));
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;
}
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_run_real(struct usbh_ftdi *cdc_acm_class) __attribute__((alias("usbh_ftdi_run")));
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;
}
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_run_real(struct usbh_ch34x *cdc_acm_class) __attribute__((alias("usbh_ch34x_run")));
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;
}
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_run_real(struct usbh_cp210x *cdc_acm_class) __attribute__((alias("usbh_cp210x_run")));
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;
}
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_run_real(struct usbh_pl2303 *cdc_acm_class) __attribute__((alias("usbh_pl2303_run")));
void usbh_pl2303_stop(struct usbh_pl2303 *pl2303_class)
{
free_serial_buffer(pl2303_class->user_data);
}
#endif

View File

@@ -0,0 +1,413 @@
/*
* 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 "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;
/* 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
/**
* @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 */
};
/**
* @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;
esp_rom_printf("\r\n");
if (proto == HID_PROTOCOL_MOUSE) {
esp_rom_printf("Mouse\r\n");
} else if (proto == HID_PROTOCOL_KEYBOARD) {
esp_rom_printf("Keyboard\r\n");
} else {
esp_rom_printf("Generic\r\n");
}
}
}
/**
* @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;
}
/**
* @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 ((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 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) {
esp_rom_printf("%c", key_char);
#if (KEYBOARD_ENTER_LF_EXTEND)
if (KEYBOARD_ENTER_MAIN_CHAR == key_char) {
esp_rom_printf("\n");
}
#endif // KEYBOARD_ENTER_LF_EXTEND
}
}
/**
* @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;
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);
esp_rom_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' : ' '));
}
static void usbh_hid_generic_report_callback(void *arg, int nbytes)
{
uint8_t *data = arg;
hid_print_new_device_report_header(HID_PROTOCOL_NONE);
for (size_t i = 0; i < nbytes; i++) {
esp_rom_printf("%02x ", data[i]);
}
esp_rom_printf("\r");
}
static void usbh_hid_callback(void *arg, int nbytes)
{
struct usbh_hid *hid_class = (struct usbh_hid *)arg;
hid_int_in_t *hid_intin = (hid_int_in_t *)hid_class->user_data;
hid_intin->is_active = false;
if (nbytes <= 0) {
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;
usbh_complete_callback_t complete_cb = usbh_hid_generic_report_callback;
if (sub_class == HID_SUBCLASS_BOOTIF) {
if (protocol == HID_PROTOCOL_KEYBOARD) {
complete_cb = usbh_hid_keyboard_report_callback;
} else if (protocol == HID_PROTOCOL_MOUSE) {
complete_cb = usbh_hid_mouse_report_callback;
}
}
complete_cb(hid_intin->buffer, nbytes);
}
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");
}
}
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;
}
}
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;
//周期间隔低速、全速单位为1ms高速和超高速是125us
ret = USBH_GET_URB_INTERVAL(hid_class->intin->bInterval, hid_class->hport->speed);
ret = (hid_class->hport->speed < USB_SPEED_HIGH) ? (ret * 1000) : (ret * 125);
esp_timer_start_periodic(hid_intin->timer, ret);
return;
error:
if (hid_intin->buffer) {
heap_caps_free(hid_intin->buffer);
}
heap_caps_free(hid_intin);
}
void usbh_hid_run_real(struct usbh_hid *) __attribute__((alias("usbh_hid_run")));
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.0~4

View File

@@ -0,0 +1,59 @@
/*
* 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 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 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 EXAMPLE_CHERRYUSB_INIT_DEINIT_LOOP
reinit:
#endif
usbh_initialize(0, ESP_USBH_BASE);
ESP_LOGI(TAG, "Init usb");
#if EXAMPLE_CHERRYUSB_INIT_DEINIT_LOOP
for (int i = 10; i >= 0; i--) {
ESP_LOGW(TAG, "Deinit usb after %d seconds...", i);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
ESP_LOGW(TAG, "Deinit usb");
usbh_deinitialize(0);
goto reinit;
#endif
}

View File

@@ -0,0 +1,12 @@
# 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_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_CH34X=y
CONFIG_CHERRYUSB_HOST_CP210X=y
CONFIG_CHERRYUSB_HOST_PL2303=y