设计实践38:嵌入式产品的OTA功能实现诀窍
嵌入式设备软件开发里必备的一项绝技就是给你的产品软件里设计好OTA在线升级功能,方便你后续开发中能非常便利的进行软件维护、升级迭代,因此把OTA功能设计好是我们做嵌入式开发的一项必备技能。
OTA(Over-The-Air )在线升级功能通常是通过Bootloader程序和固件分区管理来实现的。以下是其实现的核心步骤及关键技术:
1. 硬件基础
《存储器分区》
单片机的Flash存储器需划分为 Bootloader区 和 应用区(甚至多分区备份)。
- Bootloader区:存放引导程序,负责固件更新和跳转,需确保不被覆盖。
- 应用区:存放主程序,可被擦写更新。
- 备份区(可选):存储临时固件或旧版本,用于容错回滚。
《通信接口》
通过串口(UART)、SPI、I2C、以太网、Wi-Fi、蓝牙等接收新固件数据。
2. 核心流程
步骤1:触发升级
- 外部指令触发(如服务器推送、本地按键)。
- 主程序检测到升级请求后,跳转至Bootloader。
步骤2:数据传输
- Bootloader通过通信接口接收新固件数据,并存储到 临时存储区(如Flash备份区或外部EEPROM)。
- 数据需分块传输,并校验完整性(CRC、MD5、SHA等)。
步骤3:固件验证
- 校验固件合法性(如数字签名、版本号)。
- 防止恶意固件注入(需加密或安全启动机制)。
步骤4:擦除与写入
- 擦除目标应用区的Flash扇区。
- 将新固件从临时区写入应用区(需按Flash页操作)。
步骤5:跳转执行
- 复位或软重启后,Bootloader检查新固件有效性。
- 若验证通过,跳转至新固件入口地址;失败则回滚或报警。
3. 关键技术
(1) Bootloader设计
- 最小化代码:占用少量Flash,仅实现固件接收、校验、擦写和跳转。
- 通信协议:定义数据包格式(如帧头、长度、校验、结束符)。
- 中断处理:升级时需暂停或重定向中断向量表。
(2) Flash操作
- 扇区管理:按Flash物理扇区擦除(如STM32的Sector Erase)。
- IAP(In-Application Programming):允许程序运行时修改Flash,需关闭全局中断。
(3) 容错机制
- 双备份(A/B分区):
保留旧版本固件,新固件异常时自动回退(需额外Flash空间)。
- 看门狗(Watchdog):防止升级过程卡死。
- 断电保护:写入前校验电压,或使用非易失性存储暂存状态。
(4) 安全机制
- 加密传输:AES等加密新固件,防止窃取。
- 签名验证:RSA/ECC验证固件来源合法性。
- 防回滚攻击:版本号强制递增,避免降级漏洞。
4. 典型实现方案
方案1:基础串口升级(UART、RS232、RS485、RS422)
基本流程:
1. 主程序收到升级指令,通过串口接收HEX/BIN文件。
2. 将数据缓存至RAM或外部Flash。
3. 跳转至Bootloader,擦除应用区并写入新固件。
4. 重启运行新程序。
方案2:无线OTA(如Wi-Fi)
基本流程:
1. 设备连接服务器,下载加密固件至外部Flash。
2. 主程序校验签名后,触发Bootloader更新。
3. 采用双分区切换(如ESP32的OTA机制)。-
5. 开发工具与库
- STM32:通过IAP库实现,或使用CubeProgrammer工具链。
- ESP32:原生支持双OTA分区和HTTPS固件下载。
- 开源Bootloader:
- OpenBLT(适用于ARM Cortex-M)。
- MCUBoot(支持Zephyr/Mynewt等RTOS)。
---
6. 开发注意事项
- 资源限制:Bootloader需精简,避免占用过多Flash/RAM。
- 时序控制:Flash擦写耗时较长,需合理设计超时机制。
- 兼容性:固件需适配硬件版本(如不同型号引脚变更)。
- 测试验证:需模拟断电、数据错误等异常场景。
7.实战案例:STM32的IAP流程
1. 主程序接收到升级指令后,通过UART/USB接收新固件至外部Flash。
2. 调用IAP函数跳转到Bootloader(位于0x08000000)。
3. Bootloader擦除主程序区(如0x08008000开始的扇区)。
4. 从外部Flash复制新固件到主程序区。
5. 校验CRC,若通过则跳转到0x08008000执行新程序。
下面是代码框架:
// bootloader.c
#include "stm32f4xx.h"
#include "crc.h" // CRC校验库
#include "flash.h" // Flash操作库
#define APP_ADDR 0x08008000 // 应用程序起始地址(需在Linker Script中配置)
#define OTA_BUFFER 0x08040000 // OTA临时存储区地址
// 应用程序有效性检查
int is_app_valid(uint32_t app_addr) {
// 检查栈顶指针是否在RAM范围内
uint32_t stack_ptr = *(volatile uint32_t*)app_addr;
if (stack_ptr < SRAM_BASE || stack_ptr > (SRAM_BASE + SRAM_SIZE)) {
return 0;
}
// 检查复位向量是否合法(假设合法地址 >= 0x08000000)
uint32_t reset_handler = *(volatile uint32_t*)(app_addr + 4);
if (reset_handler < FLASH_BASE) {
return 0;
}
return 1;
}
// 跳转到应用程序
void jump_to_app(uint32_t app_addr) {
typedef void (*app_entry)(void);
app_entry start_app = (app_entry)(*(volatile uint32_t*)(app_addr + 4));
// 关闭所有中断
__disable_irq();
// 重置向量表偏移(STM32专用)
SCB->VTOR = app_addr;
// 设置栈指针并跳转
__set_MSP(*(volatile uint32_t*)app_addr);
start_app();
}
// OTA固件更新流程
void ota_update(void) {
// 1. 从外部Flash/通信接口读取新固件到OTA_BUFFER
// ...
// 2. 验证固件(示例:CRC32校验)
uint32_t received_crc = read_crc_from_packet(); // 假设从数据包获取CRC
uint32_t calculated_crc = crc32((void*)OTA_BUFFER, firmware_size);
if (received_crc != calculated_crc) {
// 校验失败,丢弃固件
return;
}
// 3. 擦除应用程序区并写入新固件
flash_erase(APP_ADDR, firmware_size);
flash_write(APP_ADDR, (uint8_t*)OTA_BUFFER, firmware_size);
// 4. 验证写入是否成功
if (memcmp((void*)APP_ADDR, (void*)OTA_BUFFER, firmware_size) != 0) {
// 写入失败,触发回滚
return;
}
// 5. 更新成功,重启设备
NVIC_SystemReset();
}
int main(void) {
// 初始化硬件(UART、Flash、CRC等)
init_hardware();
// 检查是否需要OTA(如通过GPIO/UART指令)
if (check_ota_request()) {
ota_update();
}
// 检查应用程序是否有效
if (is_app_valid(APP_ADDR)) {
jump_to_app(APP_ADDR);
} else {
// 进入固件下载模式
enter_dfu_mode();
}
// 若所有操作失败,进入死循环
while(1);
}
通过合理设计Bootloader、存储分区和安全机制,单片机可实现在线升级功能,显著提升设备的可维护性和灵活性。
欢迎朋友们关注、分享转发,请注明文章来源,尊重原创,拒绝抄袭!