1. 多核架构概览:Cortex-M7与M4的异构协同
各位同学,咱们直接进入正题。STM32H7系列这颗芯片,说白了就是一颗芯片里塞了两个核——一个Cortex-M7,一个Cortex-M4。M7是主核,跑得快,负责重活;M4是辅核,功耗低,处理些轻量级任务。我刚开始接触这个架构时,第一反应是:这不就是手机上的big.LITTLE吗?嗯,思路类似,但嵌入式场景下,两个核的协同方式完全不同。
1.1 为什么需要异构双核?
你想想看,一个系统里既要跑复杂的算法(比如电机控制、音频处理),又要实时响应外设中断。单核处理器往往顾此失彼——算法占着CPU,中断响应就慢了。M7主频高(最高480MHz),带双精度浮点单元,适合算力密集型任务;M4主频稍低(最高240MHz),但胜在功耗低、响应快,适合做实时控制。
核心分工原则:
- M7:主处理器,运行主应用、复杂算法、网络协议栈
- M4:协处理器,处理实时控制、传感器数据采集、低功耗后台任务
我在一个工业伺服驱动项目里遇到过这种情况:M7跑FOC算法和EtherCAT协议栈,CPU占用率已经到85%了,还要处理编码器中断。后来把编码器中断和电流环控制挪到M4上,M7的负载直接降到45%,系统稳定性明显提升。这就是异构协同的价值。
1.2 系统框图与内存映射
先看整体架构。STM32H7的系统框图,我建议你记住几个关键总线结构:
这张图里,我标了几个关键点:
- AXI总线矩阵:两个核通过它访问共享内存和外设。M7有专用的ITCM和DTCM接口,访问Flash和DTCM时不走AXI,延迟更低。
- D2 Domain SRAM:M4的专用内存区域,M7不能直接访问。这个设计是为了避免总线冲突。
- HSEM硬件信号量:两个核共享资源时,靠它做互斥访问。我后面会专门讲这个。
1.3 内存映射详解
STM32H7的内存映射,我建议你把它分成三个域来理解:
| 域 | 地址范围 | 内容 | 访问权限 |
|---|---|---|---|
| D1 Domain | 0x0000 0000 - 0x3FFF FFFF | Flash, ITCM, DTCM, AXI SRAM | M7独占(ITCM/DTCM),AXI SRAM共享 |
| D2 Domain | 0x4000 0000 - 0x4FFF FFFF | SRAM1/SRAM2, 外设寄存器 | M7和M4共享 |
| D3 Domain | 0x5000 0000 - 0x5FFF FFFF | 备份SRAM, RTC, 低功耗外设 | M7和M4共享,但M4优先 |
个人经验:我建议把M7的堆栈放在DTCM(0x2000 0000),访问速度最快。M4的堆栈放在D2 SRAM(0x3000 0000),避免和M7抢总线。这样做之后,系统响应时间能缩短15%-20%。
1.4 启动流程总览
双核启动,说白了就是谁先跑、谁后跑、怎么同步的问题。STM32H7的启动流程,我总结为四个阶段:
- 硬件复位:两个核同时从复位向量表取址。但M7的复位向量表默认在Flash起始地址,M4的复位向量表默认在0x0000 0000(映射到Flash)。
- M7先跑:系统设计上,M7先启动。它初始化系统时钟、PLL、外设总线,然后把M4的代码和数据加载到指定内存地址。
- 释放M4:M7通过写RCC寄存器(RCC_MPCKSELR)释放M4的复位信号。M4从它的向量表开始执行。
- 双核同步:两个核通过HSEM或共享内存中的标志位确认对方已就绪,然后开始分工协作。
注意:M4的向量表地址必须4KB对齐。我曾经因为没注意这个,M4启动后直接跑飞,查了两天才发现是向量表地址没对齐。嗯,这个坑我替你们踩过了。
启动流程的代码骨架,我给你们一个简化版:
/* M7 主核启动代码 */
void SystemInit(void) {
/* 1. 配置系统时钟 */
HAL_RCC_ClockConfig(...);
/* 2. 加载M4固件到指定地址 */
memcpy((void*)M4_FW_ADDR, M4_FW_BIN, M4_FW_SIZE);
/* 3. 配置M4向量表 */
__HAL_RCC_MPCKSELR_CLK_ENABLE();
WRITE_REG(RCC->MPCKSELR, (M4_FW_ADDR >> 12));
/* 4. 释放M4复位 */
__HAL_RCC_M4_RESET_RELEASE();
/* 5. 等待M4就绪标志 */
while (shared_flag != M4_READY);
}
/* M4 辅核启动代码 */
void M4_Startup(void) {
/* 1. 重定位向量表 */
SCB->VTOR = M4_FW_ADDR;
/* 2. 初始化M4专用外设 */
HAL_Init();
/* 3. 设置就绪标志 */
shared_flag = M4_READY;
/* 4. 进入主循环 */
while(1) {
/* 处理实时任务 */
}
}
关键点:M4的启动代码里,第一件事就是重定位向量表。如果不做这一步,M4的中断会跳到M7的向量表里去,后果你懂的——系统直接崩溃。