别再花钱买U盘了!用STM32F103C8T6的Flash自己做一个(CubeMX+USB MSC+FATFS)
2026/6/12 10:19:53 网站建设 项目流程

用STM32打造个性化U盘:从零实现Flash存储与USB MSC协议

在电子爱好者的世界里,总有那么几个被遗忘在角落的STM32开发板。它们可能曾是你学习嵌入式系统的入门伙伴,如今却静静地躺在抽屉里积灰。本文将带你重新发现这些"电子废品"的价值——将它们改造成完全可用的个性化U盘。不同于市面上千篇一律的商业产品,这种DIY方案不仅能节省开支,更能让你深入理解USB大容量存储设备(MSC)协议与Flash存储管理的核心技术。

1. 为什么选择STM32自制U盘?

1.1 成本效益分析

一块STM32F103C8T6开发板的市场价格通常在20-40元之间,而同等容量的商业U盘售价约30-50元。表面看似乎没有价格优势,但考虑以下因素:

  • 已有资源利用:多数开发者手头都有闲置开发板
  • 功能扩展性:可集成加密、自动备份等商业U盘不具备的功能
  • 学习价值:深入理解USB协议栈和存储管理原理

性能对比表

指标自制STM32 U盘商业U盘
连续写入速度约50-100KB/s5-30MB/s
擦写寿命约10,000次500-1000次
容量灵活性可自由配置(通常64KB)固定不可调
功能扩展完全可编程通常不可编程

1.2 技术可行性验证

STM32F103系列内置USB全速控制器和足够的Flash空间(C8T6型号实际有128KB,尽管标称64KB)。通过USB MSC(大容量存储设备)协议和FAT文件系统的组合,完全能实现标准U盘功能。关键点在于:

  • USB MSC协议提供标准的块设备接口
  • FATFS实现文件系统兼容性
  • Flash模拟EEPROM管理确保数据持久性

提示:虽然内部Flash速度不如外部SPI Flash,但对于小文件传输和固件更新等场景完全够用。

2. 硬件准备与CubeMX基础配置

2.1 硬件选型要点

推荐使用"蓝色药丸"开发板(STM32F103C8T6最小系统板),因其:

  • 内置USB接口
  • SWD调试接口
  • 价格低廉且广泛可用

必备组件清单

  • STM32F103C8T6开发板
  • Micro-USB数据线(需支持数据传输)
  • 4.7kΩ电阻(用于USB DP上拉)
  • 8MHz晶振(部分板载)

2.2 CubeMX工程创建

  1. 打开STM32CubeMX,选择STM32F103C8Tx系列
  2. 配置时钟源为外部晶振(HSE)
  3. 启用USB外设:选择Device模式
  4. 添加FATFS中间件:选择"MMC/SD Card"模式
  5. 设置堆栈大小(建议Heap=0x600, Stack=0x400)

关键配置参数:

/* USB MSC相关参数 */ #define MSC_MEDIA_PACKET 1024 #define USBD_MAX_NUM_INTERFACES 1 /* FATFS配置 */ #define FATFS_MAX_SS 1024 #define FATFS_MIN_SS 1024

3. Flash存储管理实现

3.1 Flash空间规划

STM32F103C8T6实际Flash布局:

0x08000000 - 0x0801FFFF (128KB) |__ Bootloader (通常占用前20KB) |__ 应用程序代码区 |__ 模拟U盘存储区 (最后64KB)

存储区定义宏:

#define FLASH_TOTAL_SIZE 128 // 单位KB #define FLASH_PAGE_SIZE 1024 // 与FATFS块大小一致 #define FLASH_PAGE_COUNT 64 // 64KB存储空间 #define FLASH_START_ADDR (0x08000000 + ((FLASH_TOTAL_SIZE-FLASH_PAGE_COUNT)*1024))

3.2 Flash读写驱动

实现基本的擦除和编程操作:

void Flash_ErasePages(uint32_t start_addr, uint32_t num_pages) { HAL_FLASH_Unlock(); FLASH_EraseInitTypeDef erase; erase.TypeErase = FLASH_TYPEERASE_PAGES; erase.PageAddress = start_addr; erase.NbPages = num_pages; uint32_t error = 0; HAL_FLASHEx_Erase(&erase, &error); HAL_FLASH_Lock(); } void Flash_Write(uint32_t addr, uint8_t *data, uint32_t len) { HAL_FLASH_Unlock(); for(uint32_t i=0; i<len; i+=4) { HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + i, *(uint32_t*)(data+i)); } HAL_FLASH_Lock(); }

注意:Flash编程必须以字(4字节)为单位,且地址必须对齐。

4. USB MSC与FATFS集成

4.1 USB MSC接口实现

修改usbd_storage_if.c实现三个关键回调:

  1. 容量报告函数:
int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size) { *block_num = FLASH_PAGE_COUNT; *block_size = FLASH_PAGE_SIZE; return USBD_OK; }
  1. 读取函数:
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) { uint8_t *src = (uint8_t*)(FLASH_START_ADDR + blk_addr*FLASH_PAGE_SIZE); memcpy(buf, src, blk_len*FLASH_PAGE_SIZE); return USBD_OK; }
  1. 写入函数:
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) { Flash_ErasePages(FLASH_START_ADDR + blk_addr*FLASH_PAGE_SIZE, blk_len); Flash_Write(FLASH_START_ADDR + blk_addr*FLASH_PAGE_SIZE, buf, blk_len*FLASH_PAGE_SIZE); return USBD_OK; }

4.2 FATFS磁盘IO适配

修改user_diskio.c实现磁盘接口:

DSTATUS USER_status(BYTE pdrv) { return RES_OK; // 始终返回正常状态 } DRESULT USER_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count) { uint8_t *src = (uint8_t*)(FLASH_START_ADDR + sector*FATFS_SECTOR_SIZE); memcpy(buff, src, count*FATFS_SECTOR_SIZE); return RES_OK; } DRESULT USER_write(BYTE pdrv, const BYTE *buff, DWORD sector, UINT count) { Flash_ErasePages(FLASH_START_ADDR + sector*FLASH_PAGE_SIZE, count); Flash_Write(FLASH_START_ADDR + sector*FLASH_PAGE_SIZE, buff, count*FLASH_PAGE_SIZE); return RES_OK; }

5. 高级功能扩展与实践技巧

5.1 延长Flash寿命的策略

由于STM32内部Flash擦写次数有限(约10,000次),可采用以下方法延长使用寿命:

  • 写缓存:积累足够数据后再执行实际写入
  • 磨损均衡:动态映射逻辑块到不同物理位置
  • 差分更新:仅写入变化的数据部分

示例写缓存实现:

#define CACHE_SIZE 1024 uint8_t write_cache[CACHE_SIZE]; uint32_t cache_pos = 0; void Cache_Write(uint8_t *data, uint32_t len) { if(cache_pos + len > CACHE_SIZE) { Flash_Write(target_addr, write_cache, CACHE_SIZE); cache_pos = 0; } memcpy(write_cache + cache_pos, data, len); cache_pos += len; }

5.2 文件系统自动初始化

首次使用时自动格式化存储区:

FRESULT Mount_Filesystem(void) { FRESULT res = f_mount(&fatfs, "0:", 1); if(res == FR_NO_FILESYSTEM) { MKFS_PARM opt = {FM_FAT, 0, 0, 0, 0}; res = f_mkfs("0:", &opt, work, sizeof(work)); if(res == FR_OK) res = f_mount(&fatfs, "0:", 1); } return res; }

5.3 性能优化技巧

  • 批量操作:合并多个小写入为单次大块写入
  • 异步处理:使用DMA释放CPU资源
  • 缓存预读:提前加载可能访问的数据

6. 实际应用场景与限制

6.1 理想使用场景

  • 固件分发与更新:将固件文件拖入U盘即可完成设备升级
  • 小容量数据采集:存储传感器读数等小文件
  • 教学演示:直观展示USB协议和文件系统工作原理

6.2 已知限制与应对方案

  1. 容量限制:最大可用约64KB

    • 解决方案:使用外部SPI Flash扩展容量
  2. 写入速度慢:约50-100KB/s

    • 优化方案:实现写缓存和批量操作
  3. 无写保护:意外断电可能导致数据损坏

    • 改进方法:添加事务日志机制

在多次项目实践中,我发现最实用的技巧是在写入前先计算所需Flash页数,然后一次性擦除所有相关页,这比多次单独擦除效率高得多。另外,保持文件系统尽可能简单(如减少目录层级)可以显著提高小容量存储的利用率。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询