下面是在原有 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
|
|