别再花钱买U盘了!手把手教你用STM32F103C8T6的内部Flash自制一个(CubeMX+USB MSC+FATFS)
2026/6/11 9:24:12 网站建设 项目流程

用STM32F103C8T6打造专属可编程U盘:从硬件复用到数据存储实战

你是否曾为临时找不到U盘而烦恼?或是厌倦了市面上千篇一律的存储设备?今天,我们将一起探索如何用手中闲置的STM32F103C8T6开发板,打造一个独一无二的可编程U盘。这不仅是一次硬件复用的实践,更是一场嵌入式开发的深度探险。

1. 项目规划与核心优势

1.1 为什么选择STM32F103C8T6

STM32F103C8T6这颗经典的Cortex-M3芯片,以其极高的性价比和丰富的外设资源,成为DIY项目的理想选择:

  • 内置128KB Flash:其中后64KB可作为存储空间
  • USB 2.0全速接口:完美支持MSC(Mass Storage Class)协议
  • 广泛的开发生态:CubeMX工具链支持完善
  • 超低成本:开发板价格通常不到20元

1.2 自制U盘的独特价值

相比商业U盘,我们的方案具有不可替代的优势:

特性商业U盘STM32自制方案
成本中等极低(利用闲置开发板)
功能固定可编程,可扩展其他功能
分区标准完全自定义
寿命较长需注意Flash擦写次数
速度较快适中(全速USB 12Mbps)

提示:虽然Flash擦写次数有限(约1万次),但通过合理的磨损均衡算法可以大幅延长使用寿命

2. 硬件准备与CubeMX配置

2.1 所需硬件清单

  • STM32F103C8T6最小系统板(蓝色pill开发板)
  • USB转Micro-USB数据线
  • 8MHz晶振(开发板通常已集成)
  • ST-Link调试器(用于烧录程序)

2.2 CubeMX工程配置步骤

  1. 新建工程,选择STM32F103C8T6型号

  2. 时钟配置:

    // 外部晶振8MHz → PLL → 72MHz系统时钟 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  3. 启用必要外设:

    • USB Device → MSC模式
    • FATFS → 选择"Use USB disk"
    • 设置MAX_SS=1024MIN_SS=1024
    • 配置MSC_MEDIA_PACKET=1024
  4. 堆栈大小调整:

    • Heap Size: 0x600
    • Stack Size: 0x400

3. 关键代码实现与优化

3.1 Flash存储区划分

STM32F103C8T6的Flash地址空间如下:

#define FLASH_SIZE 128 // 单位KB #define FMC_SECTOR_SIZE 1024 // 字节 #define FLASH_PAGE_NBR 64 // 64KB用于存储 #define FLASH_START_ADDR (0x08000000 + ((FLASH_SIZE-FLASH_PAGE_NBR)*1024))

注意:实际使用中建议保留至少16KB空间用于程序存储,避免意外擦除固件

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 = FMC_SECTOR_SIZE; return USBD_OK; } // 读取数据 int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len) { memcpy(buf, (uint8_t*)(FLASH_START_ADDR + blk_addr*FMC_SECTOR_SIZE), blk_len*FMC_SECTOR_SIZE); return USBD_OK; } // 写入数据(含擦除操作) 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*FMC_SECTOR_SIZE; erase.NbPages = blk_len; HAL_FLASHEx_Erase(&erase, &PageError); for(uint32_t i=0; i<blk_len*FMC_SECTOR_SIZE; i+=4) { HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, FLASH_START_ADDR + blk_addr*FMC_SECTOR_SIZE + i, *(uint32_t*)(&buf[i])); } HAL_FLASH_Lock(); return USBD_OK; }

3.3 FATFS文件系统集成

user_diskio.c中实现磁盘IO接口:

// 磁盘状态检查 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*FMC_SECTOR_SIZE); memcpy(buff, src, count*FMC_SECTOR_SIZE); return RES_OK; } // 磁盘写入 DRESULT USER_write(BYTE pdrv, const BYTE *buff, DWORD sector, UINT count) { // 实现与STORAGE_Write_FS类似的擦写逻辑 // ... return RES_OK; }

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

4.1 自动格式化检测

在main函数中添加初始化检测,避免每次都需要手动格式化:

FATFS_Status = f_mount(&USERFatFS, path, 1); if(FATFS_Status == FR_NO_FILESYSTEM) { uint8_t work[FMC_SECTOR_SIZE]; MKFS_PARM opt = {FM_FAT, 0, 0, 0, 0}; f_mkfs("0:", &opt, work, sizeof(work)); f_mount(NULL, path, 0); // 卸载 FATFS_Status = f_mount(&USERFatFS, path, 1); // 重新挂载 } f_mkdir("0:/Data"); // 创建默认目录

4.2 延长Flash寿命的策略

  • 写入缓存:积累到整页数据后再写入
  • 磨损均衡:动态映射逻辑地址到物理地址
  • 坏块管理:标记已损坏的Flash区块
  • 数据压缩:减少实际写入量

示例磨损均衡表结构:

逻辑块物理块擦写次数
01256
124102
.........

4.3 复合设备功能实现

利用STM32的多功能特性,可以同时实现:

  • USB串口调试:与U盘功能共存
  • 数据加密:透明加密存储内容
  • 状态指示灯:通过GPIO控制LED
  • 自动备份:监测特定文件变化
// 在main循环中添加额外功能 while(1) { if(HAL_GPIO_ReadPin(BUTTON_GPIO_Port, BUTTON_Pin) == GPIO_PIN_RESET) { backup_important_files(); } // 其他后台任务... }

5. 性能优化与故障排查

5.1 速度瓶颈分析

通过逻辑分析仪测量的典型时序:

操作时间(ms)
擦除4KB40
写入256B2
读取1KB0.5

优化建议:

  • 增大MSC_MEDIA_PACKET大小(最大1024)
  • 使用RAM缓存减少小数据写入
  • 合理组织文件结构,减少碎片

5.2 常见问题解决方案

问题1:电脑无法识别设备

  • 检查USB数据线质量
  • 确认DP(D+)引脚有1.5kΩ上拉电阻
  • 验证USB描述符配置正确

问题2:文件写入后读取错误

  • 确保每次写入前正确擦除
  • 检查Flash编程对齐(必须32位写入)
  • 验证电源稳定性(尤其USB供电时)

问题3:频繁插拔后设备失效

  • 实现安全弹出机制
  • 在USB断开回调中同步文件系统
  • 添加写保护开关
// USB断开回调示例 void HAL_PCD_DisconnectCallback(PCD_HandleTypeDef *hpcd) { f_sync(&USERFile); // 同步文件 save_current_state(); // 保存状态 }

6. 项目进阶方向

6.1 外扩存储方案

当内置Flash不足时,可以考虑:

  • SPI Flash:W25Q系列,成本低容量大
  • SD卡:通过SDIO接口,支持热插拔
  • FRAM:无限擦写次数,适合频繁更新数据

接线示例(SPI Flash):

STM32 W25Q64 PA5 CLK PA6 MISO PA7 MOSI PA4 CS

6.2 固件升级系统

利用自制U盘实现自更新:

  1. 检测特定标志文件(如UPDATE.FLG)
  2. 验证新固件(BIN文件)的完整性
  3. 跳转到系统存储器执行内置Bootloader
  4. 通过USB DFU模式完成更新
// 固件更新检测 if(f_stat("0:/FW/update.bin", &fileInfo) == FR_OK) { if(verify_firmware("0:/FW/update.bin")) { jump_to_bootloader(); } }

6.3 数据安全增强

  • AES加密:透明加密文件内容
  • 访问控制:密码保护特定文件
  • 数字签名:验证数据完整性
  • 隐藏分区:通过特殊指令访问

加密写入示例:

void encrypted_write(uint32_t addr, uint8_t *data, uint32_t len) { uint8_t encrypted[256]; AES128_ECB_encrypt(data, key, encrypted); HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr, *(uint32_t*)encrypted); }

通过这个项目,我们不仅获得了一个实用的U盘替代方案,更重要的是掌握了STM32的Flash操作、USB设备开发和FATFS文件系统集成等核心技能。在实际应用中,建议根据具体需求选择适当的存储介质和功能组合,平衡性能、成本和可靠性。

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

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

立即咨询