From 3bb1d62b32e4c03c164a680174e54f17adba3bd8 Mon Sep 17 00:00:00 2001 From: Tomas Rezucha Date: Wed, 15 Apr 2026 12:39:51 +0200 Subject: [PATCH] feat(usb/host): Extend USB Host Library example with interactive console --- .../usb/host/usb_host_lib/README.md | 174 +++----- .../usb/host/usb_host_lib/main/CMakeLists.txt | 4 +- .../host/usb_host_lib/main/Kconfig.projbuild | 21 +- .../usb/host/usb_host_lib/main/class_driver.c | 36 +- .../host/usb_host_lib/main/idf_component.yml | 4 +- .../usb/host/usb_host_lib/main/usb_commands.c | 412 ++++++++++++++++++ .../usb_host_lib/main/usb_host_lib_example.h | 41 ++ .../usb_host_lib/main/usb_host_lib_main.c | 254 +++-------- .../usb/host/usb_host_lib/partitions.csv | 6 + .../usb/host/usb_host_lib/sdkconfig.defaults | 17 + 10 files changed, 633 insertions(+), 336 deletions(-) create mode 100644 examples/peripherals/usb/host/usb_host_lib/main/usb_commands.c create mode 100644 examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_example.h create mode 100644 examples/peripherals/usb/host/usb_host_lib/partitions.csv diff --git a/examples/peripherals/usb/host/usb_host_lib/README.md b/examples/peripherals/usb/host/usb_host_lib/README.md index 9b96d291ebe..79c94bb8270 100644 --- a/examples/peripherals/usb/host/usb_host_lib/README.md +++ b/examples/peripherals/usb/host/usb_host_lib/README.md @@ -3,30 +3,41 @@ # USB Host Library Example -(See the README.md file in the upper level 'examples' directory for more information about examples.) +(See the README.md file in the upper level `examples` directory for more information about examples.) -This example demonstrates the basic usage of the [USB Host Library API](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_host.html) by implementing a pseudo class driver and a Host Library task. The example does the following: +This example demonstrates the [USB Host Library API](https://docs.espressif.com/projects/esp-usb/en/latest/esp32s2/usb_host.html) using a pseudo class driver and a dedicated USB Host Library task, together with an **interactive console** (ESP-IDF `esp_console` REPL). On boot it: -1. Install Host Library and register a client -2. Waits for a device connection -3. Prints the device's information (such as device/configuration/string descriptors) -4. Waits for the device to disconnect -5. Repeats steps 2 to 4 until a user pressess a button, which quits the `app` -6. If the button has been pressed, while a USB device is still connected, the user will be prompted to remove the device and push the button again to quit the `app` -7. Deregister the client, uninstall the Host Library and quit the `app` +1. Starts a read-eval-print loop on the configured primary serial console (UART or USB Serial/JTAG, depending on your hardware and project settings). +2. Registers common system commands from the [advanced console example](../../../../system/console/advanced/README.md) (`help`, `tasks`, and sleep-related commands where the chip supports them) plus USB-specific commands (see below). +3. Installs the USB Host stack and runs the class driver task, which waits for a device, prints descriptor information, and handles connect/disconnect. +4. Keeps running until reset; you can uninstall the USB stack from the shell and reinstall it later with `usb_install`. The example demonstrates the following aspects of the USB Host Library API: - How to use the Library API to: - Install and uninstall the USB Host Library - - Run the library event handler function and usb host library task + - Run the library event handler function and USB Host Library task - How to handle library events - How to use the Client API from a client task to: - Register and deregister a client of the USB Host Library - Run the client event handler functions - - How to handle client events via various callbacks + - How to handle client events via various callbacks (including suspend/resume notifications when supported) - Open and close a device - Get a device's descriptors +- How to drive **root port power, suspend, and resume** from application code via the console commands + +## Console commands (USB) + +| Command | Description | +| ------- | ----------- | +| `usb_install` | Install the USB Host stack and start the class driver. On ESP32-P4 it accepts argument `HS\|FS\|both` for USB peripheral selection. | +| `usb_uninstall` | Request teardown of the class client and uninstall the Host Library. | +| `usb_info` | Print host library state (device/client counts, root port state) and bus addresses of connected devices. | +| `usb_suspend` | Suspend the USB root port. On ESP32-P4, only 1 port is suspended. | +| `usb_resume` | Resume the USB root port. On ESP32-P4, only 1 port is resumed. | +| `usb_power` | Set root port power: argument `0` (off) or `1` (on). On ESP32-P4, both ports are turned on/off. | + +Type `help` in the monitor for all registered commands. ## How to use example @@ -41,18 +52,20 @@ The example demonstrates the following aspects of the USB Host Library API: idf.py menuconfig ``` -* The USB Host Stack has a maximum supported transfer size for control transfer during device enumeration. This size is specified via the USB_HOST_CONTROL_TRANSFER_MAX_SIZE configuration option and has a default value of 256 bytes. Therefore, if devices with length config/string descriptors are used, users may want to increase the size of this configuration. -* Push button GPIO selection +Under **Example Configuration**: + +* **Store command history in flash** — When enabled, mounts a small FAT partition (`storage` in `partitions.csv`) via wear levelling and saves linenoise history to `/data/history.txt`. +* **Maximum command line length** — Upper bound for a single console input line. ### Build and Flash -Build the project and flash it to the board, then run monitor tool to view serial output: +Build the project and flash it to the board, then run monitor tool to view serial output and use the console: ``` idf.py -p PORT flash monitor ``` -(Replace PORT with the name of the serial port to use.) +(Replace `PORT` with the name of the serial port to use.) (To exit the serial monitor, type ``Ctrl-]``.) @@ -60,118 +73,39 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui ## Example Output +After flashing, you should see startup logs similar to the following (exact ordering and timestamps vary), then the REPL prompt (by default `>`, e.g. `esp32s3>`): + ``` I (305) main_task: Started on CPU0 I (315) main_task: Calling app_main() I (315) USB host lib: USB host library example -I (315) gpio: GPIO[0]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:2 -I (325) USB host lib: Installing USB Host Library -I (365) CLASS: Registering Client +I (325) USB host lib: Command history enabled +I (365) USB host lib: Installing USB Host Library +I (395) USB host lib: USB Host installed with peripheral map 0x1 +I (405) USB host lib: USB host stack installed (class driver running) +I (745) CLASS: Registering Client I (745) CLASS: Opening device at address 1 I (745) CLASS: Getting device information -I (745) CLASS: Full speed -I (745) CLASS: bConfigurationValue 1 -I (745) CLASS: Getting device descriptor -*** Device descriptor *** -bLength 18 -bDescriptorType 1 -bcdUSB 2.00 -bDeviceClass 0xef -bDeviceSubClass 0x2 -bDeviceProtocol 0x1 -bMaxPacketSize0 64 -idVendor 0x303a -idProduct 0x1001 -bcdDevice 1.00 -iManufacturer 1 -iProduct 2 -iSerialNumber 3 -bNumConfigurations 1 -I (775) CLASS: Getting config descriptor -*** Configuration descriptor *** -bLength 9 -bDescriptorType 2 -wTotalLength 98 -bNumInterfaces 3 -bConfigurationValue 1 -iConfiguration 0 -bmAttributes 0xc0 -bMaxPower 500mA - *** Interface descriptor *** - bLength 9 - bDescriptorType 4 - bInterfaceNumber 0 - bAlternateSetting 0 - bNumEndpoints 1 - bInterfaceClass 0x0 - iInterface 0 - *** Endpoint descriptor *** - bLength 7 - bDescriptorType 5 - bEndpointAddress 0x82 EP 2 IN - bmAttributes 0x3 INT - wMaxPacketSize 64 - bInterval 1 - *** Interface descriptor *** - bLength 9 - bDescriptorType 4 - bInterfaceNumber 1 - bAlternateSetting 0 - bNumEndpoints 2 - bInterfaceClass 0x0 - iInterface 0 - *** Endpoint descriptor *** - bLength 7 - bDescriptorType 5 - bEndpointAddress 0x1 EP 1 OUT - bmAttributes 0x2 BULK - wMaxPacketSize 64 - bInterval 1 - *** Endpoint descriptor *** - bLength 7 - bDescriptorType 5 - bEndpointAddress 0x81 EP 1 IN - bmAttributes 0x2 BULK - wMaxPacketSize 64 - bInterval 1 - *** Interface descriptor *** - bLength 9 - bDescriptorType 4 - bInterfaceNumber 2 - bAlternateSetting 0 - bNumEndpoints 2 - bInterfaceClass 0x1 - iInterface 0 - *** Endpoint descriptor *** - bLength 7 - bDescriptorType 5 - bEndpointAddress 0x2 EP 2 OUT - bmAttributes 0x2 BULK - wMaxPacketSize 64 - bInterval 1 - *** Endpoint descriptor *** - bLength 7 - bDescriptorType 5 - bEndpointAddress 0x83 EP 3 IN - bmAttributes 0x2 BULK - wMaxPacketSize 64 - bInterval 1 -I (855) CLASS: Getting Manufacturer string descriptor -Espressif -I (855) CLASS: Getting Product string descriptor -USB JTAG/serial debug unit -I (865) CLASS: Getting Serial Number string descriptor -7C:DF:A1:E0:10:50 -W (2855) USB host lib: To shutdown example, remove all USB devices and press button again. -E (6135) USBH: Device 1 gone -I (9545) CLASS: Deregistering Client -I (9545) USB host lib: No more clients -I (9545) USB host lib: All devices marked as free -I (9545) USB host lib: No more clients and devices -I (9645) USB host lib: End of the example -I (9645) main_task: Returned from app_main() +... ``` +When a device is attached, the class driver prints device, configuration, and string descriptors (same style as in previous revisions of this example). On disconnect you may see host stack messages such as: + +``` +E (6135) USBH: Device 1 gone +``` + +After `usb_uninstall` (with no devices and the client released), the host task uninstalls the library: + +``` +I (9545) USB host lib: Get FLAGS_NO_CLIENTS +I (9545) USB host lib: All devices marked as free, no need to wait FLAGS_ALL_FREE event +I (9545) USB host lib: No more clients and devices, uninstall USB Host library +I (9645) USB host lib: USB host stack uninstalled +``` + +The console keeps running so you can run `usb_install` again or other commands. + ## Troubleshooting To obtain more debug, users should set the [log level](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/system/log.html) to debug via menuconfig. @@ -190,4 +124,4 @@ The log output demonstrates a device that has failed. The Hub Driver will output ### Blank String Descriptors -The current USB Host Library will automatically cache the Manufacturer, Product, and Serial Number string descriptors of the device during enumeration. However, when fetching the string descriptors, the USB Host Library will only fetch those strings descriptors of they of LANGID code 0x0409 (i.e., English - United States). Therefore, if the example does not print a particular descriptor, it is likely that the string descriptor was not cached during enumeration. +The current USB Host Library will automatically cache the Manufacturer, Product, and Serial Number string descriptors of the device during enumeration. However, when fetching the string descriptors, the USB Host Library will only fetch those string descriptors if they use LANGID code 0x0409 (i.e., English - United States). Therefore, if the example does not print a particular descriptor, it is likely that the string descriptor was not cached during enumeration. diff --git a/examples/peripherals/usb/host/usb_host_lib/main/CMakeLists.txt b/examples/peripherals/usb/host/usb_host_lib/main/CMakeLists.txt index 206468c4820..5869cb75073 100644 --- a/examples/peripherals/usb/host/usb_host_lib/main/CMakeLists.txt +++ b/examples/peripherals/usb/host/usb_host_lib/main/CMakeLists.txt @@ -1,4 +1,4 @@ -idf_component_register(SRCS "usb_host_lib_main.c" "class_driver.c" +idf_component_register(SRCS "usb_host_lib_main.c" "class_driver.c" "usb_commands.c" INCLUDE_DIRS "." - PRIV_REQUIRES esp_driver_gpio + PRIV_REQUIRES fatfs console vfs wear_levelling spi_flash ) diff --git a/examples/peripherals/usb/host/usb_host_lib/main/Kconfig.projbuild b/examples/peripherals/usb/host/usb_host_lib/main/Kconfig.projbuild index 680b0cc3c83..ed130290f2d 100644 --- a/examples/peripherals/usb/host/usb_host_lib/main/Kconfig.projbuild +++ b/examples/peripherals/usb/host/usb_host_lib/main/Kconfig.projbuild @@ -1,12 +1,19 @@ menu "Example Configuration" - orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps" - - config APP_QUIT_PIN - int "APP Quit button GPIO pin" - range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX - default 0 + config CONSOLE_STORE_HISTORY + bool "Store command history in flash" + default y help - GPIO pin number to be used as APP_QUIT button. + Linenoise line editing library provides functions to save and load + command history. If this option is enabled, initializes a FAT filesystem + and uses it to store command history. + + config CONSOLE_MAX_COMMAND_LINE_LENGTH + int "Maximum command line length" + default 1024 + range 128 2048 + help + This value marks the maximum length of a single command line. Once it is + reached, no more characters will be accepted by the console. endmenu diff --git a/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c b/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c index a0b656b9772..21263ef1889 100644 --- a/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c +++ b/examples/peripherals/usb/host/usb_host_lib/main/class_driver.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -78,9 +78,16 @@ static void client_event_cb(const usb_host_client_event_msg_t *event_msg, void * } } xSemaphoreGive(driver_obj->constant.mux_lock); + ESP_LOGI(TAG, "Device gone"); + break; + case USB_HOST_CLIENT_EVENT_DEV_SUSPENDED: + ESP_LOGI(TAG, "Device suspended"); + break; + case USB_HOST_CLIENT_EVENT_DEV_RESUMED: + ESP_LOGI(TAG, "Device resumed"); break; default: - ESP_LOGW(TAG, "Unsupported client event: %d (possibly suspend/resume)", event_msg->event); + ESP_LOGW(TAG, "Unsupported client event: %d", (int)event_msg->event); break; } } @@ -103,16 +110,6 @@ static void action_get_info(usb_device_t *device_obj) ESP_LOGI(TAG, "\t%s speed", (char *[]) { "Low", "Full", "High" }[dev_info.speed]); - ESP_LOGI(TAG, "\tParent info:"); - if (dev_info.parent.dev_hdl) { - usb_device_info_t parent_dev_info; - ESP_ERROR_CHECK(usb_host_device_info(dev_info.parent.dev_hdl, &parent_dev_info)); - ESP_LOGI(TAG, "\t\tBus addr: %d", parent_dev_info.dev_addr); - ESP_LOGI(TAG, "\t\tPort: %d", dev_info.parent.port_num); - - } else { - ESP_LOGI(TAG, "\t\tPort: ROOT"); - } ESP_LOGI(TAG, "\tbConfigurationValue %d", dev_info.bConfigurationValue); // Get the device descriptor next device_obj->actions |= ACTION_GET_DEV_DESC; @@ -198,6 +195,7 @@ static void class_driver_device_handle(usb_device_t *device_obj) void class_driver_task(void *arg) { + TaskHandle_t task_to_notify = (TaskHandle_t)arg; class_driver_t driver_obj = {0}; usb_host_client_handle_t class_driver_client_hdl = NULL; @@ -206,7 +204,7 @@ void class_driver_task(void *arg) SemaphoreHandle_t mux_lock = xSemaphoreCreateMutex(); if (mux_lock == NULL) { ESP_LOGE(TAG, "Unable to create class driver mutex"); - vTaskSuspend(NULL); + vTaskDelete(NULL); return; } @@ -218,7 +216,11 @@ void class_driver_task(void *arg) .callback_arg = (void *) &driver_obj, }, }; - ESP_ERROR_CHECK(usb_host_client_register(&client_config, &class_driver_client_hdl)); + if (ESP_OK != usb_host_client_register(&client_config, &class_driver_client_hdl)) { + ESP_LOGE(TAG, "Failed to register class driver client"); + vTaskDelete(NULL); + return; + } driver_obj.constant.mux_lock = mux_lock; driver_obj.constant.client_hdl = class_driver_client_hdl; @@ -229,6 +231,8 @@ void class_driver_task(void *arg) s_driver_obj = &driver_obj; + xTaskNotifyGive(task_to_notify); + while (1) { // Driver has unhandled devices, handle all devices first if (driver_obj.mux_protected.flags.unhandled_devices) { @@ -256,11 +260,13 @@ void class_driver_task(void *arg) if (mux_lock != NULL) { vSemaphoreDelete(mux_lock); } - vTaskSuspend(NULL); + s_driver_obj = NULL; + vTaskDelete(NULL); } void class_driver_client_deregister(void) { + assert(s_driver_obj != NULL); // Mark all opened devices xSemaphoreTake(s_driver_obj->constant.mux_lock, portMAX_DELAY); for (uint8_t i = 0; i < DEV_MAX_COUNT; i++) { diff --git a/examples/peripherals/usb/host/usb_host_lib/main/idf_component.yml b/examples/peripherals/usb/host/usb_host_lib/main/idf_component.yml index eed210a4676..d09710f2822 100644 --- a/examples/peripherals/usb/host/usb_host_lib/main/idf_component.yml +++ b/examples/peripherals/usb/host/usb_host_lib/main/idf_component.yml @@ -1,4 +1,6 @@ ## IDF Component Manager Manifest File dependencies: + cmd_system: + path: ${IDF_PATH}/examples/system/console/advanced/components/cmd_system espressif/usb: - version: "^1.0.0" + version: "^1.3.0" diff --git a/examples/peripherals/usb/host/usb_host_lib/main/usb_commands.c b/examples/peripherals/usb/host/usb_host_lib/main/usb_commands.c new file mode 100644 index 00000000000..3f970df6cd8 --- /dev/null +++ b/examples/peripherals/usb/host/usb_host_lib/main/usb_commands.c @@ -0,0 +1,412 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include "esp_console.h" +#include "esp_log.h" +#include "soc/soc_caps.h" +#include "usb/usb_host.h" +#include "usb_host_lib_example.h" +#include "sdkconfig.h" + +static const char *TAG = "usb_cmd"; + +#if SOC_USB_OTG_PERIPH_NUM > 1 +#define USB_INSTALL_CMD_HINT "" +#define USB_INSTALL_CMD_HELP "Install USB Host stack and class driver on specified port (HS: High Speed port, FS: Full Speed port, both: all available ports; no argument defaults to HS)" +#else +#define USB_INSTALL_CMD_HINT NULL +#define USB_INSTALL_CMD_HELP "Install USB Host stack and class driver (no-op if already installed)" +#endif + +#define HOST_LIB_TASK_PRIORITY 2 +#define CLASS_TASK_PRIORITY 3 + +#ifdef CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK +#define ENABLE_ENUM_FILTER_CALLBACK +#endif // CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK + +static uint8_t s_usb_stack_running = 0; // Bitmap of running USB host ports, 0 means not installed +static SemaphoreHandle_t s_usb_lifecycle_mx = NULL; +static SemaphoreHandle_t s_host_stopped_sem = NULL; + +typedef struct { + TaskHandle_t task_to_notify; + unsigned peripheral_map; +} usb_host_lib_task_args_t; + +static int usb_install_cmd(int argc, char **argv) +{ + unsigned peripheral_map; + +#if SOC_USB_OTG_PERIPH_NUM > 1 + if (argc > 2) { + ESP_LOGE(TAG, "Usage: usb_install "); + return 1; + } + + if (argc == 1 || strcmp(argv[1], "HS") == 0) { + peripheral_map = BIT0; + } else if (strcmp(argv[1], "FS") == 0) { + peripheral_map = BIT1; + } else if (strcmp(argv[1], "both") == 0) { + peripheral_map = BIT0 | BIT1; + } else { + ESP_LOGE(TAG, "Invalid argument '%s', expected HS, FS, or both", argv[1]); + return 1; + } +#else + if (argc != 1) { + ESP_LOGE(TAG, "Usage: usb_install"); + return 1; + } + peripheral_map = BIT0; +#endif + + esp_err_t err = usb_example_install(peripheral_map); + if (err != ESP_OK) { + ESP_LOGE(TAG, "USB install failed: %s", esp_err_to_name(err)); + return 1; + } + return 0; +} + +/** + * @brief Set configuration callback + * + * Set the USB device configuration during the enumeration process, must be enabled in the menuconfig + * + * @note bConfigurationValue starts at index 1 + * + * @param[in] dev_desc device descriptor of the USB device currently being enumerated + * @param[out] bConfigurationValue configuration descriptor index, that will be user for enumeration + * + * @return bool + * - true: USB device will be enumerated + * - false: USB device will not be enumerated + */ +#ifdef ENABLE_ENUM_FILTER_CALLBACK +static bool set_config_cb(const usb_device_desc_t *dev_desc, uint8_t *bConfigurationValue) +{ + if (dev_desc->bNumConfigurations > 1) { + *bConfigurationValue = 2; + } else { + *bConfigurationValue = 1; + } + return true; +} +#endif // ENABLE_ENUM_FILTER_CALLBACK + +/** + * @brief USB Host install and event loop; uninstalls when there are no clients and no devices. + */ +static void usb_host_lib_task(void *arg) +{ + usb_host_lib_task_args_t *task_args = (usb_host_lib_task_args_t *)arg; + + ESP_LOGI(TAG, "Installing USB Host Library"); + usb_host_config_t host_config = { + .skip_phy_setup = false, + .intr_flags = ESP_INTR_FLAG_LOWMED, +#ifdef ENABLE_ENUM_FILTER_CALLBACK + .enum_filter_cb = set_config_cb, +#endif // ENABLE_ENUM_FILTER_CALLBACK + .peripheral_map = task_args->peripheral_map, + }; + if (ESP_OK != usb_host_install(&host_config)) { + ESP_LOGE(TAG, "Failed to install USB Host Library"); + vTaskDelete(NULL); + } + xTaskNotifyGive(task_args->task_to_notify); + ESP_LOGI(TAG, "USB Host installed with peripheral map 0x%x", host_config.peripheral_map); + + bool has_clients = true; + bool has_devices = false; + while (has_clients) { + uint32_t event_flags; + ESP_ERROR_CHECK(usb_host_lib_handle_events(portMAX_DELAY, &event_flags)); // Returns error only if the library is not installed, which should not happen since we are inside the library task + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + if (ESP_OK == usb_host_device_free_all()) { + ESP_LOGI(TAG, "All devices marked as free, no need to wait FLAGS_ALL_FREE event"); + has_clients = false; + } else { + ESP_LOGI(TAG, "Wait for the FLAGS_ALL_FREE"); + has_devices = true; + } + } + if (has_devices && event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + ESP_LOGI(TAG, "Got FLAGS_ALL_FREE"); + has_clients = false; + } + } + ESP_LOGI(TAG, "No more clients and devices, uninstall USB Host library"); + + ESP_ERROR_CHECK(usb_host_uninstall()); + if (s_host_stopped_sem) { + xSemaphoreGive(s_host_stopped_sem); + } + vTaskDelete(NULL); +} + +esp_err_t usb_example_install(unsigned peripheral_map) +{ + if (s_usb_lifecycle_mx == NULL) { + s_usb_lifecycle_mx = xSemaphoreCreateMutex(); + assert(s_usb_lifecycle_mx); + } + if (s_host_stopped_sem == NULL) { + s_host_stopped_sem = xSemaphoreCreateBinary(); + assert(s_host_stopped_sem); + } + + xSemaphoreTake(s_usb_lifecycle_mx, portMAX_DELAY); + + esp_err_t ret = ESP_OK; + if (s_usb_stack_running != 0) { + ESP_LOGW(TAG, "USB host stack already installed"); + xSemaphoreGive(s_usb_lifecycle_mx); + return ESP_OK; + } + + usb_host_lib_task_args_t task_args = { + .task_to_notify = xTaskGetCurrentTaskHandle(), + .peripheral_map = peripheral_map, + }; + BaseType_t task_created = xTaskCreatePinnedToCore(usb_host_lib_task, + "usb_host", + 4096, + &task_args, + HOST_LIB_TASK_PRIORITY, + NULL, + 0); + assert(task_created == pdTRUE); + if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(5000)) == 0) { + ESP_LOGE(TAG, "Timeout waiting for USB host install"); + abort(); + } + + task_created = xTaskCreatePinnedToCore(class_driver_task, + "class", + 5 * 1024, + xTaskGetCurrentTaskHandle(), + CLASS_TASK_PRIORITY, + NULL, + 0); + assert(task_created == pdTRUE); + if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(5000)) == 0) { + ESP_LOGE(TAG, "Timeout waiting client registration"); + abort(); + } + + s_usb_stack_running = peripheral_map; + ESP_LOGI(TAG, "USB host stack installed (class driver running)"); + + xSemaphoreGive(s_usb_lifecycle_mx); + return ret; +} + +/** + * @brief Tear down the class driver client and uninstall the USB Host Library + * + * Deregisters the class driver, waits for the host library task to call `usb_host_uninstall()`, + * and clears the installed state. + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if the USB host stack is not installed + */ +static esp_err_t usb_example_teardown_locked(void) +{ + xSemaphoreTake(s_usb_lifecycle_mx, portMAX_DELAY); + if (s_usb_stack_running == 0) { + ESP_LOGW(TAG, "USB host stack not installed, nothing to uninstall"); + xSemaphoreGive(s_usb_lifecycle_mx); + return ESP_ERR_INVALID_STATE; + } + + usb_host_lib_info_t lib_info; + if (usb_host_lib_info(&lib_info) == ESP_OK && lib_info.num_devices != 0) { + ESP_LOGW(TAG, "Uninstall with %u device(s) still attached", (unsigned)lib_info.num_devices); + } + + class_driver_client_deregister(); + + if (xSemaphoreTake(s_host_stopped_sem, pdMS_TO_TICKS(15000)) != pdTRUE) { + ESP_LOGW(TAG, "Timeout waiting for USB host library uninstall"); + abort(); + } + + s_usb_stack_running = 0; + xSemaphoreGive(s_usb_lifecycle_mx); + return ESP_OK; +} + +static int usb_uninstall_cmd(int argc, char **argv) +{ + (void)argc; + (void)argv; + if (usb_example_teardown_locked() != ESP_OK) { + ESP_LOGE(TAG, "Failed to teardown USB host library"); + return 1; + } + printf("\tUSB Host Library uninstalled\n"); + return 0; +} + +static int usb_suspend_cmd(int argc, char **argv) +{ + (void)argc; + (void)argv; + esp_err_t err = usb_host_lib_root_port_suspend(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to suspend USB root port: %s", esp_err_to_name(err)); + return 1; + } + if (s_usb_stack_running == (BIT0 | BIT1)) { + ESP_LOGW(TAG, "In USB dual host mode, only HS port is suspended"); // This is a software limitation of the current implementation + } + printf("\tUSB root port suspended\n"); + return 0; +} + +static int usb_resume_cmd(int argc, char **argv) +{ + (void)argc; + (void)argv; + esp_err_t err = usb_host_lib_root_port_resume(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to resume USB root port: %s", esp_err_to_name(err)); + return 1; + } + if (s_usb_stack_running == (BIT0 | BIT1)) { + ESP_LOGW(TAG, "In USB dual host mode, only HS port is resumed"); // This is a software limitation of the current implementation + } + printf("\tUSB root port resumed\n"); + return 0; +} + +static int usb_info_cmd(int argc, char **argv) +{ + (void)argc; + (void)argv; + usb_host_lib_info_t info; + esp_err_t err = usb_host_lib_info(&info); + if (err != ESP_OK) { + ESP_LOGE(TAG, "usb_host_lib_info failed: %s", esp_err_to_name(err)); + return 1; + } + + if (s_usb_stack_running == (BIT0 | BIT1)) { + ESP_LOGW(TAG, "In USB dual host mode, only HS port can be suspended"); // This is a software limitation of the current implementation + } + + printf("\tUSB host lib: Devices=%d, Clients=%d, Root port suspended=%s\n\tDevice addresses: ", + info.num_devices, info.num_clients, info.root_port_suspended ? "yes" : "no"); + if (info.num_devices == 0) { + printf("None\n"); + return 0; + } + + uint8_t *dev_addrs = malloc(info.num_devices * sizeof(uint8_t)); + if (dev_addrs == NULL) { + ESP_LOGE(TAG, "Failed to allocate memory for device addresses"); + return 1; + } + int num_addrs = 0; + err = usb_host_device_addr_list_fill(info.num_devices, dev_addrs, &num_addrs); + if (err != ESP_OK) { + ESP_LOGE(TAG, "usb_host_device_addr_list_fill failed: %s", esp_err_to_name(err)); + free(dev_addrs); + return 1; + } + + for (int i = 0; i < num_addrs; i++) { + printf(" %u", (unsigned)dev_addrs[i]); + } + printf("\n"); + + free(dev_addrs); + return 0; +} + +static int usb_power_cmd(int argc, char **argv) +{ + if (argc != 2) { + ESP_LOGE(TAG, "Usage: usb_power <0|1>"); + return 1; + } + + bool enable; + if (strcmp(argv[1], "0") == 0) { + enable = false; + } else if (strcmp(argv[1], "1") == 0) { + enable = true; + } else { + ESP_LOGE(TAG, "Invalid argument '%s', expected 0 or 1", argv[1]); + return 1; + } + + esp_err_t err = usb_host_lib_set_root_port_power(enable); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set USB root port power: %s", esp_err_to_name(err)); + return 1; + } + + printf("\tUSB root port power %s\n", enable ? "enabled" : "disabled"); + return 0; +} + +void register_usb(void) +{ + const esp_console_cmd_t cmd_install = { + .command = "usb_install", + .help = USB_INSTALL_CMD_HELP, + .hint = USB_INSTALL_CMD_HINT, + .func = &usb_install_cmd, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd_install)); + + const esp_console_cmd_t cmd_uninstall = { + .command = "usb_uninstall", + .help = "Uninstall USB Host stack (use usb_install to restore)", + .hint = NULL, + .func = &usb_uninstall_cmd, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd_uninstall)); + + const esp_console_cmd_t cmd_info = { + .command = "usb_info", + .help = "Print USB Host Library info and connected device bus addresses", + .hint = NULL, + .func = &usb_info_cmd, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd_info)); + + const esp_console_cmd_t cmd_suspend = { + .command = "usb_suspend", + .help = "Suspend USB root port", + .hint = NULL, + .func = &usb_suspend_cmd, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd_suspend)); + + const esp_console_cmd_t cmd_resume = { + .command = "usb_resume", + .help = "Resume USB root port", + .hint = NULL, + .func = &usb_resume_cmd, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd_resume)); + + const esp_console_cmd_t cmd_power = { + .command = "usb_power", + .help = "Set USB root port power", + .hint = "<0|1>", + .func = &usb_power_cmd, + }; + ESP_ERROR_CHECK(esp_console_cmd_register(&cmd_power)); +} diff --git a/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_example.h b/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_example.h new file mode 100644 index 00000000000..9503cc7ff01 --- /dev/null +++ b/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_example.h @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#pragma once + +#include "esp_err.h" + +/** + * @brief Register USB-related console commands + * + * Registers `usb_install`, `usb_uninstall`, `usb_info`, `usb_suspend`, `usb_resume`, and + * `usb_power` with the ESP console REPL. Call once after `esp_console` is initialized. + */ +void register_usb(void); + +/** + * @brief Install the USB Host Library and start the example class driver + * + * Creates the USB host library task (which calls `usb_host_install()`) and the class driver task. + * + * @param peripheral_map Value for `usb_host_config_t.peripheral_map` passed to `usb_host_install()`. + * @return + * - ESP_OK on success or if the stack is already installed + * - ESP_ERR_INVALID_STATE if lifecycle mutexes were not created (call from `app_main` first) + */ +esp_err_t usb_example_install(unsigned peripheral_map); + +/** + * @brief FreeRTOS task entry for the example pseudo class driver + * + * @param[in] arg `TaskHandle_t` of the task to notify with `xTaskNotifyGive()` after client registration + */ +extern void class_driver_task(void *arg); + +/** + * @brief Request shutdown of the class driver task and client deregistration + */ +extern void class_driver_client_deregister(void); diff --git a/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_main.c b/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_main.c index fbea4b7443f..076cf2b29eb 100644 --- a/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_main.c +++ b/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_main.c @@ -1,226 +1,98 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "freertos/queue.h" -#include "freertos/event_groups.h" +#include "freertos/semphr.h" #include "esp_log.h" #include "esp_intr_alloc.h" +#include "esp_console.h" +#include "esp_vfs_fat.h" +#include "wear_levelling.h" +#include "soc/soc_caps.h" +#include "cmd_system.h" #include "usb/usb_host.h" -#include "driver/gpio.h" - -#define HOST_LIB_TASK_PRIORITY 2 -#define CLASS_TASK_PRIORITY 3 -#define APP_QUIT_PIN CONFIG_APP_QUIT_PIN - -#ifdef CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK -#define ENABLE_ENUM_FILTER_CALLBACK -#endif // CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK - -extern void class_driver_task(void *arg); -extern void class_driver_client_deregister(void); +#include "usb_host_lib_example.h" static const char *TAG = "USB host lib"; +#define PROMPT_STR CONFIG_IDF_TARGET -QueueHandle_t app_event_queue = NULL; - -/** - * @brief APP event group - * - * APP_EVENT - General event, which is APP_QUIT_PIN press event in this example. +/* Console command history can be stored to and loaded from a file. + * The easiest way to do this is to use FATFS filesystem on top of + * wear_levelling library. */ -typedef enum { - APP_EVENT = 0, -} app_event_group_t; +#if CONFIG_CONSOLE_STORE_HISTORY -/** - * @brief APP event queue - * - * This event is used for delivering events from callback to a task. - */ -typedef struct { - app_event_group_t event_group; -} app_event_queue_t; +#define MOUNT_PATH "/data" +#define HISTORY_PATH MOUNT_PATH "/history.txt" -/** - * @brief BOOT button pressed callback - * - * Signal application to exit the Host lib task - * - * @param[in] arg Unused - */ -static void gpio_cb(void *arg) +static void initialize_filesystem(void) { - const app_event_queue_t evt_queue = { - .event_group = APP_EVENT, + static wl_handle_t wl_handle; + const esp_vfs_fat_mount_config_t mount_config = { + .max_files = 4, + .format_if_mount_failed = true }; - - BaseType_t xTaskWoken = pdFALSE; - - if (app_event_queue) { - xQueueSendFromISR(app_event_queue, &evt_queue, &xTaskWoken); - } - - if (xTaskWoken == pdTRUE) { - portYIELD_FROM_ISR(); + esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl(MOUNT_PATH, "storage", &mount_config, &wl_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err)); + return; } } - -/** - * @brief Set configuration callback - * - * Set the USB device configuration during the enumeration process, must be enabled in the menuconfig - - * @note bConfigurationValue starts at index 1 - * - * @param[in] dev_desc device descriptor of the USB device currently being enumerated - * @param[out] bConfigurationValue configuration descriptor index, that will be user for enumeration - * - * @return bool - * - true: USB device will be enumerated - * - false: USB device will not be enumerated - */ -#ifdef ENABLE_ENUM_FILTER_CALLBACK -static bool set_config_cb(const usb_device_desc_t *dev_desc, uint8_t *bConfigurationValue) -{ - // If the USB device has more than one configuration, set the second configuration - if (dev_desc->bNumConfigurations > 1) { - *bConfigurationValue = 2; - } else { - *bConfigurationValue = 1; - } - - // Return true to enumerate the USB device - return true; -} -#endif // ENABLE_ENUM_FILTER_CALLBACK - -/** - * @brief Start USB Host install and handle common USB host library events while app pin not low - * - * @param[in] arg Not used - */ -static void usb_host_lib_task(void *arg) -{ - ESP_LOGI(TAG, "Installing USB Host Library"); - usb_host_config_t host_config = { - .skip_phy_setup = false, - .intr_flags = ESP_INTR_FLAG_LOWMED, -# ifdef ENABLE_ENUM_FILTER_CALLBACK - .enum_filter_cb = set_config_cb, -# endif // ENABLE_ENUM_FILTER_CALLBACK - .peripheral_map = BIT0, - }; - ESP_ERROR_CHECK(usb_host_install(&host_config)); - ESP_LOGI(TAG, "USB Host installed with peripheral map 0x%x", host_config.peripheral_map); - - //Signalize the app_main, the USB host library has been installed - xTaskNotifyGive(arg); - - bool has_clients = true; - bool has_devices = false; - while (has_clients) { - uint32_t event_flags; - ESP_ERROR_CHECK(usb_host_lib_handle_events(portMAX_DELAY, &event_flags)); - if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { - ESP_LOGI(TAG, "Get FLAGS_NO_CLIENTS"); - if (ESP_OK == usb_host_device_free_all()) { - ESP_LOGI(TAG, "All devices marked as free, no need to wait FLAGS_ALL_FREE event"); - has_clients = false; - } else { - ESP_LOGI(TAG, "Wait for the FLAGS_ALL_FREE"); - has_devices = true; - } - } - if (has_devices && event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { - ESP_LOGI(TAG, "Get FLAGS_ALL_FREE"); - has_clients = false; - } - } - ESP_LOGI(TAG, "No more clients and devices, uninstall USB Host library"); - - //Uninstall the USB Host Library - ESP_ERROR_CHECK(usb_host_uninstall()); - vTaskSuspend(NULL); -} +#endif // CONFIG_CONSOLE_STORE_HISTORY void app_main(void) { ESP_LOGI(TAG, "USB host library example"); - // Init BOOT button: Pressing the button simulates app request to exit - // It will uninstall the class driver and USB Host Lib - const gpio_config_t input_pin = { - .pin_bit_mask = BIT64(APP_QUIT_PIN), - .mode = GPIO_MODE_INPUT, - .pull_up_en = GPIO_PULLUP_ENABLE, - .intr_type = GPIO_INTR_NEGEDGE, - }; - ESP_ERROR_CHECK(gpio_config(&input_pin)); - ESP_ERROR_CHECK(gpio_install_isr_service(ESP_INTR_FLAG_LOWMED)); - ESP_ERROR_CHECK(gpio_isr_handler_add(APP_QUIT_PIN, gpio_cb, NULL)); +#if SOC_USB_SERIAL_JTAG_SUPPORTED && !CONFIG_ESP_CONSOLE_SECONDARY_NONE + ESP_LOGW(TAG, "A secondary serial console is output-only; consider CONFIG_ESP_CONSOLE_SECONDARY_NONE for interactive use"); +#endif - app_event_queue = xQueueCreate(10, sizeof(app_event_queue_t)); - app_event_queue_t evt_queue; + esp_console_repl_t *repl = NULL; + esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT(); + repl_config.prompt = PROMPT_STR ">"; + repl_config.max_cmdline_length = CONFIG_CONSOLE_MAX_COMMAND_LINE_LENGTH; - TaskHandle_t host_lib_task_hdl, class_driver_task_hdl; +#if CONFIG_CONSOLE_STORE_HISTORY + initialize_filesystem(); + repl_config.history_save_path = HISTORY_PATH; + ESP_LOGI(TAG, "Command history enabled"); +#else + ESP_LOGI(TAG, "Command history disabled"); +#endif - // Create usb host lib task - BaseType_t task_created; - task_created = xTaskCreatePinnedToCore(usb_host_lib_task, - "usb_host", - 4096, - xTaskGetCurrentTaskHandle(), - HOST_LIB_TASK_PRIORITY, - &host_lib_task_hdl, - 0); - assert(task_created == pdTRUE); + esp_console_register_help_command(); + register_system_common(); + register_usb(); +#if SOC_LIGHT_SLEEP_SUPPORTED + register_system_light_sleep(); +#endif +#if SOC_DEEP_SLEEP_SUPPORTED + register_system_deep_sleep(); +#endif - // Wait until the USB host library is installed - ulTaskNotifyTake(false, 1000); + ESP_ERROR_CHECK(usb_example_install(BIT0)); - // Create class driver task - task_created = xTaskCreatePinnedToCore(class_driver_task, - "class", - 5 * 1024, - NULL, - CLASS_TASK_PRIORITY, - &class_driver_task_hdl, - 0); - assert(task_created == pdTRUE); - // Add a short delay to let the tasks run - vTaskDelay(10); +#if defined(CONFIG_ESP_CONSOLE_UART_DEFAULT) || defined(CONFIG_ESP_CONSOLE_UART_CUSTOM) + esp_console_dev_uart_config_t hw_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw_config, &repl_config, &repl)); - while (1) { - if (xQueueReceive(app_event_queue, &evt_queue, portMAX_DELAY)) { - if (APP_EVENT == evt_queue.event_group) { - // User pressed button - usb_host_lib_info_t lib_info; - ESP_ERROR_CHECK(usb_host_lib_info(&lib_info)); - if (lib_info.num_devices != 0) { - ESP_LOGW(TAG, "Shutdown with attached devices."); - } - // End while cycle - break; - } - } - } +#elif defined(CONFIG_ESP_CONSOLE_USB_CDC) + esp_console_dev_usb_cdc_config_t hw_config = ESP_CONSOLE_DEV_CDC_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_console_new_repl_usb_cdc(&hw_config, &repl_config, &repl)); - // Deregister client - class_driver_client_deregister(); - vTaskDelay(10); +#elif defined(CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG) + esp_console_dev_usb_serial_jtag_config_t hw_config = ESP_CONSOLE_DEV_USB_SERIAL_JTAG_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_console_new_repl_usb_serial_jtag(&hw_config, &repl_config, &repl)); - // Delete the tasks - vTaskDelete(class_driver_task_hdl); - vTaskDelete(host_lib_task_hdl); +#else +#error Unsupported console type +#endif - // Delete interrupt and queue - gpio_isr_handler_remove(APP_QUIT_PIN); - xQueueReset(app_event_queue); - vQueueDelete(app_event_queue); - ESP_LOGI(TAG, "End of the example"); + ESP_ERROR_CHECK(esp_console_start_repl(repl)); } diff --git a/examples/peripherals/usb/host/usb_host_lib/partitions.csv b/examples/peripherals/usb/host/usb_host_lib/partitions.csv new file mode 100644 index 00000000000..27472b2454b --- /dev/null +++ b/examples/peripherals/usb/host/usb_host_lib/partitions.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 0x110000, +storage, data, fat, , 1M, diff --git a/examples/peripherals/usb/host/usb_host_lib/sdkconfig.defaults b/examples/peripherals/usb/host/usb_host_lib/sdkconfig.defaults index bf1b553b238..ae758e1a8ac 100644 --- a/examples/peripherals/usb/host/usb_host_lib/sdkconfig.defaults +++ b/examples/peripherals/usb/host/usb_host_lib/sdkconfig.defaults @@ -1,4 +1,21 @@ # This file was generated using idf.py save-defconfig. It can be edited manually. # Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration # + +# Increase main task stack size +CONFIG_ESP_MAIN_TASK_STACK_SIZE=7168 + +# Enable filesystem +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y + +# USB settings CONFIG_USB_HOST_HUBS_SUPPORTED=y +CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=4096 + +# Enable FreeRTOS stats formatting functions, needed for 'tasks' command +CONFIG_FREERTOS_USE_TRACE_FACILITY=y +CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y + +# On chips with USB serial, disable secondary console which does not make sense when using console +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y