在 EDGI-TALK 开发板上,把板载 SD 存储通过 USB MSC 提供给 Windows 访问。本文记录最终配置、启动代码,以及从“编译通过但没有设备”到文件读写成功的完整排错过程。
拿到开发板后,我想做一个实用功能:不借助读卡器,直接通过开发板的 USB 接口,让电脑访问板载 SD 存储里的文件。
看起来只需要勾选 USB MSC 和 SDIO,实际调试却遇到了驱动未参与编译、DFS 依赖缺失、MSC 线程配置缺失、SD 检测线程栈溢出,以及 USB 初始化没有被调用等问题。最终,Windows 成功识别出磁盘,读取、写入和重新连接后的数据保持测试均通过。
这篇文章先给出最终可用配置,再解释每个问题是如何定位的。文中的配置针对本次 BSP 工程,其他版本和开发板需要核对驱动及硬件资源。
1. 实验环境与实现原理
| 项目 | 本次环境 |
|---|---|
| 开发板 | PSoC E84 EDGI-TALK |
| 当前运行核心 | Cortex-M33 |
| 开发工具 | RT-Thread Studio |
| 操作系统 | RT-Thread 5.0.2 |
| USB 协议栈 | CherryUSB 1.6.0(工程内版本) |
| USB 模式 | Device / MSC / HS |
| USB 控制器驱动 | dwc2_infineon |
| 存储设备 | SDIO1 驱动注册的sd块设备 |
| 验证平台 | Windows |
这里的 MSC 是 Mass Storage Class,即 USB 大容量存储设备类。电脑担任 USB Host,开发板担任 USB Device。电脑发出的读写请求,经 CherryUSB MSC 转换为对 RT-Thread 块设备的访问,再由 SDIO 驱动完成实际存储操作。
;因此,不需要在main()中重复调用 SDIO 初始化。驱动源码必须参与构建,且相关配置有效,自动初始化流程才有意义。
下载固件并运行后,在串口msh />提示符下输入:
list device注意,本次工程用的是list device,中间为空格。实际设备列表如下:
图 2:实际设备名是sd,类型为Block Device。
这一步也纠正了前面的一个临时配置:开始时设备名暂填为sd0,但实际注册的是sd,所以 MSC 配置必须同步修改。设备名不是 Windows 的盘符,也不是磁盘卷标。
4. 补上 MSC 初始化入口
SD 设备正常后,电脑连接 USB 仍然没有反应。继续搜索发现:msc_ram_init()在工程中有定义,但没有找到调用,文件末尾也没有自动初始化注册。
这说明勾选模板只是把相关代码加入工程,还需要明确的启动入口。
图 3:模板内部查找并打开块设备,注册描述符、添加 MSC 接口,最后调用usbd_initialize()。
虽然文件名叫msc_ram_template.c,但它也包含通过条件编译启用的块设备适配代码,不能只根据文件名判断是否选错模板。
4.1 在 main.c 顶部添加声明
已有头文件无需重复添加。
#include<rtthread.h>#include<rtdevice.h>#include<board.h>#include<stdint.h>externvoidmsc_ram_init(uint8_tbusid,uintptr_treg_base);4.2 在 main() 的无限循环之前添加启动代码
/* 等待 SD 块设备注册,最多等待约 5 秒 */intretry;for(retry=0;retry<50;retry++){if(rt_device_find("sd")!=RT_NULL){break;}rt_thread_mdelay(100);}if(rt_device_find("sd")!=RT_NULL){rt_kprintf("[APP] SD ready, starting USB MSC...\n");/* 本板使用 USB 总线 0 和 USBHS_BASE */msc_ram_init(0,(uintptr_t)USBHS_BASE);rt_kprintf("[APP] USB MSC init returned\n");}else{rt_kprintf("[APP] SD not found, USB MSC not started\n");}这里的总线编号和控制器地址参考了本板官方 M33 USB 示例,并在本次工程中验证通过。
需要注意四点:
- 等待
sd注册完成后再启动 MSC。模板会立即查找设备,找不到会触发断言。 - 只调用一次
msc_ram_init()。不要放进while (1)反复执行。 - 不要额外重复调用
usbd_initialize()。模板内部已经调用。 - 这段代码是上电启动方案,不包含完整的 SD 卡热插拔恢复流程。它也依赖本次“板端不挂载 SD 文件系统”的使用方式。
5. 调试中遇到的五个问题
🔴 问题一:宏开了,驱动却没有参与编译
现象:SDIO 配置已经开启,编译没有报错,但运行后没有 SD 块设备。
为了验证驱动是否真的参与编译,我在drv_sdio.c的条件编译之外临时加入:
#errorSDIO_BUILD_CHECK重新构建仍然没有出现这个错误。随后检查工程状态,发现该文件被排除构建。
同步工程源文件列表及构建配置、使对应驱动参与构建后,这条临时错误终于触发。验证结束后再删除它,继续正常编译。
这次排查的关键是把两个问题分开:宏有没有开启,以及源文件有没有被编译。不要因为某个驱动没生效,就将整个 Common 或驱动目录全部取消排除。
另外,Common 目录显示排除标记,不意味着其中的.h文件不能使用。头文件通过#include被读取,不会单独编译成目标文件;需要关注实际头文件搜索路径。
🔴 问题二:缺少 dfs_fs.h
驱动参与构建后,gpt.c、block_dev.c出现:
dfs_fs.h: No such file or directory本次工程中的块设备代码依赖 DFS 相关头文件。开启 DFS、保存并同步工程后,错误消失。
这个问题也提醒我:启用外设驱动时,要检查它依赖的系统组件,而不只是板级宏。
🔴 问题三:MSC 要求在线程中处理读写
继续编译时,MSC 模板主动报错:
Please enable CONFIG_USBDEV_MSC_THREAD, move msc read & write from isr to thread打开实际使用的usb_config.h后,发现CONFIG_USBDEV_MSC_THREAD被注释。取消注释后编译通过。
这里不应该删除模板中的#error。它在提示一项必要配置:当前块设备模板需要让 MSC 读写在线程上下文执行。
🔴 问题四:读到容量后,MMC/SD 检测线程栈溢出
运行后,串口先输出 SD 容量,随后出现栈溢出:
[I/SDIO] SD card capacity 483840 KB. thread:mmcsd_de stack overflow图 4:读到容量说明识别流程已有进展,但随后检测线程发生栈溢出,不能据此认定 SD 初始化全部完成。
在menuconfig中按/搜索RT_MMCSD_STACK_SIZE,看到原值为1024:
图 5:这是修改前的搜索结果。按结果旁的数字跳转到配置项,最终将 1024 改为 4096。
修改后配置为:
#defineRT_MMCSD_STACK_SIZE4096重新编译、下载并复位后,栈溢出不再出现,终端恢复正常交互,随后成功查看到sd块设备。
这里改的是MMCSD 检测线程栈,不是RT_SDIO_STACK_SIZE,也与 SD 卡剩余空间无关。本次通过增加栈空间解决了现象,但没有进一步测量峰值栈用量,不能将 4096 当成所有项目的固定最优值。
🔴 问题五:SD 正常,USB 却没有启动
此时编译通过、sd已注册,但连接应用 USB 接口后,电脑没有识别到磁盘。
检查 MSC 函数的定义、调用和自动初始化注册后,定位到缺少启动入口。补上第 4 节的代码,等待块设备就绪后调用一次msc_ram_init(),电脑随后成功识别磁盘。
这个问题的核心是:代码参与编译,不代表对应功能已经执行初始化。对每个模块,都需要明确它是自动初始化,还是由应用显式启动。
6. 最终结果与验证
重新下载固件后,串口出现了应用启动信息和 USB 控制器日志:
图 6:MSC 初始化函数返回,USB 控制器相关日志开始输出。函数返回本身不等于枚举成功,还要结合电脑端结果判断。
使用支持数据传输的 USB 线连接开发板的应用 USB 接口后,Windows 显示出新的磁盘,并能读取原有文件:
图 7:本次 Windows 分配了 E 盘。盘符由电脑决定,与 RT-Thread 中的设备名sd无关。
随后完成了以下验证:
| 测试内容 | 结果 |
|---|---|
| 注册 SD 块设备 | 成功,名称为sd |
| Windows 识别 USB 磁盘 | 成功 |
| 查看原有文件目录 | 成功 |
| 将已有文件复制到电脑并打开 | 成功 |
| 写入文本文件并保存 | 成功 |
| 安全弹出、重新连接后读取 | 成功,内容保留 |
这些结果说明本次基本功能已经打通,但不代表完成了吞吐率测试、长时间压力测试或异常断电测试。
🔴 保留的问题与使用边界
启动日志中仍出现过descriptor ... not found和standard request error。本次测试没有因此阻止磁盘识别和文件读写,但尚未进一步定位、消除这些日志,因此不能说所有 USB 异常都已修复。
电脑通过 MSC 访问期间,不要让板端或另一个核心同时挂载并操作同一张卡的文件系统。若后续需要“板端采集保存”和“电脑导出文件”两种模式,需要另外设计访问权切换,而不是只给单次读写加锁。
拔线前先在 Windows 中安全弹出;修改工程前,建议保留这份已经通过功能验证的版本。
7. 这次调试留下的方法
这次任务并不是从零编写 USB 协议栈,而是把 BSP、RT-Thread 块设备和 CherryUSB 集成起来。真正花时间的地方,是确认配置、构建和运行三者是否一致。
以后再遇到“配置都开了,功能还是不工作”的情况,可以按下面的顺序检查:
- 配置层:宏、依赖组件、设备名是否正确?
- 构建层:源文件是否参与编译,头文件是否来自预期路径?
- 启动层:初始化函数由谁调用,调用时依赖设备是否已经就绪?
- 运行层:是否出现断言、栈溢出或驱动错误?
- 验证层:是否完成了实际文件读写,而不只是看到一个盘符?
按这个顺序逐层缩小范围,比同时改动多个配置更容易找到原因。
参考资料
- EDGI-TALK 官方 BSP 仓库
- 官方 M33 USB RoleSwitch 示例说明
- 官方 M33 USB 示例 main.c
- CherryUSB MSC 示例说明
本文配置和排错结论来自本次实测。官方示例与文档可能更新,复现时应以本地 BSP、配置文件和源码为准。