基于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并回车找到闪烁的引脚,在引脚上点击会弹出选择框,如下图所示。

111

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

111

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

111

这样就配置了该引脚为上拉模式,至此就把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”上拉即可,如下图所示。

111

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

111

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

111

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

111

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

111

点击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);

完成后如下图所示。

111

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

111

编译成功后,会在工程目录下生成文件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停止闪烁。操作过程如下图所示。

111

以上实现了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熄灭,同时打印相关信息。整体操作及结果如下图所示。

111

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

111

第一行hello是在启动M4固件时,由内核模块my_rpmsg发送的第一条消息,M4收到该消息后就打通了一条与A7交流的通道。第二行是A7端发来的led_on消息,用于点亮LED。第三行也是7端发来的led_off消息,用于熄灭LED。

通过以上测试,证明基于STM32MP157系统的A7与M4异核间通信完全成功。

-待续-

posted @ 2026-08-05 15:36  fxzq  阅读(38)  评论(0)    收藏  举报