《ESP32-P4开发指南—V1.0》第四十九章 USB读卡器(Slave)实验

第四十九章 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读卡器的盘符


       注意:上图是笔者使用两个实验现象合并再一起的。


请使用浏览器的分享功能分享到微信等