|
第四十七章 USB U盘实验
1)实验平台:正点原子DNESP32P4开发板
2)章节摘自【正点原子】ESP32-P4开发指南— V1.0
3)购买链接:https://detail.tmall.com/item.htm?id=873309579825
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32P4.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子DNESP32S3开发板技术交流群:132780729
本章我们介绍ESP32-P4的USB HOST应用,即通过USB HOST功能,实现读写U盘/读卡器等大容量USB存储设备。
本章分为如下几个部分:
47.1 U盘简介
47.2 硬件设计
47.3 程序设计
47.4 下载验证
47.1 U盘简介
U盘(USB闪存盘,英文名:USB flash disk)是一种通过USB接口连接主机的微型高容量移动存储设备。它无需物理驱动器,支持即插即用,是最常用的移动存储工具之一。
ESP32-P4的USB OTG HS接口支持U盘功能。Espressif官方在ESP-IDF中提供了针对USB HOST大容量存储设备(MSC)的示例代码,路径为:esp-idf\examples\peripherals\usb\host\msc。本实验将参考官方示例代码,通过ESP32-P4的USB HOST接口实现对U盘或SD卡读卡器等设备的读写操作。
47.2 硬件设计
47.2.1 程序功能
本实验代码,开机后,初始化LCD和USB HOST,并不断轮询。当检测并识别U盘后,在LCD上面显示U盘总容量和连接状态。我们可通过espressif的REPL调用FATFS相关函数,来测试U盘数据的读写了。
47.2.2 硬件资源
1)RGBLCD/MIPILCD(引脚太多,不罗列出来)
2)USB_HOST HS接口
47.2.3 原理图
本开发板的USB HOST接口采用的是贴片USB母座,它和USB SLAVE是共用USB_DM和USB_DP信号的,所以USB HOST和USB SLAVE接口不能同时使用。USB HOST和ESP32-P4的连接原理图,如下图所示:
图47.2.2.1 USB母座与ESP32-P4的连接电路图
从上图可以看出,USB母座(USB_HOST)是直接连接到ESP32-P4上面的,所以硬件上不需要我们做什么操作,可直接使用。
需要注意的是:本实验被测试的U盘是通过USB母座(USB_HOST)连接到ESP32-P4的,此时,USB_SLAVE接口是不能连接USB设备或者主机的。如果需要用到USB转串口,数据线可以通过USB_ UART接口连接到电脑。
下面,看看开发板上的USB母座(USB_HOST)的位置,请将U盘插入这个接口上。
图47.2.2.2 开发板U盘母座
47.3 程序设计
47.3.1 USB HOST MSC的IDF驱动
usb_host_msc组件驱动位于ESP-IDF在线组件注册表中。如果需要将该组件添加到项目工程中,可按照以下步骤操作:
1)打开ESP-IDF注册表。
2)搜索 “usb_host_msc”组件。
3)将组件安装到项目中。
组件安装完成后,系统会自动更新main文件夹中的特殊组件清单文件idf_component.yml在项目编译时,系统会根据清单文件从注册表中下载并集成该组件到工程中。关于上述操作流程,可参考本书籍第八章的内容。
为了使用 usb_host_msc 组件提供的功能,首先需要在代码中导入以下头文件:
接下来,作者将介绍一些常用的usb_host_msc函数,这些函数的描述及其作用如下:
1,安装USB HOST usb_host_install
该函数用于安装USB HOST,其函数原型如下:
- esp_err_t usb_host_install(const usb_host_config_t *config);
复制代码 函数形参:
表47.3.1.1 usb_host_install函数形参描述
返回值:
ESP_OK表示USB Host安装成功。
ESP_ERR_INVALID_ARG表示参数无效。
ESP_ERR_INVALID_STATE表示USB Host Library当前状态不正确。
ESP_ERR_NO_MEM表示内存不足。
config为指向USB Host Library 的配置结构体。接下来,笔者将详细介绍usb_host_config_t结构体中的各个成员变量,如下代码所示:
- /**
- * @brief USB Host Library 配置
- *
- * USB Host Library 的配置结构体,提供给 usb_host_install() 函数使用。
- */
- typedef struct {
- bool skip_phy_setup; /* 如果设置true,USB Hos将不会配置USB PHY */
- bool root_port_unpowered; /* 如果设置为true,USB Host在安装时不会为根端口供电 */
- int intr_flags; /* USB Host 栈底层中断的标志配置 */
- usb_host_enum_filter_cb_t enum_filter_cb; /* 枚举过滤器回调函数 */
- } usb_host_config_t;
复制代码 上述结构体用于传递USB Host Library配置参数,以下对各个成员做简单介绍。
1)skip_phy_setup:
若此字段设置为true,则不会配置USB PHY。
2)root_port_unpowered:
若此字段设置为true,则不会为USB HOST提供电源。
3)intr_flags:
USB Host 栈底层中断的标志配置,一般设置为0。
4)enum_filter_cb:
过滤回调函数。
2,安装USB主机大容量存储类(MSC)驱动 msc_host_install
该函数用于安装USB主机大容量存储类(MSC)驱动,其函数原型如下:
- esp_err_t msc_host_install(const msc_host_driver_config_t *config);
复制代码 函数形参:
表47.3.1.2 msc_host_install函数形参描述
返回值:
ESP_OK表示安装存储类(MSC)驱动安装成功。
其他表示安装存储类(MSC)驱动失败
config为指向MSC(大容量存储类)配置结构体。接下来,笔者将详细介绍msc_host_driver_config_t结构体中的各个成员变量,如下代码所示:
- /**
- * @brief MSC(大容量存储类)配置结构体
- */
- typedef struct {
- /* 如果设置为 true,则会创建一个后台任务来处理 USB 事件;
- 否则用户需要定期调用 msc_host_handle_events 函数来处理事件。 */
- bool create_backround_task;
- size_t task_priority; /* 创建的后台任务的优先级 */
- size_t stack_size; /* 创建的后台任务的堆栈大小 */
- BaseType_t core_id; /* 选择后台任务运行的核心 ID */
- msc_host_event_cb_t callback; /* 当发生 MSC 事件时调用的回调函数,不能为空 */
- void *callback_arg; /* 用户提供的参数,将传递给回调函数 */
- } msc_host_driver_config_t;
复制代码 msc_host_driver_config_t结构体用于传递MSC(大容量存储类)配置参数,以便在调用msc_host_install时进行初始化和设置。
3,处理 USB 主机库事件 usb_host_lib_handle_events
该函数用于处理 USB 主机库事件,其函数原型如下:
- esp_err_t usb_host_lib_handle_events(TickType_t timeout_ticks,
- uint32_t *event_flags_ret);
复制代码 函数形参:
表47.3.1.3 msc_host_install函数形参描述
返回值:
ESP_OK表示无事件需要处理。
ESP_ERR_INVALID_STATE表示USB主机库尚未安装。
ESP_ERR_TIMEOUT表示等待事件的信号量超时。
4,释放所有设备usb_host_device_free_all
该函数用于释放所有设备,其函数原型如下:
- esp_err_t usb_host_device_free_all(void);
复制代码 函数形参:
无。
返回值:
ESP_OK表示所有设备已被释放(即没有设备需要释放)
ESP_ERR_INVALID_STATE表示客户端必须先注销
ESP_ERR_NOT_FINISHED表示仍有一个或多个设备需要释放,请等待USB_HOST_LIB_EVENT_FLAGS_ALL_FREE事件。
5,卸载USB主机库usb_host_uninstall
该函数用于卸载USB主机库,其函数原型如下:
- esp_err_t usb_host_uninstall(void);
复制代码 函数形参:
无。
返回值:
ESP_OK表示USB主机库成功卸载。
ESP_ERR_INVALID_STATE表示USB主机库未安装,或存在未完成的操作。
6,初始化 MSC 设备msc_host_install_device
该函数用于处理 USB 主机库事件,其函数原型如下:
- esp_err_t msc_host_install_device( uint8_t device_address,
- msc_host_device_handle_t *device);
复制代码 函数形参:
表47.3.1.4 msc_host_install_device函数形参描述
返回值:
ESP_OK表示初始化成功。
其他错误码表示初始化失败。
7,将MSC设备注册到虚拟文件系统msc_host_vfs_register
该函数用于将MSC设备注册到虚拟文件系统,其函数原型如下:
- esp_err_t msc_host_vfs_register(msc_host_device_handle_t device,
- const char *base_path,
- const esp_vfs_fat_mount_config_t *mount_config,
- msc_host_vfs_handle_t *vfs_handle);
复制代码 函数形参:
表47.3.1.5 msc_host_vfs_register函数形参描述
返回值:
ESP_OK表示注册成功。
其他错误码表示注册失败。
8,获取MSC设备信息msc_host_get_device_info
该函数用于将MSC设备注册到虚拟文件系统,其函数原型如下:
- esp_err_t msc_host_get_device_info(msc_host_device_handle_t device,
- msc_host_device_info_t *info);
复制代码 函数形参:
表47.3.1.6 msc_host_get_device_info函数形参描述
返回值:
ESP_OK表示成功获取设备信息。
其他错误码表示获取失败。
9,从虚拟文件系统中注销MSC设备msc_host_vfs_unregister
该函数用于从虚拟文件系统中注销MSC设备,其函数原型如下:
- esp_err_t msc_host_vfs_unregister(msc_host_vfs_handle_t vfs_handle);
复制代码 函数形参:
表47.3.1.7 msc_host_get_device_info函数形参描述
返回值:
ESP_OK表示成功获取设备信息。
其他错误码表示获取失败。
10,卸载MSC设备msc_host_uninstall_device
该函数用于卸载MSC设备,其函数原型如下:
- esp_err_t msc_host_uninstall_device(msc_host_device_handle_t device);
复制代码 函数形参:
表47.3.1.8 msc_host_uninstall_device函数形参描述
返回值:
ESP_OK表示成功卸载设备。
其他错误码表示卸载失败。
47.3.2 程序流程图
图47.3.2.1 U盘实验程序流程图
47.3.3 程序解析
本章节的例程是基于13_lcd实验编写的,所以笔者重点讲解有区别的文件。
1,USB HID MSC驱动
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。USB HID MSC驱动源码包括两个文件:usb_hid_msc.c和usb_hid_msc.h。
usb_hid_msc.h主要用于声明usb_hid_msc_init函数和USB消息结构体,以便在其他文件中调用,具体内容不再赘述。
下面我们再解析usb_hid_msc.c的程序,看一下初始化函数usb_hid_msc_init,代码如下:
- QueueHandle_t usb_queue = NULL; /* 消息队列 */
- /**
- * @brief MSC设备回调(连接/断开)
- * [url=home.php?mod=space&uid=271674]@param[/url] event:MSC事件
- * @param arg:传入参数
- * @retval 无
- */
- static void msc_event_cb(const msc_host_event_t *event, void *arg)
- {
- /* 连接? */
- if (event->event == MSC_DEVICE_CONNECTED)
- {
- /* 发现usb host 已插入U盘 */
- usb_message_t message = {
- .id = USB_DEVICE_CONNECTED,
- .data.new_dev_address = event->device.address,
- };
- xQueueSend(usb_queue, &message, portMAX_DELAY);
- }/* 断开? */
- else if (event->event == MSC_DEVICE_DISCONNECTED)
- {
- /* 发现usb host 未检测到U盘 */
- usb_message_t message = {
- .id = USB_DEVICE_DISCONNECTED,
- };
- xQueueSend(usb_queue, &message, portMAX_DELAY);
- }
- }
- /**
- * @brief USB轮询任务
- * @param args:未使用
- * @retval 无
- */
- static void usb_task_fun(void *args)
- {
- /* usb host配置*/
- const usb_host_config_t host_config = {.intr_flags = ESP_INTR_FLAG_LEVEL1 };
- /* usb host初始化 */
- ESP_ERROR_CHECK(usb_host_install(&host_config));
- /* msc host设备配置 */
- const msc_host_driver_config_t msc_config = {
- .create_backround_task = true, /* 创建回调任务 */
- .task_priority = 5, /* 任务优先级 */
- .stack_size = 4096, /* 任务堆栈大小 */
- .callback = msc_event_cb, /* msc事件回调函数 */
- };
- /* msc host安装 */
- ESP_ERROR_CHECK(msc_host_install(&msc_config));
- bool has_clients = true;
- while (1)
- {
- uint32_t event_flags;
- /* 处理USB事件处理器 */
- usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
- /* 所有的客户端已从主机注销了吗? */
- if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS)
- {
- has_clients = false;
- /* 释放usb host内存 */
- if (usb_host_device_free_all() == ESP_OK)
- {
- break;
- };
- }
- /* 主机已释放所有设备? */
- if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE && !has_clients)
- {
- break;
- }
- }
- vTaskDelay(pdMS_TO_TICKS(10));
- /* 注销usb host */
- ESP_ERROR_CHECK(usb_host_uninstall());
- /* 删除usb轮询任务 */
- vTaskDelete(NULL);
- }
- /**
- * @brief USB读取U盘初始化
- * @param 无
- * @retval ESP_OK:初始化成功
- */
- esp_err_t usb_hid_msc_init(void)
- {
- /* 创建新的消息队列 */
- usb_queue = xQueueCreate(5, sizeof(usb_message_t));
- assert(usb_queue);
- BaseType_t usb_task = xTaskCreate(usb_task_fun,"usb_task",4096,NULL,2,NULL);
- assert(usb_task);
- return ESP_OK;
- }
复制代码 以上代码实现了基于ESP32-P4的USB主机功能,专注于管理USB存储设备的连接与断开,并通过消息队列将设备状态变化通知其他任务。核心任务usb_task_fun负责USB主机的初始化、事件轮询和资源释放,确保整个系统的稳定运行。回调函数msc_event_cb处理存储设备的连接和断开事件,usb_hid_msc_init则提供了一个简单的初始化入口,整合了消息队列和任务创建,便于扩展和调用。
2,main.c驱动代码
在main.c里面编写如下代码。
- /* REPL控制器命令 */
- const esp_console_cmd_t cmds[] = {
- {
- .command = "file",
- .help = "file browsing",
- .hint = NULL,
- .func = &console_file_browsing,
- },
- {
- .command = "read",
- .help = "read PATH .eg: read /usb:/README.MD",
- .hint = NULL,
- .func = &console_read,
- },
- {
- .command = "write",
- .help = "write PATH Data .eg: write /usb:/README.MD Hello ALIENTEK",
- .hint = NULL,
- .func = &console_write,
- },
- {
- .command = "info",
- .help = "Usb flash drive information",
- .hint = NULL,
- .func = &console_info,
- },
- {
- .command = "speed",
- .help = "Test read/write speed,eg: speed /usb:/README.MD",
- .hint = NULL,
- .func = &console_test_speed,
- },
- {
- .command = "device_cfg",
- .help = "Device configuration information",
- .hint = NULL,
- .func = &console_device_cfg,
- },
- };
- /**
- * @brief 递归列出目录下的所有文件和文件夹
- * @param path 需要列出的目录路径
- * @param depth 当前目录层级
- * @retval 无
- */
- static void list_files(const char *path, int depth)
- {
- /* 省略代码...... */
- }
- /**
- * @brief 测试读写速度
- * @param argc:传入参数的数量
- * @param argv:传入参数
- * @retval -1:取消挂载失败。0:取消挂载成功
- */
- static int console_test_speed(int argc, char **argv)
- {
- /* 省略代码...... */
- return 0;
- }
- /**
- * @brief 文件浏览
- * @param argc:传入参数的数量
- * @param argv:传入参数
- * @retval -1:读取失败。0:读取成功
- */
- static int console_file_browsing(int argc, char **argv)
- {
- /* 省略代码...... */
- return 0;
- }
- /**
- * @brief 读取存储设备的文件
- * @param argc:传入参数的数量
- * @param argv:传入参数
- * @retval -1:读取失败。0:读取成功
- */
- static int console_read(int argc, char **argv)
- {
- /* 省略代码...... */
- return 0;
- }
- /**
- * @brief 写入存储设备的文件
- * @param argc:传入参数的数量
- * @param argv:传入参数
- * @retval -1:读取失败。0:读取成功
- */
- static int console_write(int argc, char **argv)
- {
- /* 省略代码...... */
- return 0;
- }
- /**
- * @brief 获取U盘信息
- * @param argc:传入参数的数量
- * @param argv:传入参数
- * @retval -1:读取失败。0:读取成功
- */
- static int console_info(int argc, char **argv)
- {
- /* 省略代码...... */
- return 0;
- }
- /**
- * @brief 获取MSC配置信息
- * @param argc:传入参数的数量
- * @param argv:传入参数
- * @retval -1:读取失败。0:读取成功
- */
- static int console_device_cfg(int argc, char **argv)
- {
- /* 省略代码...... */
- return 0;
- }
- void app_main(void)
- {
- esp_err_t ret;
- uint8_t repl_state = 0xAA;
- ret = nvs_flash_init(); /* 初始化NVS */
- if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
- {
- ESP_ERROR_CHECK(nvs_flash_erase());
- ESP_ERROR_CHECK(nvs_flash_init());
- }
- led_init(); /* LED初始化 */
- lcd_init(); /* LCD初始化 */
- usb_hid_msc_init(); /* USB MSC HOST初始化 */
- /* 实验信息 */
- lcd_show_string(30, 50, 200, 16, 16, "ESP32-P4", RED);
- lcd_show_string(30, 70, 200, 16, 16, "USB disk TEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- lcd_show_string(30, 110, 200, 16, 16, "status:disconnect", RED);
- lcd_show_string(30, 130, 200, 16, 16, "Total: MB", RED);
- while (1)
- {
- /* 接收消息 */
- xQueueReceive(usb_queue, &msg, portMAX_DELAY);
- if (msg.id == USB_DEVICE_CONNECTED)
- {
- /* 检查到U盘,则安装设备 */
- ESP_ERROR_CHECK(msc_host_install_device(msg.data.new_dev_address
- , &msc_device));
- /* 使用文件系统挂载U盘 */
- const esp_vfs_fat_mount_config_t mount_config = {
- .format_if_mount_failed = false, /* 挂载子分区? */
- .max_files = 3, /* 打开文件最大数量 */
- .allocation_unit_size = 8192, /* 扇区大小 */
- };
- /* 注册文件系统 */
- ESP_ERROR_CHECK( msc_host_vfs_register(msc_device, MNT_PATH,
- &mount_config, &vfs_handle));
- if (msg.id == USB_DEVICE_CONNECTED && repl_state == 0xAA)
- {
- /* 交互监视器 */
- esp_console_repl_t *repl = NULL;
- /* 默认配置 */
- esp_console_repl_config_t repl_config =
- ESP_CONSOLE_REPL_CONFIG_DEFAULT();
- repl_config.prompt = CONFIG_IDF_TARGET ">"; /* 提示符名称 */
- /* 命令行的最大长度。如果为0,则使用默认值 */
- repl_config.max_cmdline_length = 64;
- esp_console_register_help_command(); /* 帮助命令 */
- /* 交互监视器使用串口那个通道输出 */
- esp_console_dev_uart_config_t hw_config =
- ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
- /* 新建REPL控制器 */
- ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw_config,
- &repl_config, &repl));
- for (int count = 0; count < sizeof(cmds)
- / sizeof(esp_console_cmd_t); count++)
- {
- /* 添加命令 */
- ESP_ERROR_CHECK(esp_console_cmd_register(&cmds[count]));
- }
- /* 开始REPL控制器 */
- ESP_ERROR_CHECK(esp_console_start_repl(repl));
- repl_state = 0x00;
- }
- /* 获取磁盘信息 */
- msc_host_device_info_t info;
- ESP_ERROR_CHECK(msc_host_get_device_info(msc_device, &info));
- uint64_t capacity = ((uint64_t)info.sector_size * info.sector_count)
- / (1024 * 1024);
- lcd_show_string(84, 110, 200, 16, 16, "connected.", BLUE);
- lcd_show_num(80, 130, capacity, 5, 16, BLUE);
- }
- if ((msg.id == USB_DEVICE_DISCONNECTED))
- {
- lcd_show_string(84, 110, 200, 16, 16, "disconnect", BLUE);
- if (vfs_handle)
- {
- ESP_ERROR_CHECK(msc_host_vfs_unregister(vfs_handle));
- vfs_handle = NULL;
- }
- if (msc_device)
- {
- ESP_ERROR_CHECK(msc_host_uninstall_device(msc_device));
- msc_device = NULL;
- }
- }
- }
- }
复制代码 上述代码实现了基于ESP32-P4的USB闪存驱动和REPL控制器功能,包含多个控制命令和功能模块。主要功能包括目录浏览、文件读写、USB设备信息获取、读写速度测试以及设备配置打印等。通过定义一系列控制命令(如file、read、write等)和递归函数list_files,支持对USB设备的文件系统操作。并且可实时检测U盘是否处于连接状态。
47.4 下载验证
在代码编译成功之后,我们可以在开发板上的HOST口插入U盘,此时LCD提示U盘连接成功,我们可通过终端来操作U盘中的文件,这些操作命令如下:
图47.4.1 REPL终端命令
从上图可以看到,若我们在终端输入 “file”命令,则终端会列出U盘中的所有文件目录,操作过程如下图所示。
图47.4.2 查询U盘文件目录
当然,我们也可以通过程序的方式添加自定义的操作命令,添加过程请参考本章节的实验。 |