第四十九章 USB读卡器(Slave)实验
ESP32-P4芯片都自带了USB OTG HS和FS,支持USB Host和USB Device,所有USB相关例程,均可以使用USB OTG HS和FS实现。下面,我们将介绍如何使用USB OTG HS在DNESP32P4开发板上实现一个USB读卡器。
本章分为如下几个小节:
49.1 USB读卡器简介
49.2 硬件设计
49.3 程序设计
49.4 下载验证
49.1 USB读卡器简介
USB读卡器是一种通过USB接口连接计算机或其他设备,用于读取和写入存储卡(如SD卡、TF卡、CF卡等)数据的设备。它是一种便携、易用的外设,广泛应用于数据存储、传输和管理场景。本章节,我们使用板载的SD卡和内置Flash作为存储卡,实现USB读卡器功能。对于SD卡相关知识,请读者参考本书籍的第38章节。
ESP32-P4的USB OTG HS接口是支持USB读卡器功能的。Espressif官方在ESP-IDF中提供了针对USB device大容量存储设备(MSC)的示例代码,路径为:esp-idf\examples\peripherals\usb\device\tusb_msc。本章节实验将参考官方示例代码,通过ESP32-P4的USB HOST接口实现对USB读卡器设备读写操作。
49.2 硬件设计
49.2.1 程序功能
通过使用USB A转A数据线,将开发板的HOST端口与PC的USB端口连接。连接成功后,PC会识别并显示开发板中挂载的“SD卡”和“SPI Flash”磁盘,用户即可对其进行文件读写操作。
本章节实验包含两个示例:
1)39_usb_sd_u实验:将SD卡挂载为磁盘,实现PC的文件系统交互功能。
2)40_usb_flash_u实验:将开发板内置的Flash挂载为磁盘,实现PC的文件系统交互功能。
读者可以根据实际需求选择对应的实验进行操作。
49.2.2 硬件资源
1)LED灯
LED 0 - IO51
2)RGBLCD/MIPILCD(引脚太多,不罗列出来)
3)SPIFFS
4)SD卡
CMD - IO44
CLK - IO43
D0 - IO39
D1 - IO40
D2 - IO41
D3 - IO42
49.2.3 原理图
USB HOST原理图已在48.2.3小节中详细阐述,为避免重复,此处不再赘述。
49.3 程序设计
49.3.1 esp_tinyusb的IDF驱动
esp_tinyusb组件驱动位于ESP-IDF在线组件注册表中。如果需要将该组件添加到项目工程中,可按照以下步骤操作:
1)打开ESP-IDF注册表。
2)搜索 “esp_tinyusb”组件。
3)将组件安装到项目中。
组件安装完成后,系统会自动更新main文件夹中的特殊组件清单文件idf_component.yml在项目编译时,系统会根据清单文件从注册表中下载并集成该组件到工程中。关于上述操作流程,可参考本书籍第八章的内容。
为了使用 esp_tinyusb组件提供的功能,首先需要在代码中导入以下头文件:
#include "tinyusb.h" #include "tusb_msc_storage.h"
接下来,作者将介绍本章节实验用到的esp_tinyusb函数,这些函数的描述及其作用如下:
1,在TinyUSB驱动中注册SD卡存储类型tinyusb_msc_storage_init_sdmmc
该函数用于在TinyUSB驱动中注册SD卡设备,其函数原型如下:
esp_err_t tinyusb_msc_storage_init_sdmmc( const tinyusb_msc_sdmmc_config_t*config);
函数形参:

表49.3.1.1 tinyusb_msc_storage_init_sdmmc函数形参描述
返回值:
ESP_OK表示注册成功。
ESP_ERR_NO_MEM表示分配内存失败。
config为指向配置SDMMC初始化的结构体。接下来,笔者将详细介绍tinyusb_msc_sdmmc_config_t结构体中的各个成员变量,如下代码所示:
typedef struct {
sdmmc_card_t *card; /* 指向SDMMC卡配置结构体的指针 */
/* 指向挂载/卸载操作完成后回调函数的指针 */
tusb_msc_callback_t callback_mount_changed;
/* 指向挂载/卸载操作开始前回调函数的指针 */
tusb_msc_callback_t callback_premount_changed;
const esp_vfs_fat_mount_config_t mount_config; /* FATFS挂载配置 */
} tinyusb_msc_sdmmc_config_t;
上述结构体用于配置SDMMC初始化参数,以下对各个成员做简单介绍。
1)card:
定义sdmmc_card_t结构体指针变量,用于指向SDMMC句柄。
2)callback_mount_changed:
挂载/卸载操作完成后回调函数的指针,一般我们配置为NULL。若读者向配置这一个回调函数,则可根据tusb_msc_callback_t类型自定义回调函数。
3)callback_premount_changed:
挂载/卸载操作开始前回调函数的指针,一般我们配置为NULL。若读者向配置这一个回调函数,则可根据tusb_msc_callback_t类型自定义回调函数。
4)mount_config:
FATFS挂载配置,一般我们配置mount_config.max_files字段为5。
2,注册MSC事件回调函数tinyusb_msc_register_callback
该函数用于注册MSC事件回调函数,其函数原型如下:
esp_err_t tinyusb_msc_register_callback(tinyusb_msc_event_type_t event_type, tusb_msc_callback_t callback);
函数形参:

表49.3.1.2 tinyusb_msc_register_callback函数形参描述
返回值:
ESP_OK表示注册成功。
ESP_ERR_INVALID_ARG表示参数无效。
3,将存储设备挂载到应用程序固件tinyusb_msc_storage_mount
该函数用于将存储设备挂载到应用程序固件,其函数原型如下:
esp_err_t tinyusb_msc_storage_mount(const char *base_path);
函数形参:

表49.3.1.3 tinyusb_msc_storage_mount函数形参描述
返回值:
ESP_OK表示挂载成功。
ESP_ERR_NOT_FOUND表示已挂载数达到最大数量。
ESP_ERR_NO_MEM表示内存不足或注册太多VFS。
4,安装USB设备驱动tinyusb_driver_install
该函数用于安装USB设备驱动,其函数原型如下:
esp_err_t tinyusb_driver_install(const tinyusb_config_t *config);
函数形参:

表49.3.1.4 esp_err_t tinyusb_driver_install函数形参描述
返回值:
ESP_OK表示驱动程序和TinyUSB栈成功安装。
ESP_ERR_INVALID_ARG表示参数无效导致驱动程序和TinyUSB栈安装失败。
ESP_FAIL表示内部错误导致驱动程序和TinyUSB栈安装失败。
49.3.2 程序流程图

图49.3.2.1 USB读卡器实验程序流程图
49.3.3 程序解析
本实验旨在通过调用ESP-TinyUSB组件的API函数,实现USB读卡器功能。实验的代码已经编写在main.c文件中,并提供了两种实现方式:
1,main.c驱动代码(SD卡方式)
sdmmc_card_t *card = NULL;
/* 挂载名称 */
#define BASE_PATH "/0:" /* 挂载分区大小的基本路径 */
/* TinyUSB 描述符 */
#define EPNUM_MSC 1
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN)
enum {
ITF_NUM_MSC = 0,
ITF_NUM_TOTAL
};
enum {
EDPT_CTRL_OUT = 0x00,
EDPT_CTRL_IN = 0x80,
EDPT_MSC_OUT = 0x01,
EDPT_MSC_IN = 0x81,
};
/**
* @brief sdmmc
* @param 无
* @retval ESP_OK:初始化成功
*/
esp_err_t sdmmc_init(void)
{
/* 使用默认配置SDMMC */
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
/* 传输宽度为4 */
slot_config.width = 4;
/* 根据原理图配置管脚 */
slot_config.d0 = GPIO_NUM_39;
slot_config.d1 = GPIO_NUM_40;
slot_config.d2 = GPIO_NUM_41;
slot_config.d3 = GPIO_NUM_42;
slot_config.clk = GPIO_NUM_43;
slot_config.cmd = GPIO_NUM_44;
/* 配置内部上拉 */
slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;
card = (sdmmc_card_t *)malloc(sizeof(sdmmc_card_t));
/* 配置默认 */
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; /* 设置速度最大值为40MHz */
(*host.init)();
sdmmc_host_init_slot(host.slot, (const sdmmc_slot_config_t *) &slot_config);
while (sdmmc_card_init(&host, card))
{
vTaskDelay(pdMS_TO_TICKS(3000));
}
sdmmc_card_print_info(stdout, card);
return ESP_OK;
}
/* USB设备描述符 */
static tusb_desc_device_t descriptor_config = {
.bLength = sizeof(descriptor_config), /* 描述符的大小 */
.bDescriptorType = TUSB_DESC_DEVICE, /* 描述符类型 */
.bcdUSB = 0x0200, /* BUSB规格发布号 */
.bDeviceClass = TUSB_CLASS_MISC, /* MSC类 */
.bDeviceSubClass = MISC_SUBCLASS_COMMON, /* 子类代码(由USB-IF分配) */
.bDeviceProtocol = MISC_PROTOCOL_IAD, /* 协议代码(由USB-IF分配) */
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, /* 端点0的最大数据包大小*/
.idVendor = 0x303A, /* 供应商ID(由USB-IF分配) */
.idProduct = 0x4002, /* 产品ID(由制造商指定) */
.bcdDevice = 0x100, /* 以二进制编码的十进制表示 */
.iManufacturer = 0x01, /* 描述制造商的字符串描述符的索引 */
.iProduct = 0x02, /* 描述产品的字符串描述符的索引 */
.iSerialNumber = 0x03, /* 描述设备序列号的字符串描述符索引 */
.bNumConfigurations = 0x01 /* 可能的配置数 */
};
/* 全速配置描述符 */
static uint8_t const msc_fs_configuration_desc[] = {
/* 配置号,接口计数,字符串索引,总长度,属性,功率(mA) */
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN,
TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
/* 接口编号,字符串索引,EP Out & EP In地址,EP大小 */
TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, 64),
};
#if (TUD_OPT_HIGH_SPEED)
/* USB设备限定符 */
static const tusb_desc_device_qualifier_t device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t), /* 描述符的大小 */
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, /* 设备限定符类型 */
.bcdUSB = 0x0200, /* USB规格版本号(例如,V2.00为0200H) */
.bDeviceClass = TUSB_CLASS_MISC, /* 类代码 */
.bDeviceSubClass = MISC_SUBCLASS_COMMON, /* 子类代码 */
.bDeviceProtocol = MISC_PROTOCOL_IAD, /* 协议码 */
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, /* 最大数据包大小 */
.bNumConfigurations = 0x01, /* 其他速度配置的个数 */
.bReserved = 0 /* 保留,必须为零 */
};
/* 高速配置描述符 */
static uint8_t const msc_hs_configuration_desc[] = {
/* 配置号,接口计数,字符串索引,总长度,属性,功率(mA) */
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN,
TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
/* 接口编号,字符串索引,EP Out & EP In地址,EP大小 */
TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, 512),
};
#endif
/* 字符串描述符 */
static char const *string_desc_arr[] = {
(const char[]) { 0x09, 0x04 }, /* 0: 支持英文 (0x0409) */
"TinyUSB", /* 1: 生产商 */
"TinyUSB Device", /* 2: 产品 */
"123456", /* 3: 序列 */
"Example MSC", /* 4. MSC */
};
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初始化 */
lcd_init(); /* LCD屏初始化 */
/* 显示实验信息 */
lcd_show_string(30, 50, 200, 16, 16, "ESP32-P3", RED);
lcd_show_string(30, 70, 200, 16, 16, "USB SD TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
ESP_ERROR_CHECK(sdmmc_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 */
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = msc_fs_configuration_desc,
.hs_configuration_descriptor = msc_hs_configuration_desc,
.qualifier_descriptor = &device_qualifier,
#else
.configuration_descriptor = msc_fs_configuration_desc, /* 配置描述符 */
#endif
};
/* 初始化USB */
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
while(1)
{
LED0_TOGGLE();
vTaskDelay(500);
}
}
上述代码实现了在ESP32-P4上通过SD卡模拟USB存储设备(Mass Storage Device)的功能,并利用TinyUSB库完成设备描述符和配置描述符的注册,使设备能与主机通信。为了更好地了解上述代码,这里笔者分为四个部分讲解。
1)SD卡初始化:
通过sdmmc_init函数完成对SD卡的初始化,包括SDMMC管脚的配置、数据传输宽度(4位)、内部上拉电阻的启用等。程序检测到SD卡插入后,完成卡信息的打印,为后续的文件系统挂载和USB存储功能提供支持。
2)TinyUSB设备描述符配置:
定义了USB设备描述符descriptor_config,包括USB协议版本、设备类(MSC类)、厂商ID、产品ID等内容。通过该描述符,设备向主机描述自身的功能和特点。同时,代码根据不同速度(全速/高速)定义了配置描述符(msc_fs_configuration_desc和msc_hs_configuration_desc)以支持不同的USB数据传输模式。
3)文件系统挂载与存储注册:
利用TinyUSB的API函数tinyusb_msc_storage_init_sdmmc注册SD卡作为USB存储设备。通过tinyusb_msc_storage_mount将SD卡的分区挂载为文件系统(挂载路径为/0:)。代码还支持注册回调函数,用于监听挂载事件。
4)TinyUSB驱动初始化:
使用tinyusb_driver_install函数完成TinyUSB驱动的安装,将USB设备描述符和字符串描述符注册到TinyUSB栈中,并设置是否使用外部USB PHY。设备启动后,ESP32-P4作为USB存储设备可以被主机识别和访问。
2,main.c驱动代码(Flash方式)
static const char *TAG = "example_main";
sdmmc_card_t *card = NULL;
/* 挂载名称 */
#define BASE_PATH "/0:" /* 挂载分区大小的基本路径 */
/* TinyUSB 描述符 */
#define EPNUM_MSC 1
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN)
enum {
ITF_NUM_MSC = 0,
ITF_NUM_TOTAL
};
enum {
EDPT_CTRL_OUT = 0x00,
EDPT_CTRL_IN = 0x80,
EDPT_MSC_OUT = 0x01,
EDPT_MSC_IN = 0x81,
};
/**
* @brief 子分区初始化
* @param wl_handle:wear levelling handle
* @retval ESP_OK:初始化成功
*/
static esp_err_t storage_init_spiflash(wl_handle_t *wl_handle)
{
ESP_LOGI(TAG, "Initializing wear levelling");
const esp_partition_t *data_partition = esp_partition_find_first
(ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_DATA_SPIFFS,
"storage");
if (data_partition == NULL)
{
return ESP_ERR_NOT_FOUND;
}
return wl_mount(data_partition, wl_handle);
}
/* USB设备描述符 */
static tusb_desc_device_t descriptor_config = {
.bLength = sizeof(descriptor_config), /* 描述符的大小 */
.bDescriptorType = TUSB_DESC_DEVICE, /* 描述符类型 */
.bcdUSB = 0x0200, /* BUSB规格发布号 */
.bDeviceClass = TUSB_CLASS_MISC, /* MSC类 */
.bDeviceSubClass = MISC_SUBCLASS_COMMON, /* 子类代码(由USB-IF分配) */
.bDeviceProtocol = MISC_PROTOCOL_IAD, /* 协议代码(由USB-IF分配) */
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, /* 端点0的最大数据包大小 */
.idVendor = 0x303A, /* 供应商ID(由USB-IF分配) */
.idProduct = 0x4002, /* 产品ID(由制造商指定) */
.bcdDevice = 0x100, /* 以二进制编码的十进制表示 */
.iManufacturer = 0x01, /* 描述制造商的字符串描述符的索引 */
.iProduct = 0x02, /* 描述产品的字符串描述符的索引 */
.iSerialNumber = 0x03, /* 描述设备序列号的字符串描述符索引 */
.bNumConfigurations = 0x01 /* 可能的配置数 */
};
/* 全速配置描述符 */
static uint8_t const msc_fs_configuration_desc[] = {
/* 配置号,接口计数,字符串索引,总长度,属性,功率(mA) */
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN,
TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
/* 接口编号,字符串索引,EP Out & EP In地址,EP大小 */
TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, 64),
};
#if (TUD_OPT_HIGH_SPEED)
/* USB设备限定符 */
static const tusb_desc_device_qualifier_t device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t), /* 描述符的大小 */
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, /* 设备限定符类型 */
.bcdUSB = 0x0200, /* USB规格版本号*/
.bDeviceClass = TUSB_CLASS_MISC, /* 类代码 */
.bDeviceSubClass = MISC_SUBCLASS_COMMON, /* 子类代码 */
.bDeviceProtocol = MISC_PROTOCOL_IAD, /* 协议码 */
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, /* 最大数据包大小 */
.bNumConfigurations = 0x01, /* 其他速度配置的个数 */
.bReserved = 0 /* 保留,必须为零 */
};
/* 高速配置描述符 */
static uint8_t const msc_hs_configuration_desc[] = {
/* 配置号,接口计数,字符串索引,总长度,属性,功率(mA) */
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN,
TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
/* 接口编号,字符串索引,EP Out & EP In地址,EP大小 */
TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, 512),
};
#endif
/* 字符串描述符 */
static char const *string_desc_arr[] = {
(const char[]) { 0x09, 0x04 }, /* 0: 支持英文 (0x0409) */
"TinyUSB", /* 1: 生产商 */
"TinyUSB Device", /* 2: 产品 */
"123456", /* 3: 序列 */
"Example MSC", /* 4. MSC */
};
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初始化 */
lcd_init(); /* LCD屏初始化 */
lcd_show_string(30, 50, 200, 16, 16, "ESP32-P4", RED);
lcd_show_string(30, 70, 200, 16, 16, "USB Flash TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
static wl_handle_t wl_handle = WL_INVALID_HANDLE;
ESP_ERROR_CHECK(storage_init_spiflash(&wl_handle));
const tinyusb_msc_spiflash_config_t config_spi = {
.wl_handle = wl_handle,
.callback_mount_changed = NULL,
.mount_config.max_files = 5,
};
ESP_ERROR_CHECK(tinyusb_msc_storage_init_spiflash(&config_spi));
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 */
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = msc_fs_configuration_desc,
.hs_configuration_descriptor = msc_hs_configuration_desc,
.qualifier_descriptor = &device_qualifier,
#else
.configuration_descriptor = msc_fs_configuration_desc, /* 配置描述符 */
#endif
};
/* 初始化USB */
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
while(1)
{
LED0_TOGGLE();
vTaskDelay(500);
}
}
上述代码实现了在ESP32-P4上通过内部Flash模拟USB存储设备(Mass Storage Device)的功能,并利用TinyUSB库完成设备描述符和配置描述符的注册,使设备能与主机通信。为了更好地了解上述代码,这里笔者分为四个部分讲解。
1)Flash中的子分区初始化:
通过storage_init_spiflash函数完成对Flash子分区的初始化,为后续的文件系统挂载和USB存储功能提供支持。
2)TinyUSB设备描述符配置:
定义了USB设备描述符descriptor_config,包括USB协议版本、设备类(MSC类)、厂商ID、产品ID等内容。通过该描述符,设备向主机描述自身的功能和特点。同时,代码根据不同速度(全速/高速)定义了配置描述符(msc_fs_configuration_desc和msc_hs_configuration_desc)以支持不同的USB数据传输模式。
3)文件系统挂载与存储注册:
利用TinyUSB的API函数tinyusb_msc_storage_init_sdmmc注册内部Flash子分区作为USB存储设备。通过tinyusb_msc_storage_mount将内部Flash的分区挂载为文件系统(挂载路径为/0:)。代码还支持注册回调函数,用于监听挂载事件。
4)TinyUSB驱动初始化:
使用tinyusb_driver_install函数完成TinyUSB驱动的安装,将USB设备描述符和字符串描述符注册到TinyUSB栈中,并设置是否使用外部USB PHY。设备启动后,ESP32-P4作为USB存储设备可以被主机识别和访问。
49.4 下载验证
下载程序后,使用USB A对A数据线连接开发板和PC,一端插入开发板上的HOST口,另一端插入PC的USB接口。此时,PC会识别并显示一个新的可操作磁盘(见下图所示)。
1)如果运行的是39_usb_sd_u实验,需插入一张正常工作的SD卡作为存储载体,磁盘内容基于 SD 卡。
2)如果运行的是40_usb_flash_u实验,无需插入SD卡,系统会自动使用开发板的Flash作为存储载体。

图49.4.1 电脑找到USB读卡器的盘符
注意:上图是笔者使用两个实验现象合并再一起的。