前言
做嵌入式产品,迟早要面对固件升级的问题。设备卖出去了,发现bug怎么办?要加新功能怎么办?总不能让客户把设备寄回来重新烧录吧。
之前给一个水下机器人项目做固件升级方案,从Bootloader设计到OTA自动升级折腾了快两个月。今天把整个技术方案整理出来,包括BIOS/Bootloader的工作原理、Flash分区设计、升级协议实现,希望能给同样在做这块的朋友一些参考。
BIOS与Bootloader的概念
先厘清几个容易混淆的概念。
PC上的BIOS
PC上的BIOS(Basic Input/Output System)是固化在主板ROM芯片里的程序,负责:
- 上电自检(POST)
- 初始化硬件
- 从存储设备加载操作系统
现代PC已经用UEFI取代了传统BIOS,但"BIOS"这个叫法还是保留了下来。
嵌入式中的Bootloader
嵌入式系统里,我们通常说的是Bootloader,功能类似但更简化:
- 系统上电后第一个运行的程序
- 初始化最基本的硬件(时钟、内存、串口等)
- 决定是进入升级模式还是跳转到用户程序(APP)
- 如果需要升级,负责接收新固件并写入Flash
可以把Bootloader理解为嵌入式设备的"迷你BIOS"。
┌─────────────────────────────────────────────────────────┐
│ 系统上电 │
└─────────────────────────┬───────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────┐
│ Bootloader启动 │
│ 1. 初始化时钟、GPIO、串口 │
│ 2. 检查升级标志/升级按键 │
└─────────────────────────┬───────────────────────────────┘
│
┌─────────────┴─────────────┐
▼ ▼
┌────────────────┐ ┌────────────────┐
│ 需要升级? │ │ 正常启动 │
│ YES │ │ NO │
└───────┬────────┘ └───────┬────────┘
▼ ▼
┌────────────────┐ ┌────────────────┐
│ 进入升级模式 │ │ 跳转到APP │
│ 接收新固件 │ │ │
│ 写入Flash │ │ │
└────────────────┘ └────────────────┘
Flash分区设计
要实现可靠的固件升级,首先要规划好Flash的分区。以STM32F407(1MB Flash)为例:
基础双区方案
Flash 地址空间 (1MB)
┌──────────────────────────────────────┐ 0x0800 0000
│ Bootloader (32KB) │
│ (Sector 0, 不可擦除) │
├──────────────────────────────────────┤ 0x0800 8000
│ APP 区域 (480KB) │
│ (Sector 2-6) │
│ 用户应用程序 │
├──────────────────────────────────────┤ 0x0807 8000
│ Download 区域 (480KB) │
│ (Sector 7-11) │
│ 新固件临时存放区 │
├──────────────────────────────────────┤ 0x080E 8000
│ 参数区 (16KB) │
│ 系统配置、升级标志 │
└──────────────────────────────────────┘ 0x080F FFFF
为什么要分成APP区和Download区?这是为了断电保护。
升级过程中如果断电:
- 如果正在下载新固件到Download区 → APP区完好,重启后继续使用旧版本
- 如果正在从Download区拷贝到APP区 → 下次启动检测到APP损坏,重新拷贝
分区定义代码
c
/**
* flash_partition.h - Flash分区定义
*/
#ifndef __FLASH_PARTITION_H
#define __FLASH_PARTITION_H
#include <stdint.h>
// Flash基地址
#define FLASH_BASE_ADDR 0x08000000
// Bootloader区
#define BOOT_START_ADDR 0x08000000
#define BOOT_SIZE (32 * 1024) // 32KB
#define BOOT_END_ADDR (BOOT_START_ADDR + BOOT_SIZE - 1)
// APP区
#define APP_START_ADDR 0x08008000
#define APP_SIZE (480 * 1024) // 480KB
#define APP_END_ADDR (APP_START_ADDR + APP_SIZE - 1)
// Download区(新固件暂存)
#define DOWNLOAD_START_ADDR 0x08078000
#define DOWNLOAD_SIZE (480 * 1024) // 480KB
#define DOWNLOAD_END_ADDR (DOWNLOAD_START_ADDR + DOWNLOAD_SIZE - 1)
// 参数区
#define PARAM_START_ADDR 0x080E8000
#define PARAM_SIZE (16 * 1024) // 16KB
// APP中断向量表偏移
#define APP_VECTOR_OFFSET (APP_START_ADDR - FLASH_BASE_ADDR)
// 固件头部魔数
#define FIRMWARE_MAGIC 0x46495257 // "FIRW"
// 升级标志
#define UPGRADE_FLAG_NONE 0xFFFFFFFF // 无升级
#define UPGRADE_FLAG_REQUEST 0x55AA55AA // 请求升级
#define UPGRADE_FLAG_READY 0xAA55AA55 // 新固件已就绪
#define UPGRADE_FLAG_COMPLETE 0x12345678 // 升级完成
#pragma pack(1)
// 固件头部结构(放在固件最前面)
typedef struct {
uint32_t magic; // 魔数 FIRMWARE_MAGIC
uint32_t version; // 版本号 如 0x00010203 = v1.2.3
uint32_t size; // 固件大小(不含头部)
uint32_t crc32; // 固件CRC32校验
uint32_t timestamp; // 编译时间戳
uint8_t hw_version; // 硬件版本要求
uint8_t reserved[11]; // 预留
} firmware_header_t;
// 升级状态结构(存在参数区)
typedef struct {
uint32_t upgrade_flag; // 升级标志
uint32_t new_fw_size; // 新固件大小
uint32_t new_fw_crc; // 新固件CRC
uint32_t new_fw_version; // 新固件版本
uint32_t download_offset; // 已下载字节数
uint32_t retry_count; // 重试次数
} upgrade_state_t;
#pragma pack()
#endif
Bootloader核心实现
启动流程
c
/**
* bootloader_main.c - Bootloader主程序
*/
#include "flash_partition.h"
#include "flash_driver.h"
#include "uart_driver.h"
#include "crc32.h"
// 从参数区读取升级状态
static upgrade_state_t g_upgrade_state;
// 函数指针类型,用于跳转到APP
typedef void (*app_func_t)(void);
/**
* 检查APP固件是否有效
*/
static int check_app_valid(uint32_t app_addr)
{
firmware_header_t *header = (firmware_header_t *)app_addr;
// 1. 检查魔数
if (header->magic != FIRMWARE_MAGIC) {
uart_printf("[BOOT] APP magic invalid: 0x%08X\r\n", header->magic);
return 0;
}
// 2. 检查大小合理性
if (header->size == 0 || header->size > APP_SIZE - sizeof(firmware_header_t)) {
uart_printf("[BOOT] APP size invalid: %u\r\n", header->size);
return 0;
}
// 3. 计算CRC校验
uint8_t *data = (uint8_t *)(app_addr + sizeof(firmware_header_t));
uint32_t calc_crc = crc32_calculate(data, header->size);
if (calc_crc != header->crc32) {
uart_printf("[BOOT] APP CRC mismatch: calc=0x%08X, expect=0x%08X\r\n",
calc_crc, header->crc32);
return 0;
}
uart_printf("[BOOT] APP valid, version: v%d.%d.%d\r\n",
(header->version >> 16) & 0xFF,
(header->version >> 8) & 0xFF,
header->version & 0xFF);
return 1;
}
/**
* 跳转到APP
*/
static void jump_to_app(uint32_t app_addr)
{
uint32_t app_stack; // APP的栈顶地址
uint32_t app_reset; // APP的复位向量
app_func_t app_entry;
// APP起始地址存放的是栈顶指针(中断向量表第一项)
// APP起始地址+4 存放的是复位向量(中断向量表第二项)
// 但我们的固件带了header,所以要跳过header
uint32_t vector_addr = app_addr + sizeof(firmware_header_t);
app_stack = *(volatile uint32_t *)vector_addr;
app_reset = *(volatile uint32_t *)(vector_addr + 4);
// 简单校验栈顶地址是否在RAM范围内
if ((app_stack & 0x2FFE0000) != 0x20000000) {
uart_printf("[BOOT] Invalid stack pointer: 0x%08X\r\n", app_stack);
return;
}
uart_printf("[BOOT] Jumping to APP @ 0x%08X...\r\n", vector_addr);
uart_flush();
// 关闭所有中断
__disable_irq();
// 关闭用到的外设(根据实际情况添加)
uart_deinit();
// 设置中断向量表偏移
SCB->VTOR = vector_addr;
// 设置栈指针
__set_MSP(app_stack);
// 跳转到APP的复位处理函数
app_entry = (app_func_t)app_reset;
app_entry();
// 不会执行到这里
while (1);
}
/**
* 从Download区拷贝固件到APP区
*/
static int copy_firmware_to_app(void)
{
firmware_header_t *header = (firmware_header_t *)DOWNLOAD_START_ADDR;
uint32_t total_size = sizeof(firmware_header_t) + header->size;
uart_printf("[BOOT] Copying firmware: %u bytes\r\n", total_size);
// 1. 擦除APP区
uart_printf("[BOOT] Erasing APP area...\r\n");
if (flash_erase_range(APP_START_ADDR, APP_SIZE) != 0) {
uart_printf("[BOOT] Erase APP failed!\r\n");
return -1;
}
// 2. 拷贝数据
uart_printf("[BOOT] Writing...\r\n");
if (flash_write(APP_START_ADDR, (uint8_t *)DOWNLOAD_START_ADDR, total_size) != 0) {
uart_printf("[BOOT] Write APP failed!\r\n");
return -1;
}
// 3. 校验
if (!check_app_valid(APP_START_ADDR)) {
uart_printf("[BOOT] Verify failed after copy!\r\n");
return -1;
}
uart_printf("[BOOT] Copy complete!\r\n");
return 0;
}
/**
* 读取升级状态
*/
static void load_upgrade_state(void)
{
memcpy(&g_upgrade_state, (void *)PARAM_START_ADDR, sizeof(upgrade_state_t));
}
/**
* 保存升级状态
*/
static void save_upgrade_state(void)
{
// 参数区只有一个扇区,需要先擦除
flash_erase_sector(PARAM_START_ADDR);
flash_write(PARAM_START_ADDR, (uint8_t *)&g_upgrade_state, sizeof(upgrade_state_t));
}
/**
* 清除升级标志
*/
static void clear_upgrade_flag(void)
{
g_upgrade_state.upgrade_flag = UPGRADE_FLAG_NONE;
save_upgrade_state();
}
/**
* 检查是否有按键触发强制升级
*/
static int check_force_upgrade_key(void)
{
// 假设PA0接了一个按键,按下为低电平
// 持续按住3秒进入强制升级模式
gpio_config(GPIOA, GPIO_PIN_0, GPIO_MODE_INPUT_PULLUP);
if (gpio_read(GPIOA, GPIO_PIN_0) == 0) {
uart_printf("[BOOT] Key pressed, waiting 3s...\r\n");
int count = 0;
for (int i = 0; i < 30; i++) {
delay_ms(100);
if (gpio_read(GPIOA, GPIO_PIN_0) == 0) {
count++;
}
}
if (count >= 25) {
uart_printf("[BOOT] Force upgrade mode!\r\n");
return 1;
}
}
return 0;
}
/**
* Bootloader主函数
*/
int main(void)
{
// 1. 基础硬件初始化
system_clock_init();
uart_init(115200);
flash_init();
uart_printf("\r\n========================================\r\n");
uart_printf("Bootloader v1.0.0\r\n");
uart_printf("Build: %s %s\r\n", __DATE__, __TIME__);
uart_printf("========================================\r\n");
// 2. 读取升级状态
load_upgrade_state();
// 3. 检查升级条件
int need_upgrade = 0;
// 3.1 检查强制升级按键
if (check_force_upgrade_key()) {
uart_printf("[BOOT] Enter upgrade mode by key\r\n");
need_upgrade = 1;
}
// 3.2 检查升级标志 - 新固件已下载完成
else if (g_upgrade_state.upgrade_flag == UPGRADE_FLAG_READY) {
uart_printf("[BOOT] New firmware ready, start upgrade\r\n");
// 校验Download区的固件
if (check_app_valid(DOWNLOAD_START_ADDR)) {
// 拷贝到APP区
if (copy_firmware_to_app() == 0) {
clear_upgrade_flag();
uart_printf("[BOOT] Upgrade success!\r\n");
} else {
uart_printf("[BOOT] Upgrade failed, keep old version\r\n");
clear_upgrade_flag();
}
} else {
uart_printf("[BOOT] Downloaded firmware invalid!\r\n");
clear_upgrade_flag();
}
}
// 3.3 检查升级标志 - 请求进入升级模式
else if (g_upgrade_state.upgrade_flag == UPGRADE_FLAG_REQUEST) {
uart_printf("[BOOT] Upgrade requested by APP\r\n");
need_upgrade = 1;
}
// 3.4 检查APP是否有效
else if (!check_app_valid(APP_START_ADDR)) {
uart_printf("[BOOT] APP invalid, enter upgrade mode\r\n");
need_upgrade = 1;
}
// 4. 根据检查结果决定下一步
if (need_upgrade) {
// 进入升级模式
uart_printf("[BOOT] Waiting for firmware...\r\n");
upgrade_mode_loop(); // 死循环,接收固件
} else {
// 跳转到APP
uart_printf("[BOOT] Starting APP...\r\n");
delay_ms(100);
jump_to_app(APP_START_ADDR);
}
// 不应该执行到这里
uart_printf("[BOOT] ERROR: Should not reach here!\r\n");
while (1) {
delay_ms(1000);
}
}
跳转到APP的关键点
跳转代码看起来简单,但有几个容易踩坑的地方:
1. 中断向量表偏移
STM32默认从0x08000000读取中断向量表,APP被放到了0x08008000,所以必须修改SCB->VTOR寄存器,否则中断会跳到错误的地址。
APP端也要在启动代码里设置:
c
// APP的SystemInit()中添加
SCB->VTOR = APP_START_ADDR + sizeof(firmware_header_t);
2. 关闭Bootloader用过的外设
如果Bootloader开了串口中断、定时器等,跳转前必须关闭。否则中断触发时,新的向量表还没生效,会跳到野指针。
3. 栈指针的校验
中断向量表第一项是初始栈指针,它应该指向RAM区域(0x20000000开头)。如果读出来不对,说明APP区数据损坏了,不能跳转。
升级协议设计
Bootloader进入升级模式后,需要一个协议来接收固件数据。可以走串口、CAN、USB、网络等,这里以串口为例设计一个简单的协议。
数据帧格式
┌───────┬────────┬────────┬─────────────┬───────┬───────┐
│ HEAD │ CMD │ LEN │ DATA │ CRC16 │ TAIL │
│ 0xAA │ 1byte │ 2bytes │ 0~1024bytes│ 2bytes│ 0x55 │
└───────┴────────┴────────┴─────────────┴───────┴───────┘
命令定义
c
/**
* upgrade_protocol.h - 升级协议定义
*/
#ifndef __UPGRADE_PROTOCOL_H
#define __UPGRADE_PROTOCOL_H
// 帧头帧尾
#define FRAME_HEAD 0xAA
#define FRAME_TAIL 0x55
// 命令字
#define CMD_HANDSHAKE 0x01 // 握手
#define CMD_ERASE 0x02 // 擦除Flash
#define CMD_DATA 0x03 // 传输数据
#define CMD_VERIFY 0x04 // 校验
#define CMD_RESET 0x05 // 重启
#define CMD_GET_INFO 0x06 // 获取设备信息
// 应答
#define RSP_ACK 0x80 // 成功
#define RSP_NAK 0x81 // 失败
#define RSP_BUSY 0x82 // 忙
// 错误码
#define ERR_NONE 0x00
#define ERR_CRC 0x01 // CRC错误
#define ERR_ADDR 0x02 // 地址错误
#define ERR_ERASE 0x03 // 擦除失败
#define ERR_WRITE 0x04 // 写入失败
#define ERR_VERIFY 0x05 // 校验失败
#define ERR_SIZE 0x06 // 大小错误
// 最大数据长度
#define MAX_DATA_LEN 1024
#pragma pack(1)
// 握手请求
typedef struct {
uint32_t firmware_size; // 固件总大小
uint32_t firmware_crc; // 固件CRC
uint32_t firmware_version; // 固件版本
} handshake_req_t;
// 握手应答
typedef struct {
uint8_t bootloader_version[4]; // Bootloader版本
uint32_t flash_size; // 可用Flash大小
uint32_t page_size; // 写入页大小
} handshake_rsp_t;
// 数据包
typedef struct {
uint32_t offset; // 相对于固件起始的偏移
uint16_t length; // 本包数据长度
uint8_t data[]; // 数据
} data_packet_t;
#pragma pack()
#endif
升级流程
上位机 Bootloader
│ │
│──── CMD_HANDSHAKE (固件信息) ────────>│
│<───── RSP_ACK (设备信息) ─────────────│
│ │
│──── CMD_ERASE ──────────────────────>│
│<───── RSP_ACK ───────────────────────│ (擦除Download区)
│ │
│──── CMD_DATA (offset=0, data) ──────>│
│<───── RSP_ACK ───────────────────────│
│──── CMD_DATA (offset=1024, data) ───>│
│<───── RSP_ACK ───────────────────────│
│ ... │
│──── CMD_DATA (最后一包) ────────────>│
│<───── RSP_ACK ───────────────────────│
│ │
│──── CMD_VERIFY ─────────────────────>│
│<───── RSP_ACK ───────────────────────│ (校验CRC)
│ │
│──── CMD_RESET ──────────────────────>│
│<───── RSP_ACK ───────────────────────│ (设置标志,重启)
│ │
协议解析实现
c
/**
* upgrade_protocol.c - 升级协议实现
*/
#include "upgrade_protocol.h"
#include "flash_driver.h"
#include "crc16.h"
#include "uart_driver.h"
// 接收状态机
typedef enum {
STATE_IDLE,
STATE_CMD,
STATE_LEN_L,
STATE_LEN_H,
STATE_DATA,
STATE_CRC_L,
STATE_CRC_H,
STATE_TAIL
} recv_state_t;
static recv_state_t g_recv_state = STATE_IDLE;
static uint8_t g_recv_cmd;
static uint16_t g_recv_len;
static uint16_t g_recv_index;
static uint8_t g_recv_buf[MAX_DATA_LEN + 16];
static uint16_t g_recv_crc;
// 升级上下文
static struct {
uint32_t total_size;
uint32_t received_size;
uint32_t expected_crc;
uint8_t in_progress;
} g_upgrade_ctx;
/**
* 发送应答帧
*/
static void send_response(uint8_t cmd, uint8_t *data, uint16_t len)
{
uint8_t header[4];
uint16_t crc;
header[0] = FRAME_HEAD;
header[1] = cmd;
header[2] = len & 0xFF;
header[3] = (len >> 8) & 0xFF;
// 计算CRC(包含cmd和len)
crc = crc16_calculate(&header[1], 3);
if (len > 0) {
crc = crc16_update(crc, data, len);
}
// 发送
uart_send(header, 4);
if (len > 0) {
uart_send(data, len);
}
uart_send_byte(crc & 0xFF);
uart_send_byte((crc >> 8) & 0xFF);
uart_send_byte(FRAME_TAIL);
}
/**
* 发送简单ACK/NAK
*/
static void send_ack(uint8_t error_code)
{
uint8_t rsp = (error_code == ERR_NONE) ? RSP_ACK : RSP_NAK;
send_response(rsp, &error_code, 1);
}
/**
* 处理握手命令
*/
static void handle_handshake(uint8_t *data, uint16_t len)
{
handshake_req_t *req = (handshake_req_t *)data;
handshake_rsp_t rsp;
uart_printf("[UPGRADE] Handshake: size=%u, crc=0x%08X, ver=0x%08X\r\n",
req->firmware_size, req->firmware_crc, req->firmware_version);
// 检查固件大小
if (req->firmware_size > DOWNLOAD_SIZE - sizeof(firmware_header_t)) {
uart_printf("[UPGRADE] Firmware too large!\r\n");
send_ack(ERR_SIZE);
return;
}
// 保存信息
g_upgrade_ctx.total_size = req->firmware_size;
g_upgrade_ctx.expected_crc = req->firmware_crc;
g_upgrade_ctx.received_size = 0;
g_upgrade_ctx.in_progress = 1;
// 构造应答
rsp.bootloader_version[0] = 1;
rsp.bootloader_version[1] = 0;
rsp.bootloader_version[2] = 0;
rsp.bootloader_version[3] = 0;
rsp.flash_size = DOWNLOAD_SIZE;
rsp.page_size = 1024;
send_response(RSP_ACK, (uint8_t *)&rsp, sizeof(rsp));
}
/**
* 处理擦除命令
*/
static void handle_erase(void)
{
uart_printf("[UPGRADE] Erasing download area...\r\n");
if (flash_erase_range(DOWNLOAD_START_ADDR, DOWNLOAD_SIZE) != 0) {
uart_printf("[UPGRADE] Erase failed!\r\n");
send_ack(ERR_ERASE);
return;
}
uart_printf("[UPGRADE] Erase complete\r\n");
send_ack(ERR_NONE);
}
/**
* 处理数据包
*/
static void handle_data(uint8_t *data, uint16_t len)
{
data_packet_t *pkt = (data_packet_t *)data;
uint32_t write_addr;
// 检查偏移
if (pkt->offset != g_upgrade_ctx.received_size) {
uart_printf("[UPGRADE] Offset mismatch: expect %u, got %u\r\n",
g_upgrade_ctx.received_size, pkt->offset);
send_ack(ERR_ADDR);
return;
}
// 计算写入地址
write_addr = DOWNLOAD_START_ADDR + pkt->offset;
// 检查地址范围
if (write_addr + pkt->length > DOWNLOAD_END_ADDR) {
uart_printf("[UPGRADE] Address out of range!\r\n");
send_ack(ERR_ADDR);
return;
}
// 写入Flash
if (flash_write(write_addr, pkt->data, pkt->length) != 0) {
uart_printf("[UPGRADE] Write failed @ 0x%08X\r\n", write_addr);
send_ack(ERR_WRITE);
return;
}
g_upgrade_ctx.received_size += pkt->length;
// 进度显示
uint8_t progress = (g_upgrade_ctx.received_size * 100) / g_upgrade_ctx.total_size;
uart_printf("\r[UPGRADE] Progress: %u%% (%u/%u)",
progress, g_upgrade_ctx.received_size, g_upgrade_ctx.total_size);
send_ack(ERR_NONE);
}
/**
* 处理校验命令
*/
static void handle_verify(void)
{
uart_printf("\r\n[UPGRADE] Verifying...\r\n");
// 计算已下载数据的CRC
uint32_t calc_crc = crc32_calculate(
(uint8_t *)DOWNLOAD_START_ADDR,
g_upgrade_ctx.received_size
);
if (calc_crc != g_upgrade_ctx.expected_crc) {
uart_printf("[UPGRADE] CRC mismatch: calc=0x%08X, expect=0x%08X\r\n",
calc_crc, g_upgrade_ctx.expected_crc);
send_ack(ERR_VERIFY);
return;
}
// 进一步校验固件头
if (!check_app_valid(DOWNLOAD_START_ADDR)) {
uart_printf("[UPGRADE] Firmware header invalid!\r\n");
send_ack(ERR_VERIFY);
return;
}
uart_printf("[UPGRADE] Verify OK!\r\n");
// 设置升级标志
g_upgrade_state.upgrade_flag = UPGRADE_FLAG_READY;
g_upgrade_state.new_fw_size = g_upgrade_ctx.total_size;
g_upgrade_state.new_fw_crc = g_upgrade_ctx.expected_crc;
save_upgrade_state();
send_ack(ERR_NONE);
}
/**
* 处理重启命令
*/
static void handle_reset(void)
{
uart_printf("[UPGRADE] Rebooting...\r\n");
send_ack(ERR_NONE);
uart_flush();
delay_ms(100);
// 软件复位
NVIC_SystemReset();
}
/**
* 处理完整帧
*/
static void process_frame(uint8_t cmd, uint8_t *data, uint16_t len)
{
switch (cmd) {
case CMD_HANDSHAKE:
handle_handshake(data, len);
break;
case CMD_ERASE:
handle_erase();
break;
case CMD_DATA:
handle_data(data, len);
break;
case CMD_VERIFY:
handle_verify();
break;
case CMD_RESET:
handle_reset();
break;
default:
uart_printf("[UPGRADE] Unknown cmd: 0x%02X\r\n", cmd);
send_ack(ERR_CRC);
break;
}
}
/**
* 接收状态机(逐字节处理)
*/
void upgrade_recv_byte(uint8_t byte)
{
switch (g_recv_state) {
case STATE_IDLE:
if (byte == FRAME_HEAD) {
g_recv_state = STATE_CMD;
}
break;
case STATE_CMD:
g_recv_cmd = byte;
g_recv_state = STATE_LEN_L;
break;
case STATE_LEN_L:
g_recv_len = byte;
g_recv_state = STATE_LEN_H;
break;
case STATE_LEN_H:
g_recv_len |= (byte << 8);
g_recv_index = 0;
if (g_recv_len > 0) {
g_recv_state = STATE_DATA;
} else {
g_recv_state = STATE_CRC_L;
}
break;
case STATE_DATA:
if (g_recv_index < MAX_DATA_LEN) {
g_recv_buf[g_recv_index++] = byte;
}
if (g_recv_index >= g_recv_len) {
g_recv_state = STATE_CRC_L;
}
break;
case STATE_CRC_L:
g_recv_crc = byte;
g_recv_state = STATE_CRC_H;
break;
case STATE_CRC_H:
g_recv_crc |= (byte << 8);
g_recv_state = STATE_TAIL;
break;
case STATE_TAIL:
g_recv_state = STATE_IDLE;
if (byte == FRAME_TAIL) {
// 校验CRC
uint16_t calc_crc = crc16_calculate(&g_recv_cmd, 1);
calc_crc = crc16_update(calc_crc, (uint8_t *)&g_recv_len, 2);
if (g_recv_len > 0) {
calc_crc = crc16_update(calc_crc, g_recv_buf, g_recv_len);
}
if (calc_crc == g_recv_crc) {
process_frame(g_recv_cmd, g_recv_buf, g_recv_len);
} else {
uart_printf("[UPGRADE] CRC error\r\n");
}
}
break;
}
}
/**
* 升级模式主循环
*/
void upgrade_mode_loop(void)
{
while (1) {
if (uart_available()) {
uint8_t byte = uart_read_byte();
upgrade_recv_byte(byte);
}
}
}
APP端触发升级
用户程序(APP)如何触发升级呢?一般有几种方式:
方式一:设置标志后重启
c
/**
* app_upgrade.c - APP端升级接口
*/
#include "flash_partition.h"
// 请求进入升级模式
void request_upgrade(void)
{
upgrade_state_t state;
// 读取当前状态
memcpy(&state, (void *)PARAM_START_ADDR, sizeof(state));
// 设置升级请求标志
state.upgrade_flag = UPGRADE_FLAG_REQUEST;
// 写入参数区
flash_unlock();
flash_erase_sector(PARAM_START_ADDR);
flash_write(PARAM_START_ADDR, (uint8_t *)&state, sizeof(state));
flash_lock();
printf("Rebooting to upgrade mode...\n");
delay_ms(100);
// 软件复位,Bootloader会检测到标志并进入升级模式
NVIC_SystemReset();
}
方式二:OTA自动升级
更高级的场景是APP自己下载新固件到Download区,校验通过后设置标志重启:
c
/**
* ota_upgrade.c - OTA在线升级
*/
#include "flash_partition.h"
#include "http_client.h"
// 固件服务器地址
#define OTA_SERVER_URL "http://firmware.example.com/device"
// 检查是否有新版本
int ota_check_update(uint32_t *new_version, uint32_t *new_size)
{
char url[128];
char response[256];
// 获取当前版本
firmware_header_t *current = (firmware_header_t *)APP_START_ADDR;
snprintf(url, sizeof(url), "%s/check?device=XXX&version=%08X",
OTA_SERVER_URL, current->version);
if (http_get(url, response, sizeof(response)) != 0) {
return -1;
}
// 解析响应(简化示例)
// 实际应该用JSON解析
if (sscanf(response, "version=%x,size=%u", new_version, new_size) != 2) {
return -1;
}
// 比较版本
if (*new_version <= current->version) {
printf("Already latest version\n");
return 0;
}
printf("New version available: v%d.%d.%d\n",
(*new_version >> 16) & 0xFF,
(*new_version >> 8) & 0xFF,
*new_version & 0xFF);
return 1; // 有新版本
}
// 下载并升级
int ota_download_and_upgrade(void)
{
uint32_t new_version, new_size;
char url[128];
uint8_t buffer[1024];
uint32_t offset = 0;
uint32_t crc = 0;
// 1. 检查更新
if (ota_check_update(&new_version, &new_size) != 1) {
return -1;
}
// 2. 擦除Download区
printf("Erasing flash...\n");
flash_unlock();
if (flash_erase_range(DOWNLOAD_START_ADDR, DOWNLOAD_SIZE) != 0) {
flash_lock();
return -1;
}
// 3. 分块下载
printf("Downloading firmware...\n");
snprintf(url, sizeof(url), "%s/firmware?version=%08X", OTA_SERVER_URL, new_version);
http_download_context_t ctx;
if (http_download_start(&ctx, url) != 0) {
flash_lock();
return -1;
}
while (offset < new_size) {
int read_len = http_download_read(&ctx, buffer, sizeof(buffer));
if (read_len <= 0) {
printf("Download failed at offset %u\n", offset);
http_download_close(&ctx);
flash_lock();
return -1;
}
// 写入Flash
if (flash_write(DOWNLOAD_START_ADDR + offset, buffer, read_len) != 0) {
printf("Flash write failed\n");
http_download_close(&ctx);
flash_lock();
return -1;
}
offset += read_len;
printf("\rProgress: %u%%", offset * 100 / new_size);
}
http_download_close(&ctx);
flash_lock();
printf("\nDownload complete\n");
// 4. 校验固件
printf("Verifying...\n");
firmware_header_t *header = (firmware_header_t *)DOWNLOAD_START_ADDR;
if (header->magic != FIRMWARE_MAGIC) {
printf("Invalid firmware magic\n");
return -1;
}
uint32_t calc_crc = crc32_calculate(
(uint8_t *)(DOWNLOAD_START_ADDR + sizeof(firmware_header_t)),
header->size
);
if (calc_crc != header->crc32) {
printf("CRC mismatch\n");
return -1;
}
// 5. 设置升级标志
upgrade_state_t state = {
.upgrade_flag = UPGRADE_FLAG_READY,
.new_fw_size = new_size,
.new_fw_crc = header->crc32,
.new_fw_version = header->version
};
flash_unlock();
flash_erase_sector(PARAM_START_ADDR);
flash_write(PARAM_START_ADDR, (uint8_t *)&state, sizeof(state));
flash_lock();
// 6. 重启
printf("Rebooting to apply update...\n");
delay_ms(500);
NVIC_SystemReset();
return 0;
}
固件打包工具
最后还需要一个工具,给编译出来的bin文件加上固件头:
python
#!/usr/bin/env python3
"""
firmware_pack.py - 固件打包工具
给原始bin文件添加固件头
"""
import struct
import sys
import time
import zlib
FIRMWARE_MAGIC = 0x46495257 # "FIRW"
def crc32(data):
return zlib.crc32(data) & 0xFFFFFFFF
def pack_firmware(input_bin, output_bin, version_str, hw_version=1):
"""
打包固件
Args:
input_bin: 输入的原始bin文件
output_bin: 输出的带头部的bin文件
version_str: 版本号字符串,如 "1.2.3"
hw_version: 硬件版本
"""
# 读取原始固件
with open(input_bin, 'rb') as f:
firmware_data = f.read()
# 解析版本号
parts = version_str.split('.')
major = int(parts[0]) if len(parts) > 0 else 0
minor = int(parts[1]) if len(parts) > 1 else 0
patch = int(parts[2]) if len(parts) > 2 else 0
version = (major << 16) | (minor << 8) | patch
# 计算CRC
firmware_crc = crc32(firmware_data)
# 构造头部 (32字节)
# magic(4) + version(4) + size(4) + crc32(4) + timestamp(4) + hw_version(1) + reserved(11)
header = struct.pack('<IIIIIBB10s',
FIRMWARE_MAGIC,
version,
len(firmware_data),
firmware_crc,
int(time.time()),
hw_version,
0, # reserved[0]
b'\x00' * 10 # reserved[1-10]
)
# 写入输出文件
with open(output_bin, 'wb') as f:
f.write(header)
f.write(firmware_data)
print(f"Firmware packed successfully!")
print(f" Input: {input_bin}")
print(f" Output: {output_bin}")
print(f" Version: v{major}.{minor}.{patch} (0x{version:08X})")
print(f" Size: {len(firmware_data)} bytes")
print(f" CRC32: 0x{firmware_crc:08X}")
print(f" Total: {len(header) + len(firmware_data)} bytes")
if __name__ == '__main__':
if len(sys.argv) < 4:
print(f"Usage: {sys.argv[0]} <input.bin> <output.bin> <version>")
print(f"Example: {sys.argv[0]} app.bin app_packed.bin 1.2.3")
sys.exit(1)
pack_firmware(sys.argv[1], sys.argv[2], sys.argv[3])
用法:
bash
# 编译后得到 app.bin
arm-none-eabi-objcopy -O binary app.elf app.bin
# 打包成带头部的固件
python firmware_pack.py app.bin app_v1.2.3.bin 1.2.3
# 输出
Firmware packed successfully!
Input: app.bin
Output: app_v1.2.3.bin
Version: v1.2.3 (0x00010203)
Size: 65432 bytes
CRC32: 0xA1B2C3D4
Total: 65464 bytes
安全性考虑
生产环境还要考虑安全问题:
1. 固件加密
防止固件被逆向分析,可以用AES加密固件内容,Bootloader里解密后再写入:
c
// 简化示例
void handle_encrypted_data(uint8_t *data, uint16_t len)
{
uint8_t decrypted[1024];
// AES解密
aes_decrypt(data, decrypted, len, aes_key);
// 写入Flash
flash_write(write_addr, decrypted, len);
}
2. 固件签名
防止刷入非官方固件,用RSA或ECDSA签名:
c
int verify_firmware_signature(uint8_t *firmware, uint32_t size, uint8_t *signature)
{
uint8_t hash[32];
// 计算固件SHA256
sha256_calculate(firmware, size, hash);
// 用公钥验签
return ecdsa_verify(hash, signature, public_key);
}
3. 回滚保护
记录版本号,禁止刷入更旧的版本,防止降级攻击:
c
if (new_version <= current_version) {
printf("Rollback not allowed!\n");
return -1;
}
总结
整个BIOS/Bootloader + OTA升级方案涉及的知识点:
| 模块 | 关键点 |
|---|---|
| Flash分区 | 双区设计保证断电安全 |
| Bootloader | 中断向量表重定向、跳转前清理外设 |
| 固件格式 | 头部包含魔数、版本、CRC校验 |
| 升级协议 | 帧格式、握手、分包传输、校验 |
| OTA | APP下载到暂存区,设置标志后重启 |
| 安全 | 加密、签名、防回滚 |
这套方案在我的几个项目上都稳定运行了,基本能覆盖大部分嵌入式产品的升级需求。代码可以直接拿去改,有问题评论区讨论~