1. 课程导入
各位同学,咱们今天正式开讲。第一章,我打算先把STM32H7这颗双核芯片的“骨架”给大家拆开看看。说白了,就是Cortex-M7和Cortex-M4这两个核心,到底是怎么在一个芯片里协同工作的。
我记得刚拿到H7的参考手册时,第一反应是“这玩意儿比单核复杂太多了”。但实际用下来,你会发现它的设计思路其实很清晰——M7负责“重活”,M4负责“杂活”。
M7与M4的分工哲学
为什么ST要放两个核?一个核跑得快不就行了吗?嗯,这里有个关键点:实时性和低功耗往往互相矛盾。M7主频高、性能强,但功耗也高;M4虽然慢一点,但胜在能效比好。让M7跑控制算法、图形处理这些“力气活”,让M4跑通信协议、传感器采集这些“轻量级任务”,这才是双核的真正价值。
| 特性 | Cortex-M7 | Cortex-M4 |
|---|---|---|
| 主频(典型) | 480 MHz | 240 MHz |
| 核心定位 | 高性能计算、浮点运算、图形 | 控制、通信、低功耗后台任务 |
| 缓存 | L1 I/D-Cache + L2 Cache | 无缓存(或极小) |
| 典型功耗 | 较高(~几百mW) | 较低(~几十mW) |
| 适合场景 | 电机控制FOC、音频处理、AI推理 | 协议栈、按键扫描、低功耗待机 |
我在一个工业网关项目里,就吃过“单核扛所有”的亏。M7既要处理以太网协议栈,又要跑PID控制,结果中断一多,控制周期就抖。后来把协议栈扔给M4,M7专心做控制,问题迎刃而解。
HSEM硬件信号量:双核的“红绿灯”
两个核共享外设和内存,最怕什么?抢资源。比如M7正在写一个全局变量,M4突然也来写,数据就乱套了。传统做法是用关中断来保护临界区,但双核之间关中断没用——你关不了另一个核的中断。
ST的解决方案是HSEM(硬件信号量)。你可以把它想象成一个带锁的停车位:谁拿到钥匙(信号量),谁就能用这个资源;用完了,把钥匙还回去。
HSEM的核心机制:
- 每个信号量是一个32位寄存器,只有0(空闲)和1(占用)两种状态
- 支持硬件原子操作——获取和释放是一条指令,不会被中断打断
- 最多支持16个独立信号量,每个核可以同时持有多个
我曾经在一个项目里,M7和M4都要操作同一个SPI Flash。一开始没加HSEM,结果两个核同时发指令,Flash直接“死机”了。加上HSEM后,代码逻辑变成:
// M7侧代码
void M7_WriteFlash(uint32_t addr, uint8_t *data) {
// 尝试获取信号量0,超时1000次
while (HAL_HSEM_FastTake(HSEM_ID_0) != HAL_OK) {
// 等待或做其他事
}
// 临界区:操作SPI Flash
SPI_Write(addr, data);
// 释放信号量
HAL_HSEM_Release(HSEM_ID_0, 0);
}
// M4侧代码(逻辑完全对称)
void M4_ReadFlash(uint32_t addr, uint8_t *buf) {
while (HAL_HSEM_FastTake(HSEM_ID_0) != HAL_OK);
SPI_Read(addr, buf);
HAL_HSEM_Release(HSEM_ID_0, 0);
}
个人习惯:我一般把HSEM的获取放在函数最前面,释放放在最后面。中间不要有return或break,否则容易“死锁”——一个核占着信号量不还,另一个核永远等不到。
双核启动流程:谁先醒?谁后醒?
双核启动不像单核那么简单——上电后两个核都复位,谁先跑?谁后跑?这里有个固定的“剧本”:
- 上电复位:两个核同时从复位状态释放,但只有M7开始执行代码。M4被硬件强制保持在复位状态。
- M7初始化:M7先跑自己的启动代码,配置时钟、初始化外设、加载M4的固件到指定RAM地址。
- 释放M4:M7通过写RCC寄存器(RCC_MPCKSELR)和系统控制寄存器(SYSCFG_CFGR1),将M4从复位中释放。
- M4启动:M4从它的向量表(由M7预先设置)开始执行,两个核从此“各干各的”。
避坑指南:我曾经犯过一个低级错误——M7释放M4之前,忘了把M4的向量表地址写到SCB->VTOR寄存器。结果M4一启动就跑飞了,查了两天才发现。记住:M4的向量表必须由M7在释放前设置好,而且地址要4KB对齐。
下面这张图,我把整个启动流程画出来了,大家可以对照着看:
你看,整个流程其实不复杂。核心就一句话:M7是“大哥”,M4是“小弟”。大哥先起来把场子撑好,再叫小弟起来干活。
双核通信的“高速公路”
除了HSEM,两个核之间怎么传数据?总不能全靠共享变量吧?ST提供了几种硬件机制:
- HSEM(硬件信号量):用于资源互斥,不是用来传数据的
- IPCC(核间通信控制器):专门用来在两个核之间发送“消息”,支持中断触发。M7写一个寄存器,M4就能收到中断
- 共享RAM:两个核都能访问的SRAM区域,配合HSEM使用,实现数据交换
我个人习惯的做法是:用IPCC发“事件通知”,用共享RAM传“数据载荷”。比如M7要告诉M4“传感器数据准备好了”,就通过IPCC发一个中断;M4收到中断后,去共享RAM里读数据。这样既快又安全。
双核通信的黄金法则:
- 共享数据必须放在非缓存(Non-Cacheable)区域,否则M7的Cache会把数据“藏起来”,M4读不到最新值
- 每次访问共享数据前,先获取HSEM;访问完立即释放
- IPCC中断服务函数里不要做复杂操作,只做“置标志位”或“发信号量”