4. 从核(M4)启动流程:M4核的复位与释放机制、通过HSEM硬件信号量同步、M4启动地址配置
多核启动,说白了就是让两个大脑先后醒过来,还得让它们知道彼此的存在。M4核作为从核,它的启动完全受M7核控制。我刚开始接触STM32H7时,以为把两个核的代码烧进去就能跑,结果M4死活不干活。后来才发现,M4的复位引脚一直拉着,它根本没被释放。
这一节,我们就来拆解M4的启动全流程。你跟着我走一遍,以后遇到双核启动问题,心里就有底了。
4.1 M4核的复位与释放机制
M4核在芯片上电后,默认处于复位状态。这不是bug,是设计。STM32H7的双核架构里,M7是老大,M4是小弟。老大没发话,小弟不能动。
控制M4复位释放的关键寄存器是 RCC->MP_GCR(Master Present Global Control Register)。它的bit 0叫 BOOT_M4,写1就释放M4的复位,M4开始从它的启动地址取指执行。
核心要点:M4的复位释放不是硬件自动完成的,必须由M7核在软件中主动触发。这是双核协作的第一步。
我在一个项目中遇到过这样的情况:M7已经跑起来好一会儿了,M4却一直没动静。用调试器一看,RCC->MP_GCR寄存器的BOOT_M4位还是0。嗯,代码里忘了写这一行。
释放M4的典型代码流程如下:
/* 1. 确保M4的向量表已经加载到目标地址 */
/* 2. 设置M4的启动地址(下一节讲) */
/* 3. 释放M4复位 */
RCC->MP_GCR |= RCC_MP_GCR_BOOT_M4_Msk;
这里有个坑:BOOT_M4位写1之后,M4并不会立刻开始执行。它需要几个时钟周期来完成内部复位释放序列。你可以在后面加一个小延时,或者轮询M4的状态寄存器确认它已经跑起来了。
警告:不要在M4的向量表还没准备好之前就释放复位。否则M4会从错误的地址取指,直接进HardFault。我曾经因为这个bug调了整整一个下午。
4.2 M4启动地址配置
M4释放复位后,它从哪里开始执行代码?这取决于 M4的启动地址。STM32H7里,M4的启动地址由 RCC->MP_SWCR(System Wakeup Control Register)或 SYSCFG->UR0 等寄存器控制,具体取决于芯片型号和启动模式。
最常见的做法是:把M4的代码放在某个固定的RAM或Flash地址,然后让M4的启动地址指向那里。我个人习惯把M4的代码放在 AXI SRAM 或 D2域内的SRAM 中,这样M4访问自己的代码时延迟最低。
| 存储区域 | 起始地址 | 特点 | 推荐用途 |
|---|---|---|---|
| D2域SRAM1 | 0x3000 0000 | M4专用,无总线竞争 | M4代码+数据 |
| D2域SRAM2 | 0x3002 0000 | M4专用,容量较小 | M4关键数据 |
| AXI SRAM | 0x2400 0000 | 双核共享,带宽高 | 共享数据缓冲区 |
| Flash(Bank2) | 0x0810 0000 | 非易失,但速度慢 | M4固件存储 |
配置启动地址的代码示例:
/* 假设M4的向量表放在0x3000 0000 */
#define M4_VTOR_ADDR 0x30000000UL
/* 设置M4的向量表偏移(由M7写入) */
/* 注意:M4的VTOR寄存器只能由M4自己写,M7不能直接访问 */
/* 所以我们需要在M4的启动代码中预先设置好VTOR */
/* 或者使用硬件启动地址配置寄存器(部分型号支持) */
SYSCFG->UR0 = (SYSCFG->UR0 & ~SYSCFG_UR0_M4_START_ADDR_Msk) |
(M4_VTOR_ADDR & SYSCFG_UR0_M4_START_ADDR_Msk);
技巧:如果你想让M4从Flash启动,记得把M4的向量表放在Flash的高地址区域,避免和M7的向量表冲突。我一般把M7的向量表放在0x0800 0000,M4的放在0x0810 0000,中间留出足够空间。
4.3 通过HSEM硬件信号量同步
双核启动时,最怕的就是两个核同时访问同一个资源。比如,M7在初始化DMA,M4也去配置同一个DMA通道,结果就是数据错乱。这时候就需要 HSEM(Hardware Semaphore) 出场了。
HSEM是STM32H7内部的一组硬件信号量,总共32个。每个信号量就是一个位,谁拿到谁用。它的操作是原子性的,不会被中断打断。说白了,就是一把硬件锁。
M7和M4启动时的典型同步流程:
- M7先启动,初始化系统时钟、电源、外设时钟。
- M7获取HSEM信号量0,表示“我在初始化共享资源”。
- M7释放M4复位,M4开始执行。
- M4启动后,轮询HSEM信号量0,如果被占用就等待。
- M7初始化完毕,释放HSEM信号量0。
- M4获取到信号量,开始初始化自己的部分。
你想想看,如果没有这个信号量,M4一启动就去碰M7正在用的外设,那不乱套了?
HSEM的使用代码示例:
/* M7核:获取信号量0,超时等待 */
uint32_t timeout = 10000;
while (HSEM->RLR[0] != 0) { /* 检查信号量是否可用 */
if (--timeout == 0) {
/* 超时处理,说明M4没释放 */
Error_Handler();
}
}
HSEM->CR1 |= HSEM_CR1_SEM0_LOCK; /* 锁定信号量0 */
/* ... 初始化共享资源 ... */
/* 释放信号量0 */
HSEM->CR1 |= HSEM_CR1_SEM0_UNLOCK;
/* M4核:等待信号量0被释放 */
while ((HSEM->RLR[0] & HSEM_RLR_LOCK_Msk) != 0) {
/* 忙等待,直到M7释放 */
}
/* 现在可以安全访问共享资源了 */
关键点:HSEM的RLR寄存器是只读的,读它不会改变信号量状态。真正获取和释放信号量是通过CR1寄存器操作的。很多新手在这里搞混,读RLR发现是0就以为拿到了,其实没有。
我曾经在一个项目里,M4启动后直接去写一个共享的ADC配置寄存器,结果M7也在同一时刻写它。两个核的配置互相覆盖,ADC出来的数据全是错的。后来加上HSEM保护,问题立刻解决。嗯,硬件信号量这东西,看着简单,但真能救命。
4.4 完整启动流程图
下面这张图把M4从复位到正常运行的完整流程画出来了。你对照着看,每一步都对应我们刚才讲的内容。
4.5 常见故障与定位方法
M4启动不起来,原因就那么几个。我列个清单,你对照着排查:
| 故障现象 | 可能原因 | 排查方法 |
|---|---|---|
| M4完全不执行 | BOOT_M4位未置1 | 检查RCC->MP_GCR寄存器 |
| M4执行后立即HardFault | 启动地址错误或向量表未对齐 | 检查VTOR配置,确认地址128字节对齐 |
| M4和M7数据冲突 | 未使用HSEM保护共享资源 | 在共享资源访问前后加HSEM锁 |
| M4启动后卡死 | 等待的HSEM信号量未被释放 | 检查M7是否正常释放了信号量 |
| M4偶尔启动失败 | 释放复位后未加延时,M4取指时向量表还没就绪 | 在BOOT_M4置位后加10us延时 |
调试技巧:我习惯在M4的启动代码最前面加一个GPIO翻转。如果M4真的跑起来了,这个GPIO就会变高。用示波器一看就知道M4有没有被成功释放。这比看调试器日志快多了。