STM32F107 USB HOST读写U盘:从枚举到FatFs的完整工程实战与避坑指南 简介面向使用STM32F107微控制器进行嵌入式开发的工程师与学生这份压缩包提供了在USB主机模式下读写U盘的完整工程实现可有效解决外围存储设备接入的数据管理问题。资源内容围绕ST官方库与FAT文件系统展开包含USB主机初始化、U盘枚举挂载、文件打开关闭、读写操作及目录管理等关键环节覆盖从底层驱动到应用层调用的完整链路。包体共298个文件以c源码和h头文件为主同时包含编译过程生成的object、依赖文件以及uvprojx等工程配置和hex固件整体大小约8.18MB便于直接导入工程或按需查看中间产物。压缩包目录结构清晰便于定位USB驱动、文件系统移植以及应用示例代码通过研读源码可以掌握STM32F107在主机模式下的工作流程以及如何利用FAT文件系统在U盘上可靠读写数据。目前已有354人学习这份资源对于正在搭建USB存储应用的原型开发者能够提供可复用的代码框架和避坑参考。1. STM32F107 USB HOST把U盘读写从玄学变成可复现的工程STM32F107 的 USB HOST 读写U盘看手册感觉就三步枚举、挂载、读写实际动手调起来才会发现每一步都有看不见的坑枚举超时、挂载失败、写一半卡死是最常见的三个翻车点。这个 USB HOST.zip 是一份做嵌入式多年整理出来的工程包里面带好了配置工程、FatFs 移植层和一个能直接烧录的按键读写 Demo。它解决的是 STM32F107 的 OTG_FS 全速外设在 HOST 模式下读写字库、固件升级、数据落盘这类场景问题适合正在做 U 盘固件升级、产线批量拷贝、采集数据导出的小团队也适合刚接触 USB Host 栈、想拿一份完整工程对照学习的新手。如果你已经花了好几个晚上还在跟状态机较劲这份资源值得直接打开复现一遍。2. 从 OTG 到 HOST为什么选 STM32F107模式切换与三个硬件前提2.1 OTG_FS 的三种角色HOST 模式与 OTG 模式的取舍STM32F107 内置的 USB OTG_FS 外设支持全速 12Mbps 通信物理层 PHY 在芯片内部这跟很多需要外挂 USB PHY 的芯片方案相比省掉了不少外围器件。它本身被设计成可以承担三种角色Device、Host 和 OTG。OTG 允许同一个 USB 口在主从之间动态切换但对绝大多数只插 U 盘的应用来说这种灵活反而引入了额外的复杂性。我一般会直接选 Host Only而不是让系统跑 OTG。原因有两个第一OTG 模式下的 HNP 会话协商协议本身就是一套复杂状态机调试时要多看一个变量第二OTG 需要硬件上把 ID 引脚拉出来做检测很多自制板子根本没接这个脚。把角色固定为 HOST状态机路径清晰行为也可预测这在生产环境里比什么都重要。HOST 模式下控制器负责发送 SOF 帧、管理总线调度、发起控制/批量/中断传输。U 盘属于大容量存储设备对应的类驱动是 MSC走 Bulk-Only Transport 协议底层通过 SCSI 命令跟 U 盘通信。协议栈部分由 USB Host 库实现你真正要操心的是上层 FatFs 文件系统怎么和 MSC 驱动对接。所以资源包里的代码它的分层非常清楚底层 USBH_MSC_Class、中间 diskio 映射、上层 FatFs API每一层是独立的.c文件。2.2 时钟、VBUS 与 DP/DM枚举前的三个硬件前提先讲最容易被忽略的 DP/DM 电阻问题。STM32F107 内置 PHY 的情况下HOST 侧需要的 15kΩ 下拉电阻已经集成在芯片内部不需要也绝不能再在 D/D- 上外挂下拉。我碰到过一个板子硬件工程师照抄某些 Device 模式参考设计在 D 和 D- 上各加了一个 15kΩ 下拉到地结果当然是枚举永远失败。全速设备侧的 1.5kΩ 上拉电阻由 U 盘自己提供主机侧不用管D/D- 到 USB 座之间尽量短走差分线别跨分割这是基本的信号完整性要求。第二个前提是 VBUS 5V 供电。U 盘是总线供电设备插上瞬间冲击电流不小如果板上的 5V 是某个小电流 LDO 转出来的电压会被直接拉垮U 盘主控反复复位状态机一直在枚举和断开之间横跳。正确做法是独立的 5V 电源芯片VBUS 输出脚和 U 盘座之间串自恢复保险边上放一个 22μF 电解电容稳压。VBUS 使能引脚要用 GPIO 控制不要由 USB 控制器内部自动打开这样软件上能控制上下电顺序。第三个前提是 48MHz USB 时钟。全速 USB 对时钟精度要求约 ±0.25%48MHz 偏到 47.9MHz 都会导致枚举不稳、批量传输 CRC 报错。配置工具里的时钟树一定要看到 48MHz 这个数字。用示波器量 PA8 的 MCO 输出更直接能实时看到时钟是否偏移。这里多说一句很多移植出来的工程喜欢直接用内部 HSI能跑但不稳定尤其是温度变化之后我建议还是用外部晶振走 PLL。VBUS 感知脚也值得留意。如果配置里开了 VBUS sensing硬件上必须用电阻分压把 5V 降到 3.3V 以下接到感知引脚否则控制器认为 VBUS 无效枚举根本不会开始。硬件没接分压电阻的时候有两个选择把感知关掉或者补一个分压电路。我建议补电路因为还能顺便拿来监测 U 盘是否掉电。2.3 配置生成的几个关键点外设、中间件与中断用配置工具搭工程时外设一项选择 USB_OTG_FS工作模式选 Host_Only中间件层开启 USB_HOST 并把 Class 选成 Mass Storage Host Class再开启 FATFS磁盘驱动挂到 USB 上。这里注意生成代码的顺序先有 USB Host 底层再有 diskio 映射层最后是 FatFs 应用层三层是串联关系任何一层断掉都读不了 U 盘。中断配置有两个USBH_OTG_FS_IRQHandler 和 USBH_OTG_FS_WKUP_IRQHandler都要打开。优先级设置上我一般比 SysTick 低半级避免中断里长时间卡在 USB 处理流程导致系统调度异常。另外生成代码后需要手工补的只有两处VBUS_EN 引脚的 GPIO 输出初始化以及 USBH_UserProcess 回调里的用户事件处理函数后面避坑章节会专门讲这个回调里必须做什么。整个 HOST 栈的运行机制是初始化时注册 MSC 类驱动然后主循环里持续调用 USBH_Process 驱动状态机前进。这个函数就是整个 USB HOST 的心跳少了它所有中断和回调都不会正常工作。3. 读写 U 盘实现从枚举到文件落盘的完整链路3.1 USBH_Init 与 USBH_Process主循环里的状态机泵初始化代码在 main 函数中是这样组织的先把 VBUS 使能再做 USB Host 初始化/* 打开 U 盘 5V 供电 */ HAL_GPIO_WritePin(VBUS_EN_GPIO_Port, VBUS_EN_Pin, GPIO_PIN_SET); /* USB Host 初始化注册 MSC 类驱动并启动 HOST */ MX_USB_HOST_Init();MX_USB_HOST_Init 内部实际完成两件事调用 USBH_Init 把 MSC 类驱动挂到 Host 栈上再调用 USBH_Start 启动状态机。这里第二个参数 0 表示主机通道号STM32F107 只有一个 OTG_FS 外设填 0 即可。VBUS 先于 USB 初始化上电是保证首次枚举稳定的一个细节我见过反过来的顺序第一次总是枚举失败后面每次都成功多半就是上电时序问题。主循环里必须无条件高频调用 USBH_Processwhile (1) { USBH_Process(hUsbHostFS); if (hUsbHostFS.gState HOST_APPLICATION_READY) { /* 枚举和类请求完成可以挂载文件系统 */ MountUSB(); break; /* 挂载后跳出循环进入业务逻辑 */ } }逻辑说明USBH_Process 是整个 Host 栈的推进函数负责设备插入检测、总线复位、枚举、MSC 类请求这些状态迁移。放在主循环里高频轮询是标准做法不能把它塞进某个低频任务里否则中断里收到的数据没有及时被协议栈消化U 盘会工作不稳定。gState 是当前状态的只读反馈HOST_APPLICATION_READY 表示设备已经准备好应用层这时候才能往下走。3.2 FATFS 挂载卷号、立即挂载标志与超时重试确认枚举成功后再挂载文件系统建议带超时和重试机制FATFS USBDiskFS; FRESULT res; uint8_t retry 0; /* 等待枚举完成最多重试 5 次每次 100ms */ while (hUsbHostFS.gState ! HOST_APPLICATION_READY retry 5) { USBH_Process(hUsbHostFS); HAL_Delay(100); retry; } if (hUsbHostFS.gState ! HOST_APPLICATION_READY) { return -1; /* 超时未就绪 */ } /* 立即挂载卷卷名是 USB: */ res f_mount(USBDiskFS, USB:, 1); if (res ! FR_OK) { return -2; /* 挂载失败 */ }参数说明f_mount 的第三个参数是立即挂载标志。填 1 表示 f_mount 内部立即读取 U 盘引导扇区并注册工作区失败会当场返回错误码填 0 则只是登记文件系统对象真正的挂载要等第一次 f_open 才发生。我习惯填 1把问题暴露在前而不是让错误延迟到读写时候才出现。卷名字符串 USB: 必须与 diskio.c 里实现的磁盘驱动编号对应生成代码里默认映射到 USBH 的驱动。另一个工程细节是 FATFS 默认打开长文件名支持后会额外消耗 RAM对 107 来说完全够用。如果板子 RAM 紧张可以改配置只支持短文件名代价是 U 盘里那些中文文件名和超长文件名都打不开。3.3 文件读写封装返回值、实际字节数与断点续传FatFs 的读写函数要检查的不只是返回值还要核对实际传输字节数FRESULT read_usb_file(const char *path, uint8_t *buf, uint32_t max_len, uint32_t *actual_len) { FIL f; FRESULT res; UINT rd 0; res f_open(f, path, FA_READ); if (res ! FR_OK) return res; /* 文件不存在或权限错误 */ res f_read(f, buf, max_len, rd); f_close(f); if (res ! FR_OK) return res; /* 读错误可能是介质异常 */ *actual_len rd; /* 实际读到的字节数 */ return FR_OK; }代码说明f_open 的打开模式用 FA_READ如果文件不存在会返回 FR_NO_FILE。f_read 的第三个参数是请求读取的长度第四个参数是实际读到的字节数rd 必须被检查因为底层 SCSI 读可能返回比请求少的字节。写文件同理f_write 的第四个参数是实际写入字节数空间不足时返回值可能是 FR_DENIED 或 FR_INT_ERR只看返回值会漏掉部分写入的问题。对于要频繁写配置数据的场景我会在写完一帧后调用 f_sync 强制将缓存写入 U 盘防止突然掉电丢数据。代价是每次 f_sync 都有一定耗时但换来的是掉电后文件系统不损坏。3.4 最小可运行 Demo一个按键触发读写工程里附带的是一个按键触发读写的完整流程核心代码可以浓缩成这样uint8_t test_buf[512]; void UserKey_Handler(void) { FIL f; UINT bw; FRESULT res; /* 写文件 */ memset(test_buf, 0x5A, sizeof(test_buf)); res f_open(f, USB:test.bin, FA_CREATE_ALWAYS | FA_WRITE); if (res FR_OK) { f_write(f, test_buf, sizeof(test_buf), bw); f_close(f); } }这个 Demo 的价值在于它把前面讲的等待机制、挂载逻辑、读写封装全部串成了一条可断点跟踪的链路。按下按键完成一次 512 字节写入然后重新挂载把内容读回来做比对。大多数情况下只要你把配置里 VBUS 感知关掉或者硬件做对这个 Demo 第一次就能跑通。跑不通的时候优先去查的是中断优先级而不是怀疑协议栈。4. USB HOST 避坑指南枚举失败、读写卡死与文件损坏的四个排查记录4.1 枚举失败状态机反复重启永远到不了 APPLICATION_READY现象U 盘插入后状态从 HOST_IDLE 走到 HOST_ENUMERATION然后复位重新开始来回循环gState 始终进不了 HOST_APPLICATION_READY。原因最常见的是 VBUS 供电不足。U 盘主控在枚举阶段需要从 VBUS 取电完成内部初始化如果 5V 在插上瞬间跌落超过 10%U 盘主控会反复复位主机侧看到的就是设备不停地断开重连。还有一个原因就是之前提到的 DP/DM 上被人为加了外部下拉电阻破坏了内置 PHY 的阻抗匹配。解决先去掉 D/D- 上所有外部电阻再用示波器量 VBUS 波形。如果插上 U 盘时 5V 跌落明显把供电换成独立电源并在 VBUS 输出加 100μF 电解电容。做到这两点枚举成功率基本能回到 100%。我第二版板卡上加了 VBUS 监控枚举失败率从原来 30% 降到接近 0。4.2 读写卡死程序死在 USB 中断里单步无法跳出现象U 盘可以挂载f_open 也正常但执行 f_read 或 f_write 时程序卡死调试器断在 USB_OTG_FS_IRQHandler 中断里怎么单步都不走。原因这是中断优先级配置错误导致的死锁。USB 中断优先级如果高于系统滴答定时器而中断处理里有任何等待操作就会和主循环里的 USBH_Process 形成互相等待。另外传输缓冲区没有按 4 字节对齐也常见内置 PHY 的 DMA 访问非对齐地址时触发总线错误。解决把 USB 中断优先级调到低于 SysTick传输缓冲区强制对齐/* 4字节对齐的传输缓冲区防止DMA访问出错 */ __attribute__((aligned(4))) uint8_t usb_tx_buf[512]; __attribute__((aligned(4))) uint8_t usb_rx_buf[512];代码说明attribute((aligned(4))) 是 GCC 和 MDK 的 AC5 都支持的写法告诉编译器把这个数组放到 4 字节对齐的地址上。USB 控制器做 DMA 传输时要求缓冲区首地址对齐否则会进入异常。这个问题在加了 L1 缓存的高端芯片上更严重F107 虽然没有缓存但也可能因为总线矩阵访问对齐问题卡死。4.3 挂载成功但 f_open 返回 FR_NO_FILESYSTEM现象枚举状态正常f_mount 返回 FR_OK但 f_open 创建或打开文件时报 FR_NO_FILESYSTEMU 盘在电脑上看是正常可用的。原因大概率是 U 盘格式化成了 exFAT而工程里集成的 FatFs 是旧版本只支持 FAT12/16/32。还有一种情况是 U 盘存在多个分区MSC 驱动默认访问 LUN 0而 LUN 0 恰好是个没有文件系统的隐藏分区。解决先用电脑确认 U 盘分区格式。见到 exFAT要么换成支持 exFAT 功能的 FatFs 版本要么直接重新格式化成 FAT32分配单元大小选 32KB。对于多分区 U 盘在上层读 SCSI READ_CAPACITY 命令返回的 LUN 数量并在 FatFs 挂载前确认当前 LUN 是否有可识别的引导扇区。我的习惯是产线用的 U 盘全部格式化为 FAT32不折腾 exFAT省掉一个变量。4.4 热插拔第一次读写正常拔下再插入就挂载失败现象U 盘第一次插入读写都正常拔掉后重新插入枚举状态正常但 f_mount 返回 FR_NOT_READY 或 FR_NO_FILESYSTEM必须重启才能恢复。原因拔出 U 盘后FatFs 的工作区里还缓存着旧盘的 FAT 表数据卷对象没有被释放。USB Host 栈检测到断开时只做了底层清理应用层的文件系统状态没人管第二次插入新盘后FatFs 拿着旧缓存去访问新盘自然失败。解决在 USBH_UserProcess 回调里捕获断连事件主动卸载文件系统void USBH_UserProcess(USBH_HandleTypeDef *phost, uint8_t id) { if (id HOST_USER_DISCONNECTION) { /* 拔盘时主动卸载卷释放 FatFs 工作区 */ f_mount(NULL, USB:, 1); } }代码说明f_mount 第一个参数传 NULL 表示卸载卷把之前缓存的工作区释放掉。这样第二次插入时重新走 f_mount 挂载的就是干净状态。注意这个回调运行在中断上下文FatFs 操作本身耗时我一般只在这里置一个卸载标志真正执行 f_mount 放在主循环里避免中断里做重活导致切换卡顿。5. 进阶让 USB HOST 读写稳定度再上一个台阶的三个工程习惯5.1 把 USB 生命周期收敛进一个独立状态机不要在主循环里散落地判断什么时候该挂载、什么时候该卸载我在量产工程里都是单独写一个 usb_app.c把 USB 生命周期收敛成四个状态USB_STATE_IDLE、USB_STATE_MOUNT、USB_STATE_READY、USB_STATE_EJECT。主循环只做状态机推进每个状态对应一个函数进入 READY 才开放文件操作接口检测到拔出立刻回到 IDLE。这样业务代码不直接碰 FatFs 函数出了问题也容易定位。资源包里是基于这个思路组织的你可以拿着工程直接在这个框架上加业务。5.2 写入过程强制防掉电U 盘掉电损坏文件系统是产线里最头疼的故障。我后来养成的习惯是所有写操作遵循三条规则数据先拼满一个扇区整数倍再写写关键配置后立即 f_sync写整个文件结束后主动 f_close。不要依赖 f_close 自动落盘U 盘内部的写缓存不一定马上刷到闪存。对 107 这种跑裸机的场景没有操作系统帮你做缓存管理这些习惯是必须的否则十次量产里总有一两次把 U 盘写坏。5.3 一个比串口下载省心得多的 U 盘固件升级技巧资源包里附带的固件升级 Demo 思路很实用把编译好的 bin 文件放到 U 盘特定目录上电后读取到 RAM校验 CRC校验通过跳转到 APP 执行。全速 USB 实际读速度能到 1MB/s 左右128KB 固件几秒就完成比串口下载有优势得多。做法是 bootloader 里加一段 FatFs 挂载逻辑只读 U 盘里 update.bin 这一个文件读完校验后清掉标志位。我做了这个功能后产线不再需要连调试器效率提升明显。从那以后我每次做 USB HOST 工程都会强制自己按这个顺序走一遍先量 VBUS 波形再确认 48MHz 时钟然后才写应用层代码。这套检查流程帮我避开了绝大多数莫名其妙的问题希望帮到你。本文还有配套的精品资源点击获取