TMS320F28377D 双核IPC通信机制与安全互锁设计指南

发布时间:2026/7/19 5:00:44
TMS320F28377D 双核IPC通信机制与安全互锁设计指南 文档版本V1.1适用平台TMS320F2837xD / F2838xD 系列双核Delfino MCU核心议题IPC通信机制、初始化配置、中断服务例程、安全互锁与死锁预防一、引言TMS320F28377D 是一款双核实时控制MCU包含两个C28x浮点内核CPU1和CPU2每个内核主频高达200MHz。双核架构将应用层任务人机交互、参数管理与实时控制任务PWM输出、ADC采样、PID闭环分离到不同核心执行实现更高的系统实时性和可靠性。IPCInter-Processor Communication处理器间通信模块是双核芯片内部CPU1和CPU2之间的专用通信通道。二、IPC通信机制概述2.1 核心硬件资源资源类型功能描述IPC标志位Flags每个CPU有32个标志位0~31用于事件通知IPC中断Flags 0~3其中4个标志可配置为触发对方CPU中断命令寄存器IPCSENDCOM、IPCSENDADDR、IPCSENDDATA等用于传递命令和数据消息RAMMSGRAM用于在处理器间传输批量数据的专用共享内存共享RAMGSx/LSx双核均可访问的全局共享内存2.2 通信方式分类通信方式数据量适用场景标志位中断1 bit状态变化通知、事件触发命令寄存器多个32位字小数据块、控制命令消息RAMMSGRAM批量数据参数同步、波形传输全局共享RAMGSx大批量数据大数据块交换三、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组1INT13和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// CPU1IPC0中断处理函数 // 处理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// CPU1IPC1中断处理函数 // 处理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// CPU2IPC0中断处理函数 __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// CPU2IPC1中断处理函数 __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 例程1CPU1 → 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主循环中可直接读取usWWord165.2 例程2CPU2 → 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标志通知CPU2CPU2轮询检测。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-LockCPU2作为实时核不能使用阻塞式自旋锁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等CPU2CPU2等CPU17.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或完全不使用锁锁超时机制cuint32_t timeout 100000; while(IpcRegs.IPCSTS.bit.IPC0 1 timeout--) {} if(timeout 0) { // 超时处理记录错误强制恢复 IpcRegs.IPCCLR.bit.IPC0 1; }八、通信方式选择指南数据特征推荐方式原因状态标志1 bitIPC标志 中断最低延迟单字命令16/32 bitIPC_DATA_WRITE无需内存访问小参数集 100字GSx RAM 自旋锁灵活可控大数据块 100字IPC_BLOCK_WRITE GSx效率最高