## 为什么需要自定义 Bootloader 在产品量产或现场维护时,通过JTAG/SWD烧录固件既不方便也不现实。自定义Bootloader允许设备通过串口、CAN或网络等接口接收新固件,并写入内部Flash,实现IAP升级。这不仅能降低维护成本,还能支持远程升级,是产品化必备技术。 ## 核心原理:Flash 分区与跳转 STM32内部Flash通常分为两个区域: - **Bootloader区**:存放引导程序,起始地址 `0x08000000`,大小可设为16KB或32KB。 - **App区**:存放用户应用程序,起始地址 `0x08004000`(若Bootloader为16KB),大小根据Flash容量而定。 系统上电后,CPU从 `0x08000000` 执行Bootloader。Bootloader判断是否需要升级:若需要,则接收固件并写入App区;否则直接跳转到App区执行。 跳转的关键步骤: 1. 检查App区首字是否为合法栈顶地址(通常为 `0x20000000` 到RAM末尾之间的值)。 2. 设置主堆栈指针(MSP)为App的栈顶地址。 3. 将函数指针指向App的复位向量,并跳转执行。 ## 串口通信协议设计 为了可靠传输固件,自定义简单协议: - **帧格式**:`帧头(0xAA55) + 命令(1字节) + 长度(2字节) + 数据(长度字节) + CRC16(2字节)` - **命令定义**: - `0x01`:握手,获取版本信息 - `0x02`:擦除App区 - `0x03`:写入数据(每帧最多256字节) - `0x04`:升级完成,校验并跳转 CRC16采用标准多项式 `0x8005`,保证数据传输完整性。 ## 配置步骤 1. **创建工程**:使用STM32CubeMX生成基础工程,选择对应型号(如STM32F103C8),配置USART1(波特率115200)、LED指示灯、按键输入。 2. **设置Flash起始地址**:在工程选项中,将IROM1起始地址改为 `0x08004000`,大小设为 `0x1C000`(若Flash为64KB)。这是App工程的设置,Bootloader工程保持默认。 3. **编写Bootloader代码**:实现串口接收、协议解析、Flash擦写和跳转逻辑。 4. **编写App测试程序**:在App中实现一个简单的LED闪烁,并打印版本号,验证跳转是否成功。 ## 完整代码示例 以下为Bootloader核心代码(基于STM32 HAL库): ```c /* bootloader.c */ #include "main.h" #include "flash_if.h" #include "crc16.h" #define APP_START_ADDR 0x08004000 #define APP_MAX_SIZE 0x1C000 // 112KB,根据实际调整 uint8_t rx_buffer[256]; uint32_t app_write_addr; void JumpToApp(void) { uint32_t app_stack = *(volatile uint32_t*)APP_START_ADDR; if ((app_stack & 0x2FFE0000) != 0x20000000) { // 栈顶地址不合法,停留在Bootloader return; } void (*app_reset)(void) = (void (*)(void))(*(volatile uint32_t*)(APP_START_ADDR + 4)); __set_MSP(app_stack); app_reset(); } void ProcessCommand(uint8_t cmd, uint8_t *data, uint16_t len) { switch (cmd) { case 0x01: // 握手 SendResponse(0x01, "BOOT_V1.0", 9); break; case 0x02: // 擦除 FLASH_If_Erase(APP_START_ADDR, APP_MAX_SIZE); app_write_addr = APP_START_ADDR; SendResponse(0x02, NULL, 0); break; case 0x03: // 写入 if (FLASH_If_Write(app_write_addr, data, len) == 0) { app_write_addr += len; SendResponse(0x03, NULL, 0); } else { SendResponse(0x03, "ERR", 3); } break; case 0x04: // 完成 SendResponse(0x04, NULL, 0); JumpToApp(); break; default: SendResponse(0xFF, NULL, 0); break; } } void USART_RX_IRQHandler(void) { // 接收一帧数据,解析并调用ProcessCommand // 具体实现略,需处理帧头、长度、CRC校验 } ``` App工程中,需在 `main.c` 开头添加: ```c /* app_main.c */ #include "stm32f1xx_hal.h" void SystemClock_Config(void); int main(void) { HAL_Init(); SystemClock_Config(); // 初始化LED、串口等 while (1) { HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); HAL_Delay(500); printf("App running...\r\n"); } } ``` 注意:App工程中必须将中断向量表重映射到 `0x08004000`,在 `SystemInit` 或 `main` 开头执行: ```c SCB->VTOR = APP_START_ADDR; ``` ## 注意事项 - **Flash擦写时序**:擦除前必须关闭全局中断,避免中断向量表冲突。 - **串口缓冲区**:建议使用环形缓冲区,避免数据丢失。 - **校验机制**:除了CRC,可在升级完成后对App区进行整体校验(如累加和)。 - **看门狗**:升级过程中喂狗,防止复位。 - **App起始地址对齐**:通常为0x200的倍数,确保Flash页对齐。 - **测试环境**:先用USB转TTL模块连接PC,通过串口助手发送固件文件(需将生成的bin文件按协议分包发送)。 ## 总结 本文从原理到实现,详细介绍了基于STM32的串口IAP Bootloader设计。通过合理的Flash分区、可靠的通信协议和严谨的跳转逻辑,可以实现稳定的固件升级功能。实际产品中,可进一步扩展为OTA(通过无线模块)或加密升级,提升安全性和便利性。希望本文能帮助你快速上手,打造属于自己的Bootloader。