【STM32H757XIH6 双核】双核系统 Bootloader + EEPROM 备份
1. 引言
2. STM32H757 的主要内存区域
STM32H757 集成 Cortex-M7 和 Cortex-M4 两个内核,并通过不同总线域连接 Flash、片上 SRAM 和外设。常用地址空间如下:
| 存储区域 | 起始地址 | 典型容量 | 主要用途 |
|---|---|---|---|
| ITCM RAM | 0x00000000 |
64 KB | CM7 的低延迟指令存储 |
| DTCM RAM | 0x20000000 |
128 KB | CM7 的低延迟数据、栈和变量 |
| Flash Bank 1 | 0x08000000 |
1 MB | CM7 程序和数据 |
| Flash Bank 2 | 0x08100000 |
1 MB | CM4 程序和数据,或其他 Flash 分区 |
| CM4/D2 SRAM 访问区 | 0x10000000 |
288 KB | CM4 程序、数据和栈的常用地址别名 |
| AXI SRAM / D1 SRAM | 0x24000000 |
512 KB | CM7 的大容量高速 RAM |
| D2 SRAM1 | 0x30000000 |
128 KB | 双核可访问的数据 SRAM |
| D2 SRAM2 | 0x30020000 |
128 KB | 双核可访问的数据 SRAM |
| D2 SRAM3 | 0x30040000 |
32 KB | D2 域共享数据或通信缓冲区 |
| D3 SRAM4 | 0x38000000 |
64 KB | 低功耗域 RAM、双核共享内存 |
| Backup SRAM | 0x38800000 |
4 KB | 备份域掉电保持数据 |
2.1 Flash 地址分区
芯片地址空间是硬件提供的范围,工程是否使用某一段地址,最终由链接脚本和启动代码决定。下面的分区以默认 Makefile 和链接脚本为准。
| 区域 | 起始地址 | 大小 | 作用 |
|---|---|---|---|
| CM7 Bootloader | 0x08000000 | 128 KB | CM7 引导程序/启动入口 |
| CM7 应用 Slot A | 0x08020000 | 384 KB | CM7 应用 A 固件区/运行槽 |
| 预留区域 | 0x08080000 | 128 KB | 预留区域,暂未分配 |
| CM7 应用 Slot B | 0x080A0000 | 384 KB | CM7 应用 B 固件区/运行槽 |
| CM4 Bootloader Stub | 0x08100000 | 128 KB | CM4 固定跳板/启动跳转入口 |
| CM4 应用 Slot A | 0x08120000 | 384 KB | CM4 应用 A 固件区/运行槽 |
| 预留区域 | 0x08180000 | 128 KB | 预留区域,暂未分配 |
| CM4 应用 Slot B | 0x081A0000 | 384 KB | CM4 应用 B 固件区/运行槽 |
A、B 是两个不同的链接地址。CM7 应用 A 编译出来的镜像,不能只通过修改烧录地址就当成 Slot B 镜像使用。Slot B 使用了单独的链接脚本。
2.3 向量表
Cortex-M 应用的向量表通常至少包含:
| 偏移地址(相对于向量表基址) | 内容含义 |
|---|---|
| 向量表地址 + 0x00 | 初始 MSP |
| 向量表地址 + 0x04 | Reset_Handler 地址 |
| 向量表地址 + 0x08 | NMI_Handler 地址 |
| 向量表地址 + 0x0C | HardFault_Handler 地址 |
例如 CM7 应用位于:
0x08020000
那么 Bootloader 跳转前需要做:
app_base = 0x08020000
initial_msp = *(uint32_t *)(app_base + 0x00)
reset_handler = *(uint32_t *)(app_base + 0x04)
SCB->VTOR = app_base
MSP = initial_msp
跳转到 reset_handler
- VTOR 指向新应用的向量表,确保中断从应用自己的向量表获取处理函数;
- MSP 设置为应用向量表第一个字中的初始主栈地址,确保应用从正确的栈开始执行;
- 然后跳转到向量表第二个字中的 Reset_Handler。
一句话概括:
Bootloader 跳转应用时,需要同时完成向量表重定位和运行栈切换:通过 SCB->VTOR 让中断使用应用的向量表,通过 MSP 让应用使用链接脚本为自己准备的初始主栈,最后跳转到应用的 Reset_Handler。
如果只跳到 reset_handler,但不修改 VTOR,应用运行后产生的中断仍可能从 Bootloader 的向量表取处理函数,导致系统异常。
其中关键动作包括:
- 关闭中断;
- 停止 SysTick;
- 禁用和清理 NVIC;
- 写入 DTCM 跳转标记;
- 设置 SCB->VTOR;
- 设置 MSP;
- 跳转到目标向量表第二项。
3. 链接文件
在写 Bootloader 代码之前,必须先规划好链接文件,否则很容易出现内存错误导致进入硬件错误中断函数中。
3.1 链接文件规划
由于当前 Bootloader 的策略是双分区 Bootloader —— CM7 和 CM4 各有 A/B 两槽,共 4 个应用槽,由 EEPROM 里的一份元数据统一管理状态。
| 维度 | 单分区 Bootloader | 本工程(双分区 A/B) |
|---|---|---|
| APP 区 | 1 个,OTA 就地擦写 | 2 个,OTA 写入非活动槽 |
| 升级中断电 | APP 区被擦了一半 → 变砖,须串口/ROM 救砖 | 新镜像写一半只影响非活动槽,旧槽完好,照常启动 |
| 升级失败回退 | 无法回退,只能重刷 | EEPROM 标记 pending + 3 次试启动,失败自动回退 active |
| 状态存放 | 基本不需要(固定跳转) | 必须有掉电不丢的状态记录 → 本工程用 EEPROM 双备份存 active/pending/trial |
| Boot 逻辑 | 检查 APP 有效就跳 | 选槽链 pending→active→A→B + 试启动扣减 + 落盘 |
| Flash 代价 | APP 只占 1 份空间 | APP 占 2 份(384Kx2/核),换升级安全 |
| 双核复杂度 | - | CM7/CM4 各自独立 A/B,元数据 struct_ver=2 里存两套状态,两核槽位需配套升级 |
单分区是"升级必须成功",双分区是"升级可以失败"--空间换安全,本工程再叠加双核后变成 4 槽状态管理,这也是必须引入 EEPROM 双备份元数据的根因。
4. Bootloader 运行流程
芯片复位
↓
CM7 Bootloader 启动
↓
初始化时钟、串口、I2C、EEPROM
↓
读取 EEPROM 启动记录
↓
选择 CM7 应用槽位
pending → active → Slot A → Slot B
↓
检查 CM7 应用向量表
MSP 地址、Reset_Handler 地址、Thumb 位
↓
无效 ─────────────→ 停留在 CM7 Bootloader
│
│ 有效
↓
禁止 CM4 从固定入口启动
↓
关闭中断、停止 SysTick、清除 NVIC
↓
设置 CM7 VTOR 和 MSP
↓
跳转到 CM7 应用 Reset_Handler
↓
CM7 应用启动
↓
初始化 MPU、Cache
↓
清空 SRAM4 共享区
↓
读取 EEPROM 中的 CM4 启动记录
↓
选择 CM4 应用槽位
pending → active → Slot A → Slot B
↓
将 CM4 应用地址写入 SRAM4
↓
写入 handoff magic
↓
使能 CM4 BOOT_C2
↓
CM4 Stub 启动
↓
读取 SRAM4 handoff 信息
↓
检查 CM4 应用向量表
↓
无效 ─────────────→ CM4 Stub 停留
│
│ 有效
↓
关闭中断、停止 SysTick
↓
设置 CM4 VTOR 和 MSP
↓
跳转到 CM4 应用 Reset_Handler
↓
CM4 应用启动
↓
进入 D2 STOP,等待 CM7 的 HSEM 唤醒
↓
CM7 释放 HSEM
↓
CM4 被唤醒
↓
CM4 初始化 IPC 和 FreeRTOS
↓
双核应用正常运行
也可以压缩成一张总图:
┌────────┐
│芯片复位 │
└───┬────┘
↓
┌──────────────┐
│CM7 Bootloader│
│ 读取 EEPROM │
│ 选择 CM7 A/B │
└───┬──────────┘
↓
┌──────────────┐
│检查 CM7 向量表│
└───┬───────┬──┘
│有效 │无效
↓ ↓
┌────────┐ ┌──────────────┐
│跳转 CM7│ │停留 Bootloader│
│应用 │ └──────────────┘
└───┬────┘
↓
┌────────────────────┐
│ CM7 应用 │
│ 清空 SRAM4 │
│ 选择 CM4 A/B │
│ 写入 handoff 信息 │
│ 使能 CM4 BOOT_C2 │
└───┬────────────────┘
↓
┌───────────────┐
│ CM4 Boot Stub │
│ 读取 SRAM4 │
│ 选择 CM4 应用 │
└───┬───────────┘
↓
┌──────────────┐
│检查 CM4 向量表│
└───┬───────┬──┘
│有效 │无效
↓ ↓
┌────────┐ ┌──────────┐
│跳转 CM4│ │停留 Stub │
│应用 │ └──────────┘
└───┬────┘
↓
┌──────────────┐
│ CM4 应用 │
│ D2 STOP 等待 │
│ HSEM 唤醒 │
│ 初始化 IPC │
│ 启动 FreeRTOS │
└──────────────┘
5. Bootloader 侧程序
5.1 CM7-main.c
这部分主要做这些事情
初始化硬件
↓
读取 EEPROM 中的启动元数据
↓
判断 CM7 Slot A/B 哪个可以启动
↓
检查目标槽位的向量表
↓
关闭当前 Bootloader 运行环境
↓
设置应用的 VTOR 和 MSP
↓
跳转到应用 Reset_Handler
详细代码如下:
#include "main.h"
#include "dma.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "boot_slot.h"
#include "boot_config.h"
#include "boot_jump.h"
#include "mylog.h"
#include "bsp_eeprom.h"
#include "boot_eeprom.h"
static boot_eeprom_meta_t ee; // EEPROM 中的 active、pending、试启动次数等记录
static uint8_t boot_slot; // 本次选中的 CM7 槽号
static uint32_t app_base; // 本次选中的 CM7 应用 Flash 起始地址
static volatile uint8_t cm7_slot_a_ok; // CM7 A 槽向量表检查结果
static volatile uint8_t cm7_slot_b_ok; // CM7 B 槽向量表检查结果
static volatile uint8_t cm4_slot_a_ok; // CM4 A 槽向量表检查结果
static volatile uint8_t cm4_slot_b_ok; // CM4 B 槽向量表检查结果
int main(void)
{
int32_t timeout;
/* 等待 CM4 进入 STOP,超时后继续启动,避免 CM4 未烧录时卡住 */
timeout = 0xFFFF; // 限制等待次数
while ((__HAL_RCC_GET_FLAG(RCC_FLAG_D2CKRDY) != RESET) && (timeout-- > 0))
; // D2 时钟仍就绪说明 CM4 尚未进入 STOP
if (timeout < 0)
{
/* CM4 未进 STOP;跳转函数随后会禁止 CM4 从固定入口启动 */
}
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
MX_I2C4_Init();
/* USER CODE BEGIN 2 */
printf("\r\n");
printf("\033[1;36m"); // 加粗;蓝色
printf(" ____ ___ ___ _____ _ ___ _ ____ _____ ____ \n");
printf("| __ ) / _ \\ / _ \\|_ _| | / _ \\ / \\ | _ \\| ____| _ \\ \n");
printf("| _ \\| | | | | | | | | | | | | | |/ _ \\ | | | | _| | |_) | \n");
printf("| |_) | |_| | |_| | | | | |___| |_| / ___ \\| |_| | |___| _ < \n");
printf("|____/ \\___/ \\___/ |_| |_____|\\___/_/ \\_\\____/|_____|_| \\_\\ \n");
printf("\r\n");
/* 两段预留区大小由相邻应用槽地址算出,仅用于打印分区表 */
const uint32_t cm7_reserve_size =
CM7_APP_SLOT_B_BASE - (CM7_APP_SLOT_A_BASE + CM7_APP_SLOT_A_SIZE);
const uint32_t cm4_reserve_size =
CM4_APP_SLOT_B_BASE - (CM4_APP_SLOT_A_BASE + CM4_APP_SLOT_A_SIZE);
printf("BOOT Tag: %s\r\n", BOOT_FW_TAG); // 打印固件标签
printf("\n");
/* 打印双核 Flash 分区,便于对照实际烧录位置 */
printf("============= flash partition table ==============\r\n");
printf("---------------- CM7 (Bank1, 1MB) ----------------\r\n");
printf("| name | offset | size | value |\r\n");
printf("--------------------------------------------------\r\n");
printf("| bootloader | 0x%08lx | 0x%08x | %dKB |\r\n", (unsigned long)CM7_BOOT_FLASH_BASE, CM7_BOOT_FLASH_SIZE, CM7_BOOT_FLASH_SIZE / 1024U);
printf("| slot A | 0x%08lx | 0x%08x | %dKB |\r\n", (unsigned long)CM7_APP_SLOT_A_BASE, CM7_APP_SLOT_A_SIZE, CM7_APP_SLOT_A_SIZE / 1024U);
printf("| reserve(S4) | 0x%08lx | 0x%08x | %dKB |\r\n", (unsigned long)(CM7_APP_SLOT_A_BASE + CM7_APP_SLOT_A_SIZE), (unsigned int)cm7_reserve_size, (int)(cm7_reserve_size / 1024U));
printf("| slot B | 0x%08lx | 0x%08x | %dKB |\r\n", (unsigned long)CM7_APP_SLOT_B_BASE, CM7_APP_SLOT_B_SIZE, CM7_APP_SLOT_B_SIZE / 1024U);
printf("---------------- CM4 (Bank2, 1MB) ----------------\r\n");
printf("| name | offset | size | value |\r\n");
printf("--------------------------------------------------\r\n");
printf("| stub | 0x%08lx | 0x%08x | %dKB |\r\n", (unsigned long)CM4_BOOT_STUB_BASE, CM4_BOOT_STUB_SIZE, CM4_BOOT_STUB_SIZE / 1024U);
printf("| slot A | 0x%08lx | 0x%08x | %dKB |\r\n", (unsigned long)CM4_APP_SLOT_A_BASE, CM4_APP_SLOT_A_SIZE, CM4_APP_SLOT_A_SIZE / 1024U);
printf("| reserve(S4) | 0x%08lx | 0x%08x | %dKB |\r\n", (unsigned long)(CM4_APP_SLOT_A_BASE + CM4_APP_SLOT_A_SIZE), (unsigned int)cm4_reserve_size, (int)(cm4_reserve_size / 1024U));
printf("| slot B | 0x%08lx | 0x%08x | %dKB |\r\n", (unsigned long)CM4_APP_SLOT_B_BASE, CM4_APP_SLOT_B_SIZE, CM4_APP_SLOT_B_SIZE / 1024U);
printf("==================================================\r\n");
printf("USART1 Baudrate: 115200, Pins: PA9/PA10\r\n"); // 打印 USART1 配置
printf("EEPROM Peripheral: I2C4, Pins: PD12/PD13\r\n"); // 打印 EEPROM 配置
printf("\r\n");
int ee_load_ret; // 区分 EEPROM 已有启动记录与首次使用
if (bsp_eeprom_init() == 0) // 初始化板载 EEPROM;成功返回 0
{
boot_eeprom_init(); // 准备 Boot 元数据的 EEPROM 访问
ee_load_ret = boot_eeprom_load(&ee); // 从双备份读取启动记录;无有效副本时填入默认值
if (ee_load_ret == 1) // 无有效启动记录,首次写入默认值
{
LOG_INFO("BOOT", "EEPROM empty, saving default metadata"); // 双备份均无效,提示写入首份默认记录
if (boot_eeprom_save(&ee) == 0) // 将默认启动记录写入 EEPROM
{
LOG_INFO("BOOT", "EEPROM default metadata saved"); // 默认元数据落盘成功
}
else // 保存失败:EEPROM 掉线或写超时
{
LOG_ERROR("BOOT", "EEPROM default metadata save failed"); // 落盘失败:本次仍按内存默认值继续,下次开机重试
}
}
}
/* 仅检查栈指针和复位入口等向量表条件,不计算整个镜像的 CRC */
cm7_slot_a_ok = (uint8_t)boot_slot_is_valid(CM7_APP_SLOT_A_INDEX); // 检查 CM7 A 槽向量表
cm7_slot_b_ok = (uint8_t)boot_slot_is_valid(CM7_APP_SLOT_B_INDEX); // 检查 CM7 B 槽向量表
if (cm7_slot_a_ok)
LOG_INFO("BOOT", "CM7 slot A: valid");
else
LOG_WARN("BOOT", "CM7 slot A: invalid");
if (cm7_slot_b_ok)
LOG_INFO("BOOT", "CM7 slot B: valid");
else
LOG_WARN("BOOT", "CM7 slot B: invalid");
cm4_slot_a_ok = (uint8_t)boot_slot_is_valid(CM4_APP_SLOT_A_INDEX); // 检查 CM4 A 槽向量表,仅用于打印
cm4_slot_b_ok = (uint8_t)boot_slot_is_valid(CM4_APP_SLOT_B_INDEX); // 检查 CM4 B 槽向量表,仅用于打印
if (cm4_slot_a_ok)
LOG_INFO("BOOT", "CM4 slot A: valid");
else
LOG_WARN("BOOT", "CM4 slot A: invalid");
if (cm4_slot_b_ok)
LOG_INFO("BOOT", "CM4 slot B: valid");
else
LOG_WARN("BOOT", "CM4 slot B: invalid");
LOG_INFO("BOOT", "CM7 EE: active=%u pending=%u trial=%u flags=0x%02X seq=%lu", // 打印 CM7 EEPROM 元数据
(unsigned int)ee.active_slot, // 激活槽位索引
(unsigned int)ee.pending_slot, // 待处理槽位索引
(unsigned int)ee.boot_trial, // 启动试用次数
(unsigned int)ee.flags, // 标志位
(unsigned long)ee.seq); // 序列号
LOG_INFO("BOOT", "CM7 EE ver A=%lu B=%lu | slotA valid=%u slotB valid=%u", // 打印 EEPROM 应用版本
(unsigned long)ee.app_version[CM7_APP_SLOT_A_INDEX], // 应用槽位 A 版本号
(unsigned long)ee.app_version[CM7_APP_SLOT_B_INDEX], // 应用槽位 B 版本号
(unsigned int)cm7_slot_a_ok, // 检查槽位 A 是否有效
(unsigned int)cm7_slot_b_ok); // 检查槽位 B 是否有效
LOG_INFO("BOOT", "CM4 EE: active=%u pending=%u trial=%u flags=0x%02X | ver A=%lu B=%lu", // 打印 CM4 EEPROM 元数据
(unsigned int)ee.cm4_active_slot, // 激活槽位索引
(unsigned int)ee.cm4_pending_slot, // 待处理槽位索引
(unsigned int)ee.cm4_boot_trial, // 启动试用次数
(unsigned int)ee.cm4_flags, // 标志位
(unsigned long)ee.cm4_app_version[CM4_APP_SLOT_A_INDEX - CM4_APP_SLOT_A_INDEX], // 应用槽位 A 版本号
(unsigned long)ee.cm4_app_version[CM4_APP_SLOT_B_INDEX - CM4_APP_SLOT_A_INDEX]); // 应用槽位 B 版本号
/* 按 pending、active、A、B 的顺序选 CM7 槽;试启动可能扣减次数并回退 */
boot_slot = boot_eeprom_prepare_boot(&ee); // 选择启动槽,并在需要时更新 EEPROM 试启动状态
app_base = boot_slot_base_get(boot_slot); // 将槽号换算为应用向量表的 Flash 地址
if (boot_app_is_valid(app_base)) // 再次确认目标槽地址及向量表符合启动条件
{
uint32_t sp = *(__IO uint32_t *)app_base; // 向量表第 1 项:应用启动时的栈顶 MSP
uint32_t pc = *(__IO uint32_t *)(app_base + 4U); // 向量表第 2 项:应用的 Reset_Handler 地址
LOG_INFO("BOOT", "jump slot %u @ 0x%08lX", // 打印要启动的槽号和地址
(unsigned int)boot_slot, (unsigned long)app_base);
LOG_INFO("BOOT", "vec SP=0x%08lX Reset=0x%08lX", // 打印应用栈顶和复位入口
(unsigned long)sp, (unsigned long)pc);
LOG_INFO("BOOT", "hold CM4 reset, then jump..."); // 提示跳转时将禁止 CM4 从固定入口启动
boot_jump_to_app(app_base); // 清理 Boot 中断、切换栈和向量表,跳到应用
LOG_ERROR("BOOT", "ERROR: Jump returned (should never happen)"); // 正常跳转不返回;返回说明跳转异常
}
else
{
/* 无法启动所选 CM7 槽:打印 A/B 向量表帮助定位,随后留在 Bootloader */
LOG_ERROR("BOOT", "no valid APP (tried slot %u @ 0x%08lX), stay in Boot.", // 报告失败槽号及地址
(unsigned int)boot_slot, (unsigned long)app_base);
LOG_INFO("BOOT", " A: SP=0x%08lX Reset=0x%08lX valid=%u", // A 槽栈顶、复位入口及检查结果
(unsigned long)(*(__IO uint32_t *)CM7_APP_SLOT_A_BASE), // A 槽向量表第 1 项
(unsigned long)(*(__IO uint32_t *)(CM7_APP_SLOT_A_BASE + 4U)), // A 槽向量表第 2 项
(unsigned int)cm7_slot_a_ok);
LOG_INFO("BOOT", " B: SP=0x%08lX Reset=0x%08lX valid=%u", // B 槽栈顶、复位入口及检查结果
(unsigned long)(*(__IO uint32_t *)CM7_APP_SLOT_B_BASE), // B 槽向量表第 1 项
(unsigned long)(*(__IO uint32_t *)(CM7_APP_SLOT_B_BASE + 4U)), // B 槽向量表第 2 项
(unsigned int)cm7_slot_b_ok);
}
while (1)
{
}
}
5.2 CM4-main.c
这部分主要做这些事:
初始化必要环境
↓
从 SRAM4 读取 CM7 写入的交接信息
↓
如果交接信息无效,则使用默认 CM4 Slot A
↓
检查 CM4 应用向量表
↓
设置 CM4 的 VTOR 和 MSP
↓
跳转 CM4 应用
详细代码如下:
#include "main.h"
#include "dma.h"
#include "gpio.h"
#include "cm4_boot_handoff.h"
int main(void)
{
HAL_Init();
MX_GPIO_Init();
MX_DMA_Init();
/* 不进 STOP:CM7 已在 EnableBootCore 前写好 handoff;
* App 自己会再进 STOP 等 HSEM。 */
(void)cm4_boot_handoff_jump_from_d3();
/* 跳转失败:停在此,便于调试 */
__disable_irq();
while (1)
{
}
}
6. APP 侧 Bootloader 程序
CM7 APP 侧除了自己的业务初始化,还负责 CM4 启动协调:
6.1 CM7-main.c
等待 CM4 进入预期状态
↓
初始化 CM7 的 HAL、MPU、Cache
↓
清理 SRAM4
↓
读取 EEPROM
↓
选择 CM4 应用槽位
↓
将 CM4 应用地址写入 SRAM4
↓
使能 CM4 BOOT_C2
↓
初始化 OpenAMP 和业务系统
详细代码如下:
#include "main.h"
#include "FreeRTOS.h"
#include "cmsis_os2.h"
#include "dcmi.h"
#include "dma.h"
#include "dma2d.h"
#include "i2c.h"
#include "jpeg.h"
#include "ltdc.h"
#include "lwip.h"
#include "mdma.h"
#include "openamp.h"
#include "usart.h"
#include "gpio.h"
#include "fmc.h"
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "mpu.h"
#include "bsp_sys.h"
#include "sdram.h"
#include "lcd.h"
#include "app_ipc_cm7.h"
#include "app_ov5640.h"
#include "boot_slot.h"
#include "cm4_boot_handoff.h"
#include "mylog.h"
#include "bsp_eeprom.h"
#include "boot_eeprom.h"
#include "eth.h"
#include "eth_phy.h"
/* DUAL_CORE_BOOT_SYNC_SEQUENCE: Define for dual core boot synchronization */
/* demonstration code based on hardware semaphore */
/* This define is present in both CM7/CM4 projects */
/* To comment when developping/debugging on a single core */
#define DUAL_CORE_BOOT_SYNC_SEQUENCE
#if defined(DUAL_CORE_BOOT_SYNC_SEQUENCE)
#ifndef HSEM_ID_0
#define HSEM_ID_0 (0U) /* HW semaphore 0*/
#endif
#endif /* DUAL_CORE_BOOT_SYNC_SEQUENCE */
void SystemClock_Config(void);
void MX_FREERTOS_Init(void);
static void ipc_share_region_clear(void)
{
memset((void *)0x38000000, 0, 0x00010000); // 启动前清空 64KB SRAM4;后续会在这里写 CM4 handoff
}
int main(void)
{
#if defined(DUAL_CORE_BOOT_SYNC_SEQUENCE)
int32_t timeout;
#endif
#if defined(DUAL_CORE_BOOT_SYNC_SEQUENCE)
/* Wait until CPU2 boots and enters in stop mode or timeout*/
timeout = 0xFFFF;
while ((__HAL_RCC_GET_FLAG(RCC_FLAG_D2CKRDY) != RESET) && (timeout-- > 0))
;
if (timeout < 0)
{
// Error_Handler();
}
#endif
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
__enable_irq();
memory_protection_unit_init(); // 初始化内存保护单元
scb_cache_init(); // 初始化缓存
ipc_share_region_clear(); // 先清共享内存,再写入 CM4 启动地址
SystemClock_Config();
#if defined(DUAL_CORE_BOOT_SYNC_SEQUENCE)
/* When system initialization is finished, Cortex-M7 will release Cortex-M4 by means of
HSEM notification */
/*HW semaphore Clock enable*/
__HAL_RCC_HSEM_CLK_ENABLE();
/*Take HSEM */
HAL_HSEM_FastTake(HSEM_ID_0);
/*Release HSEM in order to notify the CPU2(CM4)*/
HAL_HSEM_Release(HSEM_ID_0, 0);
#endif
MX_GPIO_Init();
MX_MDMA_Init();
MX_DMA_Init();
MX_FMC_Init();
MX_LTDC_Init();
MX_JPEG_Init();
MX_DMA2D_Init();
MX_DCMI_Init();
MX_USART1_UART_Init();
MX_I2C4_Init();
{ /* 从 EEPROM 读取启动记录,并通知 CM4 Stub 启动哪个应用槽 */
boot_eeprom_meta_t ee; // 保存 EEPROM 中的 active、pending 等启动记录
uint8_t running = boot_detect_slot_running(); // 根据当前运行地址判断 CM7 位于 A 槽还是 B 槽
uint8_t cm4_slot = CM4_APP_SLOT_A_INDEX; // EEPROM 不可用时,CM4 默认从 A 槽启动
uint32_t cm4_base = CM4_APP_SLOT_A_BASE; // CM4 默认应用的 Flash 起始地址
int ee_ok = 0; // 记录本次是否成功初始化 EEPROM
printf("\033[1;36m"); // 加粗;青色
printf(" _ ____ ____ _ ___ ____ _ _____ ___ ___ _ _ \n");
printf(" / \\ | _ \\ | _ \\ | | |_ _| / ___| / \\ |_ _||_ _| / _ \\ | \\ | |\n");
printf(" / _ \\ | |_) || |_) || | | | | | / _ \\ | | | | | | | || \\| |\n");
printf(" / ___ \\ | __/ | __/ | |___ | | | |___ / ___ \\ | | | | | |_| || |\\ |\n");
printf("/_/ \\_\\|_| |_| |_____||___| \\____|/_/ \\_\\ |_| |___| \\___/ |_| \\_|\n");
printf("\n");
if (bsp_eeprom_init() == 0) // 初始化板载 EEPROM,成功时返回 0
{
(void)boot_eeprom_init(); // 准备 Boot 元数据的 EEPROM 访问
(void)boot_eeprom_load(&ee); // 从双备份读取启动记录;无有效副本时使用默认值
ee_ok = 1; // EEPROM 可访问,后续按启动记录选择 CM4 槽
/* 打印当前 CM7 槽和 EEPROM 记录,便于核对启动状态 */
LOG_INFO("APP", "CM7 running slot=%u base=0x%08lX | EE active=%u pending=%u flags=0x%02X",
(unsigned int)running,
(unsigned long)boot_slot_base_get(running),
(unsigned int)ee.active_slot,
(unsigned int)ee.pending_slot,
(unsigned int)ee.flags);
/* 打印 CM4 的启动槽和两个应用版本 */
LOG_INFO("APP", "CM4 EE active=%u pending=%u flags=0x%02X verA=%lu verB=%lu",
(unsigned int)ee.cm4_active_slot,
(unsigned int)ee.cm4_pending_slot,
(unsigned int)ee.cm4_flags,
(unsigned long)ee.cm4_app_version[CM4_APP_SLOT_A_INDEX - CM4_APP_SLOT_A_INDEX],
(unsigned long)ee.cm4_app_version[CM4_APP_SLOT_B_INDEX - CM4_APP_SLOT_A_INDEX]);
/* 本次确实运行在待确认槽时,将其设为正式启动槽,停止试启动回退 */
if (((ee.flags & BOOT_EE_FLAG_PENDING) != 0U) &&
(ee.pending_slot == running))
{
boot_eeprom_commit_pending(&ee); // 更新 active 并清除 pending,写回 EEPROM
LOG_INFO("APP", "CM7 OTA pending committed -> active=%u",
(unsigned int)ee.active_slot);
}
cm4_slot = boot_eeprom_prepare_cm4_boot(&ee); // 按 pending、active、兜底顺序选择 CM4 槽,并处理试启动次数
cm4_base = boot_slot_base_get(cm4_slot); // 将 CM4 槽号换算成 Flash 起始地址
(void)bsp_eeprom_selftest(); // 检查 EEPROM 用户区读写,不参与本次槽位选择
}
else
{
LOG_ERROR("APP", "CM7 EEPROM init fail, CM4 default Slot A"); // 初始化失败时沿用前面设置的 CM4 A 槽默认值
}
cm4_boot_handoff_write(cm4_base); // 将 CM4 应用地址写入 SRAM4,供固定 Stub 读取
LOG_INFO("APP", "CM4 handoff slot=%u base=0x%08lX ee=%d",
(unsigned int)cm4_slot,
(unsigned long)cm4_base,
ee_ok);
/* 使能 CM4 后,Bank2 固定 Stub 读取 handoff 并跳转到所选应用槽 */
HAL_RCCEx_EnableBootCore(RCC_BOOT_C2);
LOG_INFO("APP", "CM4 boot core enabled");
}
app_ipc_cm7_init(); // 初始化 IPC 模块
sdram_init(); // 初始化 SDRAM
LOG_INFO("CM7", "main() user init OK");
osKernelInitialize(); /* Call init function for freertos objects (in cmsis_os2.c) */
MX_FREERTOS_Init();
osKernelStart();
while (1)
{
}
}
6.2 CM4-main.c
由于是最后被启动的,这里不负责 Bootloader 的任何操作。
详细代码如下:
#include "main.h"
#include "FreeRTOS.h"
#include "cmsis_os2.h"
#include "dma.h"
#include "mdma.h"
#include "openamp.h"
#include "gpio.h"
#include <stdio.h>
#include <string.h>
#include "app_ipc_cm4.h"
#include "mylog.h"
/* DUAL_CORE_BOOT_SYNC_SEQUENCE: Define for dual core boot synchronization */
/* demonstration code based on hardware semaphore */
/* This define is present in both CM7/CM4 projects */
/* To comment when developping/debugging on a single core */
#define DUAL_CORE_BOOT_SYNC_SEQUENCE
#if defined(DUAL_CORE_BOOT_SYNC_SEQUENCE)
#ifndef HSEM_ID_0
#define HSEM_ID_0 (0U) /* HW semaphore 0*/
#endif
#endif /* DUAL_CORE_BOOT_SYNC_SEQUENCE */
void MX_FREERTOS_Init(void);
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
#if defined(DUAL_CORE_BOOT_SYNC_SEQUENCE)
/*HW semaphore Clock enable*/
__HAL_RCC_HSEM_CLK_ENABLE();
/* Activate HSEM notification for Cortex-M4*/
HAL_HSEM_ActivateNotification(__HAL_HSEM_SEMID_TO_MASK(HSEM_ID_0));
/*
Domain D2 goes to STOP mode (Cortex-M4 in deep-sleep) waiting for Cortex-M7 to
perform system initialization (system clock config, external memory configuration.. )
*/
HAL_PWREx_ClearPendingEvent();
HAL_PWREx_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFE, PWR_D2_DOMAIN);
/* Clear HSEM flag */
__HAL_HSEM_CLEAR_FLAG(__HAL_HSEM_SEMID_TO_MASK(HSEM_ID_0));
#endif
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* D2 STOP 唤醒后刷新 CM4 侧 SystemCoreClock,并重建 SysTick */
SystemCoreClockUpdate();
(void)HAL_InitTick(TICK_INT_PRIORITY);
MX_GPIO_Init();
MX_MDMA_Init();
MX_DMA_Init();
app_ipc_cm4_init(); // 初始化 IPC 模块
LOG_INFO("CM4", "main() user init OK");
osKernelInitialize(); /* Call init function for freertos objects (in cmsis_os2.c) */
MX_FREERTOS_Init();
osKernelStart();
while (1)
{
}
}
-. eeprom 运行流程
EEPROM 是 A/B 双槽 OTA 的"掉电不丢的启动大脑",核心价值是升级失败能自动回退、写入中断不损坏。
| 作用 | 说明 |
|---|---|
| 记录启动状态 | 哪个槽是 active(当前稳定版)、哪个是 pending(OTA 待验证版),Flash 里放不下这个状态,必须存在 EEPROM |
| 防变砖(试启动回退) | OTA 后先标记 pending + 3 次试启动额度;新固件跑不起来就没机会 commit,3 次用尽自动退回旧版本 |
| 断电安全 | 双备份 Copy0/Copy1 + seq 轮换写 + CRC32:写到一半掉电,另一份副本仍是完整的,下次开机照常启动 |
| 镜像元数据台账 | 记录 A/B 槽的版本号、大小、CRC32,供升级判断与日志核对 |
| 双核共用 | 一份结构同时存 CM7 和 CM4 的 A/B 槽状态,两核升级状态统一管理 |
没有它也能启动(代码里 EEPROM 失败会兜底走 A 槽),但会失去 OTA 自动回退和断电保护——只能永远启动固定槽。
开机侧(CM7 Bootloader,main 函数)
MX_I2C4_Init() I2C4 总线就绪
↓
bsp_eeprom_init() BL24C16F_Init + IsReady 探测 ACK
├─ 失败 ──→ 跳过整个 EEPROM 分支,ee 用内存零值兜底(active=A)
↓ 成功
boot_eeprom_init() 置 s_ee_ready=1
↓
boot_eeprom_load(&ee) 读 Copy0@0x0000 与 Copy1@0x0100
├─ 每份:v2 校验(magic/ver/len/槽号/CRC32) ──失败──→ 试 v1 校验并内存升级
├─ 双有效 → 取 seq 大者
├─ 单有效 → 取该份
└─ 全无效 → 返回 1
↓ 返回 1
boot_eeprom_save(&ee) 写入首份默认值(seq++、轮换写较旧/无效那份)
↓
打印 EE 状态(active/pending/trial/flags/seq/版本)
↓
boot_eeprom_prepare_boot(&ee) 选槽:pending → active → A → B
├─ 若本次启动 pending 槽:trial--
│ └─ trial 扣到 0:清 PENDING、回退 active、save 落盘
↓
boot_slot_base_get → boot_app_is_valid → boot_jump_to_app()
写侧(OTA 时由 APP 调用,与开机侧闭环):
APP 写完新镜像 → boot_eeprom_set_pending() 记录镜像信息 + trial=3 + 置 PENDING → save
↓ 复位
Boot 试启动 pending 槽(每次 trial--,结果 save)
├─ APP 自检通过 → boot_eeprom_commit_pending() pending 转正 active → save
└─ 3 次都没 commit → trial 用尽 → 自动回退旧 active 版本
博客导航
本文来自博客园,作者:膝盖中箭卫兵,转载请注明原文链接:https://www.cnblogs.com/Skyrim-sssuuu/p/23154549

浙公网安备 33010602011771号
https://orcid.org/0000-0001-5102-772X