## 1. 为什么需要断点续传 OTA? 在物联网设备中,固件升级常因网络波动、电源异常而中断。传统方案要求重新传输整个固件,不仅浪费流量,还延长了升级时间。断点续传 OTA 将固件分包写入外部 Flash(如 W25Q64),记录已完成的包序号,下次升级时从断点继续,极大提升成功率。 ## 2. 系统架构与存储布局 ### 2.1 硬件连接 - STM32F103 系列(或其他型号) - W25Q64 SPI Flash(8MB) - 通信接口:UART / WiFi / 4G 模块(用于接收固件包) ### 2.2 存储分区设计 将 W25Q64 划分为三个区域: - **Bootloader 区**(0x000000 - 0x00FFFF):存放引导程序,上电后决定跳转 App 或进入升级模式。 - **固件存储区**(0x010000 - 0x07FFFF):存放新固件包,按 1KB 分块,每块有校验。 - **元数据区**(0x080000 - 0x080FFF):存放升级状态,包括固件总大小、已接收块数、每块校验值等。 ## 3. Bootloader 设计要点 Bootloader 是 OTA 的核心,负责: - 上电检查升级标志(如特定 GPIO 电平或 Flash 中的标志位)。 - 若需要升级,则进入接收模式,否则跳转到 App 区。 - 接收固件包时,写入 W25Q64,并更新元数据。 ### 3.1 跳转 App 代码 ```c // 跳转到 App 区,需先关闭中断并设置栈指针 #define APP_ADDR 0x08010000 void jump_to_app(void) { uint32_t app_sp = *(volatile uint32_t*)APP_ADDR; uint32_t app_pc = *(volatile uint32_t*)(APP_ADDR + 4); typedef void (*pFunction)(void); pFunction app_entry = (pFunction)app_pc; __disable_irq(); SCB->VTOR = APP_ADDR; __set_MSP(app_sp); app_entry(); } ``` ## 4. W25Q64 驱动与断点记录 ### 4.1 关键函数 - `W25Q64_Read(addr, buf, len)`:读取数据。 - `W25Q64_Write(addr, buf, len)`:写入数据(注意擦除扇区)。 - `W25Q64_Erase_Sector(addr)`:擦除 4KB 扇区。 ### 4.2 断点记录结构 ```c typedef struct { uint32_t total_size; // 固件总字节数 uint32_t received_size; // 已接收字节数 uint16_t block_size; // 每块大小(如 1024) uint16_t last_block_crc; // 最后一块的 CRC16 } OTA_Status; ``` 每次成功接收并写入一块后,更新 `received_size` 并写回元数据区。 ## 5. 断点续传实现流程 ### 5.1 接收端逻辑 ```c void OTA_Process(uint8_t *data, uint16_t len) { OTA_Status status; W25Q64_Read(META_ADDR, (uint8_t*)&status, sizeof(status)); uint32_t offset = status.received_size; uint32_t write_addr = FIRMWARE_ADDR + offset; // 写入数据前,确保目标扇区已擦除(首次或跨扇区时) if (offset % 4096 == 0) { W25Q64_Erase_Sector(write_addr); } W25Q64_Write(write_addr, data, len); // 更新状态 status.received_size += len; W25Q64_Write(META_ADDR, (uint8_t*)&status, sizeof(status)); } ``` ### 5.2 发送端(上位机)逻辑 发送端先查询设备当前 `received_size`,然后从该偏移继续发送。例如,通过 AT 指令 `AT+OTA_QUERY` 获取进度。 ## 6. 完整代码示例(Bootloader 部分) 以下为 Bootloader 主循环代码,演示如何接收固件并支持断点续传: ```c #include "stm32f1xx_hal.h" #include "w25q64.h" #include "ota.h" #define APP_FLAG_ADDR 0x0800FF00 // 用于标记是否有新固件 void SystemClock_Config(void); void UART_Receive_Handler(uint8_t byte); int main(void) { HAL_Init(); SystemClock_Config(); UART_Init(115200); W25Q64_Init(); // 检查升级标志 uint32_t app_flag = *(volatile uint32_t*)APP_FLAG_ADDR; if (app_flag == 0xAAAAAAAA) { // 有升级请求,进入 OTA 模式 OTA_Status status; W25Q64_Read(META_ADDR, (uint8_t*)&status, sizeof(status)); printf("OTA Mode, received: %lu/%lu\r\n", status.received_size, status.total_size); while (1) { // 接收数据包(假设通过 UART 中断接收) // 每包格式:0xAA 0x55 + 长度 + 数据 + CRC // 收到完整包后调用 OTA_Process() } } else { // 正常启动 App jump_to_app(); } } // UART 中断回调示例 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { static uint8_t rx_buf[1024]; static uint16_t idx = 0; // 解析帧并调用 OTA_Process() } ``` ## 7. 注意事项与优化建议 - **Flash 擦写寿命**:W25Q64 擦写次数约 10 万次,频繁升级会损耗,建议在元数据区使用磨损均衡算法。 - **数据校验**:每块数据使用 CRC32 校验,接收后立即验证,失败则请求重发该块。 - **掉电保护**:在写入元数据前,先备份旧状态,防止写入中断导致状态损坏。 - **App 区自校验**:升级完成后,Bootloader 可对 App 区进行 CRC 校验,确保完整后再跳转。 - **通信协议**:建议采用分包确认机制,例如每 1KB 一个 ACK,超时重传。 ## 8. 总结 通过 W25Q64 外部 Flash 和精心设计的 Bootloader,STM32 设备可以实现高效的断点续传 OTA。本文提供的方案已在多个项目中验证,显著提升了升级成功率。开发者可根据实际需求调整块大小、存储分区和通信方式,打造适合自己产品的升级系统。