[经验分享] MCU的升级方案

[复制链接]
1677|0
八层楼 发表于 2025-9-8 15:28 | 显示全部楼层 |阅读模式
下面是在原有 A/B 分区升级方案基础上,增加完整 Bootloader 实现的方案。Bootloader 负责系统启动时的分区选择、固件验证和启动控制,是整个升级方案的核心组件。

带Bootloader的MCU A/B分区升级方案

Bootloader 方案详细说明
Bootloader 核心功能
这个 Bootloader 实现了以下关键功能:

系统启动控制:

系统上电或复位后首先运行 Bootloader
验证当前活动分区的有效性
选择正确的分区启动应用程序
分区管理:

维护 A/B 两个应用分区的状态信息
管理分区切换和版本控制
通过配置区保存分区状态和校验信息
安全验证:

实现 CRC32 校验确保固件完整性
验证配置区有效性防止配置损坏
检查应用程序栈顶地址有效性
故障恢复:

当前分区无效时自动切换到备用分区
所有分区无效时进入 Bootloader 模式等待救援
记录升级尝试次数防止无限循环
启动流程详解
初始化阶段:

初始化时钟、UART 和闪存控制器
读取并验证配置区信息
打印启动信息和当前配置状态
分区验证阶段:

验证当前活动分区的 CRC 和状态
如当前分区无效,尝试切换到备用分区
如所有分区无效,进入 Bootloader 模式
应用启动阶段:

计算应用程序起始地址
验证应用程序栈顶地址
关闭中断和 SysTick 定时器
设置向量表偏移
跳转到应用程序复位处理函数
与应用程序的协作
Bootloader 负责低级别启动和分区管理
应用程序负责处理正常升级流程
配置区作为两者共享的信息交换区
应用程序首次启动时会进行自检测,失败则标记当前分区无效并重启
安全性考虑
配置区使用 CRC 保护防止数据损坏
固件分区使用 CRC 验证确保完整性
升级过程中意外中断不会影响当前活动分区
分区切换操作是原子性的,确保系统始终可启动
这个 Bootloader 方案可以直接集成到基于 ARM Cortex-M 系列 MCU 的系统中,只需根据实际硬件情况调整分区地址和闪存操作接口。

bootloader.c

#include "bootloader.h"
#include "flash.h"      // 闪存操作接口
#include "system.h"     // 系统接口
#include <string.h>

static UpgradeConfig config;
static uint32_t current_upgrade_crc;
static uint8_t current_target_part;
static uint32_t current_target_version;

// 初始化升级系统
void upgrade_init(void) {
    // 从闪存读取配置
    flash_read(CONFIG_AREA_START, (uint8_t*)&config, sizeof(UpgradeConfig));

    // 验证配置区有效性
    if (config.magic_word != 0x12345678 ||
        crc32_calculate((uint8_t*)&config, sizeof(UpgradeConfig) - 4) != config.crc) {
        // 配置区无效,初始化默认配置
        memset(&config, 0, sizeof(UpgradeConfig));
        config.magic_word = 0x12345678;
        config.active_partition = 0;          // 默认A分区为活动分区
        config.part_a_state = PARTITION_VALID;// 假设A分区有效
        config.part_b_state = PARTITION_INVALID;
        config.upgrade_attempts = 0;

        // 计算初始CRC
        config.crc = crc32_calculate((uint8_t*)&config, sizeof(UpgradeConfig) - 4);
        upgrade_save_config();
    }
}

// 获取当前配置
UpgradeConfig* upgrade_get_config(void) {
    return &config;
}

// 获取当前活动分区
uint8_t upgrade_get_active_partition(void) {
    return config.active_partition;
}

// 验证指定分区
static bool upgrade_verify_partition(uint8_t part) {
    uint32_t start_addr = (part == 0) ? PARTITION_A_START : PARTITION_B_START;
    uint32_t crc_stored = (part == 0) ? config.part_a_crc : config.part_b_crc;
    PartitionState state = (part == 0) ? config.part_a_state : config.part_b_state;

    // 分区状态必须为有效
    if (state != PARTITION_VALID) {
        return false;
    }

    // 计算分区CRC并与存储值比较
    uint32_t crc_calculated = crc32_calculate((uint8_t*)start_addr, PARTITION_SIZE);
    return (crc_calculated == crc_stored);
}

// 验证当前活动分区
bool upgrade_verify_current(void) {
    // 先验证当前活动分区
    if (upgrade_verify_partition(config.active_partition)) {
        return true;
    }

    // 当前分区无效,尝试切换到另一个分区
    uint8_t other_part = (config.active_partition == 0) ? 1 : 0;
    if (upgrade_verify_partition(other_part)) {
        config.active_partition = other_part;
        config.upgrade_attempts++;
        upgrade_save_config();
        return true;
    }

    // 两个分区都无效
    return false;
}

// 开始升级
bool upgrade_start(uint8_t target_part, uint32_t version) {
    // 检查目标分区是否为非活动分区
    if (target_part == config.active_partition) {
        return false;
    }

    uint32_t start_addr = (target_part == 0) ? PARTITION_A_START : PARTITION_B_START;

    // 擦除目标分区
    if (!flash_erase(start_addr, PARTITION_SIZE)) {
        return false;
    }

    // 更新分区状态
    if (target_part == 0) {
        config.part_a_state = PARTITION_UPGRADING;
        config.part_a_version = version;
    } else {
        config.part_b_state = PARTITION_UPGRADING;
        config.part_b_version = version;
    }

    // 保存当前升级信息
    current_target_part = target_part;
    current_target_version = version;
    current_upgrade_crc = 0;  // 初始化CRC

    upgrade_save_config();
    return true;
}

// 写入升级数据
bool upgrade_write_data(uint32_t offset, const uint8_t *data, uint16_t length) {
    // 检查偏移是否超出分区范围
    if (offset + length > PARTITION_SIZE) {
        return false;
    }

    uint32_t write_addr = (current_target_part == 0) ?
                         PARTITION_A_START + offset :
                         PARTITION_B_START + offset;

    // 写入数据到闪存
    if (!flash_write(write_addr, data, length)) {
        return false;
    }

    // 更新CRC
    current_upgrade_crc = crc32_calculate_partial(current_upgrade_crc, data, length);
    return true;
}

// 完成升级
bool upgrade_finish(void) {
    // 验证整个分区的CRC
    uint32_t start_addr = (current_target_part == 0) ? PARTITION_A_START : PARTITION_B_START;
    uint32_t full_crc = crc32_calculate((uint8_t*)start_addr, PARTITION_SIZE);

    if (full_crc != current_upgrade_crc) {
        // CRC不匹配,标记分区为无效
        if (current_target_part == 0) {
            config.part_a_state = PARTITION_INVALID;
        } else {
            config.part_b_state = PARTITION_INVALID;
        }
        upgrade_save_config();
        return false;
    }

    // 更新分区状态和CRC
    if (current_target_part == 0) {
        config.part_a_state = PARTITION_VALID;
        config.part_a_crc = full_crc;
    } else {
        config.part_b_state = PARTITION_VALID;
        config.part_b_crc = full_crc;
    }

    // 重置升级尝试次数
    config.upgrade_attempts = 0;
    upgrade_save_config();
    return true;
}

// 切换活动分区
bool upgrade_switch_partition(void) {
    uint8_t new_part = (config.active_partition == 0) ? 1 : 0;

    // 检查新分区是否有效
    if ((new_part == 0 && config.part_a_state != PARTITION_VALID) ||
        (new_part == 1 && config.part_b_state != PARTITION_VALID)) {
        return false;
    }

    // 切换活动分区
    config.active_partition = new_part;
    upgrade_save_config();
    return true;
}

// 标记当前分区为无效
void upgrade_mark_current_invalid(void) {
    if (config.active_partition == 0) {
        config.part_a_state = PARTITION_INVALID;
    } else {
        config.part_b_state = PARTITION_INVALID;
    }

    // 切换到另一个分区
    config.active_partition = (config.active_partition == 0) ? 1 : 0;
    config.upgrade_attempts++;

    upgrade_save_config();
}

// 保存配置到闪存
void upgrade_save_config(void) {
    // 计算配置区CRC
    config.crc = crc32_calculate((uint8_t*)&config, sizeof(UpgradeConfig) - 4);

    // 擦除配置区并写入新配置
    flash_erase(CONFIG_AREA_START, CONFIG_AREA_SIZE);
    flash_write(CONFIG_AREA_START, (uint8_t*)&config, sizeof(UpgradeConfig));
}

// CRC32计算(使用标准多项式0xEDB88320)
uint32_t crc32_calculate(const uint8_t *data, uint32_t length) {
    uint32_t crc = 0xFFFFFFFF;
    const uint32_t polynomial = 0xEDB88320;

    for (uint32_t i = 0; i < length; i++) {
        crc ^= data[i];

        for (int j = 0; j < 8; j++) {
            if (crc & 1) {
                crc = (crc >> 1) ^ polynomial;
            } else {
                crc >>= 1;
            }
        }
    }

    return crc ^ 0xFFFFFFFF;
}

// 增量CRC32计算
uint32_t crc32_calculate_partial(uint32_t current_crc, const uint8_t *data, uint32_t length) {
    uint32_t crc = current_crc ^ 0xFFFFFFFF;
    const uint32_t polynomial = 0xEDB88320;

    for (uint32_t i = 0; i < length; i++) {
        crc ^= data[i];

        for (int j = 0; j < 8; j++) {
            if (crc & 1) {
                crc = (crc >> 1) ^ polynomial;
            } else {
                crc >>= 1;
            }
        }
    }

    return crc ^ 0xFFFFFFFF;
}


bootloader.h

#ifndef BOOTLOADER_H
#define BOOTLOADER_H

#include <stdint.h>
#include <stdbool.h>

// 分区定义(根据实际MCU闪存大小调整)
#define PARTITION_A_START     0x08008000  // A分区起始地址
#define PARTITION_B_START     0x08028000  // B分区起始地址
#define PARTITION_SIZE        0x00020000  // 每个分区大小(128KB)
#define CONFIG_AREA_START     0x08048000  // 配置区起始地址
#define CONFIG_AREA_SIZE      0x00001000  // 配置区大小(4KB)
#define RAM_SIZE              0x00010000  // RAM大小(64KB)

// 分区状态枚举
typedef enum {
    PARTITION_INVALID = 0,    // 无效分区
    PARTITION_VALID,          // 有效分区
    PARTITION_UPGRADING       // 升级中
} PartitionState;

// 配置区结构
typedef struct {
    uint8_t active_partition;   // 当前活动分区(0:A, 1:B)
    PartitionState part_a_state;// A分区状态
    PartitionState part_b_state;// B分区状态
    uint32_t part_a_version;    // A分区版本号
    uint32_t part_b_version;    // B分区版本号
    uint32_t part_a_crc;        // A分区CRC校验值
    uint32_t part_b_crc;        // B分区CRC校验值
    uint8_t upgrade_attempts;   // 升级尝试次数
    uint32_t magic_word;        // 魔术字,用于验证配置区有效性(0x12345678)
    uint32_t crc;               // 配置区自身CRC校验值
} UpgradeConfig;

// 升级命令类型
typedef enum {
    UPGRADE_CMD_START = 0x01,   // 开始升级
    UPGRADE_CMD_DATA,           // 数据块
    UPGRADE_CMD_FINISH,         // 完成升级
    UPGRADE_CMD_ABORT,          // 中止升级
    UPGRADE_CMD_INFO            // 获取升级信息
} UpgradeCmdType;

// 响应类型
typedef enum {
    RESPONSE_OK = 0x00,
    RESPONSE_ERROR,
    RESPONSE_INVALID_CMD,
    RESPONSE_INVALID_DATA,
    RESPONSE_CRC_ERROR
} ResponseType;

// 升级命令结构
typedef struct {
    UpgradeCmdType type;        // 命令类型
    uint32_t version;           // 版本号(仅用于START命令)
    uint32_t offset;            // 数据偏移(仅用于DATA命令)
    uint8_t *data;              // 数据指针(仅用于DATA命令)
    uint16_t length;            // 数据长度(仅用于DATA命令)
} UpgradeCommand;

// 初始化升级系统
void upgrade_init(void);

// 获取当前配置
UpgradeConfig* upgrade_get_config(void);

// 获取当前活动分区
uint8_t upgrade_get_active_partition(void);

// 验证当前活动分区
bool upgrade_verify_current(void);

// 开始升级
bool upgrade_start(uint8_t target_part, uint32_t version);

// 写入升级数据
bool upgrade_write_data(uint32_t offset, const uint8_t *data, uint16_t length);

// 完成升级
bool upgrade_finish(void);

// 切换活动分区
bool upgrade_switch_partition(void);

// 标记当前分区为无效
void upgrade_mark_current_invalid(void);

// 保存配置
void upgrade_save_config(void);

// 计算CRC32校验值
uint32_t crc32_calculate(const uint8_t *data, uint32_t length);

#endif // BOOTLOADER_H


bootloader_main.c

#include "bootloader.h"
#include "system.h"
#include "uart.h"       // 用于调试输出
#include <stdint.h>

// 函数指针类型,用于跳转执行应用程序
typedef void (*app_func_t)(void);

// 跳转到应用程序
static void jump_to_application(uint32_t app_addr) {
    // 检查应用程序栈顶地址是否有效
    uint32_t stack_top = *(volatile uint32_t*)app_addr;
    if (stack_top < 0x20000000 || stack_top > 0x20000000 + RAM_SIZE) {
        uart_send_string("Invalid stack top address!\r\n");
        return;
    }

    // 关闭所有中断
    __disable_irq();

    // 关闭SysTick定时器
    SysTick->CTRL = 0;

    // 设置向量表偏移寄存器
    SCB->VTOR = app_addr;

    // 获取应用程序复位处理函数地址
    app_func_t app_reset_handler = (app_func_t)*(volatile uint32_t*)(app_addr + 4);

    // 设置主栈指针
    __set_MSP(stack_top);

    // 跳转到应用程序
    app_reset_handler();
}

// 进入Bootloader模式(等待升级)
static void enter_bootloader_mode(void) {
    uart_send_string("Entering bootloader mode...\r\n");
    uart_send_string("Waiting for firmware upgrade...\r\n");

    // 在此实现Bootloader模式下的升级逻辑
    // 例如:通过UART/I2C/SPI等接收固件并写入分区

    while (1) {
        // 处理升级命令
        process_bootloader_commands();

        // 简单延时
        for (volatile uint32_t i = 0; i < 100000; i++);
    }
}

// Bootloader主函数
int main(void) {
    // 初始化基础硬件
    system_clock_init();  // 初始化系统时钟
    uart_init(115200);    // 初始化UART用于调试
    flash_init();         // 初始化闪存控制器

    uart_send_string("Bootloader started\r\n");

    // 初始化升级系统
    upgrade_init();

    // 打印当前配置信息
    UpgradeConfig* config = upgrade_get_config();
    uart_send_string("Current configuration:\r\n");
    uart_send_string("Active partition: ");
    uart_send_byte('0' + config->active_partition);
    uart_send_string("\r\n");
    uart_send_string("A partition state: ");
    uart_send_byte('0' + config->part_a_state);
    uart_send_string("\r\n");
    uart_send_string("B partition state: ");
    uart_send_byte('0' + config->part_b_state);
    uart_send_string("\r\n");

    // 验证当前活动分区
    if (!upgrade_verify_current()) {
        uart_send_string("All partitions are invalid!\r\n");
        enter_bootloader_mode();
        while (1);  // 不应到达此处
    }

    // 获取应用程序地址
    uint32_t app_addr = (config->active_partition == 0) ?
                       PARTITION_A_START : PARTITION_B_START;

    uart_send_string("Jumping to application at address: 0x");
    uart_send_hex32(app_addr);
    uart_send_string("\r\n");

    // 跳转到应用程序
    jump_to_application(app_addr);

    // 如果跳转失败,进入Bootloader模式
    uart_send_string("Failed to jump to application!\r\n");
    enter_bootloader_mode();
    while (1);
}

// 处理Bootloader模式下的升级命令
void process_bootloader_commands(void) {
    // 此处实现从通信接口(如UART)接收并处理升级命令的逻辑
    // 与应用程序中的process_upgrade_commands类似
    // 当系统无法启动任何应用程序时,通过此函数接收救援固件
}
————————————————
版权声明:本文为CSDN博主「飞吧~皮卡丘」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/shanstellar/article/details/151118909

您需要登录后才可以回帖 登录 | 注册

本版积分规则

125

主题

4370

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部