尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ESP32-S3模拟USB U盘:TinyUSB MSC调试实战指南

ESP32-S3模拟USB U盘:TinyUSB MSC调试实战指南 最近项目里要把 ESP32-S3 模拟成一个 USB MSC 设备也就是让电脑把这块开发板当成一个 U 盘来访问。听起来不算复杂TinyUSB 官方示例也现成但真正调起来才发现枚举失败、驱动安装报错、文件拷一半掉线、Windows 提示“需要修复”……每个坑都能让人折腾一整天。这篇就把整个调试过程从头到尾记录下来包括硬件连接的几个隐藏陷阱、TinyUSB 代码骨架、完整排查链路以及用 USB 抓包实锤定位问题的方法。这篇内容适合正在用 ESP32-S3 / ESP32-S2 做 USB 设备开发的人尤其是第一次碰 MSCMass Storage Class大容量存储类的朋友。文章不会只贴代码更多是讲清楚每一步为什么要这样做以及出问题时的排查思路。1. ESP32-S3 的 USB 硬件底子选型前先搞清楚能不能做 MSC1.1 MSC 不是什么“高大上”的协议但也不是随便一个 USB 口就能跑MSC 是 USB 里的一个大容量存储类协议核心思想就是主机端把设备当成一块硬盘或者 U 盘通过读写扇区来存取数据。它的好处是主机不需要安装额外驱动——Windows、macOS、Linux 原生支持插上就能识别。所以很多嵌入式设备需要导出数据、升级固件时会选择模拟成 U 盘用户体验最好。但这里有个容易被忽略的前提要做 MSC 设备芯片必须带USB Device从机控制器而且这个控制器要支持至少一个 Bulk 端点对。普通串口转 USB 芯片、或者只带 USB-to-UART 桥接功能的芯片是做不了真正 MSC 的。有的 MCU 上面那个 USB 口只是给串口下载用的它内部的 USB 外设只实现了 CDC虚拟串口功能虽然也能枚举成设备但不可能变成 U 盘。ESP32-S3 的优势就在于它内部有两套独立的 USB 硬件一套是USB OTG也常叫 USB-OTG-FS一套是USB Serial/JTAG。前者可以自由配置成 MSC、CDC、HID 甚至是复合设备后者只是固定用来做调试串口和 JTAG 的。所以做 MSC 时必须把 USB OTG 这路引出来不能用默认的 USB Serial/JTAG 引脚去接电脑——那是另一套外设枚举出来的是串口不是 U 盘。1.2 S3 的 USB OTG 是 Full Speed不是 High Speed期望要摆正很多第一次做 USB 的人会在心里默认“USB 嘛至少也得几十 MB/s”实际完全不是这么回事。ESP32-S3 的 USB OTG 只支持Full Speed全速12Mbps不支持 High Speed高速480Mbps。12Mbps 是理论链路速率MSC 的 Bulk-Only 传输实际有效载荷还要打折扣再加上协议开销和 SD 卡读取时间实测稳定写速能到 1MB/s 左右就算不错了。这里不是劝退而是让大家提前把性能预期摆正。这个速度做配置文件导出、日志下载、几 MB 级别的固件拷贝完全够用但想通过它做高速数据备份趁早换方案。调试时如果发现读写速度上不去先别怀疑代码先确认链路本身就只有 12Mbps。另外注意S3 的 USB OTG 内部没有 PHY 的某些辅助电路VBUS 检测和 ID 检测引脚需要外部处理。这也是很多新手第一个坑硬件上只把 D/D- 接上去结果电脑毫无反应。1.3 S2 / S3 / C3 的 USB 能力对比芯片USB Serial/JTAGUSB OTG能否做 MSC备注ESP32-S3有有可以本文主角两个 USB 外设可同时存在ESP32-S2无有可以同样能跑 TinyUSB MSC但没有原生调试串口ESP32-C3有无不可以做常规 MSC只有 Serial/JTAG枚举为 CDC 类这个表值得在选型时贴墙上。C3 虽然便宜但它的 USB 口没法变成 U 盘。S2 能做 MSC但板子上如果没有额外引出 UART调试会麻烦不少因为没有 USB Serial/JTAG 那套独立外设。S3 是两个都有调试串口和 USB OTG 同时存在是最省心的选择。2. 硬件连接DP/DM/ID/5.1k下拉这几个坑一次讲明白2.1 DP/DM 引脚是固定的不能随便映射ESP32-S3 的 USB OTG 引脚固定为 GPIO20D和 GPIO19D-不经过 GPIO 矩阵不能像普通 GPIO 那样重新映射到别的引脚。这一点和 UART、SPI 完全不同板子画错了就只能飞线。所以画 PCB 或者接线之前第一件事就是确认这两个引脚没有被复用。S3 的 USB OTG 还需要开启内部的 3.3V 稳压供电这个在 IDF 菜单里有对应配置后面会讲到。硬件上 D/D- 对地各加一个 15kΩ 下拉电阻是参考设计里的做法实际很多模块不焊也能用但建议按官方参考设计来稳定压倒一切。调试初期建议直接在开发板上操作因为很多 S3 开发板已经引出了 USB OTG 的 D/D- 排针避免自己飞线的时候把差分信号线拉太长。我当时犯过一个错用杜邦线把 D/D- 飞出去 20 厘米接到一个 USB 座子上结果枚举时好时坏换成短线和洞洞板直接焊之后才稳定。USB 全速虽然对信号完整性要求不如高速那么苛刻但飞线超过 10cm 后尽量用双绞或者地线包裹。2.2 5.1k 下拉电阻到底什么时候才需要网络热词里有一条“USB 的 CC 引脚有一个 5.1k 下拉那怎么切换到主机模式”这是典型的 USB-C 和 OTG 概念混淆。先说结论如果你用的是普通 USB-A 转 Micro-B 或 Type-C 线把 S3 当设备Device插电脑那么不需要 CC 下拉电阻需要的是 D/D- 上拉到 3.3V 吗也不需要那是 Apple 设备识别的做法常规 PC 不管。需要关注 5.1k 下拉的场景是你做了一个 USB-C 母座想让 S3 作为 Device 插到电脑的 USB-C 口这时母座这边的 CC1/CC2 各下拉 5.1kΩ 到 GND让电脑识别到这是一个 UFPUpstream Facing Port也就是设备端。反之如果你想用 S3 做主机Host去插 U 盘USB-C 母座那边就要在 CC 上各上拉 5.1kΩ同时把 ID 引脚拉低。热词里“怎么切换到主机模式”的答案就是ID 引脚接地而 CC 上拉不是“主机模式”的必要条件只是 USB-C 接口的协商电阻。ID 引脚在 S3 的 USB OTG 里也要留意。做纯 Device 时ID 脚悬空或者接高都可以做 OTG 主机时ID 必须拉低。我见过有人把 ID 悬空就想去读 U 盘结果是设备始终枚举成 Device 模式。S3 的 USB OTG 模式切换是可以靠软件配置的但硬件上的 ID 状态会直接影响部分 SDK 的默认判断。2.3 供电和信号质量不要忽略 ESD 和 VBUSUSB 调试最容易忽视的是供电。S3 开发板一般有 5V 输入接口但如果你用 USB 公头直接插电脑供电要考虑板子整体电流能不能压住。MSC 场景下 SD 卡写入瞬间电流不小如果 USB 供电走的是线性稳压压降一大就会复位表现就是设备枚举成功后一读写就断连。另一个建议是 USB D/D- 线上加 ESD 保护二极管。调试阶段可能觉得没必要但如果是做产品或者长时间插拔测试ESD 导致的枚举不稳定非常难排查因为它不规律、复现难。哪怕用一个最便宜的 USBLC6-2也能省掉很多莫名其妙的“接触不良”。VBUS 检测方面S3 的 USB OTG 模块有 VBUS 检测引脚但很多开发板并没有连出来。IDF 的 TinyUSB 示例里通常可以通过tinyusb_driver_install配置vbus_monitor_io为-1表示不监测 VBUS。如果开发板确实引出了 VBUS 检测脚建议接上这样可以在代码里感知“USB 线拔了”主动卸载文件系统避免 SD 卡文件损坏。2.4 SD 卡模块和接线参考我做的是“SD 卡内容通过 USB 暴露成 U 盘”的方案SD 卡用的 SPI 模式接法如下SD 卡模块引脚ESP32-S3 GPIOCSGPIO10SCKGPIO12MOSIGPIO11MISOGPIO13VCC3.3VGNDGND注意 SD 卡的 SPI 不能用硬件 SPI 的默认引脚就用也要确认一下是不是被其他外设占用了。S3 的 GPIO 矩阵可以映射 SPI比较灵活但建议固定用一组别换来换去。SD 卡用 SPI 模式时Class 10 的卡写入速度可能只有 1~2MB/s搭配 USB FS 的 1MB/s 反而刚好不构成瓶颈。但如果你的卡特别旧或者扩容卡写一半超时会导致主机报错后面讲回调阻塞时会细说。3. TinyUSB MSC 代码骨架回调比描述符更关键3.1 为什么直接上 TinyUSB而不是自己写 USB 协议栈ESP-IDF 自带的 USB 底层驱动比较偏硬件抽象真要自己从零写 MSC 类需要处理 CBW、CSW、SCSI 命令等一系列状态机工作量很大而且容易藏 bug。TinyUSB 是开源社区用得最广的 USB 协议栈ESP-IDF 已经把它作为组件内置idf.py menuconfig里打开 TinyUSB 支持就能直接用。TinyUSB 对 MSC 的封装比较友好它把我们最头疼的 SCSI 命令解析、端点调度都做掉了我们只需要提供 6 个回调函数告诉它“我的磁盘容量多大、怎么读扇区、怎么写扇区”。这也是这篇强调“回调比描述符更关键”的原因——描述符只要照着填枚举基本能过但读写数据不稳、主机反复报错问题基本都出在回调实现上。3.2 描述符配置要点TinyUSB 的 MSC 描述符本身不难核心信息是接口描述符里的三个字节bInterfaceClass 0x08Mass StoragebInterfaceSubClass 0x06SCSI Transparent Command SetbInterfaceProtocol 0x50Bulk-Only Transport。这三个字节错了Windows 会直接无法识别或安装驱动失败。另外设备描述符里建议设置idVendor和idProduct。乐鑫的默认 VID/PID 可以用但如果打算长期用建议申请一个自己的 PID。字符串描述符也尽量写上厂商名、产品名和序列号Windows 的设备管理器和驱动安装过程会读这些字符串缺失时虽然能枚举但排查问题时信息少一大截。配置描述符里需要有一个 Bulk IN 端点和一个 Bulk OUT 端点端点大小建议设置成 64 字节全速 Bulk 端点最大就是 64。这个值不要随意改小否则主机会多很多事务速度更慢。3.3 六个回调怎么配合TinyUSB MSC 核心回调如下我用的是 IDF 5.x TinyUSB 0.15 左右的接口// 0x12 INQUIRY主机询问设备信息 bool tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) { const char vid[] ESP; const char pid[] MassStorage; const char rev[] 1.0; memcpy(vendor_id, vid, 8); memcpy(product_id, pid, 16); memcpy(product_rev, rev, 4); return true; } // 0x25 READ CAPACITY(10)主机询问容量和扇区大小 bool tud_msc_read_capacity_cb(uint8_t lun, uint32_t* block_count, uint32_t* block_size) { *block_size 512; *block_count total_blocks; // 根据 SD 卡容量计算 return true; } // 0x23 READ FORMAT CAPACITIES部分主机会先读这个 bool tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) { // 从 SD 卡读取 lba 对应的块到 bufferoffset/bufsize 做对齐处理 return sd_read_blocks(lba, buffer, bufsize / 512); } // 0x2A WRITE(10)主机写数据 bool tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) { return sd_write_blocks(lba, buffer, bufsize / 512); } // 同步缓存如果有写缓存可以在这里 flush bool tud_msc_flush_cb(uint8_t lun) { return true; } // 检查设备是否 readySD 卡挂载成功后返回 true bool tud_msc_test_unit_ready_cb(uint8_t lun) { return sd_card_ready; }这段代码本身不复杂真正的坑在别处回调里的参数lba是按“扇区号”来的而bufsize是字节数。我见过有人直接把bufsize当 LBA 用往 SD 卡里写的位置完全错了拷进去的文件能看见名字一打开就是损坏。读取的时候一定要做一次bufsize / 512转换成扇区数。3.4 磁盘缓冲对齐、回调不能阻塞、小心栈空间MSC 回调是在 TinyUSB 任务上下文里执行的Windows 对枚举和读写有严格超时要求。全速设备单块 512 字节的读写如果回调里同步去读 SD 卡偶尔慢一次可能问题不大但如果连续读写大文件SD 卡 SPI 模式一个块读 2ms累计起来很容易超过主机的超时阈值。这里有两层优化思路第一缓冲区对齐。TinyUSB 传入的buffer需要对齐到 4 字节以上SD 卡 SPI 驱动要求缓冲区 4 字节对齐。用heap_caps_malloc(4096, MALLOC_CAP_DMA)申请 DMA 安全的缓冲区能避免很多缓存一致性问题。第二不要在回调里做耗时操作。更稳妥的做法是回调里只把请求丢给一个待处理队列由专门的读取任务操作 SD 卡再通过信号量通知完成。但这个方案要做传输完成通知TinyUSB 有tud_msc_xfer_cb之类的接口比较复杂。我实测下来只要 SD 卡质量正常、读取函数不反复初始化 SPI直接在回调里同步读写 512 字节基本可行但如果遇到写大文件掉线优先改成队列异步处理。另外千万别在回调里调用printf变长日志。USB MSC 回调里做耗时的串口输出会把整个 USB 调度卡死现象就是设备掉线。调试时可以加日志但正式跑之前必须去掉。4. 枚举失败到驱动安装报错一条完整的排查链路4.1 现象一插上电脑毫无反应设备管理器里什么都没有遇到这种问题不要急着看代码先看硬件链路。USB 枚举的第一步是主机往设备发复位信号然后设备要在 D 上拉一个 1.5kΩ 电阻通常由 USB 控制器内部完成让主机识别到全速设备。如果插上去完全没反应八成是设备端根本没“活”过来。排查顺序用万用表量 VBUS5V有没有到板子。量 GPIO20D和 GPIO19D-对地电压。全速设备空闲时D 应该被拉高到 3.3V 附近。如果 D 是 0V说明 USB 控制器没启动或者固件没有调用tinyusb_driver_install。确认固件里启用了 TinyUSB 组件且配置的 USB 模式是 Device/OTG Device 模式。确认使用的是 USB OTG 引脚不是 USB Serial/JTAG 引脚。我犯过的低级错误是板子上的 USB 口走的是 USB Serial/JTAG 引脚固件里配置的却是 USB OTG结果插上后设备管理器里只出现一个“USB 串行设备”根本不是 U 盘。接线和配置必须两边对齐。4.2 现象二设备管理器出现黄色感叹号错误码 43 或“无法识别的 USB 设备”这个阶段说明 USB 枚举已经开始了设备地址也拿到了但在获取配置描述符或者设置配置阶段失败。最常见的原因是描述符里的类信息不对或者字符串描述符索引越界。打开 Wireshark 抓包能看到主机反复发GET_DESCRIPTOR设备要么不回包要么回的长度不对。经验是先用电脑端USBDeview或UsbTreeView这类工具看设备枚举到了哪一步能读到设备描述符说明底层没问题之后报错多半在配置描述符。一个非常隐蔽的问题tud_msc_inquiry_cb返回的 VID 字符串必须是 8 字节不足的部分要补空格不能直接memcpy一个短字符串。Windows 对 INQUIRY 数据长度要求严格短一字节就可能导致枚举到一半设备被判定为无效表现为“无法识别的 USB 设备”。4.3 现象三驱动安装失败“Windows 无法加载这个硬件的设备驱动”这个报错我一开始也遇到过查了很多资料才发现是描述符里bcdUSB版本写太高。Windows 对 USB 版本有兼容性判断版本号高于控制器支持等级时部分机器会拒绝加载驱动或者加载 USB 大容量存储设备驱动失败。把bcdUSB设置成0x0200USB 2.0是最稳妥的做法不要写成 0x0300哪怕硬件其实是 USB 2.0 全速也不要标 USB 3.0。另外配置描述符里bMaxPower不要写 0Windows 通常能容忍 0但有些 USB 集线器芯片会因此拒绝供电。另外注意Windows 对第一个配置描述符的wTotalLength很敏感。如果长度和实际返回的数据不一致要么识别不了要么出现“设备描述符请求失败”。建议代码里用sizeof(config_desc)自动填充不要手写死长度。4.4 排查顺序速查表现象优先排查手段毫无反应VBUS/D电压万用表枚举到一半掉线供电不足、DP/DM信号质量短接线、示波器设备管理器感叹号配置描述符、类代码USBDeview、抓包驱动安装失败bcdUSB、字符串描述符修改描述符重刷能枚举不能读写SCSI 回调、SD 卡就绪状态抓包、串口日志这张表也是我的排查“肌肉记忆”从最底层往上层一层层看不要一上来就怀疑 TinyUSB 库本身绝大多数问题都出在自己写的回调、描述符或者硬件接线上。5. USB 抓包复盘把枚举过程拉出来实锤5.1 抓包环境配置Windows 一条龙USB 抓包是定位这类问题最直观的手段。Windows 下推荐 Wireshark 加 USBPcap安装时勾选 USBPcap打开 Wireshark 后选择 USBPcap 接口插拔一次 USB 设备就能看到枚举流量。Linux 下更省事主线和usbmon模块一加载Wireshark 直接选usbmon0接口。嵌入式开发如果常用 Linux建议抓包在 Linux 上做过滤起来比 Windows 方便。抓枚举的时候把之前插过的设备拔掉先开抓包再插 USB这样能看到从GET_DESCRIPTOR Device到SET_CONFIGURATION的完整过程。过滤条件用usb.idVendor 0x303a乐鑫的 VID配合usb.urb_type URB_SUBMIT能只看主机发出来的请求。5.2 正常枚举应该看到哪些包我先说正常流程大家对照抓包主机发送GET_DESCRIPTOR Device设备返回 18 字节设备描述符。主机发送SET_ADDRESS设备地址从 0 变成新地址。主机重新GET_DESCRIPTOR Device确认新地址工作正常。主机发送GET_DESCRIPTOR Config设备返回配置描述符全文。主机可能发送GET_DESCRIPTOR String获取厂商、产品、序列号。主机发送SET_CONFIGURATION设备开始工作。对 MSC 设备SET_CONFIGURATION之后Windows 会立刻发INQUIRY、READ CAPACITY、TEST UNIT READY等一系列 SCSI 命令。抓包里如果只看到枚举到SET_CONFIGURATION就没了说明设备没在 SCSI 层回应问题在回调。5.3 一次失败枚举的实测分析我调试过程中遇到过一次很典型的失败抓包里主机反复发送GET_DESCRIPTOR Config设备回了 9 字节就停了。这明显是配置描述符里bNumInterfaces和实际接口数量对不上主机以为后面还有接口描述符设备却已经回完了。另一个案例是GET_DESCRIPTOR String索引越界。我把字符串描述符数组定义成了 3 个但配置描述符里iInterface写成 3结果主机一请求索引 3 的字符串设备直接不响应USB 总线就挂了。用iInterface 0可以彻底避开这个问题代价是部分调试工具看不到接口名称但功能不会受影响。所以抓包的重点不是看所有细节而是看“主机发了什么请求设备有没有回应、回应了什么”。一旦发现某个请求石沉大海或者回复长度不对问题基本就锁定在对应描述符索引或回调上了。5.4 从抓包看 USB-C CC 下拉误区热词里那句“usb 的 cc 引脚有一个 5.1k 下拉那怎么切换到主机模式”抓包完全能解释如果你用 Type-C 转 Type-C 线连接 S3 的 Type-C 母座和电脑设备端 CC 必须有 5.1k 下拉到 GND电脑才能识别到这是一台设备并且通过 CC 协商供电。抓包里体现为 VBUS 先上电然后才出现枚举请求。如果 CC 没有下拉或者做成了上拉电脑那边根本不会给 VBUS 供电USB 枚举包一包都看不到。所以“怎么切换到主机模式”的答案在硬件上就是ID 拉低 CC 上拉 5.1kType-C 口场景。如果是传统 Micro-B 口没有 CC切主机模式只要 ID 拉低就行。抓包时如果 VBUS 都没起来先检查 CC 电阻而不是抓包软件。6. 读写掉线、文件损坏、蓝屏MSC 真正难啃的收尾6.1 枚举全过了但 Windows 提示“需要修复”或者拷文件掉线能枚举不代表 MSC 做好了。枚举成功只是 USB 层通了真正影响体验的是 SCSI 层的读写实现。最容易遇到的现象是插上 U 盘后 Windows 弹窗“此驱动器有问题需要扫描并修复”或者打开盘符能看到文件但一复制大文件就报错。前者通常是文件系统元数据读取有问题后者多数是读写缓存或块重试逻辑有问题。先排除一个最基础的问题分区和文件系统。MSC 设备暴露的是一整块磁盘Windows 会读取 0 号扇区的 MBR/分区表。如果 SD 卡是裸的、没有分区表和 FAT32 文件系统Windows 会提示“需要格式化”。这不是代码 bug而是 SD 卡还没准备好。在电脑上用工具格式化成 FAT32再插到 S3 上做 MSC 测试能避免把文件系统问题混进 USB 问题里。6.2 扇区对齐与回调阻塞Windows 对超时零容忍Windows 的 USB 大容量存储驱动对命令超时非常敏感单条 SCSI 命令超时后它会重试连续重试失败就会把设备判为故障直接掉线。SD 卡 SPI 读取如果遇到坏块或者回调里做了一个耗时的fflush都可能触发超时。解决思路第一层是确保tud_msc_read10_cb和tud_msc_write10_cb返回之前数据真正落盘或确凿读出来了不要返回成功但没做完。第二层是如果 SD 卡驱动支持开启 4 位 SDIO 模式能明显减少单块读写时间。第三层是写回调里如果做日志输出务必去掉。这里有个实测结论SPI 模式 SD 卡读取单块 512 字节大约 0.3~2ms全速 USB 单块传输本身约 0.5ms组合起来基本能跑但连续写大文件时写缓存必须处理好。我一开始用的是 FatFs 的f_mount然后直接disk_write回调里每次写都调disk_write返回后才回 TinyUSB大文件写到一半就掉线。改成回调里只做sd_write_blocks不涉及文件系统层之后问题就消失了。关键原则MSC 回调工作在块设备层不要碰文件系统层。文件系统是主机那边的事设备只负责“给我 LBA 我就读/写这块”。6.3 SCSI 状态与 SenseKey为什么主机反复重试另一个排查了很久的问题主机发READ(10)设备返回失败后Windows 并不会马上放弃而是会发REQUEST SENSE查询具体错误原因。如果你的回调返回 false但没有设置 SenseKey部分主机会一直重试表现就是设备管理器里反复“正在安装设备驱动”、盘符一闪一闪。TinyUSB 对回调返回 false 的处理会比较简单默认可能返回CHECK_CONDITION但没有具体 Sense 数据。调试期为了简单建议tud_msc_test_unit_ready_cb只有在 SD 卡真正就绪时才返回 true否则返回 false。这样主机在 SD 卡初始化完成前会一直等而不是读到一半失败。如果想让错误处理更规范可以在返回 false 的同时设置scsi.sense.key等字段但 TUD 的默认实现不一定暴露这些接口需要自己去扩展。对于大多数应用保证“就绪前不回调读写”就够了。6.4 掉电安全拔线时文件损坏几乎必现MSC 设备天然有这个弱点主机认为数据写完了但 SD 卡可能还在 flush 缓存。如果用户随时拔线文件损坏几乎无法完全避免。硬件上能做的最有效一件事就是 VBUS 检测检测到掉电立刻进入“卸载文件系统”流程。S3 的tinyusb_driver_install支持vbus_monitor_io参数把 VBUS 检测脚连到 GPIO 上配合事件回调能在拔线的瞬间感知。实测加上这个之后异常拔线导致的 SD 卡文件损坏概率明显下降但还不是零。想更保险只能引导用户“安全弹出”这是协议和系统层面的限制不是代码能完全解决的。另外TinyUSB 的tud_msc_flush_cb要正确返回。Windows 在弹出设备时会下发SYNCHRONIZE_CACHE或START_STOP_UNIT如果flush和stop回调没有把缓存真正落盘弹出后数据一样会丢。我建议在tud_msc_flush_cb里做一次 SD 卡缓存同步把 FatFs 的f_sync逻辑放到这个回调里执行如果有文件系统层的话保证“安全弹出”时数据完整。写在最后的调试小抄USB MSC 调试最怕的不是难而是信息不透明。设备管理器只给你一个黄色感叹号Wireshark 抓包能让你看到主机到底在等什么。我个人的习惯是硬件上用最短的线、独立供电固件上先跑通 TinyUSB 官方 MSC 示例再用自己的 SD 卡读写函数替换抓包只要看到SET_CONFIGURATION之后的 SCSI 命令流就说明 USB 层已经通了后面所有读写问题都回到回调函数的时序和数据内容上去查。如果非要再说一条最容易被忽略的经验SD 卡先格式化好 FAT32、插到电脑上验证过能读写再接到 S3 上做调试。这一步能帮你把“SD 卡自身问题”和“USB MSC 实现问题”彻底分开省下大量无意义的排查时间。MSC 调试本质上就是一层一层剥开协议栈只要每一层都有明确的现象和验证方法再奇怪的 bug 也能被一步步逼出来。
返回列表