1. STM32 USB模拟U盘固件升级方案解析
在嵌入式设备量产和维护过程中,固件升级是个绕不开的痛点。传统方案要么需要专用烧录器,要么依赖串口IAP,对产线工人和技术支持人员都不够友好。最近我在STM32F103项目上实现了一套USB模拟U盘的升级方案,插上USB线就能当U盘用,直接把bin文件往里一扔就完成升级,实测效果相当不错。
这个方案的核心在于利用STM32的USB设备接口,通过MSC(Mass Storage Class)协议将自己伪装成U盘。当设备连接到电脑时,系统会识别出一个可移动磁盘,用户只需将编译好的固件.bin文件复制到这个"U盘"中,设备就会自动完成擦除、烧录和跳转的全过程。相比传统方案,这种操作方式有三大优势:
- 无需专用工具,任何电脑都能操作
- 传输速度快,实测可达60KB/s
- 操作直观,降低培训成本
2. 系统架构设计
2.1 存储空间规划
STM32F103的Flash通常为64K或128K,需要合理划分Bootloader和APP区域。建议采用以下配置:
| 地址范围 | 用途 | 大小 |
|---|---|---|
| 0x08000000 | Bootloader | 16KB |
| 0x08004000 | APP程序 | 剩余空间 |
| 0x0801F800 | 配置参数区 | 2KB |
注意:Bootloader大小需严格控制,建议使用-Os优化编译,并精简USB描述符
2.2 升级流程设计
完整升级流程包含以下关键步骤:
- 设备上电运行Bootloader
- USB初始化并枚举为MSC设备
- 电脑识别出可移动磁盘
- 用户复制.bin文件到"U盘"
- Bootloader检测到文件写入
- 擦除APP区域Flash
- 写入新固件
- 校验固件完整性
- 跳转到APP执行
3. 关键代码实现
3.1 USB MSC接口实现
核心代码在usbd_storage_if.c中,需要实现SCSI命令处理。重点处理WRITE_10命令:
c复制int8_t STORAGE_Write(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
// 检测到固件写入特定扇区时
if(blk_addr == FIRMWARE_START_SECTOR) {
firmware_size = blk_len * STORAGE_BLK_SIZ;
update_flag = 1;
last_write_time = HAL_GetTick();
}
// 实际数据写入缓冲区
memcpy(&write_buffer[blk_addr * STORAGE_BLK_SIZ], buf, blk_len * STORAGE_BLK_SIZ);
return USBD_OK;
}
3.2 Flash操作实现
STM32F103的Flash操作需要注意跨页处理:
c复制void FLASH_Program(uint32_t Address, uint8_t *Data, uint32_t Size)
{
FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_EOP | FLASH_FLAG_PGERR);
uint32_t offset = 0;
while(offset < Size) {
if((Address + offset) % FLASH_PAGE_SIZE == 0) {
FLASH_ErasePage(Address + offset);
}
FLASH_ProgramHalfWord(Address + offset, *(uint16_t*)(Data + offset));
offset += 2;
}
FLASH_Lock();
}
3.3 应用程序跳转
跳转到APP前必须做好环境清理:
c复制__asm void JumpToApp(uint32_t appAddress)
{
LDR SP, [R0] // 加载新堆栈指针
LDR PC, [R0, #4] // 加载复位向量
}
void System_JumpToApp(void)
{
__disable_irq(); // 关闭全局中断
HAL_RCC_DeInit(); // 复位时钟
HAL_DeInit(); // 复位所有外设
SCB->VTOR = APP_ADDRESS; // 设置中断向量表偏移
JumpToApp(APP_ADDRESS);
}
4. 实战经验与避坑指南
4.1 Windows写入缓存问题
Windows默认会启用写入缓存,可能导致文件看似复制完成但数据未真正写入。解决方案:
- 在复制完成后让用户手动弹出U盘
- 通过批处理执行sync命令
- 在设备端实现延时检测(建议5秒无新数据再触发升级)
4.2 文件传输完整性校验
建议在APP程序开头添加校验和:
c复制#define APP_CHECKSUM 0x12345678 // 由编译后自动计算填入
__attribute__((section(".app_header"))) const struct {
uint32_t stack_top;
void (*reset_handler)(void);
uint32_t checksum;
} app_header = { /* ... */ };
4.3 双保险进入机制
在APP程序中添加强制进入升级模式的逻辑:
c复制if(USER_BUTTON_PRESSED || *((volatile uint32_t*)FORCE_UPDATE_FLAG_ADDR) == 0x55AA55AA) {
NVIC_SystemReset(); // 软复位进入Bootloader
}
5. 性能优化技巧
- 使用DMA加速USB数据传输
- 采用双缓冲机制减少等待时间
- 对Flash擦除和写入操作进行交错处理
- 使用-Os优化级别编译Bootloader
- 精简USB描述符,移除不必要功能
6. 常见问题排查
6.1 电脑无法识别U盘
- 检查USB连接线质量
- 确认DP(D+)引脚上拉电阻正常
- 验证USB时钟配置(72MHz for Full Speed)
6.2 升级后程序不运行
- 检查APP程序中断向量表偏移设置
- 验证堆栈指针初始化值
- 确认跳转前关闭了所有外设中断
6.3 升级速度慢
- 优化Flash写入算法(按页对齐写入)
- 增加USB传输缓冲区大小
- 检查是否有其他进程占用USB带宽
这个方案经过多个量产项目验证,稳定性值得信赖。有个小技巧是在Bootloader中添加版本信息显示,可以通过特定文件名(如"version.txt")查询当前固件版本,方便现场技术支持。