STM32H755以双核为他的特点。因此学会使用双核通信是一项基础,本篇学习如何实现基于OpenAmp的基础通信。
学习资料1、官方的AN5617,他详细的阐述了基于STM32H7x5/H7x7系列的双核通信的原理以及多种实现方式。
2、B站的一个大佬的视频,详细的手把手教程大家如何实现。链接地址为:
3、官方的例程,在官方的stm32cube的H747中也有很多的教程。
【工程创建】
1、使用stm32cubeMX创建一个基于NUCLEO-H755的基础工程。
2、在NVIC中分别打开Cortex-m7以及cortex-m4的HSEM全局中断,如果不打开,那就配置不了OPENAMP
3、使能OPENAMP_M4以及OPENAMP_M4,钩选enable后,配置保持因默认即可,系统将配置OPENAMP_M7为Master,而OPENAMP_M4为Slave
4、配置Cortex-M7的MPU
OpenAMP是基于共享内存来传递数据的,因此,我们需要配置双核均可以访问的内存,选用SRAM4即地址0x3800000段,大小为64Kb,因为不需要Cache,配置如下图所示:
4、然后配置时钟以及工程的其他选项。
5、生成基于MDK的工程,并使用keil打开。
【测试代码】1、首先在.sct链接文件中最后添加内容:
RW_OPENAMP_RSC_TAB 0x38000000 0x00000400 {
*(.resource_table)
}
__OpenAMP_SHMEM__ 0x38000400 EMPTY 0xFC00 {} ; Shared Memory area used by OpenAMP
}
【注】如果使用IAR或stm32cubeIDE请参见AN5617的Table5进行对应的链接文件的修改。
2、首先编写cortex_m4的代码,打开m4的main.c,声明初始变量如下:
#define RPMSG_SEVICE_NAME "openamp_demo" //RPMSG服务名称
static volatile int message_received; //消息接收标志
static volatile int service_created; //服务创建标志
static volatile char received_data[100]; //消息接收标志
static struct rpmsg_endpoint rp_endpoint; //RPMSG端点结构体
static char data[100]; //数据缓存
3、编写接收回调函数:
static int rpmsg_recv_callback(struct rpmsg_endpoint *ept, void *data,
size_t len, uint32_t src, void *priv)
{
memcpy((void*)received_data, data, len);
received_data[len] = '\0'; // 确保字符串结束符
message_received = 1;
return 0;
}
4、编写接收消息函数:
/**
* @brief 接收消息函数
* @details 该函数用于等待并接收消息,当消息到达时返回
* @param 无参数
* @return 返回0表示成功接收到消息
*/
uint32_t receive_message(void)
{
/* 等待消息到达 */
while(message_received == 0)
{
OPENAMP_check_for_message();
}
/* 清除消息接收标志 */
message_received = 0;
return 0;
}
5、 OpenAMP 初始化代码
if(MX_OPENAMP_Init(RPMSG_REMOTE,NULL) != HAL_OK)
{
Error_Handler();
}
6、创建并注册一个通信端点
status = OPENAMP_create_endpoint(&rp_endpoint, RPMSG_SEVICE_NAME,
RPMSG_ADDR_ANY,
rpmsg_recv_callback, NULL);
if(status != 0)
{
Error_Handler();
}
7、接下来就等待接收数据,如果接收到了就通过虚拟串口打印出来。
receive_message();
sprintf(data,"Received data: %d\n",receivied_data);
HAL_UART_Transmit(&hcom_uart[COM1],(uint32_t *)data,strlen(data),1000);
8、最后我们可以注消OPENAMP
OPENAMP_DeInit();
到此cortex_M4的代码编写完毕。
【cortex_M7代码】
打开cortex_M7的main.c编写代码如下:
1、同CM4的代码一下样,先创建基本参数:
#define RPMSG_SEVICE_NAME "openamp_demo"
uint32_t message = 1234;
static volatile int message_received;
static volatile int service_created;
volatile uint32_t receivied_data;
static struct rpmsg_endpoint rp_endpoint;
2、编写接收回调函数:
/**
* @brief RPMsg接收回调函数,当RPMsg端点接收到数据时被调用
*
* @param ept RPMsg端点结构体指针,表示接收数据的端点
* @param data 接收到的数据缓冲区指针
* @param len 接收到的数据长度
* @param src 数据来源地址
* @param priv 私有数据指针
*
* @return 0 表示处理成功
*/
static int rpmsg_recv_callback(struct rpmsg_endpoint *ept, void *data,
size_t len, uint32_t src, void *priv)
{
/* 从接收到的数据中提取第一个uint32_t值并保存到全局变量 */
receivied_data = *((uint32_t *)data);
/* 设置消息接收标志位,表示已接收到新消息 */
message_received = 1;
return 0;
}
3、服务销毁回调函数
/**
* @brief 服务销毁回调函数
*
* 当RPMSG端点服务被销毁时调用此回调函数,用于重置服务创建状态标志
*
* @param ept 指向RPMSG端点结构体的指针
*
* @return 无返回值
*/
void service_destroy_cb(struct rpmsg_endpoint *ept)
{
/* 重置服务创建状态标志,表示服务已被销毁 */
service_created = 0;
};
4、编写新服务回调函数,当检测到新服务时被调用
/**
* @brief 新服务回调函数,当检测到新服务时被调用
*
* @param rdev 指向rpmsg设备的指针
* @param name 服务名称字符串
* @param dest 目标地址
*
* 该函数负责创建新的端点并标记服务已创建状态
*/
void new_service_cb(struct rpmsg_endpoint *rdev, const char *name,
uint32_t dest)
{
/* 创建新的RPMSG端点 */
OPENAMP_create_endpoint(&rp_endpoint, name, dest, rpmsg_recv_callback, service_destroy_cb);
/* 标记服务已创建 */
service_created = 1;
}
5、接收消息函数
/**
* @brief 接收消息函数
* @details 该函数用于等待并接收消息,当有消息到达时返回
* @param 无参数
* @return uint32_t 返回0表示成功接收到消息
*/
uint32_t receive_message(void)
{
/* 等待消息到达,当message_received标志为0时持续检查 */
while(message_received == 0)
{
OPENAMP_check_for_message();
}
/* 清除消息接收标志,为下一次接收做准备 */
message_received = 0;
return 0;
}
6、服务销毁回调函数
/**
* @brief 服务销毁回调函数
*
* 当RPMsg端点被销毁时调用此回调函数,用于重置服务创建状态标志。
*
* @param ept 指向RPMsg端点结构的指针
*
* @return 无返回值
*/
void service_destroy_cbk(struct rpmsg_endpoint *ept)
{
/* 重置服务创建标志,表示服务已被销毁 */
service_created = 0;
}
7、接下来在main主函数中编写初始化邮箱、RPMsg结构,配置端点名称和服务地址,并初始化OPENAMP模块,等待RPMsg端点准备就绪后向Cortex_M4发送一条信息,然后就销毁OPENAMP。其代码如下,我生成的详细的注释:
/* 初始化邮箱通信模块 */
MAILBOX_Init();
/* 初始化RPMsg端点结构体,配置端点名称和服务地址 */
rpmsg_init_ept(&rp_endpoint, RPMSG_SEVICE_NAME, RPMSG_ADDR_ANY,
RPMSG_ADDR_ANY, NULL,NULL);
/* 初始化OPENAMP模块,作为主设备运行,并注册新服务发现回调函数 */
if((MX_OPENAMP_Init(RPMSG_MASTER,new_service_cb) != HAL_OK))
{
Error_Handler();
}
/* 等待RPMsg端点准备就绪 */
OPENAMP_Wait_EndPointready(&rp_endpoint);
/* 通过RPMsg端点发送消息到远程处理器 */
status = OPENAMP_send(&rp_endpoint, (uint32_t *)msg, sizeof(msg));
if(status < 0)
{
Error_Handler();
}
/* 持续检查消息,直到服务被销毁 */
while (service_created )
{
OPENAMP_check_for_message();
}
/* 反初始化OPENAMP模块,释放资源 */
OPENAMP_DeInit();
【测试效果】
分别把两个固件都下载进开发板,接上串口助手,就可以观察到串口成功打印出接收的信息:
【官方给出的流程图】
【总结】
本章详细的通过工程如何配置,两个核的OPENAMP的配,以及详细的代码编写,等展示了如何实现两个核之间的通信。