十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

STM32F103实现U盘读写文件:CH376方案详解与工程实践

STM32F103实现U盘读写文件:CH376方案详解与工程实践 简介本资源是一套基于STM32F103单片机实现U盘文件读写功能的完整嵌入式开发例程面向嵌入式初学者与中级开发者解决USB主机模式下外接U盘进行FAT文件系统操作的实际工程问题适用于智能仪表、数据采集终端、工业HMI等需本地存储扩展的场景。压缩包共286个文件包含56个头文件.h定义硬件接口与协议结构、52个源文件.c实现CH376 USB主控芯片驱动、SPI通信、OLED显示及文件操作逻辑另有.o/.d/.crf等编译中间文件及Keil工程配置uvproj/uvopt、可执行镜像hex/axf等整体大小为8.01MB。已有893人学习下载。代码结构清晰含RCC时钟配置、I2C/OLED/触摸按键/继电器等外设初始化并集成CH376 Host模式初始化与状态反馈配合OLED实时显示运行状态便于调试验证预览中可见lm75a、rtc、usart、flash等模块支持表明该工程具备温感采集、实时时钟、串口通信与非易失存储等典型工业功能拓展基础。 如果你手头正用STM32F103做设备想给产品加上“U盘导出数据”“U盘升级固件”这类功能那这个DEMO就是个很好的起点。F103这颗料在嵌入式圈子里几乎无人不知到哪都能买到开发资料也多但关于“U盘读写文件”这件事网上能跑通的例子并不算多真正把流程走通、把坑填平的更少。这个例程解决的核心问题很简单让F103单片机识别U盘并且能像电脑一样打开文件、读数据、写数据、关闭文件。它不是把U盘当普通存储介质做裸扇区读写而是直接操作文件系统这意味着你可以在设备上生成一个“DATA.TXT”插回电脑就能直接打开不用任何专用软件。适合的人群也很明确想做数据记录仪、工业参数导入导出、离线固件升级、设备日志存储的工程师以及正在做相关毕设、竞赛作品的学生。下面我把这个DEMO背后的设计方案、核心原理、实操过程和踩坑记录全部拆开讲一遍。1. 项目整体设计与方案选型思路1.1 先搞清楚STM32F103能用自带的USB口直接插U盘吗这个问题几乎每个拿到DEMO的人都会问。答案是不能至少不能用F103自带的那个USB外设直接做Host去读U盘。USB总线是主从架构电脑是Host主机U盘是Device从机。主机需要做很多事给从机供电、产生SOF帧、发起设备枚举、分配地址、维护传输调度。而STM32F103片上集成的USB接口是一个全速Device控制器它天生是用来当“从机”的比如把F103模拟成键盘、鼠标、虚拟串口。它没有Host控制器也没有OTG功能。所以当你在网上看到标题写“STM32F103 U盘读写”的例程时先别急着打开工程文件抄代码得看一眼硬件原理图。这类工程通常有两种情况第一种是板子上外挂了CH376或者CH375这类USB Host控制芯片F103通过SPI或串口跟这颗芯片通信由这颗芯片去对接U盘第二种是工程实际用的是STM32F105/F107这两颗芯片内置USB OTG_FS控制器支持Host模式但因为F103和F105引脚兼容代码风格又接近很多人就笼统叫“F103系列”。搞清楚这一点整个项目的设计思路就顺了。1.2 两条主流硬件路线对比我接触过很多类似项目最终方案基本分两派。一派是F103外挂CH376/CH375另一派是换用F105/F107使用自带USB OTG。先看CH376方案。CH376是南京沁恒出品的一颗USB接口芯片它最讨巧的地方是内部固件直接把USB Mass Storage协议和FAT文件系统都吃掉了。单片机只需要通过SPI、UART或者并口给它发一些高层命令比如“打开文件”“读文件”“写文件”“关闭文件”它就能把U盘上的文件操作完成。F103那颗性能一般的CPU几乎不用操心USB协议栈的事情主循环还能干其他活。再看OTG方案。STM32F105/F107内置USB OTG_FS控制器可以配成Host模式配合一个完整的USB Host协议栈和FatFs文件系统库直接在F103/105上完成U盘枚举、SCSI命令下发和FAT文件解析。这条路好处是少一颗外置芯片成本低一点读写速度也更快但软件工作量大了不是一星半点USB枚举、类驱动、Bulk-Only传输、FatFs移植每一步都可能卡住几天。对比项F103 CH376方案F105/F107 OTG方案硬件成本多一颗CH376约几块钱芯片本身略贵但省外设软件复杂度低不用管USB协议高需要USB Host协议栈单片机负荷低U盘协议由CH376处理中主控要参与事务文件系统CH376内置FAT12/16/32自行移植FatFs读写速度中等受限于通信接口较快可跑满全速适用场景快速交付、小批量产品、学习追求低成本大批量、速度敏感这个DEMO如果按“F103单片机”来定位最合理、最不折腾的就是CH376方案。芯片这几年供应稳定资料全例程也多新手照着接线就能跑。我在实际项目里也偏好这个方案尤其当产品的主控已经定了F103不想因为一个U盘功能去改主控芯片时外挂CH376是代价最小的选择。1.3 这个DEMO的典型系统结构从硬件连接上看整套系统可以分成三个部分F103最小系统、CH376模块、USB座外加一些辅助的电源和指示电路。F103通过SPI接口与CH376通信通常用SPI1PA5做SCKPA6做MISOPA7做MOSI再拿一个GPIO做片选。CH376的工作模式由硬件引脚决定如果板子上把MODE引脚接到了高电平就是SPI模式这个在焊接前一定要确认我见过有人MODE引脚悬空导致芯片一直没响应的。U盘插在USB座里USB座的VBUS需要来自外部5V电源不能直接拿F103的3.3V引脚去顶U盘启动瞬间电流很大供电不足是U盘识别失败的常见原因。电源树一般是这样外部5V进来分两路一路给USB座的VBUS两路经过LDO降到3.3V给F103和CH376。信号线上DP/DM要按CH376数据手册加匹配电阻走线尽量短不过这个DEMO通常工作在低速全速模式只要不是飞线飞得特别离谱一般都能正常跑。系统里再留一个LED做U盘插入和读写指示留一个按键触发文件导入导出用串口打印日志方便调试。这样一套东西下来就是一个完整的数据导出节点了。整个设计的关键点在于把“USB协议”这件复杂事外包给CH376让F103专注业务逻辑。这也是这个DEMO最大的参考价值——它展示了一种“主控协议芯片”的分工思路在工程上有普遍意义。2. 核心原理解析U盘里到底发生了什么2.1 从“一个文件”到“一堆扇区”想彻底理解这个DEMO在做什么不能只停留在“调函数”的层面得明白文件在U盘上到底是怎么存的。U盘是一个块设备最底层是成千上万个扇区每个扇区固定512字节。文件系统做的事情就是在这个块设备上建立一套索引规则让“文件名”和“扇区位置”产生对应。FAT文件系统从DOS时代流传到现在Windows格式化U盘时默认的FAT32就是其中之一。它把存储空间划成几个区域最前面是引导扇区DBR记录文件系统参数接着是文件分配表FAT表记录每个文件占用了哪些簇再往后是根目录区记录文件名、大小、起始簇号等属性最后才是真正的数据区。好那“读写一个文件”这件事本质上可以拆成三步第一步在目录区找到文件名拿到起始簇号和文件大小第二步顺着FAT表找到这个文件占用的所有簇把扇区编号逐个算出来第三步通过USB命令让U盘把指定扇区的数据读出来或者写进去。你可以把FAT表理解成图书馆的索引卡目录区像书脊上的书名数据区才是书架上的书。CH376这颗芯片最值钱的地方就是把上面这一整套“索引逻辑”做成了固件单片机发一条“打开文件”的命令它就替你把目录区翻了一遍把起始簇号算好了。如果你用F105方案这些事就得交给FatFs库来干原理完全一样只是一个在硬件里一个在软件里。2.2 USB Mass Storage 和 Bulk-Only 传输文件系统层面搞定了还要解决“怎么把扇区数据送进U盘”的问题。U盘是USB设备遵循USB Mass Storage类协议传输方式是Bulk-Only。当主控想读写U盘时需要先封装一条SCSI命令比如READ(10)、WRITE(10)、INQUIRY然后把这条命令放进一个叫CBWCommand Block Wrapper的数据包里发给U盘U盘执行完命令后回一个CSWCommand Status Wrapper报告执行结果。数据则通过Bulk端点传输。CBW、CSW、数据这些概念看着抽象实际上就是一段一段有着固定格式的结构体。CH376的固件把这一层也封装好了芯片自己会做枚举会发INQUIRY会处理CSW单片机只关心最终的返回值。所以一次完整的“读文件”操作从软件视角看是这样的F103发“打开文件”命令给CH376CH376在内部执行FAT查找过程中通过USB总线向U盘发起SCSI读命令读取目录扇区和FAT扇区找到目标文件后F103再发“读取数据”命令CH376一条条SCSI命令把数据区的扇区读回来经过内部缓冲整理后通过SPI返回给F103。整个过程F103就像个大管家指挥着外包团队干活自己不用亲自去管USB线上那些字节。这个分工模式对单片机性能要求很低但对“命令时序”要求很严格。CH376的每次操作都有明确的等待中断、查询状态、读取数据流程代码里如果漏了某一步最典型的症状就是命令返回正常但数据全错或者干脆一直卡死在忙状态。2.3 缓冲区与读写速度的取舍读写速度是很多人关心的点但这个DEMO一般不会追求高速。CH376使用SPI通信时理论上可以达到几百KB/s的读速度实际工程里受限于缓冲区大小、U盘本身速度、SPI时钟和代码效率通常稳定在几十到一两百KB/s就够用了。做数据记录仪一秒写几十个字节完全没有任何压力。缓冲区设计很关键。CH376内部有自己的缓冲但容量有限F103发一条“读数据”命令时要指定本次想读多长芯片返回该长度的数据。如果文件很大必须循环地发命令、收数据直到文件结尾。写入也是一样循环地发“写数据”命令、发送数据、等待状态。这里有个经验之谈单次读写块越大吞吐效率越高但F103的RAM是有限的经典的F103C8只有20KB RAM开一个4KB的缓冲区都得谨慎。我通常按扇区大小或者2的幂来做既方便对齐也好管理。文件系统的“簇”也会影响速度体验。如果U盘上文件碎片很多每读一个簇都要去查一遍FAT表速度就会明显下降。实际项目里数据记录仪如果是每几分钟追加写一次长期下来文件碎片会很多这时候可以考虑定期重新格式化或者用FatFs的f_sync及时刷新避免突然拔盘导致FAT表不一致。3. 实操过程与关键代码实现3.1 硬件准备与接线这个DEMO的硬件清单很精简一块STM32F103最小系统板一个CH376模块网上很多现成的带USB座和电源电路一根USB转串口调试线一块FAT32格式化的U盘。接线以SPI模式为例CH376的SCK接PA5MISO接PA6MOSI接PA7CS接任意GPIOINT引脚接一个外部中断输入比如PA0。CH376在SPI模式下工作时MODE引脚要接高电平具体高度每个模块不一样买模块时问清楚卖家。电源接法上5V电源给USB座的VBUS同时经过AMS1117-3.3给F103和CH376供电。CH376的UD和UD-直接连到USB座的D和D-按数据手册在UD上做一个1.5kΩ上拉电阻到3.3V有的模块已经集成不用操心。注意USB的VBUS和GND之间建议加一个220μF以上的电解电容靠近USB座放置。U盘插入瞬间会有浪涌电流不加电容会导致电压跌落这是U盘枚举失败的超级常见原因。3.2 初始化流程代码初始化是整个DEMO最关键的环节顺序错了会很难排查。完整流程如下// CH376初始化 void CH376_Init(void) { // 1. 初始化SPI接口时钟4.5MHz左右 SPI_Init(SPI1, SPI_MODE_MASTER, 4_500_000); // 2. 复位CH376 CH376_Reset(); Delay_ms(30); // 3. 检测CH376是否存活 while (CH376_CheckExist() ! CH376_OK) { // 如果一直失败查硬件连接和MODE引脚电平 } // 4. 设置USB Host模式 CH376_SetUSBHostMode(); Delay_ms(20); // 5. 等待U盘插入这里用轮询或者外部中断 while (!CH376_DiskConnect()) { Delay_ms(100); } // 6. 初始化U盘等待盘符就绪 CH376_DiskInit(); // 7. 挂载文件系统读取卷信息 CH376_DiskMount(); }这段代码里最容易出问题的是第4步之后的延时。CH376切换工作模式后内部USB PHY需要稳定时间如果马上检测U盘很容易返回“设备未连接”。我通常会在设置Host模式后加一个至少20ms以上延时再开始轮询。还有个小细节CH376的检测存在性命令非常有用调试时先确认芯片回包正常再往后走。如果卡在这步多半是SPI时序或者引脚定义错了而不是U盘的问题。3.3 文件读取代码实现初始化完成后读取文件的代码就比较直观了uint8_t ReadFileFromUdisk(void) { uint8_t ret; uint16_t readLen; uint8_t buf[512]; // 1. 打开文件路径用根目录下的文件名 ret CH376_FileOpen(DATA.TXT); if (ret ! CH376_OK) { return ret; } // 2. 查询文件大小 int32_t fileSize CH376_GetFileSize(); int32_t remain fileSize; // 3. 循环读取 while (remain 0) { readLen (remain 512) ? 512 : remain; ret CH376_ByteRead(buf, readLen); if (ret ! CH376_OK) { break; } // 在这里处理数据比如存到SD卡、发送串口、解析参数 ProcessFileData(buf, readLen); remain - readLen; } // 4. 关闭文件必须执行 CH376_FileClose(); return CH376_OK; }有几个点值得展开。第一文件名建议用大写字母和数字组成不超过8.3格式。FAT文件系统对大小写不敏感但CH376固件对不同命名方式的支持程度有差异中文名、空格、特殊符号都可能出问题生产环境里不要用中文文件名。第二每次读取的字节数尽量和内部FIFO对齐512字节是扇区大小效率比较高。第三文件关闭不能省就算只读不写也必须关闭它会释放句柄执行文件目录的更新。3.4 文件写入代码实现写入比读取稍微复杂一点因为要考虑文件不存在时是否创建写完后有没有正确刷新FAT表。uint8_t WriteFileToUdisk(void) { uint8_t ret; uint16_t writeLen; uint8_t buf[512]; // 1. 打开文件如果不存在就创建 ret CH376_FileOpen(LOG.TXT); if (ret CH376_ERR_NOT_EXIST) { ret CH376_FileCreate(LOG.TXT); if (ret ! CH376_OK) { return ret; } } // 注意如果要追加写数据FileOpen后要做文件指针定位 // 把指针移到文件末尾再开始写。 CH376_ByteLocate(0xFFFFFFFF); // 移动到文件末尾 // 2. 循环写入 while (hasMoreData) { PrepareData(buf, 512); // 生成一帧数据 ret CH376_ByteWrite(buf, 512); if (ret ! CH376_OK) { break; } } // 3. 关闭文件会更新目录项和FAT表 ret CH376_FileClose(); return ret; }写入文件时最大的坑是“数据必须在关闭文件后才算真正落盘”。FAT文件系统为了性能会把一部分目录和FAT表信息放在缓冲区里关闭文件时才会回写到U盘。如果你写完还没关闭文件就直接断电或者拔U盘文件在电脑上很可能打不开目录项乱掉甚至整块U盘的文件都受到影响。所以生产环境宁可降低写频次也要保证每次写完立即关闭或者用CH376的“保存目录信息”命令把FAT表同步出来。另外新建文件和追加写入的语义不同。FileCreate会新建或覆盖文件而FileOpen是打开已有文件。日志类应用通常需要追加那就先Open找不到再Create然后把指针定位到文件末尾再开始写。每次上电都新建一个LOG001.TXT、LOG002.TXT这样的命名策略能有效避免单文件过大和目录项碎片。3.5 在工程中组织代码与状态机因为U盘操作是慢速操作如果一个读操作要几百毫秒代码还写成阻塞式的主循环里其他任务全被卡死这在产品里是不能接受的。所以Demo工程里通常会把U盘操作封装成一个状态机。状态机的状态大致包括空闲、等待U盘插入、初始化U盘、挂载文件系统、打开文件、读取/写入数据、关闭文件、弹出U盘。每个状态做一件事状态之间通过事件和返回码迁移。这样主循环每转一圈只做一个动作其他任务可以正常执行U盘读写和LED闪烁、按键扫描、串口打印互不干扰。我之前做数据导出功能时就是按这个思路写的。主循环大概2ms跑一圈U盘读写搬到状态机后导出10MB文件期间人机交互界面一点都不卡。这个模式也特别适合直接移植到带操作系统的工程里把每个状态塞进一个任务信号量驱动即可。4. 常见问题与排查技巧实录U盘读写程序的调试最磨人的是问题不固定在软件还是硬件。我整理了一张速查表都是实际项目中反复踩过的坑。现象可能原因排查方法CH376初始化就卡死SPI接错、MODE引脚电平不对、供电不足先跑检测存在命令用示波器看SCK/MOSI波形U盘插上检测不到VBUS供电不足、UD/UD-接反、U盘兼容性差确认5V电源换USB 2.0小容量U盘测试枚举成功但挂载失败U盘不是FAT12/16/32多分区用电脑格式化为FAT32检查是否只有一个分区文件打开失败文件名格式不对、文件不存在用短文件名先通过电脑在根目录建同名文件读出来数据不对SPI速率过高、时序问题、缓冲区越界降低SPI时钟逐段打印数据对比写文件后电脑打不开没关闭文件就断U盘FAT表没刷新写完后必须等待关闭返回成功再提示完成中文文件名乱码FAT短文件名编码不支持强制用英文数字命名读写中途拔出死机没有做拔出状态恢复用INT引脚监控插拔发现拔出立即复位状态机每一类问题都能单独写一篇长文这里重点讲几个新手特别容易栽的。供电问题是头号杀手。U盘启动瞬间电流可以达到100mA甚至更高如果VBUS没有足够的电容电压会被拉低到4V以下CH376内部的USB PHY就会认为设备断连。解决方法是VBUS和GND之间加220μF电容并且5V电源要选能提供500mA以上的。我之前试过用一个充电器供电的口袋开发板U盘偶尔能识别偶尔不能加了电容后稳定了。U盘兼容性排第二。USB 3.0的高速U盘插到CH376这种全速设备上理论上没问题实际却经常遇到枚举失败或者挂载超时。这不是代码问题是U盘主控的兼容性问题。建议准备几个不同品牌、不同容量的USB 2.0 U盘做交叉测试比如闪迪的小型盘、金士顿的普通盘一般都能过。批量生产的设备如果遇到客户用很奇怪的U盘只能提高容错代码的健壮性比如初始化失败后自动复位重试两次。文件系统相关的坑排第三。很多人拿U盘拷资料里面是NTFS或者exFAT格式这俩CH376都不支持必须FAT32。还有的是U盘量产时带了两个分区第一个分区是只读的引导区第二个才是数据区CH376只会挂第一个盘符自然失败。格式化时选择FAT32分配单元大小用默认值这是最稳妥的。调试时有个很实用的技巧在每次CH376命令执行后都打印返回码。CH376的命令返回码非常直白比如“文件不存在”“磁盘未挂载”“写保护”一看就知道问题在哪。把这些日志挂在串口上配合一个带时间戳的终端工具能省下大量猜的时间。我还习惯在代码里加一个“复位重试”机制U盘插入失败后不要把错误直接返回给用户而是先做一次芯片复位、重新设置Host模式、再次尝试检测很多时候能解决U盘带电插拔后状态错乱的问题。5. 影响范围与扩展应用5.1 这个DEMO能直接落地的场景虽然是个例程但它能支撑的应用场景非常广泛。最典型的是数据记录仪设备采集传感器数据攒到一定量后写入U盘用户拔出U盘插电脑就能读取分析。这个模式在环境监测、农业设备、工业设备上都适用省去了外接RS485、维护上位机软件的麻烦。第二个场景是参数配置导入导出。很多设备有大量参数比如PID系数、温度阈值、设备编号用人机界面一个个按太痛苦。在U盘根目录放一个CONFIG.TXT文件设备上电时自动读取解析后写入内部Flash几分钟就能完成一台设备的批量配置。产线调试效率提升明显。第三个场景是离线固件升级。Bootloader启动后检查U盘里有没有UPDATE.BIN文件有就擦除Flash、写入固件、校验并跳转。这对于不具备联网条件的设备是很实用的售后维护手段。客户只需要把固件拷进U盘插上设备上电等指示灯闪烁结束就升级完成了。没有USB下载线不用拆外壳。第四个场景是故障日志存储。设备运行状态定期写U盘出问题时把U盘寄回工程师直接分析现场数据。有时候现场人员拔盘时机不恰当可能导致日志文件损坏所以日志文件的写入策略要做成独立的小文件多次写入不要长时间占用一个文件句柄。5.2 后续扩展方向如果这个DEMO玩熟了想做得更深入有几个方向可以走。一是换成STM32F105/F107的OTG方案。这时就不再需要CH376直接在CubeMX里配USB_OTG_FS为Host模式加上TinyUSB或者ST官方的USB Host库配合FatFs自己实现U盘读写。这条路能学到USB协议栈的底层知识读写速度和稳定性上限也更高。移植FatFs时注意disk_read/disk_write底层函数的实现以及扇区大小、多扇区传输这些宏定义这些细节直接影响速度和兼容性。二是把文件操作升级到FatFs层。如果硬件方案已经用了F105或者你想脱离CH376自己的文件系统用FatFs可以实现更灵活的功能支持长文件名、支持多级目录、支持文件时间戳。CH376内置FAT固件虽然方便但扩展性比较有限大小写、中文名、文件夹遍历都不太灵活。三是加数据可靠性和安全机制。比如写入文件时同步计算CRC32U盘读取到终端用户手里后可以校验完整性再比如在文件末尾追加逗号分隔的数据时注意文件指针管理和缓冲区刷新避免异常断电时数据丢失还可以对配置文件做简单加密防止客户随意修改关键生产参数。四是结合实时操作系统。U盘读写放到独立任务里与其他任务用消息队列交互。像FreeRTOS、RT-Thread都支持代码结构会更清晰。有一点要提醒CH376的SPI操作是异步的它要靠中断引脚通知主控“命令完成”在RTOS里如果用了阻塞延时等待中断要把等待改成信号量模式否则任务调度会变得很不流畅。5.3 这个项目对学习者的价值从学习角度看这个DEMO涵盖了单片机开发里好几个重要知识点USB协议的基本架构、FAT文件系统的盘区布局、SPI总线通信时序、状态机编写方法、异常处理与超时机制。很多人玩单片机只停留在点灯和串口打印这个DEMO能逼着你把“外设协议”“文件系统”“数据完整性”“用户操作体验”联系起来考虑是嵌入式开发从入门到工程化的一道不错的坎。代码仓库里如果能把这个Demo跑通你已经比相当一部分只写裸机逻辑的人强了。最后再分享一个我个人的体会做U盘功能最省事的做法不是追求代码风格多优雅而是把DFU容错恢复做好把供电做稳把U盘兼容性做宽。代码里预留超时和重试产品在现场就不会因为用户插了一个奇怪U盘就死机。别嫌这些细节琐碎我在实际项目中凡是遇到“用户说设备没反应”的反馈十个里有七个是U盘坏了、格式不对或者用户插了个读卡器。把拔插提示做得清晰把状态灯用得恰当体验就会好很多。本文还有配套的精品资源点击获取
返回列表