diff --git a/examples/peripherals/usb/host/usb_host_lib/README.md b/examples/peripherals/usb/host/usb_host_lib/README.md index f6b746c7a7..bb60eab111 100644 --- a/examples/peripherals/usb/host/usb_host_lib/README.md +++ b/examples/peripherals/usb/host/usb_host_lib/README.md @@ -5,19 +5,21 @@ (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 daemon task. The example does the following: +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: 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. Deregister the client and uninstall the Host Library +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` 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 a daemon 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 @@ -30,7 +32,7 @@ The example demonstrates the following aspects of the USB Host Library API: ### Hardware Required -An ESP board that supports USB-OTG. The example uses the ESP's internal USB PHY, however the internal USB PHY's pins will need to be connected to a USB port (i.e., a USB breakout board) as follows: +An ESP board that has a push button and supports USB-OTG. The example uses the ESP's internal USB PHY, however the internal USB PHY's pins will need to be connected to a USB port (i.e., a USB breakout board) as follows: - GND and 5V signals of the ESP board to the GND and 5V lines of the USB port - GPIO 19 to D- @@ -43,6 +45,7 @@ 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 ### Build and Flash @@ -61,14 +64,17 @@ See the Getting Started Guide for full steps to configure and use ESP-IDF to bui ## Example Output ``` -I (261) cpu_start: Starting scheduler on PRO CPU. -I (267) DAEMON: Installing USB Host Library -I (297) CLASS: Registering Client -I (5067) CLASS: Opening device at address 1 -I (5067) CLASS: Getting device information -I (5067) CLASS: Full speed -I (5067) CLASS: bConfigurationValue 1 -I (5067) CLASS: Getting device descriptor +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 (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 @@ -84,7 +90,7 @@ iManufacturer 1 iProduct 2 iSerialNumber 3 bNumConfigurations 1 -I (5097) CLASS: Getting config descriptor +I (775) CLASS: Getting config descriptor *** Configuration descriptor *** bLength 9 bDescriptorType 2 @@ -153,12 +159,20 @@ bMaxPower 500mA bmAttributes 0x2 BULK wMaxPacketSize 64 bInterval 1 -I (5227) CLASS: Getting Manufacturer string descriptor +I (855) CLASS: Getting Manufacturer string descriptor Espressif -I (5237) CLASS: Getting Product string descriptor +I (855) CLASS: Getting Product string descriptor USB JTAG/serial debug unit -I (5247) CLASS: Getting Serial Number string descriptor +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() ``` ## Troubleshooting 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 260f313fea..5d0a296fb9 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" INCLUDE_DIRS "." - PRIV_REQUIRES usb + PRIV_REQUIRES usb driver ) diff --git a/examples/peripherals/usb/host/usb_host_lib/main/Kconfig.projbuild b/examples/peripherals/usb/host/usb_host_lib/main/Kconfig.projbuild new file mode 100644 index 0000000000..680b0cc3c8 --- /dev/null +++ b/examples/peripherals/usb/host/usb_host_lib/main/Kconfig.projbuild @@ -0,0 +1,12 @@ +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 + help + GPIO pin number to be used as APP_QUIT button. + +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 ce7ee44487..e7a06ea59f 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 @@ -12,13 +12,16 @@ #define CLIENT_NUM_EVENT_MSG 5 -#define ACTION_OPEN_DEV 0x01 -#define ACTION_GET_DEV_INFO 0x02 -#define ACTION_GET_DEV_DESC 0x04 -#define ACTION_GET_CONFIG_DESC 0x08 -#define ACTION_GET_STR_DESC 0x10 -#define ACTION_CLOSE_DEV 0x20 -#define ACTION_EXIT 0x40 +typedef enum { + ACTION_OPEN_DEV = 0x01, + ACTION_GET_DEV_INFO = 0x02, + ACTION_GET_DEV_DESC = 0x04, + ACTION_GET_CONFIG_DESC = 0x08, + ACTION_GET_STR_DESC = 0x10, + ACTION_CLOSE_DEV = 0x20, + ACTION_EXIT = 0x40, + ACTION_RECONNECT = 0x80, +} action_t; typedef struct { usb_host_client_handle_t client_hdl; @@ -28,6 +31,7 @@ typedef struct { } class_driver_t; static const char *TAG = "CLASS"; +static class_driver_t *s_driver_obj; static void client_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg) { @@ -122,24 +126,20 @@ static void action_get_str_desc(class_driver_t *driver_obj) driver_obj->actions &= ~ACTION_GET_STR_DESC; } -static void aciton_close_dev(class_driver_t *driver_obj) +static void action_close_dev(class_driver_t *driver_obj) { ESP_ERROR_CHECK(usb_host_device_close(driver_obj->client_hdl, driver_obj->dev_hdl)); driver_obj->dev_hdl = NULL; driver_obj->dev_addr = 0; - //We need to exit the event handler loop + //We need to connect a new device driver_obj->actions &= ~ACTION_CLOSE_DEV; - driver_obj->actions |= ACTION_EXIT; + driver_obj->actions |= ACTION_RECONNECT; } void class_driver_task(void *arg) { - SemaphoreHandle_t signaling_sem = (SemaphoreHandle_t)arg; class_driver_t driver_obj = {0}; - //Wait until daemon task has installed USB Host Library - xSemaphoreTake(signaling_sem, portMAX_DELAY); - ESP_LOGI(TAG, "Registering Client"); usb_host_client_config_t client_config = { .is_synchronous = false, //Synchronous clients currently not supported. Set this to false @@ -150,6 +150,7 @@ void class_driver_task(void *arg) }, }; ESP_ERROR_CHECK(usb_host_client_register(&client_config, &driver_obj.client_hdl)); + s_driver_obj = &driver_obj; while (1) { if (driver_obj.actions == 0) { @@ -171,18 +172,29 @@ void class_driver_task(void *arg) action_get_str_desc(&driver_obj); } if (driver_obj.actions & ACTION_CLOSE_DEV) { - aciton_close_dev(&driver_obj); + action_close_dev(&driver_obj); } if (driver_obj.actions & ACTION_EXIT) { break; } + if (driver_obj.actions & ACTION_RECONNECT) { + driver_obj.actions = 0; + } } } ESP_LOGI(TAG, "Deregistering Client"); ESP_ERROR_CHECK(usb_host_client_deregister(driver_obj.client_hdl)); - - //Wait to be deleted - xSemaphoreGive(signaling_sem); vTaskSuspend(NULL); } + +void class_driver_client_deregister(void) +{ + if (s_driver_obj->dev_hdl != NULL) { + s_driver_obj->actions = ACTION_CLOSE_DEV; + } + s_driver_obj->actions |= ACTION_EXIT; + + // Unblock, exit the loop and proceed to deregister client + ESP_ERROR_CHECK(usb_host_client_unblock(s_driver_obj->client_hdl)); +} 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 f73880cabc..db41bd3265 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,27 +1,78 @@ /* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "freertos/semphr.h" +#include "freertos/queue.h" +#include "freertos/event_groups.h" #include "esp_log.h" #include "esp_intr_alloc.h" #include "usb/usb_host.h" +#include "driver/gpio.h" -#define DAEMON_TASK_PRIORITY 2 +#define HOST_LIB_TASK_PRIORITY 2 #define CLASS_TASK_PRIORITY 3 +#define APP_QUIT_PIN CONFIG_APP_QUIT_PIN extern void class_driver_task(void *arg); +extern void class_driver_client_deregister(void); -static const char *TAG = "DAEMON"; +static const char *TAG = "USB host lib"; -static void host_lib_daemon_task(void *arg) +QueueHandle_t app_event_queue = NULL; + +/** + * @brief APP event group + * + * APP_EVENT - General event, which is APP_QUIT_PIN press event in this example. + */ +typedef enum { + APP_EVENT = 0, +} app_event_group_t; + +/** + * @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; + +/** + * @brief BOOT button pressed callback + * + * Signal application to exit the Host lib task + * + * @param[in] arg Unused + */ +static void gpio_cb(void *arg) { - SemaphoreHandle_t signaling_sem = (SemaphoreHandle_t)arg; + const app_event_queue_t evt_queue = { + .event_group = APP_EVENT, + }; + BaseType_t xTaskWoken = pdFALSE; + + if (app_event_queue) { + xQueueSendFromISR(app_event_queue, &evt_queue, &xTaskWoken); + } + + if (xTaskWoken == pdTRUE) { + portYIELD_FROM_ISR(); + } +} + +/** + * @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, @@ -29,9 +80,8 @@ static void host_lib_daemon_task(void *arg) }; ESP_ERROR_CHECK(usb_host_install(&host_config)); - //Signal to the class driver task that the host library is installed - xSemaphoreGive(signaling_sem); - vTaskDelay(10); //Short delay to let client task spin up + //Signalize the app_main, the USB host library has been installed + xTaskNotifyGive(arg); bool has_clients = true; bool has_devices = true; @@ -39,52 +89,101 @@ static void host_lib_daemon_task(void *arg) 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, "No more clients"); has_clients = false; + if (ESP_OK == usb_host_device_free_all()) { + ESP_LOGI(TAG, "All devices marked as free"); + } else { + ESP_LOGI(TAG, "Wait for the ALL FREE EVENT"); + } } if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + ESP_LOGI(TAG, "No more devices"); has_devices = false; } + } ESP_LOGI(TAG, "No more clients and devices"); //Uninstall the USB Host Library ESP_ERROR_CHECK(usb_host_uninstall()); - //Wait to be deleted - xSemaphoreGive(signaling_sem); vTaskSuspend(NULL); } void app_main(void) { - SemaphoreHandle_t signaling_sem = xSemaphoreCreateBinary(); + ESP_LOGI(TAG, "USB host library example"); - TaskHandle_t daemon_task_hdl; - TaskHandle_t class_driver_task_hdl; - //Create daemon task - xTaskCreatePinnedToCore(host_lib_daemon_task, - "daemon", - 4096, - (void *)signaling_sem, - DAEMON_TASK_PRIORITY, - &daemon_task_hdl, - 0); - //Create the class driver task - xTaskCreatePinnedToCore(class_driver_task, - "class", - 4096, - (void *)signaling_sem, - CLASS_TASK_PRIORITY, - &class_driver_task_hdl, - 0); + // 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_LEVEL1)); + ESP_ERROR_CHECK(gpio_isr_handler_add(APP_QUIT_PIN, gpio_cb, NULL)); + app_event_queue = xQueueCreate(10, sizeof(app_event_queue_t)); + app_event_queue_t evt_queue; + + TaskHandle_t host_lib_task_hdl, class_driver_task_hdl; + + //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); + + //Wait unit the USB host library is installed + ulTaskNotifyTake(false, 1000); + + //Create class driver task + task_created = xTaskCreatePinnedToCore(class_driver_task, + "class", + 4096, + NULL, + CLASS_TASK_PRIORITY, + &class_driver_task_hdl, + 0); + assert(task_created == pdTRUE); vTaskDelay(10); //Add a short delay to let the tasks run - //Wait for the tasks to complete - for (int i = 0; i < 2; i++) { - xSemaphoreTake(signaling_sem, portMAX_DELAY); + 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) { + // End while cycle + break; + } else { + ESP_LOGW(TAG, "To shutdown example, remove all USB devices and press button again."); + // Keep polling + } + } + } } + //Deregister client + class_driver_client_deregister(); + vTaskDelay(10); + //Delete the tasks vTaskDelete(class_driver_task_hdl); - vTaskDelete(daemon_task_hdl); + vTaskDelete(host_lib_task_hdl); + + // 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"); }