mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-03 03:31:41 +03:00
feat(usb/host): Extend USB Host Library example with interactive console
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#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 "<HS|FS|both>"
|
||||
#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 <HS|FS|both>");
|
||||
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));
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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 <assert.h>
|
||||
#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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user