TMS320F28377D 双核IPC通信机制与安全互锁设计指南
文档版本: 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 | 仅CPU1 | CPU2 | buffer[0] |
| StatusFromCPU2 | 仅CPU2 | CPU1 | buffer[1] |
| SharedData | 初始化后只读 | 双方 | buffer[2] |
七、防止死锁的关键原则
7.1 死锁的四个必要条件
| 条件 | 说明 |
|---|---|
| 互斥 | 资源被独占(如自旋锁) |
| 持有并等待 | 持有锁的同时等待另一个锁或事件 |
| 不可抢占 | 锁不能被强制释放 |
| 循环等待 | CPU1等CPU2,CPU2等CPU1 |
7.2 防死锁工程实践
-
临界区内严禁阻塞操作
c
// ❌ 错误 while(lock_busy); set_lock(); DELAY_US(1000); // 危险!可能导致死锁 clear_lock(); // ✅ 正确 while(lock_busy); set_lock(); shared_var = new_value; // 极快完成 clear_lock();
-
统一锁的获取顺序:如果使用多个锁,所有CPU必须按相同顺序获取
-
中断中避免使用阻塞锁:ISR中应使用Try-Lock或完全不使用锁
-
锁超时机制:
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 | 效率最高 |
更多推荐
所有评论(0)