【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 版本

博客导航

博客导航

posted @ 2026-09-29 11:53  膝盖中箭卫兵  阅读(10)  评论(0)    收藏  举报
ORCID iD icon https://orcid.org/0000-0001-5102-772X