1. STM32 BootLoader设计概述
在嵌入式系统开发中,BootLoader是系统启动时最先执行的程序,负责硬件初始化、应用程序加载和固件更新等关键任务。对于STM32F103C8T6这类资源受限的单片机,一个高效可靠的BootLoader设计尤为重要。
本方案采用AB分区设计,将64KB Flash划分为:
- B区:20KB(0-19扇区)存放BootLoader程序
- A区:44KB(20-63扇区)存放应用程序
这种分区设计的主要考虑是:
- BootLoader功能相对固定,20KB空间足够实现OTA、IAP等核心功能
- 保留44KB给应用程序,满足大多数中等复杂度应用的需求
- 1KB的扇区大小便于擦除和写入管理
2. 关键功能实现细节
2.1 Flash分区与地址映射
在main.h中定义分区参数:
c复制#define STM32_FLASH_SADDR 0x08000000 // Flash起始地址
#define STM32_PAGE_SIZE 1024 // 扇区大小
#define STM32_PAGE_NUM 64 // 总扇区数
#define STM32_B_PAGE_NUM 20 // B区扇区数
#define STM32_A_PAGE_NUM (STM32_PAGE_NUM-STM32_B_PAGE_NUM) // A区扇区数
#define STM32_A_STAET_PAGE STM32_B_PAGE_NUM // A区起始扇区
#define STM32_A_SADDR (STM32_FLASH_SADDR + STM32_A_STAET_PAGE * STM32_PAGE_SIZE) // A区起始地址
注意:地址计算必须使用无符号运算,避免溢出。在实际项目中建议添加静态断言检查分区设置是否合理。
2.2 OTA标志管理
OTA标志存储在AT24C02 EEPROM中,结构体设计如下:
c复制#define OTA_SET_FLAG 0xAABB1122 // OTA标志魔数
typedef struct {
uint32_t OTA_Flag;
uint32_t APPlen[11]; // 各程序块长度
uint8_t OTA_ver[32]; // 版本号
} OTA_InfoCB;
标志读取函数实现:
c复制void AT24C02_ReadOTAInfo(void) {
memset(&OTA_Info, 0, sizeof(OTA_InfoCB));
AT24C02_ReadData(0, (uint8_t *)&OTA_Info, sizeof(OTA_InfoCB));
}
2.3 分区跳转机制
跳转A分区的核心代码如下:
c复制__asm void MSR_SP(uint32_t addr) {
MSR MSP, r0 // 设置主堆栈指针
BX r14 // 返回
}
void LOAD_A(uint32_t addr) {
if((*(uint32_t *)addr >= 0x20000000) && (*(uint32_t *)addr <= 0x20004FFF)) {
MSR_SP(*(uint32_t *)addr); // 设置SP
load_A = (load_a)(*(uint32_t *)(addr + 4)); // 获取复位函数地址
BootLoader_Clear(); // 复位外设
load_A(); // 跳转执行
} else {
Serial_Printf("跳转A分区失败\r\n");
}
}
关键点说明:
- 必须检查SP初始值是否在有效RAM范围内
- 跳转前需要复位所有使用过的外设
- 应用程序的向量表偏移需要设置为0x5000
3. 程序更新功能实现
3.1 OTA更新流程
- 检测OTA标志置位
- 从W25Q64读取程序数据
- 擦除A区Flash
- 分片写入新程序(1KB/片)
- 清除OTA标志
- 系统重启
核心代码片段:
c复制if(OTA_Info.OTA_Flag == OTA_SET_FLAG) {
BootStatus |= UPDATA_A_FLAG;
UpDataA.W25Q64_BlockNumber = 0;
// 在main循环中处理实际更新
if(BootStatus & UPDATA_A_FLAG) {
MyFLASH_EraseFlash(STM32_A_STAET_PAGE, STM32_A_PAGE_NUM);
// 分片写入逻辑...
NVIC_SystemReset();
}
}
3.2 串口IAP实现
采用Xmodem协议实现可靠传输:
-
协议帧格式:
- SOH(0x01) + 包编号 + ~包编号 + 128B数据 + CRC16
-
CRC16校验实现:
c复制uint16_t Xmodem_CRC16(uint8_t *data, uint16_t datalen) {
uint16_t crc = 0x0000;
while(datalen--) {
crc = (*data << 8) ^ crc;
for(uint8_t i=0; i<8; i++) {
crc = (crc & 0x8000) ? (crc << 1) ^ 0x1021 : (crc << 1);
}
data++;
}
return crc;
}
- 数据传输状态机:
c复制if(BootStatus & IAP_XMODEMD_FLAG) {
if((datalen==133) && (data[0]==0x01)) { // 数据包
uint16_t crc = Xmodem_CRC16(&data[3], 128);
if(crc == (data[131]<<8 | data[132])) {
Serial_Printf("\x06"); // ACK
// 存储数据...
} else {
Serial_Printf("\x15"); // NAK
}
} else if((datalen==1) && (data[0]==0x04)) { // EOT
Serial_Printf("\x06");
// 写入剩余数据...
NVIC_SystemReset();
}
}
4. 交互式命令行设计
4.1 命令菜单结构
c复制void BootLoader_Info(void) {
Serial_Printf("\r\n");
Serial_Printf("[1]擦除A区程序\r\n");
Serial_Printf("[2]串口IAP下载A区程序\r\n");
Serial_Printf("[3]设置OTA版本号\r\n");
Serial_Printf("[4]查询OTA版本号\r\n");
Serial_Printf("[5]向外部Flash下载程序\r\n");
Serial_Printf("[6]使用外部Flash内的程序\r\n");
Serial_Printf("[7]重启\r\n");
}
4.2 版本号管理
版本号格式:VER-1.0.0-2026/03/01-11:45
设置逻辑:
c复制if(BootStatus & SET_VERSION_FLAG) {
if(datalen == 26) {
if(sscanf((char*)data,"VER-%d.%d.%d-%d/%d/%d-%d:%d",
&temp,&temp,&temp,&temp,&temp,&temp,&temp,&temp)==8) {
memcpy(OTA_Info.OTA_ver, data, 26);
AT24C02_WriteOTAInfo();
Serial_Printf("设置版本号成功\r\n");
}
}
}
5. 关键问题与解决方案
5.1 Flash写入可靠性
- 写入前必须擦除整个扇区
- 数据长度必须是4的倍数(STM32 Flash写入要求)
- 建议添加校验机制,如写入后回读验证
5.2 中断处理
- 跳转前关闭所有中断:
c复制__disable_irq();
- 应用程序需要重新配置中断向量表:
c复制SCB->VTOR = FLASH_BASE | 0x5000;
5.3 外设冲突处理
BootLoader和应用程序可能使用相同外设,解决方案:
- BootLoader退出前复位所有使用过的外设
- 应用程序初始化时重新配置所需外设
- 避免在BootLoader中进行复杂的外设操作
6. 实际开发经验
-
调试技巧:
- 在关键流程添加串口打印
- 使用LED指示灯显示BootLoader状态
- 在跳转前添加延时,方便连接调试器
-
性能优化:
- 减少不必要的擦除操作
- 使用DMA加速数据传输
- 对关键函数使用-O3优化
-
安全性考虑:
- 添加固件签名验证
- 实现回滚机制
- 关键操作需要密码验证
-
常见问题:
- 向量表偏移设置错误导致HardFault
- 堆栈指针初始化不当导致内存错误
- 外设未完全复位导致应用程序异常
这个BootLoader方案在实际项目中已经过验证,支持多种更新方式,具有较高的可靠性。开发者可以根据具体需求调整分区大小、添加新功能或增强安全性。