本章学习ESP32S3 的 USB HOST 应用,即通过 USB HOST 功能,将某个分区表实现模拟 U 盘/读卡器等大容量 USB 存储设备。
        本章分为如下几个小节:
46.1 SD 卡模拟 U 盘简介
46.2 硬件设计
46.3 程序设计
46.4 下载验证

46.1 SD 卡模拟 U 盘简介

        当 USB 数据线插入 ESP32-S3 开发板的 USB 端口后,系统会将 SD 卡模拟成 U 盘。 本实验利用 ESP32自带的 USB功能,通过 USB连接电脑后, SD卡会在电脑上进行加载,并显示该SD卡的容量,我们可测试 SD 卡数据的读写了。
        LED 闪烁,提示程序运行。 USB 和电脑连接成功后。

46.2 硬件设计
46.2.1 例程功能

        本实验利用 ESP32自带的 USB功能,通过 USB连接电脑后,子分区会在电脑上进行加载,并显示该子分区的容量,测试子分区数据的读写了。
        LED 闪烁,提示程序运行, USB 和电脑连接成功。

46.2.2 硬件资源

        1. LED 灯
                LED -IO0
        2.独立按键
                KEY0(XL9555) - IO1_7
                KEY1(XL9555) - IO1_6
                KEY2(XL9555) - IO1_5
                KEY3(XL9555) - IO1_4
        3. XL9555
                IIC_SDA-IO41
                IIC_SCL-IO42
        4. SPILCD
                CS-IO21
                SCK-IO12
                SDA-IO11
                DC-IO40(在 P5 端口,使用跳线帽将 IO_SET 和 LCD_DC 相连)
                PWR- IO1_3(XL9555)
                RST- IO1_2(XL9555)
        5. SD
                CS-IO2
                SCK-IO12
                MOSI-IO11
                MISO-IO13
        6. UART_NUM_0(U0TX、 U0RX 连接至板载 USB 转串口芯片上)
                U0TXD-IO43
                U0RXD-IO44
        7. USB

46.2.3 原理图

        本章实验使用 USB 接口与 PC 进行连接,开发板板载了一个 USB 接口,用于连接其他 USB
设备, USB 接口与 MCU 的连接原理图,如下图所示:

图 46.2.3.1 USB 接口与 MCU 的连接原理图

46.3 程序设计
46.3.1 程序流程图

        本实验的程序流程图:

图 46.3.1.1 SD 卡模拟 U 盘实验程序流程图

46.3.2 SD 卡模拟 U 盘函数解析

        ESP-IDF 提供了一套 API 来配置 Flash。要使用此功能,需要导入必要的头文件:

#include "ff.h"
#include "diskio.h"
#include "esp_vfs_fat.h"
#include "tinyusb.h"

        接下来,将介绍一些常用的 ESP32-S3 中的 Flash 函数,这些函数的描述及其作用如下:

(1)注册存储类型sd卡与tinyusb驱动程序

        该函数专门用于初始化 ​​SD/MMC 卡​​ 作为 TinyUSB MSC(大容量存储设备)的存储介质。函数原型如下:

esp_err_t tinyusb_msc_storage_init_sdmmc(const tinyusb_msc_sdmmc_config_t *config)

        参数说明:
        ​​config​​: 指向 tinyusb_msc_sdmmc_config_t结构体的指针,该结构体包含了初始化 SD/MMC MSC 所需的所有配置信息,告诉 MSC 驱动如何使用 SD 卡。
        tinyusb_msc_sdmmc_config_t结构体详解:

typedef struct {
    sdmmc_card_t *card;                             /*!< Pointer to sdmmc card configuration structure */
    tusb_msc_callback_t callback_mount_changed;     /*!< Pointer to the function callback that will be delivered AFTER mount/unmount operation is successfully finished */
    tusb_msc_callback_t callback_premount_changed;  /*!< Pointer to the function callback that will be delivered BEFORE mount/unmount operation is started */
    const esp_vfs_fat_mount_config_t mount_config; /*!< FATFS mount config */
} tinyusb_msc_sdmmc_config_t;

        成员变量说明:​​
        ​​sdmmc_card_t *card​​ (必须):
        ​​作用​​:这是​​最重要​​的参数。它是一个指向已成功初始化的 sdmmc_card_t结构体的指针。
​​        如何获取​​:这个指针需要您事先使用 ​​SD/MMC 驱动​​ (sdmmc_host_t, sdmmc_slot_config_t等) 初始化好 SD 卡并获取到。它代表了已经准备好进行块读写操作的 SD 卡设备。
        ​​要求​​:​​必须是一个有效的、已初始化的 SDMMC 卡指针​​。
        ​​tusb_msc_callback_t callback_mount_changed​​ (可选):
        ​​作用​​:USB 主机挂载或卸载磁盘时的回调函数。可以在初始化时静态设置,也可以在之后用 tinyusb_msc_register_callback动态注册。
        ​​tusb_msc_callback_t callback_premount_changed​​ (可选):
​​        作用​​:挂载状态即将发生变化前的回调函数。
        esp_vfs_fat_mount_config_t mount_config(只关注​​int max_files​​ (必须)):
​​        作用​​:当 USB 主机卸载磁盘后,如果 ESP32 本地调用 tinyusb_msc_storage_mount重新挂载 FATFS 时,这个值决定了允许同时打开的最大文件数量。这会影响 FATFS 驱动分配的内存大小。

(2)注册回调的其他方法,即使用单独的API注册

        这个函数用于在 ​​初始化之后​​,​​动态地​​ 为 TinyUSB MSC 事件注册、更换或移除回调函数。它非常灵活,是管理事件响应的主要方式。函数原型如下:

esp_err_t tinyusb_msc_register_callback(
    tinyusb_msc_event_type_t event_type,
    tusb_msc_callback_t callback);

        参数说明:
        ​​event_type​​ (输入参数):
​​        类型​​:tinyusb_msc_event_type_t(枚举)
        ​​含义​​:指定要监听的事件类型。
​​        可选值​​:
        TINYUSB_MSC_EVENT_MOUNT_CHANGED:USB主机挂载或卸载了磁盘。
        TINYUSB_MSC_EVENT_PREMOUNT_CHANGED:挂载状态即将发生改变。
        ​​callback​​ (输入参数):
        ​​类型​​:tusb_msc_callback_t(函数指针)
        ​​含义​​:事件发生时被调用的函数。传入 NULL即可​​注销​​之前为该事件注册的回调。

        函数总结对比:

特性

tinyusb_msc_storage_init_sdmmc

tinyusb_msc_register_callback

​​目的​​

​一次性初始化​​,将​​SD/MMC卡​​配置为TinyUSB MSC的存储介质。

​动态管理​​事件监听,在MSC生命周期内可随时调用,用于注册、更换或移除​​回调函数​​。

​​核心参数​​

sdmmc_card_t *card(已初始化的SD卡对象)

event_type(要监听的事件), callback(处理函数)

​​调用时机​​

在SD卡初始化和TinyUSB驱动安装​​之后​​,​​只调用一次​​。

在MSC初始化​​之后​​的​​任何时刻​​,可以调用​​多次​​。

​​关系​​

​提供存储载体​​(SD卡)和​​基础的静态回调配置​​。

​提供事件响应机制​​,是管理主机与设备交互行为的​​控制中心​​。

表46.3.2.1 tinyusb_msc_storage_init_sdmmc()和tinyusb_msc_register_callback()对比

总结:​​init_sdmmc是把SD卡“插入”USB MSC框架,而 register_callback是给这个框架装上“事件监听器”​​,两者配合才能构建出一个完整、稳定、可控的USB磁盘功能。

(3)USB 设备登记

        使用tinyusb_driver_install()函数,参见上一个章节已介绍

46.3.3 SD 卡模拟 U 盘驱动解析

        在 IDF 版StandardExampleIDF(v5.3.x)\35_usb_sd_u,在34_usb_flash_u\managed_components\espressif_esp_tinyusb文件夹下增加USB 驱动文件。主要实现函数为上面介绍的函数,函数实现比较简单,可以自行研究。

        上图中位于 components 文件夹下的是自己编写的一些外设驱动, main 文件夹下包含了一个 一个后缀为.yml 的文件。 espressif_esp_tinyusb下包含的是 FLASH 模拟 U 盘(USB)代码,而后缀为.yml 的文件其主要作用是将项目中各组件的依赖项定义在单独的清单文件中,并以上图所示的方式进行命名。在我们的例程中提现出的作用就是简化了整个工程结构。在编译的过程中,系统便会帮我们自动生成 USB 外设所需要的依赖库: espressif_esp_tinyusb 以及espressif_tinyusb。做到了即能简化项目工程, 又能有效规避了在编译中遇到的错误,但前提是运行时得确保个人的电脑处于联网状态。

45.3.4 CMakeLists.txt 文件

        打开本实验 managed_components 文件下的 CMakeLists.txt 文件,其内容如下所示:

set(srcs
    "descriptors_control.c"
    "tinyusb.c"
    "usb_descriptors.c"
    )

if(NOT CONFIG_TINYUSB_NO_DEFAULT_TASK)
    list(APPEND srcs "tusb_tasks.c")
endif() # CONFIG_TINYUSB_NO_DEFAULT_TASK

if(CONFIG_TINYUSB_CDC_ENABLED)
    list(APPEND srcs
        "cdc.c"
        "tusb_cdc_acm.c"
        )
    if(CONFIG_VFS_SUPPORT_IO)
        list(APPEND srcs
            "tusb_console.c"
            "vfs_tinyusb.c"
            )
    endif() # CONFIG_VFS_SUPPORT_IO
endif() # CONFIG_TINYUSB_CDC_ENABLED

if(CONFIG_TINYUSB_MSC_ENABLED)
    list(APPEND srcs
        tusb_msc_storage.c
        )
endif() # CONFIG_TINYUSB_MSC_ENABLED

if(CONFIG_TINYUSB_NET_MODE_NCM)
    list(APPEND srcs
         tinyusb_net.c
         )
endif() # CONFIG_TINYUSB_NET_MODE_NCM

idf_component_register(SRCS ${srcs}
                       INCLUDE_DIRS "include"
                       PRIV_INCLUDE_DIRS "include_private"
                       PRIV_REQUIRES usb
                       REQUIRES fatfs vfs                 
                       )

# Determine whether tinyusb is fetched from component registry or from local path
idf_build_get_property(build_components BUILD_COMPONENTS)
if(tinyusb IN_LIST build_components)
    set(tinyusb_name tinyusb) # Local component
else()
    set(tinyusb_name espressif__tinyusb) # Managed component
endif()

# Pass tusb_config.h from this component to TinyUSB
idf_component_get_property(tusb_lib ${tinyusb_name} COMPONENT_LIB)
target_include_directories(${tusb_lib} PRIVATE "include")
46.3.5 实验应用代码

        打开 main/main.c 文件,该文件定义了工程入口函数,名为 app_main。该函数代码如下。

/**
 * @brief       程序入口
 * @param       无
 * @retval      无
 */
void app_main(void)
{
    esp_err_t ret;
    
    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初始化 */
    my_spi_init();              /* SPI初始化 */
    myiic_init();               /* MYIIC初始化 */
    xl9555_init();              /* XL9555初始化 */
    spilcd_init();              /* SPILCD初始化 */
    /* 显示实验信息 */
    spilcd_show_string(30, 50, 200, 16, 16, "ESP32-S3", RED);
    spilcd_show_string(30, 70, 200, 16, 16, "USB SD TEST", RED);
    spilcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);

    ESP_ERROR_CHECK(sd_spi_init());
    /* 配置SDMMC */
    const tinyusb_msc_sdmmc_config_t config_sdmmc = {
        .card = card,                   /* 指向sdmmc卡配置结构的指针 */
        .callback_mount_changed = NULL, /* 注册回调函数,用来初始化子分区表 */
        .mount_config.max_files = 5,    /* 最大文件打开数量 */
    };
    /* 注册存储类型sd卡与tinyusb驱动程序 */
    ESP_ERROR_CHECK(tinyusb_msc_storage_init_sdmmc(&config_sdmmc));
    /* 注册回调的其他方法,即使用单独的API注册 */
    ESP_ERROR_CHECK(tinyusb_msc_register_callback(TINYUSB_MSC_EVENT_MOUNT_CHANGED, NULL));
    // /* 挂载设备 */
    // ESP_ERROR_CHECK(tinyusb_msc_storage_mount(BASE_PATH));
    /* 配置USB */
    const tinyusb_config_t tusb_cfg = {
        .device_descriptor = &descriptor_config,    /* 设备描述符 */
        .string_descriptor = string_desc_arr,       /* 字符串描述符 */
        .string_descriptor_count = sizeof(string_desc_arr) / sizeof(string_desc_arr[0]),    /* 字符串描述符大小 */
        .external_phy = false,                      /* 使用内部USB PHY */
        .configuration_descriptor = msc_fs_configuration_desc,      /* 配置描述符 */
    };
    /* 初始化USB */
    ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));

    while(1)
    {
        LED0_TOGGLE();
        vTaskDelay(500);
    }
}
46.4 下载验证

         将程序下载到开发板后(注意:先插在UART 端口进行程序下载,程序下载完成后,然后要插在 USB 端口!),打开设备管理器,在通用串行总线控制器->出现 USB大容量存储设备,表示实验成功:

图 46.4.1 实验成功显示USB大容量存储设备

        如图 46.4.1, ESP32 通过 SD 卡模拟 U 盘,被电脑识别了,通用串行总线控制器显示的是:USB 大容量存储设备(显示总共14.8G,SD卡总共16G)。此时,开发板的 LED 在闪烁,提
示程序运行。

图 45.4.2 ESP32 SD 卡模拟 U 盘实验测试

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐