在嵌入式开发中,数据存储是个永恒的话题。我做过不少数据采集项目,早期都是用SD卡存储数据,直到有一次客户要求必须支持U盘导出数据,这才发现USB_HOST功能的重要性。相比SD卡,U盘有三大优势:容量大(随便一个U盘都是32GB起步)、插拔方便(不需要读卡器)、普及度高(几乎人人都有)。
但实现起来并不简单。第一次尝试时,我遇到了U盘识别不稳定、文件系统挂载失败、写入速度慢等问题。后来发现,STM32CubeMX工具其实已经帮我们封装好了底层协议栈,关键在于正确配置USB_HOST和FatFs的联动。比如有个项目需要每小时存储1MB的传感器数据,用CubeMX配置后,实测写入速度能达到800KB/s,完全满足需求。
我推荐使用STM32F4系列开发板,比如正点原子探索者(F407)或野火指南者(F429),它们都自带USB_HOST接口。要注意的是:
有一次我用F103芯片做测试,发现死活识别不了U盘。后来查资料才知道,F103的USB模块是Device-only的,根本不能做Host!这个坑让我白折腾了两天。
USB_HOST驱动U盘主要涉及两个协议层:
有趣的是,当你用逻辑分析仪抓USB数据时,会发现即便只是读取一个文本文件,底层也要经历数十次SCSI命令交换。好在CubeMX生成的代码已经封装了这些细节,我们只需要关注文件操作。
打开CubeMX后,关键配置步骤如下:
c复制// 生成的初始化代码示例
MX_USB_HOST_Init(); // 会自动调用以下三个函数
USBH_Init(&hUsbHostFS, USBH_UserProcess, HOST_FS);
USBH_RegisterClass(&hUsbHostFS, USBH_MSC_CLASS);
USBH_Start(&hUsbHostFS);
在Middleware中启用FATFS,注意两个关键参数:
有个容易忽略的点:在fatfs_conf.h中要把_USE_LFN设为2(支持长文件名),否则中文文件名会乱码。我曾经因为这个问题,导致客户采集的气象数据文件名全部变成问号。
CubeMX会自动生成状态机代码,我们需要在回调函数中处理不同状态:
c复制void USBH_UserProcess(USBH_HandleTypeDef *phost, uint8_t id) {
switch(id) {
case HOST_USER_CONNECTION: // U盘插入
printf("U盘已连接,等待初始化...\n");
break;
case HOST_USER_CLASS_ACTIVE: // 就绪状态
f_mount(&USBHFatFS, "0:", 1); // 挂载文件系统
break;
case HOST_USER_DISCONNECTION: // U盘拔出
f_mount(NULL, "0:", 1); // 卸载文件系统
printf("U盘已安全移除\n");
break;
}
}
实测发现,某些山寨U盘需要额外延时处理。我总结的经验是:在HOST_USER_CLASS_ACTIVE后延迟300ms再挂载FatFs,成功率更高。
直接使用f_write()写入小文件会导致速度极慢(可能只有50KB/s)。我的优化方案是:
c复制// 高速写入示例
#define BUF_SIZE 8192
static uint8_t buf[BUF_SIZE];
void fast_write(const char* path) {
FIL fp;
UINT bw;
f_open(&fp, path, FA_WRITE | FA_CREATE_ALWAYS);
f_lseek(&fp, f_size(&fp)); // 追加写入
while(has_data) {
fill_buffer(buf, BUF_SIZE); // 填充数据
f_write(&fp, buf, BUF_SIZE, &bw);
if(bw != BUF_SIZE) {
printf("写入错误!\n");
break;
}
if(++count % 10 == 0) f_sync(&fp); // 每10次同步一次
}
f_close(&fp);
}
在F407芯片上,这种方法可以实现600KB/s以上的持续写入速度,足够应对大多数数据采集场景。
先检查硬件:
软件方面重点看:
错误代码FR_NO_FILESYSTEM通常有两种原因:
有个项目遇到反复挂载失败,最后发现是客户用的exFAT格式U盘。解决方案是在CubeMX中启用exFAT支持(需要购买ST的中间件license),或者要求客户使用FAT32格式。
通过Hub连接多个U盘时,需要修改USBH_MSC_Core.c中的处理逻辑。核心思路是:
c复制// 多LUN操作示例
USBH_SelectInterface(&hUsbHostFS, lun_num); // 切换LUN
f_mount(&fatfs[lun], path, 1); // 独立挂载
突然断电可能导致文件系统损坏。我的解决方案是:
c复制// 安全写入流程
f_open(&fp, "temp.dat", FA_WRITE);
f_write(...);
f_close(&fp);
f_rename("temp.dat", "data.dat"); // 原子操作
在工业现场部署的设备,经过这种优化后,三年内没有出现一起数据损坏案例。
我用STM32F407+USB3300高速PHY做了组对比测试:
| 操作类型 | FAT32簇4KB | FAT32簇32KB | exFAT |
|---|---|---|---|
| 创建100个文件 | 1.2s | 0.8s | 0.6s |
| 写入10MB数据 | 16.5s | 12.3s | 9.8s |
| 读取10MB数据 | 3.2s | 2.9s | 2.7s |
结论很明显:大簇+exFAT性能更好,但兼容性会下降。普通应用建议用32KB簇的FAT32,是性能和兼容性的最佳平衡点。