diff --git a/examples/peripherals/usb/host/cherryusb_host/CMakeLists.txt b/examples/peripherals/usb/host/cherryusb_host/CMakeLists.txt new file mode 100644 index 0000000000..580c0a8d0f --- /dev/null +++ b/examples/peripherals/usb/host/cherryusb_host/CMakeLists.txt @@ -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) diff --git a/examples/peripherals/usb/host/cherryusb_host/README.md b/examples/peripherals/usb/host/cherryusb_host/README.md new file mode 100644 index 0000000000..d682c6d74b --- /dev/null +++ b/examples/peripherals/usb/host/cherryusb_host/README.md @@ -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 +``` diff --git a/examples/peripherals/usb/host/cherryusb_host/main/CMakeLists.txt b/examples/peripherals/usb/host/cherryusb_host/main/CMakeLists.txt new file mode 100644 index 0000000000..b55ad345ce --- /dev/null +++ b/examples/peripherals/usb/host/cherryusb_host/main/CMakeLists.txt @@ -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() diff --git a/examples/peripherals/usb/host/cherryusb_host/main/Kconfig.projbuild b/examples/peripherals/usb/host/cherryusb_host/main/Kconfig.projbuild new file mode 100644 index 0000000000..17e0682741 --- /dev/null +++ b/examples/peripherals/usb/host/cherryusb_host/main/Kconfig.projbuild @@ -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 diff --git a/examples/peripherals/usb/host/cherryusb_host/main/cdc_acm.c b/examples/peripherals/usb/host/cherryusb_host/main/cdc_acm.c new file mode 100644 index 0000000000..f82ed6be4d --- /dev/null +++ b/examples/peripherals/usb/host/cherryusb_host/main/cdc_acm.c @@ -0,0 +1,308 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#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 diff --git a/examples/peripherals/usb/host/cherryusb_host/main/hid.c b/examples/peripherals/usb/host/cherryusb_host/main/hid.c new file mode 100644 index 0000000000..ae50189eda --- /dev/null +++ b/examples/peripherals/usb/host/cherryusb_host/main/hid.c @@ -0,0 +1,413 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#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"); +} diff --git a/examples/peripherals/usb/host/cherryusb_host/main/idf_component.yml b/examples/peripherals/usb/host/cherryusb_host/main/idf_component.yml new file mode 100644 index 0000000000..8a39822972 --- /dev/null +++ b/examples/peripherals/usb/host/cherryusb_host/main/idf_component.yml @@ -0,0 +1,3 @@ +## IDF Component Manager Manifest File +dependencies: + cherry-embedded/cherryusb: ~=1.5.0~4 diff --git a/examples/peripherals/usb/host/cherryusb_host/main/main.c b/examples/peripherals/usb/host/cherryusb_host/main/main.c new file mode 100644 index 0000000000..4d8ab4adfb --- /dev/null +++ b/examples/peripherals/usb/host/cherryusb_host/main/main.c @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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 + +} diff --git a/examples/peripherals/usb/host/cherryusb_host/sdkconfig.defaults b/examples/peripherals/usb/host/cherryusb_host/sdkconfig.defaults new file mode 100644 index 0000000000..39a33d32c7 --- /dev/null +++ b/examples/peripherals/usb/host/cherryusb_host/sdkconfig.defaults @@ -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