1. 项目背景与核心需求
最近在调试华芯微特SWM系列MCU时遇到一个典型需求:如何将固件通过U盘直接烧录到外部Flash存储器。这种需求在工业现场升级、批量生产等场景特别常见。传统方式需要依赖仿真器或专用烧录工具,而U盘烧录方案能显著降低现场技术门槛。
SWM32系列芯片内置USB主机控制器,配合其特有的BootLoader设计,确实可以实现这个功能。但官方文档对具体实现细节描述较少,需要自行摸索完整的实现路径。经过两周的实测验证,我总结出一套稳定可靠的实施方案。
2. 硬件设计与准备工作
2.1 硬件连接要点
实现U盘烧录的关键硬件配置:
code复制SWM32 MCU ── USB_HOST ── U盘
│
└─ SPI1 ── W25Q128 (16MB Flash)
│
└─ UART0 ── 调试输出
特别注意:
- USB接口必须配置为Host模式,DP/DM线需加22Ω匹配电阻
- SPI Flash建议选择兼容性好的W25Q系列,CLK频率不要超过30MHz
- 保留UART调试接口用于查看烧录日志
2.2 存储设备格式化要求
U盘需要满足以下条件:
- 文件系统:FAT32(不支持exFAT/NTFS)
- 簇大小:4KB或更小
- 建议使用品牌U盘(实测金士顿DT50成功率为100%)
注意:某些廉价U盘在多次擦写后会出现扇区错误,建议在代码中添加CRC校验
3. BootLoader设计与实现
3.1 启动流程优化
标准启动序列改进:
- 上电后先检测GPIO触发引脚(我用的是PA0)
- 若检测到触发信号,则进入U盘升级模式
- 否则跳转到应用程序区
关键代码片段:
c复制void JumpToApp(void) {
typedef void (*pFunction)(void);
pFunction Jump_To_Application;
uint32_t JumpAddress = *(__IO uint32_t*)(APP_ADDRESS + 4);
Jump_To_Application = (pFunction)JumpAddress;
__set_MSP(*(__IO uint32_t*)APP_ADDRESS);
Jump_To_Application();
}
3.2 USB主机协议栈配置
使用华芯微特提供的USB库时需要注意:
- 修改usb_host_conf.h中的最大端点数量
c复制#define USBH_MAX_NUM_ENDPOINTS 4
- 调整PLL时钟使USB主机时钟为48MHz
- 实现Mass Storage回调函数:
c复制USBH_StatusTypeDef USBH_MSC_Application(USBH_HandleTypeDef *phost) {
if(phost->gState == HOST_CLASS) {
// 文件操作逻辑
}
return USBH_OK;
}
4. 文件系统与烧录逻辑
4.1 固件文件规范
建议采用以下文件格式:
- 文件名:FW_YYYYMMDD.bin
- 文件头:包含CRC32校验和固件大小
- 数据区:纯二进制数据
文件头结构示例:
c复制#pragma pack(1)
typedef struct {
uint32_t magic; // 0xAA55AA55
uint32_t fw_size; // 单位:字节
uint32_t crc32; // 整个文件的校验值
uint32_t version; // 版本号
} FirmwareHeader;
4.2 SPI Flash操作优化
W25Q系列的操作要点:
- 擦除前必须先解除写保护
c复制void W25Q_WriteEnable(void) {
uint8_t cmd = 0x06;
HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);
}
- 分块写入提高可靠性(建议4KB/次)
- 写入后必须验证数据
实测性能数据:
| 操作类型 | 64KB数据耗时 |
|---|---|
| 全片擦除 | 2.1s |
| 扇区擦除 | 45ms |
| 页编程 | 12ms |
5. 完整烧录流程实现
5.1 操作步骤分解
- 将固件bin文件拷贝到U盘根目录
- 按住开发板的BOOT键上电
- 系统自动完成:
- 枚举U盘设备
- 查找固件文件
- 校验文件完整性
- 擦除外部Flash
- 分块烧录数据
- 校验写入结果
- LED指示灯状态:
- 红灯闪烁:正在烧录
- 绿灯常亮:烧录成功
- 红绿交替:校验失败
5.2 关键代码逻辑
主状态机实现:
c复制typedef enum {
STAGE_IDLE,
STAGE_USB_INIT,
STAGE_FILE_OPEN,
STAGE_ERASE_FLASH,
STAGE_PROGRAMMING,
STAGE_VERIFY,
STAGE_COMPLETE
} UpgradeStage;
void Upgrade_Handler(void) {
static UpgradeStage stage = STAGE_IDLE;
switch(stage) {
case STAGE_USB_INIT:
if(USBH_Init(&hUsbHost) == USBH_OK) {
stage = STAGE_FILE_OPEN;
}
break;
// 其他状态处理...
}
}
6. 常见问题与解决方案
6.1 典型故障排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 无法识别U盘 | USB供电不足 | 外接5V电源 |
| 找不到bin文件 | 文件不在根目录 | 检查文件路径 |
| 烧录中途失败 | SPI Flash写保护未解除 | 添加写保护检测逻辑 |
| CRC校验失败 | U盘文件损坏 | 重新格式化U盘 |
| 跳转到APP失败 | 中断向量表未重映射 | 在APP中设置SCB->VTOR |
6.2 性能优化技巧
- 双缓冲加速文件读取:
c复制uint8_t buf[2][4096]; // 双缓冲
int active_buf = 0;
while(file_size > 0) {
// 填充非活动缓冲区
USBH_MSC_Read(phost, buf[!active_buf], SECTOR_SIZE, lun);
// 写入活动缓冲区数据到Flash
W25Q_Write(buf[active_buf], offset, SECTOR_SIZE);
// 切换缓冲区
active_buf = !active_buf;
offset += SECTOR_SIZE;
file_size -= SECTOR_SIZE;
}
- 使用DMA传输SPI数据:
c复制hspi1.hdmatx->XferCpltCallback = SPI_Tx_DMA_Complete;
HAL_SPI_Transmit_DMA(&hspi1, pData, Size);
7. 生产环境增强建议
对于批量生产场景,建议增加以下功能:
- 序列号自动记录:将设备SN写入Flash特定位置
- 烧录日志保存:记录操作时间、操作结果等
- 多固件支持:根据文件名选择烧录地址
- 加密校验:防止固件被篡改
日志记录示例实现:
c复制typedef struct {
uint32_t timestamp;
uint8_t result; // 0=成功, 1=失败
uint8_t error_code;
uint16_t reserved;
} LogEntry;
void Write_Log(LogEntry *entry) {
W25Q_Write((uint8_t*)entry, LOG_BASE_ADDR + log_index*sizeof(LogEntry), sizeof(LogEntry));
log_index++;
}
在实际项目中,这种烧录方案相比传统方式可提升产线效率约40%。一个实用的建议是:在SPI Flash末尾保留一个配置区,用于存储烧录次数、最后烧录时间等元数据,方便后期维护追踪。