news 2026/10/22 7:25:57

从 SD 卡到电脑 U 盘:PSoC E84 + RT-Thread + CherryUSB 实战与排错记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从 SD 卡到电脑 U 盘:PSoC E84 + RT-Thread + CherryUSB 实战与排错记录

在 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 驱动完成实际存储操作。


![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=images%2F01-data-path.png&pos_id=img-mdYfM5GT-1792336447947

图 1:数据通路示意。箭头表示请求和数据的往返关系;开发板这一侧完成 USB 与 SD 块设备之间的转接。

因此,这个功能需要两条链路都打通:一条是SDIO 驱动 → SD 块设备,另一条是USB 控制器 → CherryUSB MSC → 块设备绑定。

本次由电脑解析卡上的文件系统,板端没有挂载 SD 文件系统。开启 DFS 是为了满足本次 BSP 中块设备代码的依赖,并不等于板端必须挂载 FAT 文件系统。

2. 最终配置:先把参数选对

2.1 CherryUSB 配置

进入menuconfig中的Using USB with CherryUSB,配置如下。

配置项最终设置说明
USB Device 模式开启开发板作为设备连接电脑
USB Host 模式关闭当前由电脑担任主机
Select usb device speedHS选择高速模式
Select usb device ipdwc2_infineon使用英飞凌对应的控制器驱动
Enable usb msc device勾选开启大容量存储设备类
其他 USB 类不勾选本次只使用 MSC
控制传输最大缓冲区512本次保持默认
MSC 最大数据缓冲区512本次保持默认
Select usb device templatemsc_blkdev使用实际块设备作为存储介质
USB MSC block device namesd必须与实际设备名一致

这里容易混淆的地方有三个:

  • Device IP指 USB 控制器的硬件 IP,不是网络 IP 地址。
  • msc_ram使用 RAM 模拟存储;本次访问 SD 卡,应选择msc_blkdev。
  • 电脑通过 USB 连接开发板,并不意味着要选 WinUSB。本次目标是标准 MSC 磁盘,不需要选winusbv1 / winusbv2。

2.2 板级驱动和 DFS

进入Hardware Drivers Config,开启板级 USB 和 SDIO:

配置位置设置
Onboard Peripheral Drivers → Enable USB开启
On-chip Peripheral Drivers → Enable SDIO开启
SDIO1开启
SDIO0关闭

本次在 SDIO1 配置下完成了实际识别与读写验证。换到其他开发板时,不应直接照搬控制器编号,需要核对原理图和 BSP。

另外,在RT-Thread Components中开启DFS: device virtual file system,本次使用DFS v1.0。没有为了导出 U 盘而额外开启 Elm FatFs。

几个关键配置最终如下,供检查时对照,不建议直接用这几行替换整个rtconfig.h:

#defineBSP_USING_USB#defineBSP_USING_SDIO#defineBSP_USING_SDIO1#defineRT_USING_SDIO#defineRT_MMCSD_STACK_SIZE4096#defineCONFIG_USBDEV_MSC_BLOCK_DEV_NAME"sd"

2.3 打开 MSC 工作线程

本次实际使用的 USB 配置头文件位于:

libraries/Common/board/ports/usb/usb_config.h

取消下面这一行的注释:

#defineCONFIG_USBDEV_MSC_THREAD

当前块设备模板要求 MSC 读写在线程上下文执行。这个配置缺失时,模板会通过#error阻止编译,后文会说明。

3. 先确认 SD 设备,再启动 USB

SD 驱动末尾已经有自动初始化注册:

INIT_DEVICE_EXPORT(rt_hw_sdio_init);

因此,不需要在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 示例,并在本次工程中验证通过。

需要注意四点:

  1. 等待sd注册完成后再启动 MSC。模板会立即查找设备,找不到会触发断言。
  2. 只调用一次msc_ram_init()。不要放进while (1)反复执行。
  3. 不要额外重复调用usbd_initialize()。模板内部已经调用。
  4. 这段代码是上电启动方案,不包含完整的 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 集成起来。真正花时间的地方,是确认配置、构建和运行三者是否一致。

以后再遇到“配置都开了,功能还是不工作”的情况,可以按下面的顺序检查:

  1. 配置层:宏、依赖组件、设备名是否正确?
  2. 构建层:源文件是否参与编译,头文件是否来自预期路径?
  3. 启动层:初始化函数由谁调用,调用时依赖设备是否已经就绪?
  4. 运行层:是否出现断言、栈溢出或驱动错误?
  5. 验证层:是否完成了实际文件读写,而不只是看到一个盘符?

按这个顺序逐层缩小范围,比同时改动多个配置更容易找到原因。

参考资料

  • EDGI-TALK 官方 BSP 仓库
  • 官方 M33 USB RoleSwitch 示例说明
  • 官方 M33 USB 示例 main.c
  • CherryUSB MSC 示例说明

本文配置和排错结论来自本次实测。官方示例与文档可能更新,复现时应以本地 BSP、配置文件和源码为准。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 7:25:47

【C语言】低级指针

学C语言&#xff0c;指针应该是大多数人绕不过去的一座大山&#xff0c;以下是我在指针学习时遇到的问题及解答&#xff0c;欢迎提出见解 • 为什么 &arr 和 arr 打印出来的地址一样&#xff0c;加 1 之后却不一样了&#xff1f; • 在推导 &#xff08;(arri)j) 的过程中&…

作者头像 李华
网站建设 2026/10/22 7:25:46

4、Python 内置容器类型:list、tuple、set、dict 详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/22 7:25:19

高并发登录接口性能测试全解析

功能模块性能测试用例验证 功能模块上线前&#xff0c;性能测试用例是保障系统稳定性的关键环节。本文以高并发接口为例&#xff0c;演示性能测试用例的设计、执行与验证流程。 一、测试目标与场景设计 被测模块&#xff1a;用户登录鉴权接口 /api/login&#xff0c;核心指标如…

作者头像 李华
网站建设 2026/10/22 7:24:24

python杂识--24--Nuitak打包exe

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/22 7:21:40

【脚本】 【Linux】重命名网卡

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华