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的系统框图,我建议你记住几个关键总线结构:

STM32H7 双核系统框图 Cortex-M7 480MHz, DPFPU L1 Cache: I/D 16KB Cortex-M4 240MHz, FPU 无L1 Cache AXI 总线矩阵 (64-bit) 主端口: M7/M4/DMA1/DMA2/LTDC Flash (2MB) ITCM/DTCM 接口 SRAM1/SRAM2 (512KB) AXI 访问 D2 Domain SRAM (128KB) M4 专用 外设总线: GPIO / TIM / ADC / SPI / I2C / UART / CAN / USB / ETH HSEM 硬件信号量

这张图里,我标了几个关键点:

  • 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的启动流程,我总结为四个阶段:

  1. 硬件复位:两个核同时从复位向量表取址。但M7的复位向量表默认在Flash起始地址,M4的复位向量表默认在0x0000 0000(映射到Flash)。
  2. M7先跑:系统设计上,M7先启动。它初始化系统时钟、PLL、外设总线,然后把M4的代码和数据加载到指定内存地址。
  3. 释放M4:M7通过写RCC寄存器(RCC_MPCKSELR)释放M4的复位信号。M4从它的向量表开始执行。
  4. 双核同步:两个核通过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的向量表里去,后果你懂的——系统直接崩溃。