概述
处理器间通信(Inter-Processor Communication, IPC)是专为多核异构系统设计的硬件加速通信机制,通过共享SRAM实现跨核数据交互,确保多核间低延迟的信息传递。其核心目标是为多核之间提供高效协同的工作能力。
IPC 架构
硬件架构特性
IPC 结构框图如下所示:
对称结构
每个CPU都有一套相同的IPC模块。
双工通信模型
支持双向全双工通信。
通道隔离性
通道间采用硬件级资源隔离设计,任意通道的数据传输互不阻塞。
支持并行多通道操作(如CPUA可同时通过通道n发送传感器数据、通道m传输日志信息)。
信息池
预分配的一块内存,每个IPC通道通过信息池中预设的固定地址来收发信息。
共享内存
当收发消息大于4字节时,发送端动态分配一块共享内存,并将内存地址作为消息写入信息池,接收端在发送端释放共享内存前,将数据拷贝到本地。
当收发消息小于等于4字节时,将消息写入信息池。
NVIC中断生成器
在IPC通道准备好数据之后,通过写IPC某块中的寄存器,在接收端产生接收中断, 同样在接收端完成接收后,也可以在接收端触发发送完成中断。
数据流模型
发送端流程:[共享信息] → 选择可用通道 → 写入共享内存 → 更新通道寄存器 → 触发对端中断 → 对端读取内存
接收端流程:中断上报 → 读取通道寄存器 → 定位共享内存 → 拷贝数据到本地 → 处理数据 → 清除中断标志
IPC 基本定义
通道
概念:IPC通道是连接两个IPC实体之间的双向独立通信路径,每个方向包含多组通道。
作用:用于在两个CPU之间传输发送和接收事件,实现核间通信。
组件
IPC 配置表(IPC_INIT_TABLE)
作用:定义了整个传输链路的所有信息,包括数据类型、接收和发送回调函数、中断处理函数参数、传输方向和IPC通道号。
使用说明:用户需要将此结构体实例存放到
IPC_TABLE_DATA_SECTION
中,SDK会根据结构体的成员信息开启相应通道的Tx/Rx中断,并注册中断处理函数。结构体定义:
IPC_TABLE_DATA_SECTION typedef struct _IPC_INIT_TABLE_ { u32 USER_MSG_TYPE; /* 数据类型:IPC_USER_DATA(小于32bit数据)或IPC_USER_POINT(大于32bit数据的地址) */ void (*Rxfunc)(void *Data, u32 IrqStatus, u32 ChanNum); /* 接收中断回调函数 */ void *RxIrqData; /* 接收回调函数参数 */ void (*Txfunc)(void *Data, u32 IrqStatus, u32 ChanNum); /* 发送完成中断回调函数 */ void *TxIrqData; /* 发送回调函数参数 */ u32 IPC_Direction; /* 传输方向 */ u32 IPC_Channel; /* IPC通道号 */ } IPC_INIT_TABLE, *PIPC_INIT_TABLE;
IPC 数据结构(IPC_MSG_STRUCT)
作用:在消息池中,每个IPC通道拥有一个固定位置用于存放
IPC_MSG_STRUCT
结构体,用于存储和传输数据。结构体定义:
typedef struct ipc_msg_struct { u32 msg_type; /* 数据类型,与USER_MSG_TYPE相同 */ u32 msg; /* 需要传递的32bit数据或数据地址(对于大于32bit的数据) */ u32 msg_len; /* 数据长度 */ u32 rsvd; /* 保留字段 */ } IPC_MSG_STRUCT, *PIPC_MSG_STRUCT, ipc_msg_struct_t;
IPC 标准使用方法
通信协议栈注册
1.1 选择通道
文件路径 : ameba_ipccfg.h
选择与定义通道:
通过取消注释并修改通道定义来选择和自定义通道。例如:
/* 取消通道n的注释,并添加功能描述 */ #define IPC_N2A_Channeln n /*!< 用户自定义通道:KM0到KM4的传感器数据传输 */ ... //#define IPC_A2N_Channeln n /*!< 用户自定义通道:KM4到KM0的传感器数据传输 */
确认传输方向:
#define IPC_KM0_TO_KM4 0 // KM0为发送端,KM4为接收端 #define IPC_KM4_TO_KM0 1 // 反向通信方向
注意事项:
每个方向配置16组物理通道,故通道号需在0-15范围内。
避免使用系统已预留的通道,这些通道可能用于WiFi/BT等系统组件。
1.2 定义接收端回调函数
接收端回调函数,从线程池中读取消息,若消息类型为指针,使用
_memcpy()
将共享信息拷贝到本地。中断处理函数退出后,SDK自动清除中断标志.uint32_t local_buffer[32] = {0}; /* 接收处理函数(中断上下文)*/ void Rx_Channeln_Handler(void) { PIPC_MSG_STRUCT msg = ipc_get_message(IPC_KM0_TO_KM4, IPC_N2A_Channeln);/*获取消息元数据*/ uint32_t *sensor_data = (uint32_t*)msg->msg; /*提取数据指针*/ _memcpy(local_buffer, (void*)sensor_data, data_size); /*数据拷贝到本地,数据长度双方提前约定好*/ rtos_sema_give(ipc_sema); /*触发任务级处理*/ }
1.3 注册IPC配置表
在接收端为每个通道注册一个IPC配置表, SDK会自动开启通道的接收中断。
/* 通道配置表*/ IPC_TABLE_DATA_SECTION const IPC_INIT_TABLE ipc_chn_config = { .msg_type = IPC_USER_POINT, // 消息传递方式:指针传递(数据大于32bit) .rx_func = Rx_Channeln_Handler, .rx_irq_data = NULL, // 无附加参数 .tx_func = NULL, // 单向传输无需TX回调 .tx_irq_data = NULL, .direction = IPC_KM0_TO_KM4, // 方向标识 .channel_num = IPC_N2A_Channeln // 通道号宏定义 };
数据收发实现
2.1 发送端实现
调用
ipc_send_message()
函数,并指定传输方向,通道号和消息。IPC通过通道n从KM0向KM4发送IPC请求。void send_sensor_data(void) { IPC_MSG_STRUCT ipc_msg_temp; sensor_buffer = read_sensor(); // 更新共享内存 ipc_msg_temp.msg_type = IPC_USER_POINT; // 传递指针 ipc_msg_temp.msg = (u32)&sensor_buffer; ipc_msg_temp.msg_len = 1; // 数据长度(字节) ipc_msg_temp.rsvd =0; /* 发送共享数据(SDK标准API), 带超时处理 */ ipc_send_message(IPC_KM0_TO_KM4, IPC_N2A_Channeln, & ipc_msg_temp); }
2.2 接收端处理
为了协同工作,接收端线程监控ipc_sema,收到数据后及时处理,并做下一步操作。
/* 中断上下文:快速捕获数据 */ void Rx_Channeln_Handler(void *data) { } /* 任务上下文:数据处理 */ void ipc_task(void *param) { while (1) { /*等待数据到达*/ rtos_sema_take(ipc_sema, RTOS_WAIT_FOREVER); /*处理数据(已通过_memcpy复制到local_buffer)*/ data_pipeline_process(local_buffer); } }
通信协议栈注册
1.1 选择通道
文件路径 : ameba_ipccfg.h
选择与定义通道:
通过取消注释并修改通道定义来选择和自定义通道。例如:
1/* 取消通道n的注释,并添加功能描述 */ 2#define IPC_R2M_Channeln n /*!< 用户自定义通道:KR4到KM4的传感器数据传输 */ 3... 4//#define IPC_M2R_Channeln n /*!< 用户自定义通道:KM4到KR4的传感器数据传输 */
确认传输方向:
1#define IPC_KR4_TO_KM4 0 // KR4为发送端,KM4为接收端 2#define IPC_KM4_TO_KR4 2 // 反向通信方向
注意事项:
每个方向配置8组物理通道,故通道号需在0-7范围内。
避免使用系统已预留的通道,这些通道可能用于WiFi/BT等系统组件。
1.2 定义接收端回调函数
接收端回调函数,从线程池中读取消息,若消息类型为指针,使用
_memcpy()
将共享信息拷贝到本地。中断处理函数退出后,SDK自动清除中断标志.1uint32_t local_buffer[32] = {0}; 2 3/* 接收处理函数(中断上下文)*/ 4void Rx_Channeln_Handler(void) { 5 PIPC_MSG_STRUCT msg = ipc_get_message(IPC_KR4_TO_KM4, IPC_R2M_Channeln);/*获取消息元数据*/ 6 uint32_t *sensor_data = (uint32_t*)msg->msg; /*提取数据指针*/ 7 _memcpy(local_buffer, (void*)sensor_data, data_size); /*数据拷贝到本地,数据长度双方提前约定好*/ 8 rtos_sema_give(ipc_sema); /*触发任务级处理*/ 9}
1.3 注册IPC配置表
需要在接收端为每个通道注册一个IPC配置表, SDK会自动开启通道的接收中断。
1/* 通道配置表*/ 2IPC_TABLE_DATA_SECTION 3const IPC_INIT_TABLE ipc_chn_config = { 4 .msg_type = IPC_USER_POINT, // 消息传递方式:指针传递(数据大于32bit) 5 .rx_func = Rx_Channeln_Handler, 6 .rx_irq_data = NULL, // 无附加参数 7 .tx_func = NULL, // 单向传输无需TX回调 8 .tx_irq_data = NULL, 9 .direction = IPC_KR4_TO_KM4, // 方向标识 10 .channel_num = IPC_R2M_Channeln // 通道号宏定义 11};
数据收发实现
2.1. 发送端实现
调用
ipc_send_message()
函数,并指定传输方向,通道号和消息。IPC通过通道n从KR4向KM4发送IPC请求。1void send_sensor_data(void) { 2 3 IPC_MSG_STRUCT ipc_msg_temp; 4 sensor_buffer = read_sensor(); // 更新共享内存 5 6 ipc_msg_temp.msg_type = IPC_USER_POINT; // 传递指针 7 ipc_msg_temp.msg = (u32)&sensor_buffer; 8 ipc_msg_temp.msg_len = 1; // 数据长度(字节) 9 ipc_msg_temp.rsvd =0; 10 11 /* 发送共享数据(SDK标准API), 带超时处理 */ 12 ipc_send_message(IPC_KR4_TO_KM4, IPC_R2M_Channeln, & ipc_msg_temp); 13}
2.2. 接收端处理
为了协同工作,接收端线程监控
ipc_sema
,收到数据后及时处理,并做下一步操作。1/* 中断上下文:快速捕获数据 */ 2void Rx_Channeln_Handler(void *data) { 3} 4 5/* 任务上下文:数据处理 */ 6void ipc_task(void *param) { 7 while (1) { 8 /*等待数据到达*/ 9 rtos_sema_take(ipc_sema, RTOS_WAIT_FOREVER); 10 /*理数据(已通过_memcpy复制到local_buffer)*/ 11 data_pipeline_process(local_buffer); 12 } 13}
通信协议栈注册
1.1 选择通道
文件路径 : ameba_ipccfg.h
选择与定义通道:
通过取消注释并修改通道定义来选择和自定义通道。例如:
1/* 取消通道n的注释,并添加功能描述 */ 2#define IPC_R2M_Channeln n /*!< 用户自定义通道:KR4到KM4的传感器数据传输 */ 3... 4//#define IPC_M2R_Channeln n /*!< 用户自定义通道:KM4到KR4的传感器数据传输 */ 5//#define IPC_D2R_Channeln n /*!< 用户自定义通道:DSP到KR4的传感器数据传输 */ 6//#define IPC_R2D_Channeln n /*!< 用户自定义通道:KR4到DSP的传感器数据传输 */ 7//#define IPC_D2M_Channeln n /*!< 用户自定义通道:DSP到KM4的传感器数据传输 */ 8//#define IPC_M2D_Channeln n /*!< 用户自定义通道:KM4到DSP的传感器数据传输 */
确认传输方向:
1#define IPC_KR4_TO_KM4 0 // KR4为发送端,KM4为接收端 2#define IPC_KR4_TO_DSP 1 // KR4为发送端,DSP为接收端 3#define IPC_KM4_TO_KR4 2 // KM4为发送端,KR4为接收端 4#define IPC_KM4_TO_DSP 3 // KM4为发送端,DSP为接收端 5#define IPC_DSP_TO_KR4 4 // DSP为发送端,KR4为接收端 6#define IPC_DSP_TO_KM4 5 // DSP为发送端,KM4为接收端
注意事项:
每个方向配置8组物理通道,故通道号需在0-7范围内。
避免使用系统已预留的通道,这些通道可能用于WiFi/BT等系统组件。
1.2 定义接收端回调函数
接收端回调函数,从线程池中读取消息,若消息类型为指针,使用
_memcpy()
将共享信息拷贝到本地。中断处理函数退出后,SDK自动清除中断标志.1uint32_t local_buffer[32] = {0}; 2 3/* 接收处理函数(中断上下文)*/ 4void Rx_Channeln_Handler(void) { 5 PIPC_MSG_STRUCT msg = ipc_get_message(IPC_KR4_TO_KM4, IPC_R2M_Channeln);/*获取消息元数据*/ 6 uint32_t *sensor_data = (uint32_t*)msg->msg; /*提取数据指针*/ 7 _memcpy(local_buffer, (void*)sensor_data, data_size); /*数据拷贝到本地,数据长度双方提前约定好*/ 8 rtos_sema_give(ipc_sema); /*触发任务级处理*/ 9}
1.3 注册IPC配置表
需要在接收端为每个通道注册一个IPC配置表, SDK会自动开启通道的接收中断。
1/* 通道配置表*/ 2IPC_TABLE_DATA_SECTION 3const IPC_INIT_TABLE ipc_chn_config = { 4 .msg_type = IPC_USER_POINT, // 消息传递方式:指针传递(数据大于32bit) 5 .rx_func = Rx_Channeln_Handler, 6 .rx_irq_data = NULL, // 无附加参数 7 .tx_func = NULL, // 单向传输无需TX回调 8 .tx_irq_data = NULL, 9 .direction = IPC_KR4_TO_KM4, // 方向标识 10 .channel_num = IPC_R2M_Channeln // 通道号宏定义 11};
数据收发实现
2.1. 发送端实现
调用
ipc_send_message()
函数,并指定传输方向,通道号和消息。IPC通过通道n从KR4向KM4发送IPC请求。1void send_sensor_data(void) { 2 3 IPC_MSG_STRUCT ipc_msg_temp; 4 sensor_buffer = read_sensor(); // 更新共享内存 5 6 ipc_msg_temp.msg_type = IPC_USER_POINT; // 传递指针 7 ipc_msg_temp.msg = (u32)&sensor_buffer; 8 ipc_msg_temp.msg_len = 1; // 数据长度(字节) 9 ipc_msg_temp.rsvd =0; 10 11 /* 发送共享数据(SDK标准API), 带超时处理 */ 12 ipc_send_message(IPC_KR4_TO_KM4, IPC_R2M_Channeln, & ipc_msg_temp); 13}
2.2. 接收端处理
为了协同工作,接收端线程监控
ipc_sema
,收到数据后及时处理,并做下一步操作。1/* 中断上下文:快速捕获数据 */ 2void Rx_Channeln_Handler(void *data) { 3} 4 5/* 任务上下文:数据处理 */ 6void ipc_task(void *param) { 7 while (1) { 8 /*等待数据到达*/ 9 rtos_sema_take(ipc_sema, RTOS_WAIT_FOREVER); 10 /*理数据(已通过_memcpy复制到local_buffer)*/ 11 data_pipeline_process(local_buffer); 12 } 13}
通信协议栈注册
1.1 选择通道
文件路径 : ameba_ipccfg.h
选择与定义通道:
通过取消注释并修改通道定义来选择和自定义通道。例如:
1/* 取消通道n的注释,并添加功能描述 */ 2#define IPC_L2N_Channeln n /*!< 用户自定义通道:LP到NP的传感器数据传输 */ 3... 4//#define IPC_N2L_Channeln n /*!< 用户自定义通道:NP到LP的传感器数据传输 */ 5//#define IPC_L2A_Channeln n /*!< 用户自定义通道:LP到AP的传感器数据传输 */ 6//#define IPC_A2L_Channeln n /*!< 用户自定义通道:AP到LP的传感器数据传输 */ 7//#define IPC_N2A_Channeln n /*!< 用户自定义通道:NP到AP的传感器数据传输 */ 8//#define IPC_A2N_Channeln n /*!< 用户自定义通道:AP到NP的传感器数据传输 */
确认传输方向:
1#define IPC_LP_TO_NP 0 // LP为发送端,NP为接收端 2#define IPC_LP_TO_AP 1 // LP为发送端,AP为接收端 3#define IPC_NP_TO_LP 2 // NP为发送端,LP为接收端 4#define IPC_NP_TO_AP 3 // NP为发送端,AP为接收端 5#define IPC_AP_TO_LP 4 // AP为发送端,LP为接收端 6#define IPC_AP_TO_NP 5 // AP为发送端,NP为接收端
注意事项:
每个方向配置8组物理通道,故通道号需在0-7范围内。
避免使用系统已预留的通道,这些通道可能用于WiFi/BT等系统组件。
1.2 定义接收端回调函数
接收端回调函数,从线程池中读取消息,若消息类型为指针,使用
_memcpy()
将共享信息拷贝到本地。中断处理函数退出后,SDK自动清除中断标志.1uint32_t local_buffer[32] = {0}; 2 3/* 接收处理函数(中断上下文)*/ 4void Rx_Channeln_Handler(void) { 5 PIPC_MSG_STRUCT msg = ipc_get_message(IPC_LP_TO_NP, IPC_L2N_Channeln);/*获取消息元数据*/ 6 uint32_t *sensor_data = (uint32_t*)msg->msg; /*提取数据指针*/ 7 _memcpy(local_buffer, (void*)sensor_data, data_size); /*数据拷贝到本地,数据长度双方提前约定好*/ 8 rtos_sema_give(ipc_sema); /*触发任务级处理*/ 9}
1.3 注册IPC配置表
需要在接收端为每个通道注册一个IPC配置表, SDK会自动开启通道的接收中断。
1/* 通道配置表*/ 2IPC_TABLE_DATA_SECTION 3const IPC_INIT_TABLE ipc_chn_config = { 4 .msg_type = IPC_USER_POINT, // 消息传递方式:指针传递(数据大于32bit) 5 .rx_func = Rx_Channeln_Handler, 6 .rx_irq_data = NULL, // 无附加参数 7 .tx_func = NULL, // 单向传输无需TX回调 8 .tx_irq_data = NULL, 9 .direction = IPC_LP_TO_NP, // 方向标识 10 .channel_num = IPC_L2N_Channeln // 通道号宏定义 11};
数据收发实现
2.1. 发送端实现
调用
ipc_send_message()
函数,并指定传输方向,通道号和消息。IPC通过通道n从LP向NP发送IPC请求。1void send_sensor_data(void) { 2 3 IPC_MSG_STRUCT ipc_msg_temp; 4 sensor_buffer = read_sensor(); // 更新共享内存 5 6 ipc_msg_temp.msg_type = IPC_USER_POINT; // 传递指针 7 ipc_msg_temp.msg = (u32)&sensor_buffer; 8 ipc_msg_temp.msg_len = 1; // 数据长度(字节) 9 ipc_msg_temp.rsvd =0; 10 11 /* 发送共享数据(SDK标准API), 带超时处理 */ 12 ipc_send_message(IPC_LP_TO_NP, IPC_L2N_Channeln, & ipc_msg_temp); 13}
2.2. 接收端处理
为了协同工作,接收端线程监控
ipc_sema
,收到数据后及时处理,并做下一步操作。1/* 中断上下文:快速捕获数据 */ 2void Rx_Channeln_Handler(void *data) { 3} 4 5/* 任务上下文:数据处理 */ 6void ipc_task(void *param) { 7 while (1) { 8 /*等待数据到达*/ 9 rtos_sema_take(ipc_sema, RTOS_WAIT_FOREVER); 10 /*理数据(已通过_memcpy复制到local_buffer)*/ 11 data_pipeline_process(local_buffer); 12 } 13}
IPC 进阶使用方法
应用场景
通道繁忙时,发送线程及时让出调度,避免轮询占用CPU资源,使系统运行流畅。
传输回执配置
定义发送端回调函数
如果
IPC_INIT_TABLE
初始化了发送端回调函数, SDK会自动开启通道n的传输完成中断,并在通道繁忙时初始化通道n的传输信号量(ipc_Semaphore
)并等待释放, 以此让出调度,免长时间占用CPU。我们建议将此处理函数注册为 ameba_ipc_api.c 中的:func:IPC_TXHandler() 。在此函数中,会释放通道n的信号量(
ipc_Semaphore
)。SDK会在IRQ退出后自动清除中断标志。
void IPC_TXHandler(void *Data, u32 IrqStatus, u32 ChanNum) UNUSED(Data); UNUSED(IrqStatus); u32 CPUID = SYS_CPUID(); IPC_TypeDef *IPCx = IPC_GetDevById(CPUID); /*关闭tx channeln 中断*/ IPC_INTConfig(IPCx, ChanNum, DISABLE); if (ipc_Semaphore[ChanNum - IPC_TX_CHANNEL_SHIFT] != NULL) { /*释放信号量*/ rtos_sema_give(ipc_Semaphore[ChanNum - IPC_TX_CHANNEL_SHIFT]); } }
注册IPC配置表
在发送端为每个通道注册一个IPC配置表(可以将传输和接收配置于同一表中,需要双核都编译这个表)
/* 通道配置表*/ IPC_TABLE_DATA_SECTION const IPC_INIT_TABLE ipc_chn_config = { .msg_type = IPC_USER_POINT, // 消息传递方式:指针传递(数据大于32bit) .rx_func = NULL, // 接收函数置空 .rx_irq_data = NULL, // 无附加参数 .tx_func = IPC_TXHandlerL, // 单向传输无需TX回调 .tx_irq_data = NULL, .direction = IPC_Direction, // 方向标识 .channel_num = IPC_Channel // 通道号宏定义 };
IPC 常见问题排查
通道冲突
现象 |
打印错误日志:Channel Conflict for CPU A Channel n ! Ignore If CPU Has Reset |
原因 |
同一个通道注册了两个中断回调函数 |
解决方法 |
检查 IPC 配置表( |
发送超时
现象 |
打印错误日志:IPC Request Timeout |
原因 |
数据还未被接收,发送端试图再次通过同一通道发送数据 |
解决方法 |
|
获取信号量超时
现象 |
打印错误日志:IPC Get Semaphore Timeout |
原因 |
发送端注册了发送完成回调函数,数据还未被接收,发送端试图再次通过同一通道发送数据,发送端等待信号量超时 |
解决方法 |
|
接收端无响应
现象1 |
接收端某个IPC通道无响应 |
原因 |
接收端该通道中断被误关闭 |
解决方法 |
检查接收端是否误关中断,通过调用 |
现象2 |
接收端所有IPC通道无响应 |
原因 |
|
解决方法 |
|