CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址:https://hceng.cn/2020/05/09/STM32MP157%E2%80%94%E2%80%94Remoteproc%E5%92%8CRPMsg/#more
简单介绍基于STM32MP157的Remoteproc和RPMsg框架。
STM32MP1系列产品,是STM32进军Linux的首款微处理器,采用MCU+MPU的组合,集成两颗主频为650MHz的Cortex-A7应用处理器内核和一颗主频为209MHz的Cortex-M4微控制器内核。
非对称多处理Asymmetric Multiprocessing(AMP)虽然目前在嵌入式还不是主流,但未来肯定是趋势。将多媒体处理扔给专用的MCU,亦或将对控制延时敏感的传感器交给MCU实时控制,更多的组合给人更多的遐想。
对于非对称多核架构,不同的核心是如何启动运行,又是如何进行通信?这些疑惑在上手STM32MP157后,逐渐明朗,因此记录下笔记。
在进行启动M4之前,需要先建立工程,生成M4固件,这里以点灯为例,简单说下创建STM32MP157的M4工程。
这里要M4点灯,涉及到资源的分配,资源分配如下图所示。
支持STM32开发的集成开发环境有很多,国内熟知的有Keil MDK-ARM
和IAR EWAR
。这两个IDE都很好用,但它们都是商业软件,免费或评估版要么有器件型号限制,要么有程序容量限制。于是出现了免费的Eclipse+GNU GCC来搭建STM32开发环境,但搭建过程繁琐、版本差异大导致教程不统一,对新手很不友好。
STM32CubeIDE是ST公司基于Eclipse/CDT框架和GUN GCC工具链制作的免费IDE,并集成了STM32CubeMX。一个软件就可以实现STM32系列芯片的外围设备配置、代码生成、代码编辑、代码编译、在线调试,并且支持数百个Eclipse现有插件。
打开STM32CubeIDE,创建一个新的“STM32 Project”。
在弹出的STM32CubeMX,选择“STM32MP157A”,具体型号以自己使用的开发板为准。注意这里的“芯片资料区”,提供了该型号的芯片手册,不用再去网上找了。
再设置工程名字,打开STM32CubeMX的关联视图。
我使用的板子,LED灯接在PD13引脚上,因此这里把PD13设置为输出引脚。
需要注意,这里还要选中该引脚,右键弹出“Pin Reservation”,选择“Cortex-M4”,不然不会自动生成GPIO初始化代码。
最后,如图设置下GPIO的属性。
设置完后,在标签栏选择“Project”->“Generate Code”,即可自动生成相关初始化代码。默认的初始化代码如下图,需要注意的是“main.c”文件,在里面添加LED灯的控制逻辑。还有“stm32mp1xx_hal_gpio.c”,这个是hal库源码,从里面可知hal提供的GPIO相关操作函数,比如这里用到的HAL_GPIO_WritePin()
。
{% codeblock lang:c %}
HAL_GPIO_WritePin(LED_BLUE_GPIO_Port, LED_BLUE_Pin, GPIO_PIN_SET);
HAL_Delay(1000);
HAL_GPIO_WritePin(LED_BLUE_GPIO_Port, LED_BLUE_Pin, GPIO_PIN_RESET);
HAL_Delay(1000);
{% endcodeblock %}
添加完LED的控制逻辑代码后,在标签栏选择“Project”->“Build Project”即可编译工程,得到GPIO_LED_CM4.elf
。该文件就是M4的固件,包含Cortex-A7和Cortex-M4都可以访问的资源表(.resource_table
)和LED的控制程序等。
在Linux里,使用readelf -a GPIO_LED_CM4.elf
命令,可以获取ELF文件的更多信息。
Remoteproc(Remote Processor Framework
),主要作用就是对远程处理器的生命周期进行管理,即启动、停止远程处理器。
以STM32MP157为例,Cortex-A内核先启动,然后使用Linux RemoteProc框架进行加载Cortex-M4固件,启动M4内核。
ST官方提供的内核已经默认配置了Remoteproc驱动,进入系统后,首先将要运行的M4固件放在/lib/firmware/
目录下,然后将固件名字写到/sys/class/remoteproc/remoteproc0/firmware
,再操作/sys/class/remoteproc/remoteproc0/state
启动、停止M4处理器。
{% codeblock lang:shell %}
[root@stm32mp157:~]# ls /lib/firmware/
DEMO_LED_CM4.elf
[root@stm32mp157:~]# echo GPIO_LED_CM4.elf > /sys/class/remoteproc/remoteproc0/firmware
[root@stm32mp157:~]# cat /sys/class/remoteproc/remoteproc0/state
offline
[root@stm32mp157:~]# echo start > /sys/class/remoteproc/remoteproc0/state
[22683.222322] remoteproc remoteproc0: powering up m4
[22683.229097] remoteproc remoteproc0: Booting fw image GPIO_LED_CM4.elf, size 1899976
[22683.235549] remoteproc remoteproc0: header-less resource table
[22683.241235] remoteproc remoteproc0: not resource table found for this firmware
[22683.248749] remoteproc remoteproc0: header-less resource table
[22683.254414] remoteproc remoteproc0: remote processor m4 is now up
[root@stm32mp157:~]# echo stop > /sys/class/remoteproc/remoteproc0/state
[22709.281733] remoteproc remoteproc0: warning: remote FW shutdown without ack
[22709.287325] remoteproc remoteproc0: stopped remote processor m4
{% endcodeblock %}
除了在Linux的用户态控制M4内核的生命周期,还能在Linux内核态使用API控制(参考linux-origin_master/Documentation/remoteproc.txt
),甚至U-boot中控制。
Remoteproc框架实现了对远程处理器生命周期的管理,RPMsg框架(Remote Processor Messaging Framework
)则是实现对远程处理器信息传递。
RPMsg是基于VirtIO的消息总线,它允许内核驱动程序与系统上可用的远程处理器进行通信,同时,驱动程序可以根据需要公开适当的用户空间接口(参考linux-origin_master/Documentation/rpmsg.txt
)。
STM32MP1多核通信框架如下图。
消息服务基于共享内存,使用RPMsg
和Virtio
框架,RemoteProc
框架则控制远程处理器生命周期。
信号通知(Mailbox
)服务则基于内部IPCC(Inter-Processor communication controller
),ST提供OpenAMP
相关库。
这里列举两个示例:
第一个示例在Linux的用户态和M4通信,实现A7控制M4的灯,A7和M4的相互唤醒;
第二个示例则是在Linux的内核态创建一个简单的RPMsg客户端,实现A7和M4的大量数据传输。
ST官方提供的内核已经默认配置了RPMSG_TTY驱动,Linux这边就不需要做什么了。
STM32MP1多核消息通信应用接口框图如下,在RPMsg
和Virtio
框架创建一个面向用户态的/dev/ttyRPMSG
接口,M4在OpenAMP
上创建虚拟串口,两者最终效果像是串口透传。
创建一个STM32工程,在STM32CubeMX里,依次配置GPIO用于LED、配置UART5用于M4打印、以及配置IPCC和OPENAMP用于通信。
注意配置IPCC时,需要在NVIC Settings
选项卡里,将IPCC RX1 occupied interrupt
和IPCC TX1 free interrupt
的使能勾选上,不然后面的OPENAMP的Activated
始终为灰色,无法激活。
生成初始化代码后,在USER CODE BEGIN 0
和USER CODE END 0
之间添加printf
的重定向函数,让UART5与printf
绑定。
{% codeblock lang:c %}
/* USER CODE BEGIN 0 /
#ifdef GNUC
/ With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
set to ‘Yes’) calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE f)
#endif / GNUC /
PUTCHAR_PROTOTYPE
{
/ Place your implementation of fputc here */
HAL_UART_Transmit(&huart5, (uint8_t )&ch, 1, HAL_MAX_DELAY);
return ch;
}
/ USER CODE END 0 */
{% endcodeblock %}
这里计划创建两个RPMsg tty通道,一个用来LED控制命令,一个用来传输唤醒命令。
RPMsg tty
虚拟串口{% codeblock lang:c %}
if (VIRT_UART_Init(&huart0) != VIRT_UART_OK) {
printf(“VIRT_UART_Init UART0 failed.\r\n”);
Error_Handler();
}
if (VIRT_UART_Init(&huart1) != VIRT_UART_OK) {
printf(“VIRT_UART_Init UART1 failed.\r\n”);
Error_Handler();
}
{% endcodeblock %}
2.注册回调函数以按通道接收消息
{% codeblock lang:c %}
if(VIRT_UART_RegisterCallback(&huart0, VIRT_UART_RXCPLT_CB_ID, VIRT_UART0_RxCpltCallback) != VIRT_UART_OK)
{
Error_Handler();
}
if(VIRT_UART_RegisterCallback(&huart1, VIRT_UART_RXCPLT_CB_ID, VIRT_UART1_RxCpltCallback) != VIRT_UART_OK)
{
Error_Handler();
}
{% endcodeblock %}
3.编写虚拟串口回调函数
当RPMsg收到数据后,将调用该回调函数。在此函数里,需要将接收的数据复制到用户内存,并修改接收标志位,通知用户完成数据接收。
{% codeblock lang:c %}
/* USER CODE BEGIN 4 */
void VIRT_UART0_RxCpltCallback(VIRT_UART_HandleTypeDef *huart)
{
printf(“Msg received on VIRTUAL UART0 channel: %s \n\r”, (char *) huart->pRxBuffPtr);
/* copy received msg in a variable to sent it back to master processor in main infinite loop*/
VirtUart0ChannelRxSize = huart->RxXferSize < MAX_BUFFER_SIZE? huart->RxXferSize : MAX_BUFFER_SIZE-1;
memcpy(VirtUart0ChannelBuffRx, huart->pRxBuffPtr, VirtUart0ChannelRxSize);
VirtUart0RxMsg = SET;
}
void VIRT_UART1_RxCpltCallback(VIRT_UART_HandleTypeDef *huart)
{
printf(“Msg received on VIRTUAL UART1 channel: %s \n\r”, (char *) huart->pRxBuffPtr);
/* copy received msg in a variable to sent it back to master processor in main infinite loop*/
VirtUart1ChannelRxSize = huart->RxXferSize < MAX_BUFFER_SIZE? huart->RxXferSize : MAX_BUFFER_SIZE-1;
memcpy(VirtUart1ChannelBuffRx, huart->pRxBuffPtr, VirtUart1ChannelRxSize);
VirtUart1RxMsg = SET;
}
/* USER CODE END 4 */
{% endcodeblock %}
4.主函数轮询RPMsg消息
OPENAMP_check_for_message()
查询MailBox状态。
当收到数据时,VIRT_UARTx_RxCpltCallback()
会保存好收到数据,然后修改VirtUartxRxMsg
标志位。
主函数里发现VirtUartxRxMsg
标志位发生变化时,即可获取接收的数据。
{% codeblock lang:c %}
while (1)
{
OPENAMP_check_for_message();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if (VirtUart0RxMsg)
{
VirtUart0RxMsg = RESET;
/*VirUART0收到数据*/
}
if (VirtUart1RxMsg)
{
VirtUart1RxMsg = RESET;
/*VirUART1收到数据*/
}
}
{% endcodeblock %}
5.VirUART0接收控制LED指令
每次VirtUart0RxMsg
发生变化,说明VirUART0收到了数据。
然后比较收到的数据内容,执行对应的操作。
这里,M4收到MSG_LED_ON
(*led_on
)则打开LED灯,并发送消息给A7;M4收到MSG_LED_OFF
(*led_off
)则关闭LED灯,并发送消息给A7。
{% codeblock lang:c %}
if (VirtUart0RxMsg)
{
VirtUart0RxMsg = RESET;
if (!strncmp((char *)VirtUart0ChannelBuffRx, MSG_LED_ON, strlen(MSG_LED_ON)))
{
strcpy((char *)BuffTx, "m4:led on\n");
printf("%s\r", BuffTx);
VIRT_UART_Transmit(&huart0, BuffTx, strlen((const char *)BuffTx));
HAL_GPIO_WritePin(LED_BLUE_GPIO_Port, LED_BLUE_Pin, GPIO_PIN_RESET);
}
if (!strncmp((char *)VirtUart0ChannelBuffRx, MSG_LED_OFF, strlen(MSG_LED_OFF)))
{
strcpy((char *)BuffTx, "m4:led off\n");
printf("%s\r", BuffTx);
VIRT_UART_Transmit(&huart0, BuffTx, strlen((const char *)BuffTx));
HAL_GPIO_WritePin(LED_BLUE_GPIO_Port, LED_BLUE_Pin, GPIO_PIN_SET);
}
memset(VirtUart0ChannelBuffRx, 0 ,VirtUart0ChannelRxSize);
memset(BuffTx, 0 ,strlen((const char *)BuffTx));
}
{% endcodeblock %}
6.VirUART1接收休眠唤醒指令
每次VirtUart1RxMsg
发生变化,说明VirUART1收到了数据。
然后比较收到的数据内容,执行对应的操作。
这里,M4收到MSG_STOP
(*stop
)则进入CStop
模式,中途A7再发任意数据给M4,由于IPCC也可设置为中断唤醒源,将M4唤醒;M4收到MSG_DELAY
(*delay
)则等待20S后发数据给A7,在这20S内,将A7先休眠,随后将被M4唤醒。
{% codeblock lang:c %}
if (VirtUart1RxMsg)
{
VirtUart1RxMsg = RESET;
if (!strncmp((char *)VirtUart1ChannelBuffRx, MSG_STOP, strlen(MSG_STOP)))
{
strcpy((char *)BuffTx, "m4:stop\n");
printf("%s\r", BuffTx);
VIRT_UART_Transmit(&huart1, BuffTx, strlen((const char *)BuffTx));
//RCC_backupClocks();
/* Clear the MCU flags before going into CSTOP */
SET_BIT(PWR->MCUCR, PWR_MCUCR_CSSF);
printf("Going to CStop mode\r\n");
/* (C)STOP protection mechanism
* Only the IT with the highest priority (0 value) can interrupt.
* RCC_WAKEUP_IRQn IT is intended to have the highest priority and to be the
* only one IT having this value
* RCC_WAKEUP_IRQn is generated only when RCC is completely resumed from
* CSTOP */
__set_BASEPRI(1 << (8 - __NVIC_PRIO_BITS));
HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFI);
/* To allow Systick to increment after CSTOP (Eg.: to not block during
* TIMEOUT routines), TICK_INT_PRIORITY < BASEPRI
* For this example as TICK_INT_PRIORITY = 1, BASEPRI should be 2 */
__set_BASEPRI(2 << (8 - __NVIC_PRIO_BITS));
printf("Leaving CStop mode\r\n");
/* Test if system was on STOP mode */
if( (PWR->MCUCR & PWR_MCUCR_STOPF) == PWR_MCUCR_STOPF)
{
printf("System was on STOP mode\r\n");
/* Clear the MCU flags */
SET_BIT(PWR->MCUCR, PWR_MCUCR_CSSF);
/* Restore clocks */
/*
if (RCC_restoreClocks() == HAL_OK)
{
printf("CM4 restored clocks successfully\r\n");
}
*/
}
/* All level of ITs can interrupt */
__set_BASEPRI(0U);
}
if (!strncmp((char *)VirtUart1ChannelBuffRx, MSG_DELAY, strlen(MSG_DELAY)))
{
printf("Waiting 20 secs before sending the answer message\r\n");
HAL_Delay(20 *1000);
strcpy((char *)BuffTx, "m4:wakeup A7\n");
printf("%s\r", BuffTx);
VIRT_UART_Transmit(&huart1, BuffTx, strlen((const char *)BuffTx));
}
memset(VirtUart1ChannelBuffRx, 0 ,VirtUart1ChannelRxSize);
memset(BuffTx, 0 ,strlen((const char *)BuffTx));
}
{% endcodeblock %}
为了A7能发消息将M4唤醒,还需要IPCC作为M4的中断唤醒源。
{% codeblock lang:c %}
EXTI_ConfigTypeDef EXTI_ConfigStructure;
EXTI_HandleTypeDef hexti62;
/*
/*
Enable RCC_IT_WKUP to exit M4 from CStop mode.
Indeed, due to SOC issue, M4 firmware shall make sure
RCC_WAKEUP interrupt is the first one used to exit M4 from CStop mode.
Therefore, M4 masks all NVIC interrupts with priority higher than 0
before entering CStop mode and unmasks them when moving from WFI.
(in HAL_PWR_EnterSTOPMode function)
Note: All other NVIC interrupts shall be set to a different value
from 0 to make sure that this workaround works well.
*/
__HAL_RCC_ENABLE_IT(RCC_IT_WKUP);
{% endcodeblock %}
{% codeblock lang:c [main.c] %}
/* USER CODE BEGIN Header /
/*
opensource.org/licenses/BSD-3-Clause
/
/ USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include “main.h”
#include “openamp.h”
/* Private includes ----------------------------------------------------------/
/ USER CODE BEGIN Includes /
#include “virt_uart.h”
/ USER CODE END Includes */
/* Private typedef -----------------------------------------------------------/
/ USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------/
/ USER CODE BEGIN PD /
#define MAX_BUFFER_SIZE RPMSG_BUFFER_SIZE
/ USER CODE END PD */
/* Private macro -------------------------------------------------------------/
/ USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
IPCC_HandleTypeDef hipcc;
UART_HandleTypeDef huart5;
/* USER CODE BEGIN PV */
VIRT_UART_HandleTypeDef huart0;
VIRT_UART_HandleTypeDef huart1;
__IO FlagStatus VirtUart0RxMsg = RESET;
uint8_t VirtUart0ChannelBuffRx[MAX_BUFFER_SIZE];
uint16_t VirtUart0ChannelRxSize = 0;
__IO FlagStatus VirtUart1RxMsg = RESET;
uint8_t VirtUart1ChannelBuffRx[MAX_BUFFER_SIZE];
uint16_t VirtUart1ChannelRxSize = 0;
uint8_t BuffTx[MAX_BUFFER_SIZE];
#define MSG_LED_ON “*led_on”
#define MSG_LED_OFF “*led_off”
#define MSG_STOP “*stop”
#define MSG_DELAY "delay"
/ USER CODE END PV */
/* Private function prototypes -----------------------------------------------/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_IPCC_Init(void);
static void MX_UART5_Init(void);
int MX_OPENAMP_Init(int RPMsgRole, rpmsg_ns_bind_cb ns_bind_cb);
/ USER CODE BEGIN PFP */
void VIRT_UART0_RxCpltCallback(VIRT_UART_HandleTypeDef *huart);
void VIRT_UART1_RxCpltCallback(VIRT_UART_HandleTypeDef huart);
/ USER CODE END PFP */
/* Private user code ---------------------------------------------------------/
/ USER CODE BEGIN 0 /
#ifdef GNUC
/ With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
set to ‘Yes’) calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE f)
#endif / GNUC /
PUTCHAR_PROTOTYPE
{
/ Place your implementation of fputc here */
HAL_UART_Transmit(&huart5, (uint8_t )&ch, 1, HAL_MAX_DELAY);
return ch;
}
/ USER CODE END 0 */
/**
/* 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();
}
/* 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_UART5_Init();
/ USER CODE BEGIN 2 */
printf(“RPMsg user mode test\r\n”);
if (VIRT_UART_Init(&huart0) != VIRT_UART_OK) {
printf(“VIRT_UART_Init UART0 failed.\r\n”);
Error_Handler();
}
if (VIRT_UART_Init(&huart1) != VIRT_UART_OK) {
printf(“VIRT_UART_Init UART1 failed.\r\n”);
Error_Handler();
}
if(VIRT_UART_RegisterCallback(&huart0, VIRT_UART_RXCPLT_CB_ID, VIRT_UART0_RxCpltCallback) != VIRT_UART_OK)
{
Error_Handler();
}
if(VIRT_UART_RegisterCallback(&huart1, VIRT_UART_RXCPLT_CB_ID, VIRT_UART1_RxCpltCallback) != VIRT_UART_OK)
{
Error_Handler();
}
EXTI_ConfigTypeDef EXTI_ConfigStructure;
EXTI_HandleTypeDef hexti62;
/*
/*
Enable RCC_IT_WKUP to exit M4 from CStop mode.
Indeed, due to SOC issue, M4 firmware shall make sure
RCC_WAKEUP interrupt is the first one used to exit M4 from CStop mode.
Therefore, M4 masks all NVIC interrupts with priority higher than 0
before entering CStop mode and unmasks them when moving from WFI.
(in HAL_PWR_EnterSTOPMode function)
Note: All other NVIC interrupts shall be set to a different value
from 0 to make sure that this workaround works well.
/
__HAL_RCC_ENABLE_IT(RCC_IT_WKUP);
/ USER CODE END 2 */
/* Infinite loop /
/ USER CODE BEGIN WHILE /
while (1)
{
OPENAMP_check_for_message();
/ USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if (VirtUart0RxMsg)
{
VirtUart0RxMsg = RESET;
if (!strncmp((char *)VirtUart0ChannelBuffRx, MSG_LED_ON, strlen(MSG_LED_ON)))
{
strcpy((char *)BuffTx, "m4:led on\n");
printf("%s\r", BuffTx);
VIRT_UART_Transmit(&huart0, BuffTx, strlen((const char *)BuffTx));
HAL_GPIO_WritePin(LED_BLUE_GPIO_Port, LED_BLUE_Pin, GPIO_PIN_RESET);
}
if (!strncmp((char *)VirtUart0ChannelBuffRx, MSG_LED_OFF, strlen(MSG_LED_OFF)))
{
strcpy((char *)BuffTx, "m4:led off\n");
printf("%s\r", BuffTx);
VIRT_UART_Transmit(&huart0, BuffTx, strlen((const char *)BuffTx));
HAL_GPIO_WritePin(LED_BLUE_GPIO_Port, LED_BLUE_Pin, GPIO_PIN_SET);
}
memset(VirtUart0ChannelBuffRx, 0 ,VirtUart0ChannelRxSize);
memset(BuffTx, 0 ,strlen((const char *)BuffTx));
}
if (VirtUart1RxMsg)
{
VirtUart1RxMsg = RESET;
if (!strncmp((char *)VirtUart1ChannelBuffRx, MSG_STOP, strlen(MSG_STOP)))
{
strcpy((char *)BuffTx, "m4:stop\n");
printf("%s\r", BuffTx);
VIRT_UART_Transmit(&huart1, BuffTx, strlen((const char *)BuffTx));
//RCC_backupClocks();
/* Clear the MCU flags before going into CSTOP */
SET_BIT(PWR->MCUCR, PWR_MCUCR_CSSF);
printf("Going to CStop mode\r\n");
/* (C)STOP protection mechanism
* Only the IT with the highest priority (0 value) can interrupt.
* RCC_WAKEUP_IRQn IT is intended to have the highest priority and to be the
* only one IT having this value
* RCC_WAKEUP_IRQn is generated only when RCC is completely resumed from
* CSTOP */
__set_BASEPRI(1 << (8 - __NVIC_PRIO_BITS));
HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFI);
/* To allow Systick to increment after CSTOP (Eg.: to not block during
* TIMEOUT routines), TICK_INT_PRIORITY < BASEPRI
* For this example as TICK_INT_PRIORITY = 1, BASEPRI should be 2 */
__set_BASEPRI(2 << (8 - __NVIC_PRIO_BITS));
printf("Leaving CStop mode\r\n");
/* Test if system was on STOP mode */
if( (PWR->MCUCR & PWR_MCUCR_STOPF) == PWR_MCUCR_STOPF)
{
printf("System was on STOP mode\r\n");
/* Clear the MCU flags */
SET_BIT(PWR->MCUCR, PWR_MCUCR_CSSF);
/* Restore clocks */
/*
if (RCC_restoreClocks() == HAL_OK)
{
printf("CM4 restored clocks successfully\r\n");
}
*/
}
/* All level of ITs can interrupt */
__set_BASEPRI(0U);
}
if (!strncmp((char *)VirtUart1ChannelBuffRx, MSG_DELAY, strlen(MSG_DELAY)))
{
printf("Waiting 20 secs before sending the answer message\r\n");
HAL_Delay(20 *1000);
strcpy((char *)BuffTx, "m4:wakeup A7\n");
printf("%s\r", BuffTx);
VIRT_UART_Transmit(&huart1, BuffTx, strlen((const char *)BuffTx));
}
memset(VirtUart1ChannelBuffRx, 0 ,VirtUart1ChannelRxSize);
memset(BuffTx, 0 ,strlen((const char *)BuffTx));
}
}
/* USER CODE END 3 */
}
/**
/** Initializes the CPU, AHB and APB busses clocks
/
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 IPCC_Init 0 */
/* USER CODE END IPCC_Init 0 */
/* USER CODE BEGIN IPCC_Init 1 */
/* USER CODE END IPCC_Init 1 /
hipcc.Instance = IPCC;
if (HAL_IPCC_Init(&hipcc) != HAL_OK)
{
Error_Handler();
}
/ USER CODE BEGIN IPCC_Init 2 */
/* USER CODE END IPCC_Init 2 */
}
/**
/* USER CODE BEGIN UART5_Init 0 */
/* USER CODE END UART5_Init 0 */
/* USER CODE BEGIN UART5_Init 1 */
/* USER CODE END UART5_Init 1 /
huart5.Instance = UART5;
huart5.Init.BaudRate = 115200;
huart5.Init.WordLength = UART_WORDLENGTH_8B;
huart5.Init.StopBits = UART_STOPBITS_1;
huart5.Init.Parity = UART_PARITY_NONE;
huart5.Init.Mode = UART_MODE_TX_RX;
huart5.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart5.Init.OverSampling = UART_OVERSAMPLING_16;
huart5.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart5.Init.ClockPrescaler = UART_PRESCALER_DIV1;
huart5.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart5) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetTxFifoThreshold(&huart5, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetRxFifoThreshold(&huart5, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_DisableFifoMode(&huart5) != HAL_OK)
{
Error_Handler();
}
/ USER CODE BEGIN UART5_Init 2 */
/* USER CODE END UART5_Init 2 */
}
/**
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
/*Configure GPIO pin : LED_BLUE_Pin */
GPIO_InitStruct.Pin = LED_BLUE_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(LED_BLUE_GPIO_Port, &GPIO_InitStruct);
}
/* USER CODE BEGIN 4 */
void VIRT_UART0_RxCpltCallback(VIRT_UART_HandleTypeDef *huart)
{
printf(“Msg received on VIRTUAL UART0 channel: %s \r\n”, (char *) huart->pRxBuffPtr);
/* copy received msg in a variable to sent it back to master processor in main infinite loop*/
VirtUart0ChannelRxSize = huart->RxXferSize < MAX_BUFFER_SIZE? huart->RxXferSize : MAX_BUFFER_SIZE-1;
memcpy(VirtUart0ChannelBuffRx, huart->pRxBuffPtr, VirtUart0ChannelRxSize);
VirtUart0RxMsg = SET;
}
void VIRT_UART1_RxCpltCallback(VIRT_UART_HandleTypeDef *huart)
{
printf(“Msg received on VIRTUAL UART1 channel: %s \r\n”, (char *) huart->pRxBuffPtr);
/* copy received msg in a variable to sent it back to master processor in main infinite loop*/
VirtUart1ChannelRxSize = huart->RxXferSize < MAX_BUFFER_SIZE? huart->RxXferSize : MAX_BUFFER_SIZE-1;
memcpy(VirtUart1ChannelBuffRx, huart->pRxBuffPtr, VirtUart1ChannelRxSize);
VirtUart1RxMsg = SET;
}
/* USER CODE END 4 */
/**
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
where the assert_param error has occurred.
/************************ © COPYRIGHT STMicroelectronics *END OF FILE/
{% endcodeblock %}
rpmsg_user_CM4.elf
放在/lib/firmware目录下
。ttyRPMSG0
和ttyRPMSG1
,同时M4也打印了测试开始信息。cd /sys/class/remoteproc/remoteproc0
echo rpmsg_user_CM4.elf > firmware
echo start > state
然后还需要设置虚拟串口/dev/ttyRPMSG0
。
-onlcr
是不将NL
字符映射为CR-NL
字符,就是说发送给M4的数据,不会自动加上回车,不然这里发送led_on
,M4收到的为led_on\n\r
,正确的应该是收到*led_on\r
。
-echo
是禁止回显,以方便查看接收的字符。
stty -onlcr -echo -F /dev/ttyRPMSG0
cat /dev/ttyRPMSG0 &
最后,向/dev/ttyRPMSG0
写入预定义的指令,M4收到指令便会控制LED灯,并发送结果给A7。
echo "*led_on" > /dev/ttyRPMSG0
echo "*led_off" > /dev/ttyRPMSG0
/dev/ttyRPMSG1
。stty -onlcr -echo -F /dev/ttyRPMSG1
cat /dev/ttyRPMSG1 &
然后向/dev/ttyRPMSG1
写入*stop
,M4随后卡在打印Going to CStop mode
后,接着向/dev/ttyRPMSG1
写入wakeup
(任意字符即可),M4随后打印Leaving CStop mode
。即实现了A7控制M4的休眠和唤醒。
echo "*stop" > /dev/ttyRPMSG1
echo "wakeup" >/dev/ttyRPMSG1
再测试M4唤醒A7。
先使能A7唤醒,然后向/dev/ttyRPMSG1
写入*delay
,M4收到指令后,20S后会向A7发数据,从而唤醒A7。
此时控制A7进入休眠状态,20S后,A7被唤醒。
echo enabled > /sys/devices/platform/soc/4c001000.mailbox/power/wakeup
echo "*delay" > /dev/ttyRPMSG1
echo mem > /sys/power/state
内核已经提供了示例驱动程序linux-origin_master/samples/rpmsg/rpmsg_client_sample.c
,这里直接使用该驱动,简单的修改了下打印内容,方便查看。
{% codeblock lang:c [rpmsg_client_sample.c] %}
/*
#include
#include
#include
#define MSG “hello world! A7->M4”
#define MSG_LIMIT 100
struct instance_data {
int rx_count;
};
static int rpmsg_sample_cb(struct rpmsg_device *rpdev, void *data, int len,
void *priv, u32 src)
{
int ret;
struct instance_data *idata = dev_get_drvdata(&rpdev->dev);
//dev_info(&rpdev->dev, "incoming msg %d (src: 0x%x)\n", ++idata->rx_count, src);
//print_hex_dump(KERN_DEBUG, __func__, DUMP_PREFIX_NONE, 16, 1, data, len, true);
printk(KERN_DEBUG "received_rpmsg: %s %d \n", data, ++idata->rx_count);
/* samples should not live forever */
if (idata->rx_count >= MSG_LIMIT) {
//dev_info(&rpdev->dev, "goodbye!\n");
return 0;
}
/* send a new message now */
ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
if (ret)
dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
else
printk(KERN_DEBUG "send_rpmsg: %s \n", MSG);
return 0;
}
static int rpmsg_sample_probe(struct rpmsg_device *rpdev)
{
int ret;
struct instance_data *idata;
dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n",
rpdev->src, rpdev->dst);
idata = devm_kzalloc(&rpdev->dev, sizeof(*idata), GFP_KERNEL);
if (!idata)
return -ENOMEM;
dev_set_drvdata(&rpdev->dev, idata);
/* send a message to our remote processor */
ret = rpmsg_send(rpdev->ept, MSG, strlen(MSG));
if (ret) {
dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
return ret;
}
else
printk(KERN_DEBUG "send_rpmsg: %s \n", MSG);
return 0;
}
static void rpmsg_sample_remove(struct rpmsg_device *rpdev)
{
dev_info(&rpdev->dev, “rpmsg sample client driver is removed\n”);
}
static struct rpmsg_device_id rpmsg_driver_sample_id_table[] = {
{ .name = “rpmsg-client-sample” },
{ },
};
MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_sample_id_table);
static struct rpmsg_driver rpmsg_sample_client = {
.drv.name = KBUILD_MODNAME,
.id_table = rpmsg_driver_sample_id_table,
.probe = rpmsg_sample_probe,
.callback = rpmsg_sample_cb,
.remove = rpmsg_sample_remove,
};
module_rpmsg_driver(rpmsg_sample_client);
MODULE_DESCRIPTION(“Remote processor messaging sample client driver”);
MODULE_LICENSE(“GPL v2”);
{% endcodeblock %}
驱动比较简单,核心是rpmsg_sample_cb()
,rpmsg
收到数据后将回调该函数。
从data
获得M4发来的数据,通过rpmsg_send()
将数据发给M4。
将该驱动编译成模块,并在A7中加载。
参考前面的示例,依次在STM32CubeMX配置UART5、IPCC和OPENAMP,然后生成初始化代码。
注意这里的RPMSG_SERVICE_NAME
和驱动里rpmsg_device_id
结构体的.name
的名字一样。
之后,一旦rpmsg
通道数据,将回调rx_callback()
函数。
2.编写接收回调函数
此函数里,需要将接收的数据复制到用户内存,并修改接收标志位。
{% codeblock lang:c %}
static int rx_callback(struct rpmsg_endpoint *rp_chnl, void *data, size_t len, uint32_t src, void priv)
{
/ copy received msg, and raise a flag */
memcpy(received_rpmsg, data, len > sizeof(received_rpmsg) ? sizeof(received_rpmsg) : len);
printf(“received_rpmsg=%s\r\n”, received_rpmsg);
rx_status = SET;
return 0;
{% endcodeblock %}
3.主函数轮询RPMsg消息
OPENAMP_check_for_message()
查询MailBox状态。
当收到数据时,rx_callback()
会保存好收到数据,然后修改rx_status
标志位。
主函数里发现rx_status
标志位发生变化时,即可获取接收的数据。
{% codeblock lang:c %}
while (1)
{
OPENAMP_check_for_message();
/* USER CODE END WHILE /
if (rx_status == SET)
{
/ Message received: send back a message anwser */
rx_status = RESET;
/rpmsg收到数据/
}
/* USER CODE BEGIN 3 */
}
{% endcodeblock %}
4.向M4发送数据
使用OPENAMP_send()
向A7发送数据。
{% codeblock lang:c %}
if (++count < 100)
sprintf((char *)msg, “hello world! M4->A7 %02ld”, count);
else
strcpy((char *)msg, “goodbye!”);
if (OPENAMP_send(&resmgr_ept, msg, strlen((char *)msg) + 1) < 0)
{
printf("Failed to send message\r\n");
Error_Handler();
}
else
printf("send_rpmsg=%s\r\n", msg);
{% endcodeblock %}
{% codeblock lang:c [main.c] %}
/* USER CODE BEGIN Header /
/*
opensource.org/licenses/BSD-3-Clause
/
/ USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include “main.h”
#include “openamp.h”
/* Private includes ----------------------------------------------------------/
/ USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------/
/ USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------/
/ USER CODE BEGIN PD /
#define RPMSG_SERVICE_NAME “rpmsg-client-sample”
/ USER CODE END PD */
/* Private macro -------------------------------------------------------------/
/ USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
IPCC_HandleTypeDef hipcc;
UART_HandleTypeDef huart5;
/* USER CODE BEGIN PV /
__IO FlagStatus rx_status = RESET;
uint8_t received_rpmsg[128];
/ USER CODE END PV */
/* Private function prototypes -----------------------------------------------/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_IPCC_Init(void);
static void MX_UART5_Init(void);
int MX_OPENAMP_Init(int RPMsgRole, rpmsg_ns_bind_cb ns_bind_cb);
/ USER CODE BEGIN PFP */
static int rx_callback(struct rpmsg_endpoint *rp_chnl, void *data, size_t len, uint32_t src, void priv);
/ USER CODE END PFP */
/* Private user code ---------------------------------------------------------/
/ USER CODE BEGIN 0 /
#ifdef GNUC
/ With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
set to ‘Yes’) calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE f)
#endif / GNUC /
PUTCHAR_PROTOTYPE
{
/ Place your implementation of fputc here */
HAL_UART_Transmit(&huart5, (uint8_t )&ch, 1, HAL_MAX_DELAY);
return ch;
}
/ USER CODE END 0 */
/**
/* 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();
}
/* 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_UART5_Init();
/ USER CODE BEGIN 2 /
printf(“RPMsg kernel mode test\r\n”);
/ Create an rpmsg channel to communicate with the Master processor CPU1(CA7) /
OPENAMP_create_endpoint(&resmgr_ept, RPMSG_SERVICE_NAME, RPMSG_ADDR_ANY,
rx_callback, NULL);
/ USER CODE END 2 */
/* Infinite loop /
/ USER CODE BEGIN WHILE /
while (1)
{
OPENAMP_check_for_message();
/ USER CODE END WHILE /
if (rx_status == SET)
{
/ Message received: send back a message anwser */
rx_status = RESET;
if (++count < 100)
sprintf((char *)msg, "hello world! M4->A7 %02ld", count);
else
strcpy((char *)msg, "goodbye!");
if (OPENAMP_send(&resmgr_ept, msg, strlen((char *)msg) + 1) < 0)
{
printf("Failed to send message\r\n");
Error_Handler();
}
else
printf("send_rpmsg=%s\r\n", msg);
}
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
/** Initializes the CPU, AHB and APB busses clocks
/
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 IPCC_Init 0 */
/* USER CODE END IPCC_Init 0 */
/* USER CODE BEGIN IPCC_Init 1 */
/* USER CODE END IPCC_Init 1 /
hipcc.Instance = IPCC;
if (HAL_IPCC_Init(&hipcc) != HAL_OK)
{
Error_Handler();
}
/ USER CODE BEGIN IPCC_Init 2 */
/* USER CODE END IPCC_Init 2 */
}
/**
/* USER CODE BEGIN UART5_Init 0 */
/* USER CODE END UART5_Init 0 */
/* USER CODE BEGIN UART5_Init 1 */
/* USER CODE END UART5_Init 1 /
huart5.Instance = UART5;
huart5.Init.BaudRate = 115200;
huart5.Init.WordLength = UART_WORDLENGTH_8B;
huart5.Init.StopBits = UART_STOPBITS_1;
huart5.Init.Parity = UART_PARITY_NONE;
huart5.Init.Mode = UART_MODE_TX_RX;
huart5.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart5.Init.OverSampling = UART_OVERSAMPLING_16;
huart5.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart5.Init.ClockPrescaler = UART_PRESCALER_DIV1;
huart5.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart5) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetTxFifoThreshold(&huart5, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_SetRxFifoThreshold(&huart5, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
{
Error_Handler();
}
if (HAL_UARTEx_DisableFifoMode(&huart5) != HAL_OK)
{
Error_Handler();
}
/ USER CODE BEGIN UART5_Init 2 */
/* USER CODE END UART5_Init 2 */
}
/**
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOB_CLK_ENABLE();
}
/* USER CODE BEGIN 4 */
static int rx_callback(struct rpmsg_endpoint *rp_chnl, void *data, size_t len, uint32_t src, void priv)
{
/ copy received msg, and raise a flag /
memcpy(received_rpmsg, data, len > sizeof(received_rpmsg) ? sizeof(received_rpmsg) : len);
printf(“received_rpmsg=%s\r\n”, received_rpmsg);
rx_status = SET;
return 0;
}
/ USER CODE END 4 */
/**
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
where the assert_param error has occurred.
/************************ © COPYRIGHT STMicroelectronics *END OF FILE/
{% endcodeblock %}
首先加载驱动rpmsg_client_sample.ko
,并修改打印等级。
insmod rpmsg_client_sample.ko
echo 8 > /proc/sys/kernel/printk
然后加载M4固件,可以看到A7和M4都同时发送和接收到相互之间的数据。
cd /sys/class/remoteproc/remoteproc0
echo rpmsg_kernel_CM4.elf > firmware
echo start > state