文档版本: V1.1
适用平台: TMS320F2837xD / F2838xD 系列双核Delfino MCU
核心议题: IPC通信机制、初始化配置、中断服务例程、安全互锁与死锁预防

一、引言

TMS320F28377D 是一款双核实时控制MCU,包含两个C28x浮点内核(CPU1和CPU2),每个内核主频高达200MHz。双核架构将应用层任务(人机交互、参数管理)与实时控制任务(PWM输出、ADC采样、PID闭环)分离到不同核心执行,实现更高的系统实时性和可靠性。

IPC(Inter-Processor Communication,处理器间通信)模块是双核芯片内部CPU1和CPU2之间的专用通信通道。

二、IPC通信机制概述

2.1 核心硬件资源

资源类型功能描述
IPC标志位(Flags)每个CPU有32个标志位(0~31),用于事件通知
IPC中断(Flags 0~3)其中4个标志可配置为触发对方CPU中断
命令寄存器IPCSENDCOM、IPCSENDADDR、IPCSENDDATA等,用于传递命令和数据
消息RAM(MSGRAM)用于在处理器间传输批量数据的专用共享内存
共享RAM(GSx/LSx)双核均可访问的全局共享内存

2.2 通信方式分类

通信方式数据量适用场景
标志位+中断1 bit状态变化通知、事件触发
命令寄存器多个32位字小数据块、控制命令
消息RAM(MSGRAM)批量数据参数同步、波形传输
全局共享RAM(GSx)大批量数据大数据块交换

三、IPC初始化配置

3.1 链接器命令文件(CMD)配置

使用IPC驱动API时,需要在CMD文件中为消息RAM分配空间:

c

// 在CPU1和CPU2的CMD文件中添加
MEMORY
{
    PAGE 0 :
    CPU2TOCPU1RAM : origin = 0x03F800, length = 0x000400
    CPU1TOCPU2RAM : origin = 0x03FC00, length = 0x000400
}

SECTIONS
{
    GROUP : > CPU1TOCPU2RAM, PAGE = 1
    {
        PUTBUFFER
        PUTWRITEIDX
        GETREADIDX
    }
    GROUP : > CPU2TOCPU1RAM, PAGE = 1
    {
        GETBUFFER : TYPE = DSECT
        GETWRITEIDX : TYPE = DSECT
        PUTREADIDX : TYPE = DSECT
    }
}

3.2 全局IPC控制器定义

应用代码必须在CPU1和CPU2上各定义并初始化至少一个 tIpcController 全局实例:

c

// CPU1和CPU2均需定义
volatile tIpcController g_sIpcController1;
volatile tIpcController g_sIpcController2;

3.3 CPU1 初始化例程

c

// ========== CPU1 主程序(初始化部分) ==========
#include "F28x_Project.h"
#include "F2837xD_Ipc_drivers.h"

// 定义IPC控制器实例
volatile tIpcController g_sIpcController1;
volatile tIpcController g_sIpcController2;

// 共享数据缓冲区
uint16_t usCPU01Buffer[512];
uint16_t usWWord16;

void main(void)
{
    uint32_t *pulMsgRam;

    // Step 1: 系统初始化
    InitSysCtrl();
    InitGpio();
    InitPieCtrl();
    InitPieVectTable();

    // Step 2: 初始化IPC控制器
    // g_sIpcController1 绑定 IPC_INT0,用于单字/命令通信
    IPCInitialize(&g_sIpcController1, IPC_INT0, IPC_INT0);
    // g_sIpcController2 绑定 IPC_INT1,用于数据块通信
    IPCInitialize(&g_sIpcController2, IPC_INT1, IPC_INT1);

    // Step 3: 注册IPC中断服务函数
    EALLOW;
    PieVectTable.IPC0_INT = &CPU02toCPU01IPC0IntHandler;
    PieVectTable.IPC1_INT = &CPU02toCPU01IPC1IntHandler;
    EDIS;

    // Step 4: 使能PIE中的IPC中断(PIE组1,INT13和INT14)
    PieCtrlRegs.PIEIER1.bit.INTx13 = 1;  // IPC0
    PieCtrlRegs.PIEIER1.bit.INTx14 = 1;  // IPC1
    IER |= M_INT1;  // 使能CPU中断组1
    EINT;           // 使能全局中断

    // Step 5: 填充地址映射表(让CPU2知道数据缓冲区的地址)
    pulMsgRam = (void *)CPU01TOCPU02_PASSMSG;
    pulMsgRam[0] = (uint32_t)&usWWord16;        // 16位状态字地址
    pulMsgRam[2] = (uint32_t)&usCPU01Buffer[0]; // 512字缓冲区地址

    // Step 6: 发送IPC17标志,通知CPU2地址已就绪
    IpcRegs.IPCSET.bit.IPC17 = 1;

    // Step 7: 启动CPU2
    #ifdef _FLASH
        while(IPCBootCPU2(C1C2_BROM_BOOTMODE_BOOT_FROM_FLASH)){}
    #else
        IPCBootCPU2(C1C2_BROM_BOOTMODE_BOOT_FROM_RAM);
    #endif

    // 主循环
    while(1)
    {
        // 应用程序代码...
    }
}

3.4 CPU2 初始化例程

c

// ========== CPU2 主程序(初始化部分) ==========
#include "F28x_Project.h"
#include "F2837xD_Ipc_drivers.h"

// 定义IPC控制器实例(与CPU1对应)
volatile tIpcController g_sIpcController1;
volatile tIpcController g_sIpcController2;

// 指向CPU1共享缓冲区的指针
uint16_t *pusCPU01BufferPt;
uint16_t usWWord16;  // 从CPU1接收的状态字

void main(void)
{
    uint32_t *pulMsgRam;

    // Step 1: 系统初始化
    InitSysCtrl();
    InitPieCtrl();
    InitPieVectTable();

    // Step 2: 初始化IPC控制器
    IPCInitialize(&g_sIpcController1, IPC_INT0, IPC_INT0);
    IPCInitialize(&g_sIpcController2, IPC_INT1, IPC_INT1);

    // Step 3: 注册IPC中断服务函数
    EALLOW;
    PieVectTable.IPC0_INT = &CPU01toCPU02IPC0IntHandler;
    PieVectTable.IPC1_INT = &CPU01toCPU02IPC1IntHandler;
    EDIS;

    // Step 4: 使能PIE中的IPC中断
    PieCtrlRegs.PIEIER1.bit.INTx13 = 1;  // IPC0
    PieCtrlRegs.PIEIER1.bit.INTx14 = 1;  // IPC1
    IER |= M_INT1;
    EINT;

    // Step 5: 等待CPU1发送地址表(等待IPC17标志)
    while(IpcRegs.IPCSTS.bit.IPC17 != 1) {}
    IpcRegs.IPCACK.bit.IPC17 = 1;

    // Step 6: 读取CPU1传递的地址
    pulMsgRam = (void *)CPU01TOCPU02_PASSMSG;
    pusCPU01BufferPt = (uint16_t *)pulMsgRam[2];  // 指向CPU1的512字缓冲区

    // 主循环
    while(1)
    {
        // 应用程序代码...
    }
}

四、中断服务函数(ISR)例程

4.1 CPU1 的 IPC0 中断处理函数(接收CPU2的单字数据)

c

// ========== CPU1:IPC0中断处理函数 ==========
// 处理CPU2通过IPC0发送的单字数据或命令
__interrupt void CPU02toCPU01IPC0IntHandler(void)
{
    tIpcMessage sMessage;

    // 持续处理消息,直到GetBuffer为空
    while(IpcGet(&g_sIpcController1, &sMessage, DISABLE_BLOCKING) != STATUS_FAIL)
    {
        switch (sMessage.ulcommand)
        {
            case IPC_DATA_WRITE:
                // 处理CPU2写入的单字数据
                IPCRtoLDataWrite(&sMessage);
                break;

            case IPC_SET_BITS:
                // 处理位设置命令
                IPCRtoLSetBits(&sMessage);
                break;

            case IPC_CLEAR_BITS:
                // 处理位清除命令
                IPCRtoLClearBits(&sMessage);
                break;

            case IPC_FUNC_CALL:
                // 处理远程函数调用
                IPCRtoLFunctionCall(&sMessage);
                break;

            default:
                // 未知命令,记录错误
                ErrorFlag = 1;
                break;
        }
    }

    // 应答IPC0中断,清除标志
    IpcRegs.IPCACK.bit.IPC0 = 1;
    PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
}

4.2 CPU1 的 IPC1 中断处理函数(接收CPU2的大数据块)

c

// ========== CPU1:IPC1中断处理函数 ==========
// 处理CPU2通过IPC1发送的大数据块(如波形数据)
__interrupt void CPU02toCPU01IPC1IntHandler(void)
{
    tIpcMessage sMessage;

    while(IpcGet(&g_sIpcController2, &sMessage, DISABLE_BLOCKING) != STATUS_FAIL)
    {
        switch (sMessage.ulcommand)
        {
            case IPC_BLOCK_WRITE:
                // 处理CPU2写入的大数据块
                IPCRtoLBlockWrite(&sMessage);
                break;

            case IPC_BLOCK_READ:
                // 处理CPU2的块读取请求
                IPCRtoLBlockRead(&sMessage);
                break;

            case IPC_SET_BITS_PROTECTED:
                // 处理受保护的位设置(用于内存权限申请)
                IPCRtoLSetBits_Protected(&sMessage);
                break;

            case IPC_CLEAR_BITS_PROTECTED:
                // 处理受保护的位清除
                IPCRtoLClearBits_Protected(&sMessage);
                break;

            default:
                ErrorFlag = 1;
                break;
        }
    }

    // 应答IPC1中断
    IpcRegs.IPCACK.bit.IPC1 = 1;
    PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
}

4.3 CPU2 的 IPC0 中断处理函数(接收CPU1的单字数据)

c

// ========== CPU2:IPC0中断处理函数 ==========
__interrupt void CPU01toCPU02IPC0IntHandler(void)
{
    tIpcMessage sMessage;

    while(IpcGet(&g_sIpcController1, &sMessage, DISABLE_BLOCKING) != STATUS_FAIL)
    {
        switch (sMessage.ulcommand)
        {
            case IPC_DATA_WRITE:
                IPCRtoLDataWrite(&sMessage);
                break;

            case IPC_SET_BITS:
                IPCRtoLSetBits(&sMessage);
                break;

            case IPC_CLEAR_BITS:
                IPCRtoLClearBits(&sMessage);
                break;

            default:
                ErrorFlag = 1;
                break;
        }
    }

    IpcRegs.IPCACK.bit.IPC0 = 1;
    PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
}

4.4 CPU2 的 IPC1 中断处理函数(接收CPU1的大数据块)

c

// ========== CPU2:IPC1中断处理函数 ==========
__interrupt void CPU01toCPU02IPC1IntHandler(void)
{
    tIpcMessage sMessage;

    while(IpcGet(&g_sIpcController2, &sMessage, DISABLE_BLOCKING) != STATUS_FAIL)
    {
        switch (sMessage.ulcommand)
        {
            case IPC_BLOCK_WRITE:
                IPCRtoLBlockWrite(&sMessage);
                break;

            case IPC_BLOCK_READ:
                IPCRtoLBlockRead(&sMessage);
                break;

            case IPC_SET_BITS_PROTECTED:
                IPCRtoLSetBits_Protected(&sMessage);
                break;

            case IPC_CLEAR_BITS_PROTECTED:
                IPCRtoLClearBits_Protected(&sMessage);
                break;

            default:
                ErrorFlag = 1;
                break;
        }
    }

    IpcRegs.IPCACK.bit.IPC1 = 1;
    PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
}

五、核间通信完整例程

5.1 例程1:CPU1 → CPU2 发送单字数据(使用IPC0中断)

场景:CPU1向CPU2发送一个16位的状态命令,触发CPU2的IPC0中断。

CPU1 发送端代码

c

// ========== CPU1:发送单字数据到CPU2 ==========
void SendWordToCPU2(uint16_t data)
{
    // 使用g_sIpcController1(绑定IPC_INT0)
    // 参数:控制器, 目标地址, 数据, 16位, 阻塞模式, 无标志
    while(IPCLtoRDataWrite(&g_sIpcController1,
                           pulMsgRam[0],      // CPU2端的接收地址
                           (uint32_t)data,
                           IPC_LENGTH_16_BITS,
                           ENABLE_BLOCKING,
                           NO_FLAG)) {};
}

CPU2 接收端代码(在IPC0中断中自动处理):

c

// CPU2的IPC0中断处理函数会自动调用IPCRtoLDataWrite,
// 将数据写入pulMsgRam[0]指向的变量(如usWWord16)
// CPU2主循环中可直接读取usWWord16

5.2 例程2:CPU2 → CPU1 发送大数据块(使用IPC1中断)

场景:CPU2向CPU1发送512字的波形数据,触发CPU1的IPC1中断。

CPU2 发送端代码

c

// ========== CPU2:发送大数据块到CPU1 ==========
#define BLOCK_SIZE 512

uint16_t usCPU02Buffer[BLOCK_SIZE];  // CPU2本地数据缓冲区
uint16_t *pusCPU02BufferPt;          // 指向GS0共享内存

void SendBlockToCPU1(void)
{
    uint16_t counter;

    // Step 1: 申请GS0共享内存的主人权限(CPU2成为Master)
    IPCReqMemAccess(&g_sIpcController2, GS0_ACCESS,
                    IPC_GSX_CPU2_MASTER, ENABLE_BLOCKING);
    while(MemCfgRegs.GSxMSEL.bit.MSEL_GS0 != 1U) {};

    // Step 2: 将数据从本地缓冲区复制到共享内存
    for(counter = 0; counter < BLOCK_SIZE; counter++)
    {
        pusCPU02BufferPt[counter] = usCPU02Buffer[counter];
    }

    // Step 3: 触发IPC1中断,通知CPU1读取数据
    // 参数:控制器, CPU1目标地址, CPU2源地址, 长度, 16位, 阻塞模式
    IPCLtoRBlockWrite(&g_sIpcController2,
                      pulMsgRam[2],              // CPU1的512字缓冲区地址
                      (uint32_t)pusCPU02BufferPt, // CPU2的源地址(GS0)
                      BLOCK_SIZE,
                      IPC_LENGTH_16_BITS,
                      ENABLE_BLOCKING);

    // Step 4: 可选 - 等待CPU1处理完成(等待标志被清除)
    // while (IPCLtoRFlagBusy(IPC_FLAG1) == 1);
}

CPU1 接收端代码(在IPC1中断中自动处理):

c

// CPU1的IPC1中断处理函数会自动调用IPCRtoLBlockWrite,
// 将GS0中的数据复制到usCPU01Buffer
// CPU1主循环中可直接使用usCPU01Buffer中的数据

5.3 例程3:使用IPC标志位进行简单握手(无需中断)

场景:CPU1通过置位IPC标志通知CPU2,CPU2轮询检测。

CPU1 发送端

c

// ========== CPU1:通过标志位通知CPU2 ==========
void NotifyCPU2_Flag(void)
{
    // 置位IPC标志5(不产生中断,仅作为状态标志)
    IpcRegs.IPCSET.bit.IPC5 = 1;
}

void ClearNotifyCPU2_Flag(void)
{
    // 清除IPC标志5
    IpcRegs.IPCCLR.bit.IPC5 = 1;
}

CPU2 接收端(轮询)

c

// ========== CPU2:轮询检测CPU1的标志 ==========
void CheckCPU1Flag(void)
{
    if(IpcRegs.IPCSTS.bit.IPC5 == 1)
    {
        // CPU1已置位标志5,执行相应处理
        // ...

        // 处理完成后应答(清除标志)
        IpcRegs.IPCACK.bit.IPC5 = 1;
    }
}

六、安全互锁机制(防止数据冲突)

6.1 问题描述

当两个CPU同时对同一共享内存地址执行“读-改-写”操作时,会发生数据撕裂。必须通过互锁机制保证数据一致性。

6.2 自旋锁实现(使用IPC0标志位)

c

// ========== 自旋锁模板(双方CPU都必须遵守) ==========
void AccessSharedMemory(void)
{
    // 1. 等待锁空闲(轮询IPC0状态位)
    while(IpcRegs.IPCSTS.bit.IPC0 == 1);

    // 2. 抢占锁(置位IPC0)
    IpcRegs.IPCSET.bit.IPC0 = 1;

    // 3. 临界区:读写共享内存(仅做最快操作)
    usSharedBuffer[index] = newValue;

    // 4. 释放锁(清除IPC0标志)
    IpcRegs.IPCCLR.bit.IPC0 = 1;
}

关键原则

  • 锁是双方协议:CPU1和CPU2都必须执行相同的锁操作

  • 临界区最小化:锁内只做最少操作,严禁DELAY/等待

  • 实时核用Try-Lock:CPU2作为实时核,不能使用阻塞式自旋锁

6.3 非阻塞尝试锁(Try-Lock,适用于实时核心)

c

// ========== 非阻塞尝试锁(CPU2专用) ==========
void TryAccessSharedMemory(void)
{
    // 仅当锁空闲时尝试获取
    if(IpcRegs.IPCSTS.bit.IPC0 == 0)
    {
        IpcRegs.IPCSET.bit.IPC0 = 1;   // 抢锁
        usSharedBuffer[index] = newValue; // 临界区
        IpcRegs.IPCCLR.bit.IPC0 = 1;   // 释放锁
    }
    // 锁忙则跳过,下次循环再试(不阻塞实时任务)
}

6.4 地址隔离方案(推荐,无需互锁)

将共享数据按写权限分配到不同地址,从根本上消除冲突:

数据项写权限读权限地址
CmdFromCPU1仅CPU1CPU2buffer[0]
StatusFromCPU2仅CPU2CPU1buffer[1]
SharedData初始化后只读双方buffer[2]

七、防止死锁的关键原则

7.1 死锁的四个必要条件

条件说明
互斥资源被独占(如自旋锁)
持有并等待持有锁的同时等待另一个锁或事件
不可抢占锁不能被强制释放
循环等待CPU1等CPU2,CPU2等CPU1

7.2 防死锁工程实践

  1. 临界区内严禁阻塞操作

    c

    // ❌ 错误
    while(lock_busy);
    set_lock();
    DELAY_US(1000);  // 危险!可能导致死锁
    clear_lock();
    
    // ✅ 正确
    while(lock_busy);
    set_lock();
    shared_var = new_value;  // 极快完成
    clear_lock();
  2. 统一锁的获取顺序:如果使用多个锁,所有CPU必须按相同顺序获取

  3. 中断中避免使用阻塞锁:ISR中应使用Try-Lock或完全不使用锁

  4. 锁超时机制

    c

    uint32_t timeout = 100000;
    while(IpcRegs.IPCSTS.bit.IPC0 == 1 && timeout--) {}
    if(timeout == 0) {
        // 超时处理:记录错误,强制恢复
        IpcRegs.IPCCLR.bit.IPC0 = 1;
    }

八、通信方式选择指南

数据特征推荐方式原因
状态标志(1 bit)IPC标志 + 中断最低延迟
单字命令(16/32 bit)IPC_DATA_WRITE无需内存访问
小参数集(< 100字)GSx RAM + 自旋锁灵活可控
大数据块(> 100字)IPC_BLOCK_WRITE + GSx效率最高

更多推荐