diff --git a/examples/peripherals/usb/device/cherryusb_serial_device/CMakeLists.txt b/examples/peripherals/usb/device/cherryusb_serial_device/CMakeLists.txt new file mode 100644 index 0000000000..96ff33bcfa --- /dev/null +++ b/examples/peripherals/usb/device/cherryusb_serial_device/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.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +idf_build_set_property(MINIMAL_BUILD ON) +project(cherryusb_serial_device) diff --git a/examples/peripherals/usb/device/cherryusb_serial_device/README.md b/examples/peripherals/usb/device/cherryusb_serial_device/README.md new file mode 100644 index 0000000000..48e1000333 --- /dev/null +++ b/examples/peripherals/usb/device/cherryusb_serial_device/README.md @@ -0,0 +1,85 @@ +| Supported Targets | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | -------- | + +# CherryUSB Serial Device Example + +Starts a CDC Device, and sends back any received data from the host. + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +## How to use example + +This example can also be configured to act as double serial device. +Run `idf.py menuconfig` and in `Example Configuration → Enable usb cdc acm two channel` enable. + +### Hardware Required + +Any ESP board that have USB-OTG supported. + +#### Pin Assignment + +_Note:_ In case your board doesn't have micro-USB connector connected to USB-OTG peripheral, you may have to DIY a cable and connect **D+** and **D-** to the pins listed below. + +See common pin assignments for USB Device examples from [upper level](../../README.md#common-pin-assignments). + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +```bash +idf.py -p PORT flash monitor +``` + +(Replace PORT with the name of the serial port to use.) + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example output + +When running, the example will print the following output: + +``` +I (271) main_task: Started on CPU0 +I (291) main_task: Calling app_main() +[I/USB] cherryusb, version: v1.4.3 +[I/USB] ========== dwc2 udc params ========== +[I/USB] CID:00000000 +[I/USB] GSNPSID:4f54400a +[I/USB] GHWCFG1:00000000 +[I/USB] GHWCFG2:224dd930 +[I/USB] GHWCFG3:00c804b5 +[I/USB] GHWCFG4:d3f0a030 +[I/USB] dwc2 fsphy type:1, hsphy type:0, dma support:2 +[I/USB] dwc2 has 7 endpoints and dfifo depth(32-bit words) is 200, default config: 7 endpoints +[I/USB] ================================= +[I/USB] fifo0 size:0010, offset:0050 +[I/USB] fifo1 size:0010, offset:0060 +[I/USB] fifo2 size:0010, offset:0070 +[I/USB] fifo3 size:0010, offset:0080 +[I/USB] fifo4 size:0010, offset:0090 +[I/USB] fifo5 size:0010, offset:00a0 +[I/USB] fifo6 size:0010, offset:00b0 +I (391) device_cdc_main: usb cdc acm init done +I (721) device_cdc_main: intf:0, dtr:0 +I (721) device_cdc_main: intf:0, rts:0 +I (2981) device_cdc_main: intf:0, dtr:0 +I (2981) device_cdc_main: intf:0, rts:0 +I (2981) device_cdc_main: intf:0, dtr:0 +I (2981) device_cdc_main: intf:0, rts:0 +I (2981) device_cdc_main: intf:0, dtr:0 +I (2981) device_cdc_main: intf:0, rts:0 +I (2991) device_cdc_main: intf:0, dtr:0 +I (2991) device_cdc_main: intf:0, rts:0 +I (2991) device_cdc_main: intf:0, dtr:0 +I (2991) device_cdc_main: intf:0, rts:0 +I (3001) device_cdc_main: intf:0, dtr:0 +I (3001) device_cdc_main: intf:0, rts:0 +I (6011) device_cdc_main: actual out len:13 +I (6021) device_cdc_main: actual in len:13 +I (10411) device_cdc_main: actual out len:13 +I (10421) device_cdc_main: actual in len:13 +I (11111) device_cdc_main: intf:0, dtr:0 +I (11111) device_cdc_main: intf:0, rts:0 +``` diff --git a/examples/peripherals/usb/device/cherryusb_serial_device/main/CMakeLists.txt b/examples/peripherals/usb/device/cherryusb_serial_device/main/CMakeLists.txt new file mode 100644 index 0000000000..6795aeaca4 --- /dev/null +++ b/examples/peripherals/usb/device/cherryusb_serial_device/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "device_cdc_main.c" + INCLUDE_DIRS "." +) diff --git a/examples/peripherals/usb/device/cherryusb_serial_device/main/Kconfig.projbuild b/examples/peripherals/usb/device/cherryusb_serial_device/main/Kconfig.projbuild new file mode 100644 index 0000000000..cbff764142 --- /dev/null +++ b/examples/peripherals/usb/device/cherryusb_serial_device/main/Kconfig.projbuild @@ -0,0 +1,38 @@ +menu "Example Configuration" + + choice EXAMPLE_USB_DEVICE_RHPORT + prompt "USB Device Peripheral" + default EXAMPLE_USB_DEVICE_RHPORT_HS if IDF_TARGET_ESP32P4 + default EXAMPLE_USB_DEVICE_RHPORT_FS + help + Allows set the USB Peripheral Controller for USB device. + + - High-speed (USB OTG2.0 Peripheral for High-, Full- and Low-speed) + - Full-speed (USB OTG1.1 Peripheral for Full- and Low-speed) + + config EXAMPLE_USB_DEVICE_RHPORT_HS + bool "OTG2.0" + depends on IDF_TARGET_ESP32P4 + config EXAMPLE_USB_DEVICE_RHPORT_FS + bool "OTG1.1" + + endchoice + + config EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL + bool "Enable usb cdc acm two channel" + default n + + config EXAMPLE_CHERRYUSB_INIT_DEINIT_LOOP + bool "Perform init deinit of cherryusb stack in a loop" + default n + + config EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS + bool "set usb read buffer size to MPS" + default y + help + If this option is disabled, a larger receive buffer will be configured for DMA at a time, + which may improve efficiency a little. However, when the host sends data with a length that + is an integer multiple of MPS, the host may not send ZLP, resulting in failure to report + the receipt of data in a timely manner. + +endmenu diff --git a/examples/peripherals/usb/device/cherryusb_serial_device/main/device_cdc_main.c b/examples/peripherals/usb/device/cherryusb_serial_device/main/device_cdc_main.c new file mode 100644 index 0000000000..520b62da63 --- /dev/null +++ b/examples/peripherals/usb/device/cherryusb_serial_device/main/device_cdc_main.c @@ -0,0 +1,410 @@ +/* + * 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" + +#include "usbd_core.h" +#include "usbd_cdc_acm.h" + +static char *TAG = "device_cdc_main"; + +#ifndef CONFIG_EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL +#define CDC_ACM_CHANNEL_NUM 1 +#else +#define CDC_ACM_CHANNEL_NUM 2 +#endif + +#define INTERFACES_NUM (2 * CDC_ACM_CHANNEL_NUM) + +#define NOTIFY_USB_CONFIGURED_BIT BIT0 +#define NOTIFY_EXIT_BIT BIT1 +#define NOTIFY_EP_BIT BIT2 + +static TaskHandle_t s_task_handle = NULL; + +/*!< endpoint address */ +#define CDC_IN_EP 0x81 +#define CDC_OUT_EP 0x01 +#define CDC_INT_EP 0x83 +#if CDC_ACM_CHANNEL_NUM >= 2 +#define CDC_IN_EP1 0x82 +#define CDC_OUT_EP1 0x02 +#define CDC_INT_EP1 0x84 +#endif + +#define USBD_VID 0xFFFF +#define USBD_PID 0xFFFF +#define USBD_MAX_POWER 100 +#define USBD_LANGID_STRING 1033 + +/*!< config descriptor size */ +#define USB_CONFIG_SIZE (9 + CDC_ACM_DESCRIPTOR_LEN * CDC_ACM_CHANNEL_NUM) + +#define CDC_FS_MAX_MPS 64 +#define CDC_HS_MAX_MPS 512 + +#ifdef CONFIG_USB_HS +#define CDC_MAX_MPS CDC_HS_MAX_MPS +#else +#define CDC_MAX_MPS CDC_FS_MAX_MPS +#endif + +typedef struct { + uint8_t rx_addr; + uint8_t tx_addr; + bool rx_busy_flag; + bool tx_busy_flag; + uint32_t read_len; + uint32_t write_len; +} ep_status_t; + +static ep_status_t s_ep_status[CDC_ACM_CHANNEL_NUM] = { + { CDC_OUT_EP, CDC_IN_EP, false, false, 0, 0 }, +#ifdef CONFIG_EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL + { CDC_OUT_EP1, CDC_IN_EP1, false, false, 0, 0 }, +#endif +}; + +#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) + +#define WRITE_BUFFER_SIZE ALIGN_UP(ALIGN_UP(2048, CDC_MAX_MPS), CONFIG_USB_ALIGN_SIZE) + +#if CONFIG_EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS +#define READ_BUFFER_SIZE ALIGN_UP(CDC_MAX_MPS, CONFIG_USB_ALIGN_SIZE) +#else +#define READ_BUFFER_SIZE WRITE_BUFFER_SIZE +#endif + +static uint32_t s_mps = CDC_MAX_MPS; +static DRAM_DMA_ALIGNED_ATTR uint8_t read_buffer[CDC_ACM_CHANNEL_NUM][READ_BUFFER_SIZE]; +static DRAM_DMA_ALIGNED_ATTR uint8_t write_buffer[CDC_ACM_CHANNEL_NUM][WRITE_BUFFER_SIZE]; + +static const uint8_t device_descriptor[] = { + USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0xEF, 0x02, 0x01, USBD_VID, USBD_PID, 0x0100, 0x01) +}; + +static const uint8_t config_descriptor_fs[] = { + USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, INTERFACES_NUM, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), + CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, CDC_FS_MAX_MPS, 0x02), +#ifdef CONFIG_EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL + CDC_ACM_DESCRIPTOR_INIT(0x02, CDC_INT_EP1, CDC_OUT_EP1, CDC_IN_EP1, CDC_FS_MAX_MPS, 0x02), +#endif +}; + +#ifdef CONFIG_USB_HS +static const uint8_t config_descriptor_hs[] = { + USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, INTERFACES_NUM, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), + CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, CDC_HS_MAX_MPS, 0x02), +#ifdef CONFIG_EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL + CDC_ACM_DESCRIPTOR_INIT(0x02, CDC_INT_EP1, CDC_OUT_EP1, CDC_IN_EP1, CDC_HS_MAX_MPS, 0x02), +#endif +}; +#endif + +static const uint8_t device_quality_descriptor[] = { + /////////////////////////////////////// + /// device qualifier descriptor + /////////////////////////////////////// + 0x0a, + USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x40, + 0x00, + 0x00, +}; + +static const char *string_descriptors[] = { + (const char[]) + { + 0x09, 0x04 + }, /* Langid */ + "CherryUSB", /* Manufacturer */ + "CherryUSB CDC MULTI DEMO", /* Product */ + "2025060518", /* Serial Number */ +}; + +static const uint8_t *device_descriptor_callback(uint8_t speed) +{ + return device_descriptor; +} + +static const uint8_t *config_descriptor_callback(uint8_t speed) +{ +#ifdef CONFIG_USB_HS + if (speed >= USB_SPEED_HIGH) { + s_mps = CDC_HS_MAX_MPS; + return config_descriptor_hs; + } + s_mps = CDC_FS_MAX_MPS; +#endif + return config_descriptor_fs; +} + +static const uint8_t *device_quality_descriptor_callback(uint8_t speed) +{ + return device_quality_descriptor; +} + +static const char *string_descriptor_callback(uint8_t speed, uint8_t index) +{ + if (index > 3) { + return NULL; + } + return string_descriptors[index]; +} + +const struct usb_descriptor cdc_descriptor = { + .device_descriptor_callback = device_descriptor_callback, + .config_descriptor_callback = config_descriptor_callback, + .device_quality_descriptor_callback = device_quality_descriptor_callback, + .string_descriptor_callback = string_descriptor_callback +}; + +//Note: This callback is in the interrupt context +void usbd_event_handler(uint8_t busid, uint8_t event) +{ + switch (event) { + case USBD_EVENT_RESET: + break; + case USBD_EVENT_CONNECTED: + break; + case USBD_EVENT_DISCONNECTED: + break; + case USBD_EVENT_RESUME: + break; + case USBD_EVENT_SUSPEND: + break; + case USBD_EVENT_CONFIGURED: { + BaseType_t HPTaskAwoken = pdFALSE; + xTaskNotifyFromISR(s_task_handle, NOTIFY_USB_CONFIGURED_BIT, eSetBits, &HPTaskAwoken); + if (HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } + break; + } + case USBD_EVENT_SET_REMOTE_WAKEUP: + break; + case USBD_EVENT_CLR_REMOTE_WAKEUP: + break; + + default: + break; + } +} + +//Note: This callback is in the interrupt context +static void usbd_cdc_acm_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ + ESP_EARLY_LOGI(TAG, "ep 0x%02X actual out len:%d", ep, nbytes); + BaseType_t HPTaskAwoken = pdFALSE; + for (size_t i = 0; i < CDC_ACM_CHANNEL_NUM; i++) { + ep_status_t *ep_status = &s_ep_status[i]; + if (ep_status->rx_addr == ep) { + if (nbytes > 0) { + if (ep_status->write_len == 0) { + memcpy(write_buffer[i], read_buffer[i], nbytes); + ep_status->write_len = nbytes; +#if CONFIG_EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS + usbd_ep_start_read(0, ep, read_buffer[i], s_mps); +#else + usbd_ep_start_read(0, ep, read_buffer[i], sizeof(read_buffer[0])); +#endif + } else { + ep_status->read_len = nbytes; + ep_status->rx_busy_flag = false; + } + xTaskNotifyFromISR(s_task_handle, NOTIFY_EP_BIT, eSetBits, &HPTaskAwoken); + } else { +#if CONFIG_EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS + usbd_ep_start_read(0, ep, read_buffer[i], s_mps); +#else + usbd_ep_start_read(0, ep, read_buffer[i], sizeof(read_buffer[i])); +#endif + } + break; + } + } + if (HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } +} + +//Note: This callback is in the interrupt context +static void usbd_cdc_acm_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ + BaseType_t HPTaskAwoken = pdFALSE; + ESP_EARLY_LOGI(TAG, "ep 0x%02X actual in len:%d", ep, nbytes); + if ((nbytes % s_mps) == 0 && nbytes) { + usbd_ep_start_write(0, ep, NULL, 0); + } else { + for (size_t i = 0; i < CDC_ACM_CHANNEL_NUM; i++) { + ep_status_t *ep_status = &s_ep_status[i]; + if (ep_status->tx_addr == ep) { + ep_status->write_len = 0; + ep_status->tx_busy_flag = false; + xTaskNotifyFromISR(s_task_handle, NOTIFY_EP_BIT, eSetBits, &HPTaskAwoken); + break; + } + } + } + if (HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } +} + +struct usbd_endpoint cdc_out_ep = { + .ep_addr = CDC_OUT_EP, + .ep_cb = usbd_cdc_acm_bulk_out +}; + +struct usbd_endpoint cdc_in_ep = { + .ep_addr = CDC_IN_EP, + .ep_cb = usbd_cdc_acm_bulk_in +}; + +struct usbd_interface intf0; +struct usbd_interface intf1; + +#ifdef CONFIG_EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL +struct usbd_endpoint cdc_out_ep1 = { + .ep_addr = CDC_OUT_EP1, + .ep_cb = usbd_cdc_acm_bulk_out +}; + +struct usbd_endpoint cdc_in_ep1 = { + .ep_addr = CDC_IN_EP1, + .ep_cb = usbd_cdc_acm_bulk_in +}; + +struct usbd_interface intf2; +struct usbd_interface intf3; +#endif + +static void cdc_acm_init(void) +{ + usbd_desc_register(0, &cdc_descriptor); + + usbd_add_interface(0, usbd_cdc_acm_init_intf(0, &intf0)); + usbd_add_interface(0, usbd_cdc_acm_init_intf(0, &intf1)); + usbd_add_endpoint(0, &cdc_out_ep); + usbd_add_endpoint(0, &cdc_in_ep); +#ifdef CONFIG_EXAMPLE_CHERRYUSB_CDC_ACM_TWO_CHANNEL + usbd_add_interface(0, usbd_cdc_acm_init_intf(0, &intf2)); + usbd_add_interface(0, usbd_cdc_acm_init_intf(0, &intf3)); + usbd_add_endpoint(0, &cdc_out_ep1); + usbd_add_endpoint(0, &cdc_in_ep1); +#endif + +#if CONFIG_EXAMPLE_USB_DEVICE_RHPORT_HS + usbd_initialize(0, ESP_USB_HS0_BASE, usbd_event_handler); +#else + usbd_initialize(0, ESP_USB_FS0_BASE, usbd_event_handler); +#endif +} + +//Note: This callback is in the interrupt context +void usbd_cdc_acm_set_dtr(uint8_t busid, uint8_t intf, bool dtr) +{ + ESP_EARLY_LOGI(TAG, "intf:%u, dtr:%d", intf, dtr); +} + +//Note: This callback is in the interrupt context +void usbd_cdc_acm_set_rts(uint8_t busid, uint8_t intf, bool rts) +{ + ESP_EARLY_LOGI(TAG, "intf:%u, rts:%d", intf, rts); +} + +void cdc_acm_task(void *arg) +{ + uint32_t notify_value = 0; + ESP_LOGI(TAG, "usb cdc acm task start"); + // Initialize the USB driver and CDC interface + cdc_acm_init(); + ESP_LOGI(TAG, "usb cdc acm init done"); + + while (1) { + xTaskNotifyWait(0, 0xFFFFFFFF, ¬ify_value, portMAX_DELAY); + if (notify_value & NOTIFY_EXIT_BIT) { + break; + } + if (notify_value & NOTIFY_USB_CONFIGURED_BIT) { + /* setup first out ep read transfer */ + for (size_t i = 0; i < sizeof(s_ep_status) / sizeof(s_ep_status[0]); i++) { + ep_status_t *ep_status = &s_ep_status[i]; + ep_status->write_len = 0; + ep_status->tx_busy_flag = false; + + ep_status->read_len = 0; + ep_status->rx_busy_flag = true; +#if CONFIG_EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS + usbd_ep_start_read(0, ep_status->rx_addr, read_buffer[i], s_mps); +#else + usbd_ep_start_read(0, ep_status->rx_addr, read_buffer[i], sizeof(read_buffer[0])); +#endif + } + } + for (size_t i = 0; i < sizeof(s_ep_status) / sizeof(s_ep_status[0]); i++) { + ep_status_t *ep_status = &s_ep_status[i]; + if (ep_status->tx_busy_flag == false) { + if (ep_status->write_len == 0 && ep_status->read_len > 0) { + memcpy(&write_buffer[i][0], &read_buffer[i][0], ep_status->read_len); + ep_status->write_len = ep_status->read_len; + ep_status->read_len = 0; + } + if (ep_status->write_len > 0) { + ep_status->tx_busy_flag = true; + usbd_ep_start_write(0, ep_status->tx_addr, write_buffer[i], ep_status->write_len); + } + } + if (ep_status->rx_busy_flag == false) { + if (ep_status->read_len == 0) { + ep_status->rx_busy_flag = true; +#if CONFIG_EXAMPLE_CHERRYUSB_SET_READ_BUFFER_SIZE_MPS + usbd_ep_start_read(0, ep_status->rx_addr, read_buffer[i], s_mps); +#else + usbd_ep_start_read(0, ep_status->rx_addr, read_buffer[i], sizeof(read_buffer[i])); +#endif + } + } + } + } + + usbd_deinitialize(0); + ESP_LOGW(TAG, "task exit"); + s_task_handle = NULL; + vTaskDelete(NULL); +} + +void app_main(void) +{ +#if CONFIG_EXAMPLE_CHERRYUSB_INIT_DEINIT_LOOP + while (1) +#endif + { + xTaskCreatePinnedToCore(&cdc_acm_task, "cdc_acm_task", 1024 * 3, NULL, 10, &s_task_handle, 0); + +#if CONFIG_EXAMPLE_CHERRYUSB_INIT_DEINIT_LOOP + for (int i = 10; i >= 0; i--) { + ESP_LOGW(TAG, "Deinit usb after %d seconds...", i); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + ESP_LOGW(TAG, "Notify deinit usb"); + xTaskNotify(s_task_handle, NOTIFY_EXIT_BIT, eSetBits); + while (s_task_handle) { + vTaskDelay(100 / portTICK_PERIOD_MS); + } +#endif + } + +} diff --git a/examples/peripherals/usb/device/cherryusb_serial_device/main/idf_component.yml b/examples/peripherals/usb/device/cherryusb_serial_device/main/idf_component.yml new file mode 100644 index 0000000000..a92de9e1a2 --- /dev/null +++ b/examples/peripherals/usb/device/cherryusb_serial_device/main/idf_component.yml @@ -0,0 +1,3 @@ +## IDF Component Manager Manifest File +dependencies: + cherry-embedded/cherryusb: 1.5.2~1 diff --git a/examples/peripherals/usb/device/cherryusb_serial_device/sdkconfig.defaults b/examples/peripherals/usb/device/cherryusb_serial_device/sdkconfig.defaults new file mode 100644 index 0000000000..b239994c16 --- /dev/null +++ b/examples/peripherals/usb/device/cherryusb_serial_device/sdkconfig.defaults @@ -0,0 +1,4 @@ +CONFIG_CHERRYUSB=y +CONFIG_CHERRYUSB_DEVICE=y +CONFIG_CHERRYUSB_DEVICE_DWC2_ESP=y +CONFIG_CHERRYUSB_DEVICE_CDC_ACM=y 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..9b84404469 --- /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.22) + +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..defe591b91 --- /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), 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 don’t 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 +``` 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..2da571cbb7 --- /dev/null +++ b/examples/peripherals/usb/host/cherryusb_host/main/CMakeLists.txt @@ -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() 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..7c19fff6ed --- /dev/null +++ b/examples/peripherals/usb/host/cherryusb_host/main/Kconfig.projbuild @@ -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 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..ce720ef85c --- /dev/null +++ b/examples/peripherals/usb/host/cherryusb_host/main/cdc_acm.c @@ -0,0 +1,341 @@ +/* + * 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 "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 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..eeb75bad65 --- /dev/null +++ b/examples/peripherals/usb/host/cherryusb_host/main/hid.c @@ -0,0 +1,580 @@ +/* + * 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 "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"); +} 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..a92de9e1a2 --- /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.2~1 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..a9aab4675e --- /dev/null +++ b/examples/peripherals/usb/host/cherryusb_host/main/main.c @@ -0,0 +1,65 @@ +/* + * 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 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 + } + +} diff --git a/examples/peripherals/usb/host/cherryusb_host/main/msc.c b/examples/peripherals/usb/host/cherryusb_host/main/msc.c new file mode 100644 index 0000000000..97f897c812 --- /dev/null +++ b/examples/peripherals/usb/host/cherryusb_host/main/msc.c @@ -0,0 +1,404 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include +#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); +} 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..2e48491f11 --- /dev/null +++ b/examples/peripherals/usb/host/cherryusb_host/sdkconfig.defaults @@ -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 diff --git a/examples/peripherals/usb/host/hid/main/hid_host_example.c b/examples/peripherals/usb/host/hid/main/hid_host_example.c index 3dfff0b931..29d016c462 100644 --- a/examples/peripherals/usb/host/hid/main/hid_host_example.c +++ b/examples/peripherals/usb/host/hid/main/hid_host_example.c @@ -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