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,否则容易“死锁”——一个核占着信号量不还,另一个核永远等不到。

双核启动流程:谁先醒?谁后醒?

双核启动不像单核那么简单——上电后两个核都复位,谁先跑?谁后跑?这里有个固定的“剧本”:

  1. 上电复位:两个核同时从复位状态释放,但只有M7开始执行代码。M4被硬件强制保持在复位状态。
  2. M7初始化:M7先跑自己的启动代码,配置时钟、初始化外设、加载M4的固件到指定RAM地址。
  3. 释放M4:M7通过写RCC寄存器(RCC_MPCKSELR)和系统控制寄存器(SYSCFG_CFGR1),将M4从复位中释放。
  4. M4启动:M4从它的向量表(由M7预先设置)开始执行,两个核从此“各干各的”。

避坑指南:我曾经犯过一个低级错误——M7释放M4之前,忘了把M4的向量表地址写到SCB->VTOR寄存器。结果M4一启动就跑飞了,查了两天才发现。记住:M4的向量表必须由M7在释放前设置好,而且地址要4KB对齐。

下面这张图,我把整个启动流程画出来了,大家可以对照着看:

双核启动流程图 上电复位 M7开始执行 M7初始化系统 加载M4固件到RAM M7释放M4复位 M4开始执行 注意: M4在复位期间,所有 外设和内存对M4不可见 只有M7能访问全部资源 关键寄存器: RCC_MPCKSELR SYSCFG_CFGR1

你看,整个流程其实不复杂。核心就一句话: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中断服务函数里不要做复杂操作,只做“置标志位”或“发信号量”