4. 多核启动流程:BootROM执行、M7内核启动、M4内核启动、双核握手协议
好,咱们今天聊点硬核的——多核启动。说实话,我第一次接触STM32H7的双核启动时,也被绕进去过。M7和M4两个核,谁先跑?谁后跑?怎么保证它们不打架?这些坑,我一个个踩过,今天全给你抖出来。
4.1 BootROM到底干了什么
芯片上电后,第一个执行的代码不是你的应用程序,而是芯片内部固化的BootROM。这玩意儿是出厂就写死的,你改不了,但得理解它。
BootROM的执行流程,说白了就三步:
- 硬件初始化:配置系统时钟、电源管理、Flash控制器等基础外设
- 启动模式判断:根据BOOT0/BOOT1引脚电平,决定从Flash、系统存储器还是SRAM启动
- 跳转执行:找到合法启动地址后,跳转到用户代码
我当年做第一个双核项目时,以为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的标准流程:
- 加载M4固件:M7从Flash或外部存储器读取M4的二进制代码
- 设置M4向量表:通过
RCC->GCR寄存器配置M4的启动地址 - 释放M4复位:清除
RCC->GCR中的M4复位位 - 等待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
下面这张图把整个多核启动流程串起来了,建议你保存下来对照着看:
4.6 启动时序与注意事项
最后,我把启动过程中容易出问题的地方列出来,这些都是我实际项目中踩过的坑:
- M4固件加载时机:必须在M7初始化完共享内存之后,再加载M4固件。否则M4跑起来后读到的共享内存是脏数据。
- 握手超时设置:我一般设500ms超时。太短容易误判,太长影响系统启动速度。
- 向量表对齐:M4的启动地址低7位必须为0,这个前面说过,但值得再强调一遍。
- 共享内存初始化:M7在唤醒M4之前,先把共享内存区域全部清零。防止M4读到随机值。
重要提醒:不要在M4还没完成握手之前,就让M7访问M4负责的外设。我见过有人这么干,结果M7读到的数据全是错的,因为外设还没初始化。
好了,多核启动这块就聊到这儿。流程不复杂,但细节很多。你只要记住一个原则:M7是老大,M4是小弟,老大确认小弟准备好了再干活。下次咱们聊共享资源保护,那个才是真正让人头疼的地方。