基于STM32MP157的异核间通信
在STM32MP157芯片内,有两个A7核和一个M4核。一般情况下,A7核用来跑Linux系统,M4核用来跑实时性要求较高的程序,可以跑裸机也可以跑实时系统(比如FreeRTOS)。但无论哪种方式,都要涉及两种异核间的通信,因此必须提供一种即高效又便捷的多核异构(AMP)的通信机制。在Linux系统中就提供了这种异核通信的技术,其核心围绕着以OpenAMP和RPMsg为代表的软件框架展开,主要解决不同架构核心(如ARM、RISC-V、DSP)高效协同工作的问题。下面就来讨论一下如何在STM32MP157芯片上实现这一技术。
首先介绍一下这个软件框架,它由三个部分组成,具体如下。
1、Remoteproc框架。它负责管理远程处理器的生命周期,如启动、加载固件和关闭从核。
2、RPMsg框架。它是异核通信的核心协议,基于共享内存提供标准化消息总线。具有基于VirtIO高效管理内存、零拷贝传输、并自带同步互斥机制。
3、OpenAMP框架。它提供了标准API来简化异构多核开发,整合有生命周期管理、消息传递等核心功能。
注意,上述三个框架中,Remoteproc和RPMsg位于主核(如A7)端(Linux系统),OpenAMP位于从核(如M4)端(踝机或RTOS)。
以上框架依赖于硬件提供的支持,主要包括:共享内存和硬件邮箱中断(如IPCC)等。共享内存提供一块所有核心都能访问的物理内存区域,用于数据交换。硬件邮箱中断用于核心间发送中断信号,作为“通知机制”触发对方处理新消息。
下面还是以点亮一个LED为例来进行讨论,本例选取接在PA13引脚上的LED,原理图可参见“嵌入式Linux中的LED驱动控制”一文。
首先在M4端使用踝机方式实现一个LED的秒闪功能,这里使用STM32CubeIDE1.6.1版本进行开发。关于该软件的安装和配置可参考“笔记本电脑安装LinuxMint(续一)”一文。
先新建一个名为AMP_test的STM32工程,然后选择芯片为STM32MP157ACCx。
接着配置PA13引脚,在搜索框中输入PA13并回车找到闪烁的引脚,在引脚上点击会弹出选择框,如下图所示。

用鼠标选择“GPIO_Output”一项,配置该引脚为输出模式。然后再次在该引脚上单击鼠标右键,并在弹出的对话框中选择Cortex-M4,如下图所示。

这样就把该引脚指定给了M4核。然后单击左侧的“System Core”一项展开它,并在其中单击“GPIO”项,然后点击中间的PA13一栏,在下方的“GPIO Pull-up/Pull-down”后面选择“Pull-up”。如下图所示。

这样就配置了该引脚为上拉模式,至此就把PA13引脚配置好了。为了方便后面调试,这里顺带把USART3也配置了。USART3的收/发引脚分别为PD9和PD8。配置方式与上面一样,只需要点击左键进行配置即可。PD8配置为USART3_TX,PD9配置为USART3_RX。完成后点击左侧的“Connectivity”一项展开它,并在其中单击“USART3”项,然后勾选Cortex-M4下面的方框,把USART3分配给M4。然后在“Mode”后面选择“Asynchronous”异步模式。接着在下面分别点击PD8和PD9,把各自的“GPIO Pull-up/Pull-down”项都配置为“Pull-up”上拉即可,如下图所示。

接下来配置IPCC。在左侧展开的“System Core”中点击“IPCC”项,然后勾选Cortex-M4下面的方框,这时A7也会同步被勾选上。接着勾选“Activated“,激活它们。最后在下面的“NVIC Settings”选项卡中,同时勾选“Enabled”,使能IPCC的发送和接收中断。如下图所示。

在默认情况下,HSEM(硬件信号量)是被勾选的,如上图中所示。若没有勾选要手动勾选上。接下来配置OPEMAMP。点击左侧的”Middleware“一项展开它,并点击”OPENAMP“项,勾选Cortex-M4下面的方框,接着勾选“Activated“,激活它们。如下图所示。

以上配置完成后就可以生成代码了。先点击上方的”Project Manager“标签页,然后点击左侧的”Code Generator“项,再勾选”Generated files“里的”Generate peripheral initialization as a pair of '.c/.h' files per peripheral“一项。以为不同的外设生成自己的c文件和头文件。如下图所示。

最后点击保存按钮,会有如下提示信息。

点击Yes按钮,然后在出现的提示框中再点击Yes按钮,就会自动生成代码了。如果没有弹出提示框,也可以在菜单”Project“中点击”Generate Code“。
默认在STM32CubeIDE中会打开main.c文件,若没有打开可展开左侧的”AMP_test_CM4“->"Core"->"Src",并双击其下的main.c即可打开。找到main函数下的while(1)循环部分,然后在/* USER CODE BEGIN 3 */提示的下面输入以下代码。
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_13); HAL_Delay(500); HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_13); HAL_Delay(500);
完成后如下图所示。

最后编译工程,在”AMP_test_CM4“上点击右键,选择”Build Project“进行编译,如下图所示。

编译成功后,会在工程目录下生成文件AMP_test_CM4.elf,这就是要下载到M4去执行的程序文件。由于M4与A7集成在同一块芯片中,所以M4并不像传统MCU那样有自己的CodeFlashRom,因此产不能把AMP_test_CM4.elf文件烧写进M4中去执行。这里就需要通过Remoteproc框架来把AMP_test_CM4.elf文件传递给M4,并控制M4执行或停止。
先启动开发板进入Linux系统,然后把刚才编译生成的AMP_test_CM4.elf文件拷贝到开发板目录/lib/firmware下。然后执行命令”echo AMP_test_CM4.elf > /sys/class/remoteproc/remoteproc0/firmware“把文件传递给M4(这里的remoteproc0就是M4),接着执行命令”echo start > /sys/class/remoteproc/remoteproc0/state“启动执行,此时可以看到开发板上的LED已经在闪烁了。此时可执行命令”echo stop > /sys/class/remoteproc/remoteproc0/state“停止执行,LED停止闪烁。操作过程如下图所示。

以上实现了Remoteproc框架的功能测试,接下来讨论RPMsg及OpenAMP框架的通信功能。进一步更改上面的例子,要求通过A7向M4发送字符串”led_on“和”led_off“来实现LED的亮灭控制。同时,M4也向A7发送LED亮灭信息。
首先编写Linux下的内核模块程序,文件名为my_rpmsg.c,内容如下。
#include <linux/rpmsg.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/kfifo.h> #include <linux/wait.h> #include <linux/spinlock.h> #include <linux/uaccess.h> #include <linux/module.h> #include <linux/slab.h> #define DEVICE_NAME "rpmsg0" //设备节点名称 #define FIFO_SIZE 1024 //缓冲区大小 //以下定义一个m4的私有结构体 struct instance_m4 { dev_t devid; //定义设备号 struct cdev rpmsg_cdev; //定义字符型结构体 struct device *dev_rpmsg; //创建的设备 struct kfifo rpmsg_fifo; //定义内核FIFO缓冲区 wait_queue_head_t read_wq; //定义等待队列头 spinlock_t lock; //定义自旋锁变量 struct rpmsg_device *rpdev; //定义rpmsg_device结构体 }; static struct class *rpmsg_class; //定义类结构体 static bool dev_registered = false; //定义布尔变量 //实现open函数,为file_oprations结构体成员函数 static int rpmsg_open(struct inode *inode, struct file *filp) { struct instance_m4 *idata = container_of(inode->i_cdev, struct instance_m4, rpmsg_cdev); filp->private_data = idata;//把上面获取的m4结构体存入file的私有变量private_data中,后面的函数会用到 return 0; } //实现read函数,为file_oprations结构体成员函数 static ssize_t rpmsg_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { int ret; unsigned int copied; //复制数据长度变量 unsigned long flags; //中断状态保存变量 struct instance_m4 *idata = filp->private_data; //把私有变量private_data中的值赋值给指针instance_m4 spin_lock_irqsave(&idata->lock, flags); //自旋锁上锁 //以下进行阻塞直到接收到有效数据 while(kfifo_is_empty(&idata->rpmsg_fifo)) //若fifo为空则阻塞休眠 { spin_unlock_irqrestore(&idata->lock, flags); //进入休眠前要解锁 //以下如果应用程序打开设备文件时设置了O_NONBLOCK,就立即返回-EAGAIN if(filp->f_flags & O_NONBLOCK) return -EAGAIN; //进入休眠,若进入休眠出错则返回 ret = wait_event_interruptible(idata->read_wq, !kfifo_is_empty(&idata->rpmsg_fifo)); if(ret) return ret; spin_lock_irqsave(&idata->lock, flags); //退出休眠后重新上锁 } ret = kfifo_to_user(&idata->rpmsg_fifo, buf, count, &copied);//把fifo中的数据复制到用户空间 spin_unlock_irqrestore(&idata->lock, flags); //结束读取时要解锁 if(ret) return ret; //若复制出错则返回 return copied; //返回已复制数据的长度 } //实现write函数,为file_oprations结构体成员函数 static ssize_t rpmsg_write(struct file *filp, const char __user *buf, size_t count, loff_t *offt) { void *kbuf; int ret; //以下把私有变量private_data中的值赋值给指针led(该值在上面open函数中存入) struct instance_m4 *idata = filp->private_data; if(count == 0 || count > 512) //限制单次写入的长度,避免过大内存分配 return -EINVAL; kbuf = kmalloc(count, GFP_KERNEL); //为变量分配内存空间 if(!kbuf) return -ENOMEM; if(copy_from_user(kbuf, buf, count)) //从应用空间获取信息 { ret = -EFAULT; goto out_free; } ret = rpmsg_send(idata->rpdev->ept, kbuf, count);//发送数据到远程处理器 if(ret) { dev_err(idata->dev_rpmsg, "rpmsg_send failed: %d\n", ret); ret = -EIO; goto out_free; } ret = count; //成功,返回写入的字节数 out_free: //处理错误 kfree(kbuf); return ret; } //填充一个file_oprations类型的结构体,名为rpmsg_fops,包含上述声明的成员函数 static const struct file_operations rpmsg_fops = { .owner = THIS_MODULE, .open = rpmsg_open, //指定open函数成员 .read = rpmsg_read, //指定read函数成员 .write = rpmsg_write, //指定write函数成员 }; //rpmsg接收回调函数,当收到数据时触发 static int rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, void *priv, u32 src) { struct instance_m4 *idata = dev_get_drvdata(&rpdev->dev);//获取当前m4对应的结构体 unsigned int copied; unsigned long flags; spin_lock_irqsave(&idata->lock, flags); //自旋锁上锁 copied = kfifo_in(&idata->rpmsg_fifo, data, len); //把收到的数据塞入内核fifo缓冲区 spin_unlock_irqrestore(&idata->lock, flags); //自旋锁解锁 if(copied < len) //若塞入的长度与收到的长度不相符则报错(溢出) dev_warn(&rpdev->dev, "fifo overflow: copied %u/%d bytes\n", copied, len); wake_up_interruptible(&idata->read_wq); //唤醒等待队列上睡眠的进程 return 0; } //probe函数中,创建并完成字符设备的注册 static int rpmsg_probe(struct rpmsg_device *rpdev) { int ret; struct instance_m4 *idata; if(dev_registered) //检测设备存不存在,若已存在则报错 { dev_err(&rpdev->dev, "device already registered\n"); return -EBUSY; } //以下动态申请idata结构体大小的内存 idata = devm_kzalloc(&rpdev->dev, sizeof(*idata), GFP_KERNEL); if(!idata) return -ENOMEM; //以下申请内核fifo缓冲区内存 if(kfifo_alloc(&idata->rpmsg_fifo, FIFO_SIZE, GFP_KERNEL)) { dev_err(&rpdev->dev, "failed to allocate fifo\n"); return -ENOMEM; } init_waitqueue_head(&idata->read_wq); //初始化等待队列头 spin_lock_init(&idata->lock); //初始化自旋锁 //以下创建类,若创建失败则报错并返回 if(!rpmsg_class) { rpmsg_class = class_create(THIS_MODULE, "my_rpmsg"); if (IS_ERR(rpmsg_class)) { ret = PTR_ERR(rpmsg_class); rpmsg_class = NULL; dev_err(&rpdev->dev, "class_create failed: %d\n", ret); goto err_fifo; } } //以下申请主设备号,若创建失败则报错并返回 ret = alloc_chrdev_region(&idata->devid, 0, 1, DEVICE_NAME); if(ret) { dev_err(&rpdev->dev, "alloc_chrdev_region failed: %d\n", ret); goto err_class; } //绑定前面声明的file_oprations类型的结构体到字符设备 cdev_init(&idata->rpmsg_cdev, &rpmsg_fops); //填充上面申请到的主设备号到字符设备,若失败则报错并返回 ret = cdev_add(&idata->rpmsg_cdev, idata->devid, 1); if(ret) { dev_err(&rpdev->dev, "cdev_add failed: %d\n", ret); goto err_region; } //创建一个设备节点,若创建失败则报错并返回 idata->dev_rpmsg = device_create(rpmsg_class, &rpdev->dev, idata->devid, NULL, DEVICE_NAME); if(IS_ERR(idata->dev_rpmsg)) { ret = PTR_ERR(idata->dev_rpmsg); dev_err(&rpdev->dev, "device_create failed: %d\n", ret); goto err_cdev; } dev_set_drvdata(&rpdev->dev, idata); //把idata数据信息存入设备平台驱动结构体中 idata->rpdev = rpdev; //记录当前配置的rpdev结构体 dev_registered = true; //标记设备已被注册 //以下打印消息,显示源地址、目的地址和设备名称 dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x, /dev/%s ready\n", rpdev->src, rpdev->dst, DEVICE_NAME); //以下发送一条消息,用于激活通道 ret = rpmsg_send(rpdev->ept, "hello", 6); if(ret) { dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret); return ret; } return 0; //以下处理各类错误 err_cdev: cdev_del(&idata->rpmsg_cdev); err_region: unregister_chrdev_region(idata->devid, 1); err_class: if(rpmsg_class) { class_destroy(rpmsg_class); rpmsg_class = NULL; } err_fifo: kfifo_free(&idata->rpmsg_fifo); return ret; } //remove函数中,删除设备并释放设备号 static void rpmsg_remove(struct rpmsg_device *rpdev) { struct instance_m4 *idata = dev_get_drvdata(&rpdev->dev); //获取当前idata对应的结构体 if (!dev_registered) //若设备没有注册则返回 return; device_destroy(rpmsg_class, idata->devid); //销毁设备节点 cdev_del(&idata->rpmsg_cdev); //删除字符设备 unregister_chrdev_region(idata->devid, 1); //释放主设备号 kfifo_free(&idata->rpmsg_fifo); //释放fifo缓冲区 class_destroy(rpmsg_class); //销毁类 rpmsg_class = NULL; dev_registered = false; //标记设备为未注册 dev_info(&rpdev->dev, "rpmsg sample client driver removed\n"); //打印信息 } //以下是内核定义的结构体,用于匹配远程处理器创建的通道名称 static struct rpmsg_device_id rpmsg_driver_id_table[] = { { .name = "my-rpmsg-client" }, { }, }; //以下将设备ID表导出到模块信息中,供depmod和用户态热插拔工具使用,系统会在匹配到相应通道时自动加载该驱动模块 MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_id_table); //以下填充RPMsg驱动结构体 static struct rpmsg_driver my_rpmsg_client = { .drv.name = KBUILD_MODNAME, .id_table = rpmsg_driver_id_table, .probe = rpmsg_probe, .callback = rpmsg_cb, .remove = rpmsg_remove, }; module_rpmsg_driver(my_rpmsg_client); //向内核注册上面填充好的RPMsg驱动结构体 MODULE_LICENSE("GPL v2");
上述代码中加入了自旋锁来防止多核竞争的冲突,可做为工程实践的参考,若只是单纯学习可以把自旋锁的相关内容去除,代码会更精简。接下来是配套的Makefile文件,内容如下。
KERNEL_DIR=/opt/ebf_linux_kernel_mp157_depth1/build_image/build/ ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- export ARCH CROSS_COMPILE obj-m := my_rpmsg.o all: $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules clean: $(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
编译上述内核模块程序,把生成的my_rpmsg.ko文件通过NFS拷贝到开发板中,并放在目录/lib/modules/4.19.94-stm-r1/kernel/samples/rpmsg/下,先执行“depmod -a”更新一下别名,然后执行“sync”同步一下就可以了。在后面加载M4固件时它会自动插入内核。
接下来是应用程序,文件名为app.c,内容如下。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #define DEV_PATH "/dev/rpmsg0" //设备节点名称 #define BUF_SIZE 128 //缓冲区大小 int main(int argc, char *argv[]) { int fd; ssize_t n; char *msg; unsigned char rbuf[BUF_SIZE]; if(argc != 2) { fprintf(stderr, "Usage: %s on|off\n", argv[0]); return EXIT_FAILURE; } if(strcmp(argv[1], "on") == 0) //参数为on则发送led_on字符串 msg = "led_on"; else if(strcmp(argv[1], "off") == 0) //参数为off则发送led_off字符串 msg = "led_off"; else //参数错误提示 { fprintf(stderr, "Invalid argument: %s (must be 'on' or 'off')\n", argv[1]); return EXIT_FAILURE; } fd = open(DEV_PATH, O_RDWR); //以读写方式打开设备 if(fd < 0) { perror("open " DEV_PATH " failed"); return EXIT_FAILURE; } n = write(fd, msg, strlen(msg)); //发送命令字符串 if(n < 0) { perror("write failed"); close(fd); return EXIT_FAILURE; } printf("Sent '%s' (%zd bytes) to M4.\n", msg, n); //打印发送的命令字符串 printf("Waiting for reply from M4...\n"); //打印提示信息 do //阻塞等待M4回复 { n = read(fd, rbuf, sizeof(rbuf)); }while (n < 0 && errno == EINTR); //若被信号中断(比如Ctrtl+C)则重试 if(n < 0) { perror("read error"); close(fd); return EXIT_FAILURE; } else if(n == 0) printf("Device closed (EOF) - no reply received.\n"); else { //以下打印接收到的数据(当作字符串,限制长度) printf("Received %zd bytes from M4: %.*s\n", n, (int)n, rbuf); /* 如果数据不是纯文本,可以使用以下方式同时输出十六进制: printf("Hex: "); for (int i = 0; i < n; i++) printf("%02x ", rbuf[i]); printf("\n"); */ } close(fd); //关闭设备 return 0; }
最后来看M4端的程序,在上面的AMP_test_CM4工程中更改。下面给出改好的整个main.c文件内容(包含模板内容)。
/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * <h2><center>© Copyright (c) 2026 STMicroelectronics. * All rights reserved.</center></h2> * * This software component is licensed by ST under BSD 3-Clause license, * the "License"; You may not use this file except in compliance with the * License. You may obtain a copy of the License at: * opensource.org/licenses/BSD-3-Clause * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "ipcc.h" #include "openamp.h" #include "openamp/rpmsg.h" #include "usart.h" #include "gpio.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ #define RPMSG_SERVICE_NAME "my-rpmsg-client" //rpmsg服务名称,用于匹配内核模块驱动 __IO FlagStatus rx_status = RESET; //接收标志 uint8_t received_rpmsg[128]; //接收缓冲区 uint8_t BuffTx[128]; //发送缓冲区 #define LED1_ON "led_on" //匹配亮灯字符串命令 #define LED1_OFF "led_off" //匹配熄灯字符串命令 //以下申明接收回调函数 static int rx_callback(struct rpmsg_endpoint *rp_chnl, void *data, size_t len, uint32_t src, void *priv); //以下重定位USART3串口到printf函数 #ifdef __GNUC__ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endif PUTCHAR_PROTOTYPE { while ((USART3->ISR & 0x40) == 0); USART3->TDR = (uint8_t)ch; return ch; } /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ struct rpmsg_endpoint resmgr_ept; //定义rpmsg端点结构体 /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ if(IS_ENGINEERING_BOOT_MODE()) { /* Configure the system clock */ SystemClock_Config(); } else { /* IPCC initialisation */ MX_IPCC_Init(); /* OpenAmp initialisation ---------------------------------*/ MX_OPENAMP_Init(RPMSG_REMOTE, NULL); } /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART3_UART_Init(); /* USER CODE BEGIN 2 */ //以下新建端点 if(OPENAMP_create_endpoint(&resmgr_ept, RPMSG_SERVICE_NAME, RPMSG_ADDR_ANY, rx_callback, NULL) < 0) printf("Failed to creat endpoint!\r\n"); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ OPENAMP_check_for_message(); //调用邮箱中断检测函数 if(rx_status == SET) //若接收到信息 { rx_status = RESET; //接收标志复位 if (!strncmp((char *)received_rpmsg, LED1_ON, strlen(LED1_ON))) //匹配亮灯字符串 { OPENAMP_send(&resmgr_ept, "led_on", 6); //发送亮灯字符串 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_13, GPIO_PIN_RESET); //点亮LED } if (!strncmp((char *)received_rpmsg, LED1_OFF, strlen(LED1_OFF))) //匹配熄灯字符串 { OPENAMP_send(&resmgr_ept, "led_off", 7); //发送熄灯字符串 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_13, GPIO_PIN_SET); //熄灭LED } memset(received_rpmsg, 0, 128); //清空接收缓冲区 memset(BuffTx, 0, 128); //清空发送缓冲区 } } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_LSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = 16; RCC_OscInitStruct.HSIDivValue = RCC_HSI_DIV1; RCC_OscInitStruct.LSIState = RCC_LSI_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; RCC_OscInitStruct.PLL2.PLLState = RCC_PLL_NONE; RCC_OscInitStruct.PLL3.PLLState = RCC_PLL_NONE; RCC_OscInitStruct.PLL4.PLLState = RCC_PLL_NONE; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** RCC Clock Config */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_ACLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2 |RCC_CLOCKTYPE_PCLK3|RCC_CLOCKTYPE_PCLK4 |RCC_CLOCKTYPE_PCLK5; RCC_ClkInitStruct.AXISSInit.AXI_Clock = RCC_AXISSOURCE_HSI; RCC_ClkInitStruct.AXISSInit.AXI_Div = RCC_AXI_DIV1; RCC_ClkInitStruct.MCUInit.MCU_Clock = RCC_MCUSSOURCE_HSI; RCC_ClkInitStruct.MCUInit.MCU_Div = RCC_MCU_DIV1; RCC_ClkInitStruct.APB4_Div = RCC_APB4_DIV1; RCC_ClkInitStruct.APB5_Div = RCC_APB5_DIV1; RCC_ClkInitStruct.APB1_Div = RCC_APB1_DIV1; RCC_ClkInitStruct.APB2_Div = RCC_APB2_DIV1; RCC_ClkInitStruct.APB3_Div = RCC_APB3_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct) != HAL_OK) { Error_Handler(); } } /* USER CODE BEGIN 4 */ //以下为接收回调函数 static int rx_callback(struct rpmsg_endpoint *rp_chnl, void *data, size_t len, uint32_t src, void *priv) { //以下拷贝接收到的信息到接收缓冲区 memcpy(received_rpmsg, data, len > sizeof(received_rpmsg) ? sizeof(received_rpmsg) : len); printf("received_rpmsg=%s\r\n", received_rpmsg); //打印接收到的信息(通过USART3口) rx_status = SET; //接收标志置位 return 0; } /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
同前面一样,把编译后生成的AMP_test_CM4.elf文件拷贝到开发板目录/lib/firmware下。然后执行命令”echo AMP_test_CM4.elf > /sys/class/remoteproc/remoteproc0/firmware“把文件加载给M4,接着执行命令”echo start > /sys/class/remoteproc/remoteproc0/state“启动执行,可看到打印的相关信息,还可以通过lsmod命令查看一下前面的my_rpmsg.ko模块是否已经加载进内核。
确认无误后,可执行应用程序进行测试。执行“./app on”时开发板LED亮,并且打印出发送的信息和接收到的信息,同时在开发板的USART3也打印出了接收到的信息。接着执行“./app off”,开发板LED熄灭,同时打印相关信息。整体操作及结果如下图所示。

下图是M4串口3打印的信息。

第一行hello是在启动M4固件时,由内核模块my_rpmsg发送的第一条消息,M4收到该消息后就打通了一条与A7交流的通道。第二行是A7端发来的led_on消息,用于点亮LED。第三行也是7端发来的led_off消息,用于熄灭LED。
通过以上测试,证明基于STM32MP157系统的A7与M4异核间通信完全成功。
-待续-

浙公网安备 33010602011771号