《DNESP32P4开发指南_V1.0》第四十七章 USB U盘实验

第四十七章 USB U盘实验

本章我们介绍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的连接原理图,如下图所示:

image001

图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盘插入这个接口上。

image003

图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 组件提供的功能,首先需要在代码中导入以下头文件:

#include "usb_hid_msc.h"

接下来,作者将介绍一些常用的usb_host_msc函数,这些函数的描述及其作用如下:
1,安装USB HOST usb_host_install
该函数用于安装USB HOST,其函数原型如下:

esp_err_t usb_host_install(const usb_host_config_t *config);

函数形参:

QQ截图20260903145456

表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);

函数形参:

QQ截图20260903145511

表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);

函数形参:

QQ截图20260903145524

表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);

函数形参:

QQ截图20260903145534

表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);

函数形参:

QQ截图20260903145544

表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);

函数形参:

QQ截图20260903145553

表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);

函数形参:

QQ截图20260903145605

表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);

函数形参:

QQ截图20260903145615

表47.3.1.8 msc_host_uninstall_device函数形参描述

返回值:
ESP_OK表示成功卸载设备。
其他错误码表示卸载失败。

47.3.2 程序流程图

image006

图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设备回调(连接/断开)
 * @param     	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盘中的文件,这些操作命令如下:

image007

图47.4.1 REPL终端命令

从上图可以看到,若我们在终端输入 “file”命令,则终端会列出U盘中的所有文件目录,操作过程如下图所示。

image009

图47.4.2 查询U盘文件目录

当然,我们也可以通过程序的方式添加自定义的操作命令,添加过程请参考本章节的实验。

posted @ 2026-09-04 10:17  正点原子  阅读(6)  评论(0)    收藏  举报