4. 多核启动流程:BootROM执行、M7内核启动、M4内核启动、双核握手协议

好,咱们今天聊点硬核的——多核启动。说实话,我第一次接触STM32H7的双核启动时,也被绕进去过。M7和M4两个核,谁先跑?谁后跑?怎么保证它们不打架?这些坑,我一个个踩过,今天全给你抖出来。

4.1 BootROM到底干了什么

芯片上电后,第一个执行的代码不是你的应用程序,而是芯片内部固化的BootROM。这玩意儿是出厂就写死的,你改不了,但得理解它。

BootROM的执行流程,说白了就三步:

  1. 硬件初始化:配置系统时钟、电源管理、Flash控制器等基础外设
  2. 启动模式判断:根据BOOT0/BOOT1引脚电平,决定从Flash、系统存储器还是SRAM启动
  3. 跳转执行:找到合法启动地址后,跳转到用户代码

我当年做第一个双核项目时,以为BootROM会自动把两个核都初始化好。结果发现——它只管M7。M4?你得自己想办法唤醒它。

关键点:BootROM只负责M7内核的启动。M4内核在复位后处于停机状态,需要M7通过系统控制寄存器主动唤醒。

4.2 M7内核启动流程

M7是老大,它先跑。启动流程大致是这样的:

步骤 操作 说明
1 复位向量表加载 从0x08000000(Flash起始)读取栈指针和复位向量
2 SystemInit() 配置系统时钟、PLL、Flash等待周期
3 __main() / main() 进入C运行时环境,初始化BSS段、数据段
4 用户main() 执行你的业务逻辑

嗯,这里要注意一个细节:M7的向量表必须放在0x08000000,这是硬件规定的。如果你用IAP或者Bootloader,得通过SCB->VTOR寄存器重映射向量表。

个人习惯:我一般会在SystemInit()的最后加一个LED闪烁,用来确认M7已经跑起来了。调试时特别有用,一眼就能看出系统卡在哪一步。

4.3 M4内核启动流程

M4的启动,完全由M7控制。你想想看,M4连复位向量都没地方取,它怎么跑?

M7唤醒M4的标准流程:

  1. 加载M4固件:M7从Flash或外部存储器读取M4的二进制代码
  2. 设置M4向量表:通过RCC->GCR寄存器配置M4的启动地址
  3. 释放M4复位:清除RCC->GCR中的M4复位位
  4. 等待M4就绪:通过握手标志确认M4已正常运行

代码实现大概是这样的:

/* M7代码:唤醒M4内核 */
#define M4_BOOT_ADDRESS   0x08100000  /* M4固件存放地址 */

void Start_M4_Core(void)
{
    /* 1. 设置M4的向量表偏移 */
    RCC->GCR |= (M4_BOOT_ADDRESS & 0x1FFFFF80);
    
    /* 2. 释放M4复位 */
    RCC->GCR &= ~RCC_GCR_BOOT_C2;
    
    /* 3. 等待M4启动完成 */
    while(!(RCC->GCR & RCC_GCR_C2_BOOT));
    
    /* 4. 设置握手标志 */
    SHARED_MEM->m7_ready = 1;
}

我曾经犯过一个低级错误:M4的固件地址没做对齐。结果M4启动后直接跑飞,查了两天才发现是地址没按128字节对齐。嗯,这个坑你们别踩。

注意:M4的启动地址必须128字节对齐(低7位为0)。否则硬件会忽略你设置的值,M4永远起不来。

4.4 双核握手协议

两个核都跑起来了,然后呢?它们得互相打个招呼,确认对方活着。这就是握手协议。

我常用的握手协议分三个阶段:

阶段 M7动作 M4动作
1. 初始化 清空共享内存,设置M7就绪标志 等待M7就绪标志
2. 确认 等待M4应答标志 设置M4应答标志
3. 同步 发送同步序列号 回传同步序列号+1

说白了,就是两个核通过共享内存里的几个标志位互相确认。我一般会在SRAM里划出一块区域专门做这个:

/* 共享内存结构体 - 放在SRAM固定地址 */
#define SHARED_MEM_BASE   0x30000000

typedef struct {
    volatile uint32_t m7_ready;    /* M7就绪标志 */
    volatile uint32_t m4_ready;    /* M4就绪标志 */
    volatile uint32_t sync_seq;    /* 同步序列号 */
    volatile uint32_t error_flag;  /* 错误标志 */
} SharedMem_t;

#define SHARED_MEM    ((SharedMem_t *)SHARED_MEM_BASE)

握手失败怎么办?我遇到过M4启动超时的情况。后来加了个超时机制,M7等不到M4应答就复位M4重新启动。你想想看,工业现场的环境千奇百怪,不加超时就是给自己埋雷。

避坑指南:共享内存的变量一定要加volatile关键字。我曾经因为漏了这个,M7写进去的值M4读不到,编译器优化把写操作给优化掉了。

4.5 整体启动流程SVG

下面这张图把整个多核启动流程串起来了,建议你保存下来对照着看:

STM32H7双核启动完整流程图 芯片上电复位 BootROM执行 M7内核启动 M4内核保持停机 ① 加载复位向量表 ② SystemInit() 时钟配置 ③ 进入 main() 用户代码 M7唤醒M4 M4内核启动 ④ 设置M4就绪标志 双核握手协议 M7 ↔ M4 通过共享内存交换就绪标志 双核协同工作

4.6 启动时序与注意事项

最后,我把启动过程中容易出问题的地方列出来,这些都是我实际项目中踩过的坑:

  • M4固件加载时机:必须在M7初始化完共享内存之后,再加载M4固件。否则M4跑起来后读到的共享内存是脏数据。
  • 握手超时设置:我一般设500ms超时。太短容易误判,太长影响系统启动速度。
  • 向量表对齐:M4的启动地址低7位必须为0,这个前面说过,但值得再强调一遍。
  • 共享内存初始化:M7在唤醒M4之前,先把共享内存区域全部清零。防止M4读到随机值。

重要提醒:不要在M4还没完成握手之前,就让M7访问M4负责的外设。我见过有人这么干,结果M7读到的数据全是错的,因为外设还没初始化。

好了,多核启动这块就聊到这儿。流程不复杂,但细节很多。你只要记住一个原则:M7是老大,M4是小弟,老大确认小弟准备好了再干活。下次咱们聊共享资源保护,那个才是真正让人头疼的地方。