4. 核间通信实战

双核项目里,最让人头疼的往往不是单个核的代码怎么写,而是两个核怎么「说话」。我刚开始做STM32H7双核开发时,就踩过一个大坑——两个核各自跑得飞快,但数据交换时莫名其妙丢包。查了三天,最后发现是通信机制没选对。

说白了,双核通信就三种路子:Mailbox邮箱、共享内存区、IPC中断同步。咱们一个一个说清楚。

Mailbox邮箱通信

Mailbox,你可以把它想象成一个「信箱」。一个核往里塞消息,另一个核来取。硬件上,STM32的Mailbox有8个深度,每个能放一个32位数据。

我个人习惯把Mailbox用在控制类消息上,比如「启动ADC采集」、「进入低功耗模式」这种短指令。为什么?因为它简单、快、不需要考虑互斥。

核心特点:
  • 一次传32位数据,适合命令/状态码
  • 硬件自带队列,不会丢消息
  • 发送方不用等接收方处理完

来看一段实际代码。这是CM7核发送消息给CM4核:

// CM7核发送
void Mailbox_SendToCM4(uint32_t msg) {
    // 等待邮箱有空位
    while(HSEM->RLR[0] != 0);
    
    // 写入消息并释放
    HSEM->R[0] = msg;
    HSEM->RLR[0] = 1;  // 释放信号量
}

CM4核这边接收:

// CM4核接收(中断方式)
void HSEM_IRQHandler(void) {
    uint32_t msg;
    
    if(HSEM->MISR & (1 << 0)) {
        msg = HSEM->R[0];
        ProcessMessage(msg);
        HSEM->ICR = (1 << 0);  // 清中断
    }
}

嗯,这里要注意:Mailbox的深度只有8。如果你连续发超过8条消息还没人取,就会阻塞。我在一个项目中就遇到过这种情况——CM7核疯狂发日志,CM4核处理不过来,结果CM7核卡死了。后来加了个超时机制才解决。

共享内存区设计

共享内存,说白了就是两个核都能访问同一块SRAM。适合传大数据,比如音频帧、图像数据、传感器批量数据。

但共享内存有个大问题:两个核同时写怎么办?你想想看,如果CM7正在写一个结构体,写到一半CM4来读了,读到的就是半残数据。

避坑指南: 我曾经在一个电机控制项目里,CM7写PID参数到共享内存,CM4读参数去算控制量。结果CM4读到的参数,比例系数是新的,积分系数还是旧的——电机直接抖起来了。从那以后,我所有共享内存都加了「双缓冲」。

双缓冲的设计思路很简单:

  1. 准备两块缓冲区,A和B
  2. CM7写A时,CM4读B
  3. 写完后交换角色

代码实现大概这样:

// 共享内存结构体定义
typedef struct {
    uint32_t flag;      // 0: CM7写A, CM4读B; 1: 反之
    uint8_t bufferA[1024];
    uint8_t bufferB[1024];
} SharedMem_t;

// 放在DTCM或SRAM4中
__attribute__((section(".shared_ram")))
volatile SharedMem_t shared_mem;

写数据时:

// CM7核写数据
void WriteToShared(const uint8_t* data, uint32_t len) {
    uint8_t* target;
    
    if(shared_mem.flag == 0) {
        target = shared_mem.bufferA;
    } else {
        target = shared_mem.bufferB;
    }
    
    memcpy(target, data, len);
    
    // 写完后翻转flag
    __DMB();  // 数据内存屏障,确保写完成
    shared_mem.flag ^= 1;
}
小技巧: 共享内存一定要加volatile关键字,否则编译器优化可能让你怀疑人生。另外,跨核访问时,用__DMB()或__DSB()保证内存操作顺序。

IPC中断同步

Mailbox和共享内存解决了「怎么传数据」,但没解决「什么时候传」。IPC中断就是用来同步的——一个核发中断通知另一个核:「数据准备好了,快来取」。

STM32H7里,两个核之间可以通过HSEM(硬件信号量)触发中断。具体做法:

步骤 CM7核操作 CM4核操作
1 写数据到共享内存 等待中断
2 释放HSEM信号量 收到HSEM中断
3 继续做其他事 读取共享内存数据
4 — 处理完成后清中断

实际项目中,我通常把三种机制组合使用:

  • Mailbox:传命令和状态码,比如「开始采集」、「采集完成」
  • 共享内存:传批量数据,比如1024点ADC采样值
  • IPC中断:通知对方数据已就绪

举个例子,一个数据采集系统的工作流:

  1. CM7通过Mailbox发「开始采集」给CM4
  2. CM4启动ADC,采集1024点,存入共享内存
  3. CM4发IPC中断给CM7,通知数据就绪
  4. CM7收到中断,从共享内存读数据
  5. CM7处理完,通过Mailbox发「采集完成」给CM4

这个流程里,每种机制各司其职,不会乱。

关键点总结:
  • Mailbox:短消息,深度8,硬件队列
  • 共享内存:大数据,双缓冲,加volatile和内存屏障
  • IPC中断:同步通知,配合HSEM使用
  • 实际项目:三种组合用,别只用一种

下面这张图,把三种机制的关系画清楚了:

双核通信机制架构图 双核通信机制架构 CM7核 主核:控制与处理 Mailbox发送/接收 CM4核 从核:采集与执行 Mailbox发送/接收 Mailbox邮箱通道 命令/状态码(32位) 共享内存区 双缓冲设计(Buffer A / Buffer B) 批量数据:ADC/音频/图像 IPC中断同步 实际项目建议 Mailbox传命令 → 共享内存传数据 → IPC中断做同步,三者配合使用

最后说一句:别想着只用一种机制搞定所有场景。Mailbox传大数据会堵死,共享内存不加同步会读脏数据,IPC中断没有数据载体就是空响。三种机制配合好,双核通信才能稳如老狗。