嵌入式BIOS/Bootloader原理剖析——从启动流程到OTA自动升级实现

前言

做嵌入式产品,迟早要面对固件升级的问题。设备卖出去了,发现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下载到暂存区,设置标志后重启
安全 加密、签名、防回滚

这套方案在我的几个项目上都稳定运行了,基本能覆盖大部分嵌入式产品的升级需求。代码可以直接拿去改,有问题评论区讨论~

相关推荐
天远Date Lab3 小时前
Python实现用户消费潜力评估:天远个人消费能力等级API对接全攻略
java·大数据·网络·python
岁岁种桃花儿11 小时前
Nginx 站点垂直扩容(单机性能升级)全攻略
网络·nginx·dns
Xの哲學11 小时前
Linux SMP 实现机制深度剖析
linux·服务器·网络·算法·边缘计算
一颗青果12 小时前
公网构建全流程与参与主体深度解析
网络
小北方城市网14 小时前
Python + 前后端全栈进阶课程(共 10 节|完整版递进式|从技术深化→项目落地→就业进阶,无缝衔接基础课)
大数据·开发语言·网络·python·数据库架构
山上三树15 小时前
task_struct 详解
运维·服务器·网络
传感器与混合集成电路15 小时前
175℃持续工作:专为随钻测量系统设计的高温AC-DC电源
网络·能源
日更嵌入式的打工仔15 小时前
Ehercat代码解析中文摘录<1>
网络·笔记·ethercat
一只鹿鹿鹿16 小时前
网络信息与数据安全建设方案
大数据·运维·开发语言·网络·mysql