别再花钱买U盘了!用STM32F103C8T6的64K内部Flash自制一个(CubeMX+USB MSC+FATFS保姆级教程)

发布时间:2026/6/11 1:21:02

别再花钱买U盘了!用STM32F103C8T6的64K内部Flash自制一个(CubeMX+USB MSC+FATFS保姆级教程) 用STM32F103C8T6打造极简U盘内部Flash的创意应用指南你是否曾经遇到过这样的场景手头急需一个U盘传输几兆的小文件却发现身边没有可用的存储设备或者作为一名电子爱好者总是对那些闲置的开发板感到可惜今天我们将探索如何将一块常见的STM32F103C8T6开发板变身为实用的迷你U盘充分利用其内部Flash的后64K空间。这不仅是一个有趣的DIY项目更能让你深入理解USB Mass Storage Class(MSC)协议和FAT文件系统的底层实现。1. 项目准备与环境搭建在开始之前我们需要明确几个关键点。STM32F103C8T6虽然标称只有64KB Flash但实际上大多数芯片都有128KB的物理空间。这个隐藏的64KB空间将成为我们自制U盘的存储介质。相比传统U盘方案这种设计有三大优势零成本利用现有开发板资源无需额外购买存储芯片低功耗内部Flash操作比外部SPI Flash更省电教学价值完整实现USB MSC协议栈和FAT文件系统硬件准备清单STM32F103C8T6开发板蓝桥杯板或最小系统板USB数据线带D/D-信号线ST-Link调试器可选用于固件烧录软件工具链STM32CubeMX最新版本Keil MDK或STM32CubeIDEUSB驱动通常系统自带提示确保开发板的USB接口电路完整部分廉价开发板可能省略了USB所需的22Ω阻抗匹配电阻。2. CubeMX工程配置详解启动STM32CubeMX创建一个新工程并选择STM32F103C8T6芯片。我们将逐步配置各个模块的关键参数。2.1 时钟树配置时钟是USB稳定工作的基础。按照以下步骤配置在Pinout Configuration选项卡中设置HSE为外部晶振通常8MHz切换到Clock Configuration标签页设置PLL倍频为9使系统时钟达到72MHz确保USB时钟为48MHz由PLL经过1.5分频得到关键参数验证表参数名称期望值检查方法SYSCLK72MHzClock Configuration页HCLK72MHz同上PCLK136MHz同上PCLK272MHz同上USB Clock48MHz必须精确2.2 USB外设配置在Connectivity部分启用USB设备并做如下设置模式选择Device Only在Middleware部分启用USB_DEVICE选择Class For FS IP为MSCMass Storage Class配置MSC_MEDIA_PACKET为1024与后续FATFS设置保持一致2.3 FATFS中间件配置在Middleware部分添加FATFS组件关键参数设置#define MAX_SS 1024 // 扇区大小 #define MIN_SS 1024 // 必须与MAX_SS相同 #define USE_LFN 1 // 启用长文件名支持注意SSSector Size值必须与MSC_MEDIA_PACKET完全一致否则会导致文件系统错误。3. 关键代码修改与实现生成基础工程后我们需要手动修改几个关键文件以实现Flash存储功能。3.1 Flash存储区域定义首先在usbd_storage_if.c文件中添加以下宏定义#define FLASH_SIZE 128 // 单位KB实际使用后64KB #define FLASH_PAGE_SIZE 0x400 // 1KB页大小 #define FLASH_PAGE_NBR 64 // 使用64页(64KB) #define FLASH_START_ADDR (0x08000000 ((FLASH_SIZE - FLASH_PAGE_NBR) * 1024))这段代码定义了从Flash末尾开始的64KB空间作为我们的U盘存储区。3.2 USB MSC接口实现修改usbd_storage_if.c中的三个核心函数容量获取函数- 告诉主机我们的磁盘有多大int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size) { *block_num FLASH_PAGE_NBR; // 总块数 *block_size FLASH_PAGE_SIZE; // 每块大小 return USBD_OK; }读取函数- 实现从Flash读取数据int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) { if(lun 0) { memcpy(buf, (uint8_t *)(FLASH_START_ADDR blk_addr*FLASH_PAGE_SIZE), blk_len*FLASH_PAGE_SIZE); return USBD_OK; } return USBD_FAIL; }写入函数- 实现向Flash写入数据包含擦除操作int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) { FLASH_EraseInitTypeDef erase; uint32_t PageError 0; HAL_FLASH_Unlock(); // 先擦除目标扇区 erase.TypeErase FLASH_TYPEERASE_PAGES; erase.PageAddress FLASH_START_ADDR blk_addr*FLASH_PAGE_SIZE; erase.NbPages blk_len; HAL_FLASHEx_Erase(erase, PageError); // 以字(32bit)为单位写入 for(uint16_t i0; iblk_len*FLASH_PAGE_SIZE; i4) { HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, FLASH_START_ADDR blk_addr*FLASH_PAGE_SIZE i, *(uint32_t *)(buf[i])); } HAL_FLASH_Lock(); return USBD_OK; }3.3 FATFS磁盘接口适配修改user_diskio.c文件实现FATFS所需的底层驱动磁盘状态检测DSTATUS USER_status(BYTE pdrv) { static DSTATUS stat STA_NOINIT; stat ~STA_NOINIT; // 标记磁盘已初始化 return stat; }磁盘读取DRESULT USER_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count) { uint8_t *src (uint8_t *)(FLASH_START_ADDR sector*FLASH_PAGE_SIZE); for(uint16_t i0; icount*FLASH_PAGE_SIZE; i) { buff[i] src[i]; } return RES_OK; }磁盘写入DRESULT USER_write(BYTE pdrv, const BYTE *buff, DWORD sector, UINT count) { // 重用之前实现的STORAGE_Write_FS逻辑 uint16_t i; HAL_FLASH_Unlock(); FLASH_EraseInitTypeDef f; f.TypeErase FLASH_TYPEERASE_PAGES; f.PageAddress FLASH_START_ADDR sector*FLASH_PAGE_SIZE; f.NbPages count; uint32_t PageError 0; HAL_FLASHEx_Erase(f, PageError); for(i0; icount*FLASH_PAGE_SIZE; i4) { HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, FLASH_START_ADDR sector*FLASH_PAGE_SIZE i, *(uint32_t *)(buff[i])); } HAL_FLASH_Lock(); return RES_OK; }磁盘控制DRESULT USER_ioctl(BYTE pdrv, BYTE cmd, void *buff) { switch(cmd) { case CTRL_SYNC: return RES_OK; case GET_SECTOR_SIZE: *(DWORD*)buff FLASH_PAGE_SIZE; break; case GET_SECTOR_COUNT: *(DWORD*)buff FLASH_PAGE_NBR; break; case GET_BLOCK_SIZE: *(DWORD*)buff 1; break; // 擦除块大小(以扇区为单位) default: return RES_PARERR; } return RES_OK; }4. 优化与实用技巧虽然基础功能已经实现但在实际使用中还需要考虑一些优化和可靠性问题。4.1 Flash寿命管理STM32F103的Flash通常有10,000次擦写寿命我们需要采取措施延长使用寿命写缓存实现一个RAM缓存累积到一定量再实际写入Flash磨损均衡在64KB空间内轮换使用不同区块减少擦除尽量在单次操作中写入更多数据示例写缓存实现#define CACHE_SIZE 1024 // 1KB缓存 uint8_t write_cache[CACHE_SIZE]; uint32_t cache_pos 0; void flush_cache(uint32_t sector) { if(cache_pos 0) { STORAGE_Write_FS(0, write_cache, sector, 1); cache_pos 0; } } void cache_write(uint8_t *data, uint32_t size, uint32_t sector) { if(cache_pos size CACHE_SIZE) { flush_cache(sector); } memcpy(write_cache[cache_pos], data, size); cache_pos size; }4.2 性能优化技巧内部Flash的写入速度较慢以下方法可以改善用户体验增大MSC_MEDIA_PACKET在CubeMX中设置为最大允许值需与Flash页大小匹配批量写入累积多个扇区数据后一次性写入后台擦除提前擦除下一个可能使用的扇区4.3 文件系统自动初始化在main函数中添加自动格式化逻辑当检测到未格式化的存储时自动执行FATFS_Status f_mount(USERFatFS, path, 1); if(FATFS_Status FR_NO_FILESYSTEM) { uint8_t work[FF_MAX_SS]; // 格式化缓冲区 f_mkfs(path, FM_FAT, 0, work, sizeof(work)); // 快速格式化 FATFS_Status f_mount(NULL, path, 0); // 卸载 FATFS_Status f_mount(USERFatFS, path, 1); // 重新挂载 } if(FATFS_Status FR_OK) { f_mkdir(0:/FW); // 创建固件目录 }5. 实际应用场景扩展这个自制的迷你U盘虽然容量有限但在特定场景下非常实用固件升级将新固件拖拽到U盘设备自动检测并更新配置存储保存设备配置文件方便在不同设备间迁移数据采集临时存储传感器数据通过USB快速导出教学演示直观展示USB MSC协议和文件系统工作原理对于需要更大容量的场景可以扩展使用外部SPI Flash或SD卡只需修改user_diskio.c中的底层驱动即可。这种架构设计体现了良好的分层思想使我们可以灵活更换存储介质而不影响上层应用。

相关新闻