ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ESP32-P4 USB Host实战:U盘读写硬件设计、TinyUSB与FATFS详解

ESP32-P4 USB Host实战:U盘读写硬件设计、TinyUSB与FATFS详解 ESP32-P4到手之后我第一个想试的并不是网络或者显示而是它的USB Host。原因很简单这块芯片是乐鑫目前唯一一颗内置USB 2.0 High-Speed OTG控制器、而且主频能跑到双核400MHz的型号之前那颗带WiFi的S3虽然也带USB但只有12Mbps全速插个U盘只能算“能读”速度感人。而P4把USB直接拉到480Mbps这才真正有了把U盘当产品级数据交换介质的底气。这篇博文就围绕《DNESP32P4开发指南_V1.0》第四十七章的USB U盘实验展开聊聊为什么U盘方案在嵌入式里值得做、硬件上哪些坑必须躲、软件上TinyUSB和FATFS怎么配合以及我调试过程中最常用的排查手段。如果你正在做数据采集、固件升级、日志导出这类需要“用户手动插个U盘就能拷数据”的产品这篇文章应该能帮你少走不少弯路。1. 实验背景与整体思路拆解1.1 ESP32-P4的USB外设到底强在哪ESP32-S3那个USB大家应该不陌生SDK里叫USB OTG 1.1跑的是Full Speed也就是12Mbps。别看“1.1”和“Full Speed”这些词不算起眼实际用起来你会发现全速模式做U盘读取基本是折磨理论带宽1.5MB/s再加上协议开销实际读写一个小文件都慢得让人怀疑人生。P4这颗芯片直接把USB控制器升级到USB 2.0 High-Speed480Mbps虽然实际读写U盘不可能跑满但实测下来二三十MB/s是能到的这个量级才谈得上“产品可用”。另外P4的USB控制器不光是速率升级它内部有独立的PHY支持OTG角色切换。也就是说同一套硬件既可以当Host去读U盘也可以当Device被电脑枚举成虚拟串口甚至可以连USB摄像头这种流式设备。我最早看到“esp32-s3 usb摄像头”这种搜索词频繁出现就知道大家其实对USB外设生态很感兴趣只是被S3的全速带宽卡住了。P4的出现正好解开这个结而U盘又是最基础、最能验证整条USB Host链路是否可靠的应用场景。1.2 为什么选U盘而不直接用SD卡嵌入式存储方案里SD卡一直是主流TF卡槽便宜、电路简单、SDIO接口在几乎所有MCU上都有现成驱动。但SD卡有几个天然短板第一用户手里不一定有读卡器插拔体验对普通用户不友好第二卡座是机械结构抗振动、耐插拔次数都不如USB口第三很多产品要求用户自己拷数据进去U盘“插上就能认”的通用性是SD卡给不了的。U盘方案最大的价值在于它是“通用数据交换介质”。现场调试的工程师、不会拆机壳的终端用户、甚至笔记本电脑上拷文件的人都会用U盘。做产品的时候一台设备如果支持U盘读写就等于有了一个极低成本的数据导入导出通道配置文件拷进去、日志数据拷出来、固件通过U盘升级全部可以脱离上位机软件完成。这也是我在P4平台上首选U盘实验的原因它验证的不只是“MCU能不能读U盘”而是整条“产品数据链路”能不能跑通。1.3 整体软硬件架构P4平台跑U盘在软件上其实是一个“三段式”结构最底层是TinyUSB协议栈它负责把EHCI控制器的硬件事件转换成USB协议事件中间层是Mass Storage Class也就是MSC设备类驱动它负责发SCSI命令去读U盘的扇区最顶层是FATFS文件系统它把扇区抽象成文件、目录、路径让你在应用层用f_open/f_read/f_write这种API操作文件。硬件上则需要把P4的USB DP/DM引脚接到一个USB-A母座或者Type-C座子上同时处理VBUS的5V供电。因为P4的USB操作电压是3.3V电平而USB规范里DP/DM总线信号在高速模式下是差分400mV中间还涉及终端电阻匹配所以硬件上不是简单拉两根线过去就完事。下面这一节就专门讲硬件设计里最容易出问题的地方。2. 硬件准备与USB电路设计要点2.1 硬件清单与最小系统我这个实验用的是DNESP32P4开发板板载串口、JTAG、LED这些常规外设都有方便调试。除此之外我还额外准备了这些物料USB-A母座直插式方便接普通U盘5V/2A电源适配器或者带供电的USB HUB若干公对公USB延长线方便插拔观察一个Format好格式的U盘建议先格式化成FAT32示波器或者逻辑分析仪用于排查信号问题接线方面P4的USB DP/DM引脚在开发板上一般会引出来或者在板上已经接好了一个USB口。如果做自己的板子注意走线要做差分对DP/DM尽量等长距离不要拉太长控制在2厘米以内最好。U盘的D/D-本身是USB设备端引脚在Host侧我们不需要在DP/DM上拉电阻但这个概念很多人会搞混下面详细说。2.2 VBUS供电与带载能力设计USB Host和USB Device一个本质区别就是Host要给设备供电。USB规范里默认设备从VBUS取电一个单元负载是100mA最高可以申请5个单元负载也就是500mA。高速U盘在读写瞬间电流可能波动峰值甚至可以到700mA以上。因此设计VBUS电源一定要留够余量我建议直接用5V/2A以上的电源轨不要用LDO因为LDO压差大、效率低读写U盘时VBUS跌落会造成掉盘。另一个关键点是VBUS要能“软开关”。很多开发板直接把5V接到USB口的VBUS上一上电U盘就带电但此时P4的USB控制器还没初始化U盘提前进入枚举状态等系统起来反而可能识别失败。我的习惯是VBUS经过一个负载开关芯片比如RT9742、TPS2041或者最简单的P-MOS管开关用GPIO控制EN引脚。代码里先拉高EN打开VBUS延时200ms等U盘上电稳定再初始化USB Host栈。这个细节看似不起眼但能省掉非常多“时灵时不灵”的烦恼。没做过USB Host的朋友可能不理解为什么一开机U盘就识别不了你想想U盘内部的MCU也要做上电初始化VBUS刚上电一瞬间它还在运行自己的固件如果此时Host立刻发控制传输比如GET_DESCRIPTORU盘根本没有准备好直接不响应Host等待超时后可能就把这个设备判定为无效。如果Host的枚举代码还会在设备枚举失败之后重试那还好说如果不重试就只能断电重来。所以“先给VBUS供电延时再开始枚举”是最稳妥的做法。2.3 D/D-信号完整性与关键电容电阻这里我要专门说说“usb dd-电容大小”这个热点话题。很多新手在参考电路里看到DP/DM线上并排着电容以为是随便选的其实这几个元件都有讲究。高速USB的D/D-是差分对信号幅度只有400mV左右且高速信号对阻抗有要求90Ω差分阻抗。因此做过USB产品的工程师都知道几个常用手段在DP/DM线上各串联一个22Ω左右的电阻。这个电阻的作用是限制信号边沿的过冲同时保护MCU引脚避免插拔瞬间的静电或短路损伤。有些设计用33Ω有些用10Ω但22Ω是USB高速设计最常见的值。对地并联的电容常见取值是15pF~22pF。这个电容不能太大如果并了100pF甚至更大会把高速信号上升沿拉慢USB眼图测试直接失败U盘就会识别为低速设备或者干脆无法枚举。所以如果你看到某块板子在DP/DM上并了“看起来挺大”的电容那大概率是抄板抄出来的错误设计。ESD保护器件几乎是必选项。USB口是外露接口人体静电很容易灌进去推荐用专用USB ESD保护管比如USBLC6-2SC6它能钳位瞬态过压同时寄生电容只有0.6pF对高速信号几乎没有影响。有人为了省成本直接用TVS二极管但TVS的结电容往往太大用上去高速USB必出问题千万别省这个钱。我这块开发板原设计已经带了完整的USB防护电路但我仍然自己做了个飞线扩展板来测试不同的电阻电容组合实测下来22Ω串阻 15pF对地电容是最稳妥的配置既能过信号质量检查也不会因为负载太重而拉低电压。2.4 Type-C接口的CC引脚处理如果你的产品想用Type-C口来插U盘那CC引脚绕不开。Type-C的CC1/CC2引脚不只是用来检测正反插还承担着角色识别的功能Host电源提供方DFP需要在CC1/CC2上各接一个5.1kΩ下拉电阻到地Device电源消费者UFP则需要接5.1kΩ上拉电阻或者用Ra/Rd组合来标识。我看到网上经常有朋友问“USB的CC引脚有一个5.1k下拉那怎么切换到主机模式”这其实是个经典的误区。Type-C的CC引脚下拉到地只是告诉对面“我是一个DFP我能供电”它在物理层决定了电源角色而USB控制器跑的是Host逻辑还是Device逻辑是软件层的配置两者不是一回事。你的嵌入式设备要用Type-C口当Host去读U盘硬件上必须先把CC1/CC2下拉到地否则对端的U盘或Type-C转接头会默认以为自己是Host、你是设备链路协商就不对了。很多U盘本身是USB-A口插到Type-C座子上是通过转接线或者转接头转接的转接线的内部已经把CC处理好了直插Type-C的U盘则遵循规范里的Rd下拉识别所以你在自己的板子上也要按要求加5.1k下拉这条非常重要。除了CC之外USB 2.0只用到D/D-两根数据线Type-C座子上的SBU1/SBU2、高速差分对等引脚可以悬空接线只接VBUS、GND、CC1/CC2、D/D-即可。3. 软件开发环境与工程配置3.1 ESP-IDF环境搭建与SDK版本选择P4这颗芯片虽然2024年底才逐步开放完整支持但ESP-IDF的迭代速度很快。我在实验里用的是IDF v5.3版本这个版本的TinyUSB组件已经比较成熟ESP-IDF官方还提供了usb/host/msc这个例程路径在examples/peripherals/usb/host/msc目录下。如果你的IDF版本太老建议先升级否则编译P4相关例程会报各种不兼容的宏定义错误。第一次编译P4工程前记得先设置目标芯片idf.py set-target esp32p4 idf.py menuconfig如果你是直接用官方MSC例程基本不需要改太多配置但建议把“Component config → USB Host Stack → Enable TinyUSB Host”确认打开同时把“FATFS”相关的长文件名支持选项打开否则你往U盘里放中文名文件会读取不出来。3.2 menuconfig关键配置项USB Host实验里最容易踩的配置坑有三个第一是TinyUSB Host栈必须启用。有些开发环境默认只启用了TinyUSB DeviceUSB Host功能完全没有编译进去你写了半天代码结果编译报错说找不到tusb_host_install就是这里没开。第二是FATFS的编码选项。嵌入式用FATFS挂载Windows格式化的U盘常见问题就是exFAT分区的U盘挂不上。FATFS默认只支持FAT12/16/32exFAT是独立宏开关。在menuconfig里找到“Component config → FAT Filesystem support → Enable exFAT support”打开才能识别大容量U盘。不过要注意exFAT是微软的专利格式商用产品需要考虑授权问题个人学习无所谓。第三是文件系统卷标。ESP-IDF的VFS和FATFS会用卷标来区分不同设备比如SD卡挂载到“/sd”U盘挂载到“/usb”。卷标配置在代码里注册VFS驱动时指定后面会写到。如果这个编号冲突挂载第二个设备时会返回FR_INVALID_DRIVE或者直接报错“mount failed”。3.3 从扇区到文件USB数据流分层理解在写代码之前我强烈建议先把USB读U盘的数据流理一遍不然调试的时候会遇到“文件打不开但是枚举一切正常”这种魔幻问题。当你在应用层调用f_open打开一个“/usb/test.txt”文件时底层发生的事情是FATFS向diskio层请求读取某个扇区的数据。注意FATFS并不知道“U盘”是什么它只认扇区。diskio层我们自己写的底层驱动把“读扇区”转换成MSC类的SCSI命令比如READ CAPACITY、READ(10)这些命令被封装成CBWCommand Block Wrapper包通过USB总线发给U盘。U盘收到CSWCommand Status Wrapper响应后把数据返回给Hostdiskio层再把数据交给FATFS。FATFS根据FAT表、目录项、簇链等元数据最终组织出“test.txt”这个文件的内容。这个过程和你在PC上读U盘的机制没有本质区别只是底层USB传输、SCSI命令、FATFS解析都被我们“摊开”了。理解这一层之后你调代码的思路就会清晰很多文件读不出来先查FATFS挂载是否成功挂载失败查diskio层的READ(10)有没有数据返回数据都为空查USB枚举是否成功、U盘是否进入了配置完成状态。一层一层排查比头痛医头强得多。4. 核心代码逻辑与实现4.1 USB Host栈初始化流程P4的USB Host初始化和我以前在STM32上折腾的USB库完全不是一个思路。STM32的USB库是标准库风格一堆回调函数要你自己填ESP-IDF用TinyUSB初始化流程非常简洁安装Host栈→安装MSC客户端→主循环里调用任务处理函数。#include tinyusb.h #include tusb_host_msc.h #include esp_vfs_fat.h #include diskio_impl.h static msc_host_t msc_host { .callback msc_event_cb, }; void app_main(void) { // 1. 先打开VBUS电源给U盘上电 gpio_set_level(VBUS_EN_PIN, 1); vTaskDelay(pdMS_TO_TICKS(200)); // 2. 安装TinyUSB Host栈 tinyusb_host_config_t host_cfg {0}; ESP_ERROR_CHECK(tinyusb_host_install(host_cfg)); // 3. 安装MSC客户端驱动 ESP_ERROR_CHECK(msc_host_install(msc_host)); // 4. 主循环 while (1) { tinyusb_host_task(); // 其他业务代码 } }这里msc_event_cb是MSC设备插拔的回调函数。值得说明的是TinyUSB Host和Device不同它没有自动的“死循环”处理你需要在自己的主循环里反复调用tinyusb_host_task否则USB事件得不到处理U盘插上去也不会枚举。很多人刚开始会犯这个错把tinyusb_host_task放到单独线程里跑结果忘记开线程调度整个系统卡死。4.2 U盘枚举与块设备读取MSC驱动安装好之后当U盘插入TinyUSB会完成枚举并回调msc_event_cb。这时你需要判断事件类型MSC_HOST_EVENT_CONNECTED表示设备连上了MSC_HOST_EVENT_MOUNTED表示已经挂载成功可以开始读写扇区MSC_HOST_EVENT_ERROR则枚举出错。枚举成功之后我们才能真正把U盘当成块设备来操作。diskio层是FATFS和USB设备之间的“翻译官”我需要自己实现disk_initialize、disk_status、disk_read、disk_write、disk_ioctl这几个函数。static DSTATUS disk_initialize(BYTE pdrv) { if (msc_host_get_connected(msc_host) ! ESP_OK) { return STA_NOINIT; } return RES_OK; } static DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) { if (msc_host_read_sectors(msc_host, sector, count, buff) ! ESP_OK) { return RES_ERROR; } return RES_OK; } static DRESULT disk_write(BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count) { if (msc_host_write_sectors(msc_host, sector, count, buff) ! ESP_OK) { return RES_ERROR; } return RES_OK; } static DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff) { // GET_SECTOR_COUNT / GET_SECTOR_SIZE / CTRL_SYNC return RES_OK; }不同SDK版本的MSC API名字可能略有差别比如有的版本叫tusb_msc_read有的叫msc_host_read_sectors但整体逻辑是一致的。最关键的一点是disk_read/disk_write必须同步返回如果USB传输时间太长FATFS会把它当作超时错误处理所以在MSC层调用时最好检查返回值和扇区数确保真的读写完成了再返回RES_OK。4.3 FATFS文件系统挂载与文件操作diskio层就位之后就可以把U盘注册到ESP-IDF的VFS文件系统里了。这里推荐用esp_vfs_fat_register它会帮你完成FATFS案例的挂载、卷标分配、以及VFS路径注册应用层直接fopen就能读写文件。static FATFS s_fs; static char s_drive[8]; void msc_event_cb(msc_host_t *msc, msc_host_event_t event) { switch (event) { case MSC_HOST_EVENT_CONNECTED: ESP_LOGI(TAG, U盘已连接); break; case MSC_HOST_EVENT_MOUNTED: ESP_LOGI(TAG, U盘已挂载开始注册FATFS); // 分配卷标注册VFS snprintf(s_drive, sizeof(s_drive), %d:, msc- connected_drive_num); ESP_ERROR_CHECK(esp_vfs_fat_register(/usb, s_drive, 1, s_fs)); ESP_LOGI(TAG, VFS已注册/usb); break; case MSC_HOST_EVENT_ERROR: ESP_LOGE(TAG, U盘枚举或挂载出错); break; default: break; } }注册完成后文件读写就变成标准C库操作了FILE *fp fopen(/usb/hello.txt, w); if (fp) { fprintf(fp, Hello from ESP32-P4 USB Host\n); fclose(fp); } fp fopen(/usb/hello.txt, r); if (fp) { char buf[64] {0}; fgets(buf, sizeof(buf), fp); fclose(fp); ESP_LOGI(TAG, 读取内容%s, buf); }这里注意一个细节卷标分配。上面代码里“%d:”的冒号是必须的FATFS的卷标格式就是“0:”“1:”这种如果你只写“0”FATFS不认。另外esp_vfs_fat_register的第一个参数“/usb”是VFS挂载点应用层fopen时用这个路径来访问U盘文件。4.4 完整示例遍历U盘根目录并读取文件为了验证实验是否跑通我在main函数里写了一个小功能U盘挂载成功后列出根目录下所有文件名并且读取第一个txt文件的内容打印到串口。static void list_usb_files(void) { DIR *dir opendir(/usb); if (dir NULL) { ESP_LOGE(TAG, 打开目录失败); return; } struct dirent *entry; char filepath[64]; while ((entry readdir(dir)) ! NULL) { ESP_LOGI(TAG, 找到文件%s, entry-d_name); // 只读取第一个txt文件 if (strstr(entry-d_name, .txt)) { snprintf(filepath, sizeof(filepath), /usb/%s, entry-d_name); FILE *fp fopen(filepath, r); if (fp) { char buf[256] {0}; fgets(buf, sizeof(buf), fp); ESP_LOGI(TAG, 文件内容%s, buf); fclose(fp); } } } closedir(dir); }这个例子的意义在于验证整条链路USB枚举→SCSI命令→FATFS解析→目录遍历→文件读取。如果你把这几个功能都调试通过说明你的P4 USB Host已经可以干实事了。5. 常见问题与USB调试经验5.1 U盘识别不稳定的排查实录我在实验过程中遇到的最常见问题就是“时灵时不灵”。第一天能正常读写的U盘第二天插上去就是“usb device not accepting address”类似的现象而且不是每次都失败大概插十次有两次识别不到。排查第一步是看日志里的枚举流程。TinyUSB的日志如果开着的话会打印从设备地址分配、获取描述符到设置配置的整个过程。如果日志在“SET_ADDRESS”之后就戛然而止大概率是控制传输超时也就是USB总线上设备没响应。这时候先用万用表量U盘供电VBUS是不是稳定在5V插上U盘瞬间电压有没有跌落。如果供电正常再查DP/DM波形。用示波器在插U盘瞬间抓DP线上的信号你会看到主机发SetAddress等控制包然后设备返回ACK。如果设备一直不响应最常见原因就是你板子上的DP/DM接反了。DP和DM是一对差分线接反后设备也能上电但永远无法枚举成功日志里一直重复“ERR_TIMEOUT”。再一个容易被忽视的问题是USB线材。很多劣质USB线内部只有电源线没有数据线或者数据线极细信号衰减严重。我后来干脆换了一根带屏蔽层的品牌线问题立刻少了。做产品时如果U盘识别的可靠率上不去先怀疑线材和接插件这俩的成本最低但故障率最高。5.2 文件系统挂载失败检查清单U盘枚举成功、MSC也打印了MOUNTED事件但挂载FATFS时却报错这是第二类高频问题。我整理了一张排查清单现象可能原因排查方法f_mount返回FR_NO_FILESYSTEMU盘是exFAT或NTFS格式FATFS未开启exFAT支持打开CONFIG_FATFS_EXFAT或将U盘格式化为FAT32f_mount返回FR_INVALID_DRIVE卷标分配错误或与已有设备冲突检查磁盘号是否被SD卡占用改用未使用的卷标f_open报FR_INVALID_NAMEVFS路径写错没有带挂载点前缀确认路径是“/usb/文件名”不是“usb/文件名”读取目录但内容为空U盘是GPT分区格式FATFS只认MBR用DG或系统格式化工具将U盘改为MBR分区表文件能读但中文乱码FATFS长文件名宏与编码问题打开LFN支持确保文件系统代码页为CP936或UTF-8特别说一下GPT分区的问题。现在Windows 10/11格式化大容量U盘有时会默认用GPT分区表而FATFS官方实现只支持MBR分区表和简单的“超级软盘”式布局遇到GPT分区会直接挂载失败。解决方法是把U盘重新格式化为FAT32MBR多见于右键格式化的默认选项不够用需要用DiskGenius或者命令行工具强制改分区表类型。5.3 USB抓包分析从入门到实战USB调试到后面日志已经很难看出问题根源了这时候就必须上抓包工具。很多人以为USB抓包要买几千块的USB分析仪其实对于P4这种低速/全速/高速场景软件方案就够了。Linux下可以直接用usbmon接口配Wireshark抓包Windows下可以用USBPcap这些都是免费工具。抓的时候把PC的USB口接入一个USB HUB再把U盘插到HUB上这样被抓的设备是HUB下挂的U盘抓包才能看到完整的枚举过程和SCSI指令交互。注意抓包工具只能抓到PC主机和U盘之间的包没法抓到P4发出的包但没关系我们抓包的目的是反向验证“正常枚举长什么样”。比如你看到PC枚举U盘时会发GET_DESCRIPTOR、SET_CONFIGURATION会连续读取几次设备描述符这就是标准流程。P4这边如果枚举卡住你就可以对比检查是哪一步丢了。另一种更贴近实战的方案是用逻辑分析仪抓DP/DM线上的原始波形。低速和全速USB的信号是3.3V单端信号逻辑分析仪完全可以直接解码高速USB则要示波器看眼图逻辑分析仪带宽不够。不过对于U盘调试绝大多数问题出在低速/全速状态下的枚举阶段U盘上电后先以Full Speed枚举再由主机发起高速握手所以逻辑分析仪就够用了。我调试时最常用的一招在TinyUSB日志里打开“Transfer log”级别的打印这样能看到每一个URB请求和完成状态。如果某个控制传输一直“pending”那就说明设备没有响应然后去抓包看PC对同款U盘是否也有类似操作从而判断是设备不兼容还是软件时序问题。这个思路配合前面说的USB抓包工具能解决九成以上的“U盘不识别”问题。5.4 关于U盘兼容性的几点经验最后说说U盘本身。U盘看似简单内部主控五花八门兼容性问题特别多。我手上攒了好几个品牌和杂牌的U盘实测下来发现几个规律一线品牌金士顿、闪迪、三星的高速U盘兼容性最好枚举稳定读写正常。部分老款U盘只支持USB 2.0 Full Speed插上后也能识别但速度被限制在1.5MB/s左右不是代码问题是设备本身不支持高速。杂牌U盘非常容易出怪问题枚举成功但读取扇区返回错误、分区表损坏、甚至插上之后VBUS被拉低导致系统重启。做产品时建议把自己U盘固件里支持的主控列表做一张兼容性测试表至少验证5款以上U盘再发布。那些“U盘变成raw格式”“写保护”之类的报错大多是U盘本身损坏或主控进入了保护模式可以用量产工具重新量产但不建议在产品里做这种修复逻辑成本高、收益低。我还试过用USB HUB扩多个U盘同时读取但TinyUSB Host对HUB的支持目前还不够完善多设备同时挂载时偶发枚举异常个人学习用没问题产品上就用单U盘方案比较稳。最后再分享一个小技巧如果你实验做下来感觉“能读但速度慢”先别急着怀疑代码。P4的USB HS理论速度是480Mbps但实际瓶颈往往在FATFS的簇大小和MSC命令的批量传输长度上。FATFS的簇如果太小文件碎片多读一个文件要发大量扇区读取命令速度自然上不去。建议U盘格式化时选择分配单元大小为64KB实测读取大文件的速度能提升不少。另一个办法是调整MSC类的最大传输扇区数把单次传输从默认的128扇区提到256扇区吞吐量会有明显改善。U盘实验看起来简单但它是检验整条USB Host链路是否健壮的最好试金石。我个人的体会是先把硬件信号质量做好再谈软件调试遇到问题时从上到下、从现象到协议逐层排查别被“看起来像驱动问题”的表象带偏。希望这篇记录对正在折腾ESP32-P4 USB Host的朋友有帮助。
返回列表