资讯动态

STM32F107 USB HOST 读写 U 盘实战:从枚举到 FatFs 挂载避坑指南

发布时间:2026/10/16 16:20:26 来源:尧图企业网站定制
简介这份资源面向嵌入式开发初学者与中级工程师聚焦STM32F107微控制器在USB主机模式下的U盘读写实现解决嵌入式设备与移动存储设备进行数据交换的典型问题。压缩包共298个文件约8.18MB以C源码与头文件为主体包含USB主机驱动、FatFS文件系统移植、STM32外设配置及Keil工程文件另附少量编译产物与说明文档便于直接编译验证。项目完整呈现了从USB主机初始化、U盘枚举挂载到文件打开、读写、目录操作及错误处理的实现流程读者可借此掌握ST官方库与FatFS的配合方式理解USB主机通信与文件系统移植的关键细节。目前已有354人学习下载适合需要快速搭建USB主机读写U盘原型的开发者参考借鉴。1. 拿到 USB HOST.zip 之后STM32F107 读写 U 盘这件事到底难在哪很多人第一次在 STM32F107 上做 USB HOST 读写 U 盘都会卡在一个反直觉的地方板子插上 U 盘灯亮了但枚举就是过不去或者枚举过了f_open 返回 FR_NOT_READY。问题往往不在 USB 协议本身而在于时钟配置、中断优先级和文件系统挂载这三件事没对齐。这份 USB HOST.zip 就是围绕 STM32F107 的 USB OTG FS 外设把主机模式枚举 U 盘、挂载 FatFs、读写文件这条链路完整跑通的工程。它适合正在做数据采集存储、固件升级包读取、日志导出这类功能的嵌入式开发者尤其是用标准外设库或 HAL 库在 F107 上折腾过 USB 但没跑通的人。下面按「先看清结构、再跑起来、最后能改」的顺序拆一遍。2. 工程结构拆解从 USB OTG 中断到 FatFs 的调用链2.1 目录分层与各层职责拿到压缩包解压后根目录通常能看到几个并列的文件夹这是 STM32 USB HOST 工程的典型布局。以标准外设库版本为例结构大致如下USB_HOST/ ├── Libraries/ │ ├── STM32_USB_OTG_Driver/ # USB OTG 底层驱动操作寄存器 │ ├── STM32_USB_HOST_Library/ # 主机协议栈处理枚举、类请求 │ │ ├── usbh_core.c # 主机状态机核心 │ │ ├── usbh_stdreq.c # 标准请求GET_DESCRIPTOR 等 │ │ └── usbh_msc.c # MSC 类驱动U 盘就靠它 │ └── FatFs/ # 文件系统与 USB 解耦 ├── User/ │ ├── main.c # 初始化、主循环、文件操作入口 │ ├── usb_bsp.c # 时钟、GPIO、中断优先级配置 │ └── usbh_usr.c # 用户回调挂载/拔出事件 └── Project/ # 工程文件Keil/IAR这个分层的关键在于USB 主机协议栈和 FatFs 之间只通过USBH_MSC_Read/Write这组底层磁盘接口通信。FatFs 不关心数据是从 U 盘还是 SD 卡来的它只调用disk_read和disk_write。所以你要换存储介质改的是diskio.c不是 USB 协议栈。2.2 枚举流程与状态机USB HOST 库的核心是一个状态机跑在USBH_Process()里这个函数必须被周期性调用。它的推进顺序是/* main.c 主循环中的典型调用 */ while (1) { USBH_Process(USB_OTG_Core, USB_Host); /* 推进主机状态机 */ if (USBH_MSC_IsReady(USB_Host)) /* U 盘就绪 */ { if (app_state APPLICATION_READY) { /* 这里做文件读写 */ } } }USBH_Process内部依次经历HOST_IDLE、HOST_DEV_ATTACHED、HOST_ENUMERATION、HOST_CLASS_REQUEST、HOST_CLASS等状态。U 盘插入后先复位端口再读设备描述符拿到 VID/PID然后读配置描述符最后根据接口类代码0x08Mass Storage加载 MSC 类驱动。整个过程依赖 SOF 中断和 OTG 中断提供的时间基准与事件触发。2.3 中断优先级最容易翻车的一环USB OTG FS 的中断优先级必须高于 SysTick否则枚举会随机失败。在usb_bsp.c里通常这样配/* NVIC 配置OTG_FS_IRQn 优先级要足够高 */ NVIC_InitStructure.NVIC_IRQChannel OTG_FS_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 0; /* 抢占优先级最高 */ NVIC_InitStructure.NVIC_IRQChannelSubPriority 0; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure);抢占优先级给 0 是为了保证 USB 中断能打断其他任务。如果这里配成和 SysTick 同级或者被其他外设中断长时间阻塞枚举阶段的控制传输就会超时表现为设备描述符读不全。这是我在多个 F107 项目上踩过的坑后面避坑章节会展开。3. 跑起来时钟、堆栈与 FatFs 挂载的实操步骤3.1 系统时钟与 USB 48MHz 时钟STM32F107 的 USB OTG FS 要求 48MHz 时钟来源可以是外部晶振经 PLL 倍频也可以是内部 RC。用外部 8MHz 晶振时常见配置是 PLL 倍频到 72MHz 给系统再分频出 48MHz 给 USB/* system_stm32f10x.c 中的时钟配置片段 */ /* PLL: 8MHz / 2 * 9 36MHz? 不对F107 的 USB 预分频要单独算 */ RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); /* 8*972MHz */ RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_Div1_5); /* 72/1.548MHz */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE);注意RCC_USBCLKSource_PLLCLK_Div1_5这个分频系数F107 的 USB 时钟必须精确 48MHz偏差超过 0.25% 就可能导致枚举不稳定。如果你用的是 25MHz 晶振分频系数要重新算不能照抄。3.2 堆栈调整与 MSC 缓冲区USB HOST 协议栈和 FatFs 都会吃栈。默认的 0x400 栈在枚举大容量设备时可能不够建议在启动文件里把栈调到 0x800 以上。另外 MSC 类驱动需要一个扇区缓冲区通常定义在usbh_msc_scsi.c或usbh_msc_bot.c里/* MSC 读写用的扇区缓冲必须 4 字节对齐 */ __align(4) uint8_t MSC_BOT_Data[MSC_MEDIA_PACKET]; /* 通常 512 或 4096 */MSC_MEDIA_PACKET如果定义成 512读写速度会受限于每次一个扇区改成 4096 或 8192 能明显提升连续读写性能但要确认你的 RAM 够用。F107 有 64KB RAM一般够。3.3 FatFs 挂载与文件操作U 盘枚举成功后USBH_MSC_IsReady返回真此时才能挂载 FatFs。挂载和读写的典型流程FATFS fatfs; /* 文件系统对象 */ FIL file; /* 文件对象 */ FRESULT res; UINT bw; /* 等待 U 盘就绪后挂载 */ res f_mount(fatfs, 0:, 1); /* 1 表示立即挂载 */ if (res ! FR_OK) { /* 挂载失败常见原因U 盘未格式化或文件系统不支持 */ return; } /* 写文件 */ res f_open(file, 0:/log.txt, FA_CREATE_ALWAYS | FA_WRITE); if (res FR_OK) { f_write(file, hello usb host\r\n, 16, bw); f_close(file); } /* 读文件 */ res f_open(file, 0:/log.txt, FA_READ); if (res FR_OK) { char buf[64]; f_read(file, buf, sizeof(buf), bw); f_close(file); }f_mount的第三个参数为 1 时会立即执行挂载动作内部会调用disk_initialize最终走到USBH_MSC_Read去读 U 盘的引导扇区。如果这里返回FR_NOT_READY说明 MSC 层还没准备好要回头查枚举状态。提示U 盘格式建议用 FAT32且分区表为 MBR。exFAT 和 GPT 在默认 FatFs 配置下不支持需要额外使能。4. 避坑与排查枚举失败、挂载超时、读写异常的定位方法4.1 现象U 盘插入后无任何反应USBH_Process状态停在 HOST_IDLE原因通常是 OTG 中断没使能或者 VBUS 供电检测没配。F107 作为主机时要给 U 盘供电如果板子没有独立 VBUS 开关需要确认 5V 是否真的送到了 USB 口。解决检查USB_OTG_Init里host.vbus_sensing_enable的设置以及usb_bsp.c中 GPIO 是否把 PA9VBUS 使能拉高。用万用表量一下 U 盘座子的 5V 脚没有 5V 后面都白搭。4.2 现象枚举到一半失败串口打印USBH_DEV_DISCONNECTED原因多半是中断优先级被抢占或者控制传输的 NAK 超时太短。F107 的 OTG 中断如果被 SysTick 或其他高优先级中断打断超过几毫秒控制传输就会超时。解决把 OTG_FS_IRQn 抢占优先级设为 0同时检查usbh_core.c里USBH_Process的调用周期确保主循环里没有长时间阻塞的操作。另外有些 U 盘上电后需要几百毫秒稳定时间可以在枚举前加个延时。4.3 现象f_mount返回FR_NOT_READY但USBH_MSC_IsReady已经是 1原因是 MSC 就绪和 FatFs 挂载之间有时序差。USBH_MSC_IsReady只表示 SCSI 层可以接受命令但 FatFs 第一次读引导扇区时可能遇到 U 盘内部忙。解决在挂载前加一个重试循环或者先调用一次USBH_MSC_Read读物理扇区 0 确认能通再f_mount。我一般会在usbh_usr.c的USBH_USR_Configuration_Desc回调里置一个标志主循环检测到标志后再延时 100ms 挂载。4.4 现象写文件后拔 U 盘再插上发现文件内容为空或文件系统损坏原因是f_write后没有f_close或f_syncFatFs 的缓存没刷到 U 盘。U 盘和 SD 卡不同它的写入缓存更大掉电或拔出时未刷新的数据会丢。解决每次写操作后调用f_sync或者至少f_close。如果做数据记录建议每写若干条就f_sync一次牺牲一点性能换数据安全。4.5 现象读写大文件时速度极慢只有几十 KB/s原因是MSC_MEDIA_PACKET定义太小或者 FatFs 的_MAX_SS和_MIN_SS配置不匹配。默认 512 字节扇区、每次一个扇区的读写在 USB 全速 12Mbps 下理论也就 1MB/s 左右实际因为协议开销会更低。解决把MSC_MEDIA_PACKET调到 4096 或 8192同时确认ffconf.h里_MAX_SS不小于 512。另外避免频繁f_open/f_close批量写用同一个文件对象。5. 二次开发改造从能读到好用几个可落地的优化方向5.1 把读写封装成独立任务原始工程通常在主循环里直接做文件操作这在加入其他功能后会互相阻塞。改造思路是把 USB 处理和文件操作拆成两个状态用标志位或简单调度器驱动typedef enum { USB_STATE_WAIT, USB_STATE_READY, USB_STATE_ERROR } usb_app_state_t; static usb_app_state_t app_state USB_STATE_WAIT; void usb_app_task(void) { USBH_Process(USB_OTG_Core, USB_Host); switch (app_state) { case USB_STATE_WAIT: if (USBH_MSC_IsReady(USB_Host)) app_state USB_STATE_READY; break; case USB_STATE_READY: /* 执行文件读写完成后可回到 WAIT 或保持 */ break; case USB_STATE_ERROR: /* 错误处理尝试重新枚举 */ break; } }这样主循环里只调用usb_app_task其他任务不受影响。如果上 RTOS可以把USBH_Process放在一个低优先级任务里文件操作放在另一个任务用队列传递数据。5.2 增加热插拔与错误恢复U 盘可能随时被拔掉USBH_Process会检测到断开并回到HOST_IDLE。但 FatFs 的文件对象不会自动失效下次挂载前要确保f_mount(NULL, 0:, 0)卸载旧卷。可以在usbh_usr.c的USBH_USR_DeviceDisconnected回调里做清理void USBH_USR_DeviceDisconnected(void) { f_mount(NULL, 0:, 0); /* 卸载文件系统 */ app_state USB_STATE_WAIT; }同时如果枚举失败不要死等加一个重试计数超过次数就复位 USB 端口重新来。5.3 性能与可靠性的取舍读写速度和数据完整性往往矛盾。我的习惯是小文件配置、日志每次写完f_sync大文件固件包、采集数据用 4KB 以上缓冲每写 64KB 同步一次。另外U 盘质量参差不齐有些盘在连续写入时会内部垃圾回收导致超时可以在disk_write里加重试。最后别忘了在ffconf.h里把_FS_TINY设为 0用独立缓冲区减少栈压力。从那以后我每次做 USB HOST 项目都会先把时钟和中断优先级用示波器或串口确认一遍再动文件系统。这个顺序反过来排查成本会翻好几倍。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑