STM32MP157——Remoteproc和RPMsg

本文深入探讨STM32MP157的Remoteproc和RPMsg框架,讲解非对称多处理(AMP)在嵌入式领域的应用前景,以及如何在STM32MP157上实现多核通信,包括M4固件生成、Remoteproc框架使用、RPMsg框架实现A7与M4间的用户态和内核态通信。

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后,逐渐明朗,因此记录下笔记。

1.生成M4固件

在进行启动M4之前,需要先建立工程,生成M4固件,这里以点灯为例,简单说下创建STM32MP157的M4工程。

这里要M4点灯,涉及到资源的分配,资源分配如下图所示。

深蓝色的IP为A7独占,浅蓝色的IP为M4独占,竖线分割的IP为同一时刻只能一个占有,斜线分割的IP为任意时刻两者可以同时占用。 比如这里GPIO就为两者可以同时占用,在Linux中可以控制GPIO,同时M4也可控制GPIO。UART则为只能一个独占,分配给M4后,A7将不能控制。

支持STM32开发的集成开发环境有很多,国内熟知的有Keil MDK-ARMIAR 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文件的更多信息。

2.Remoteproc框架

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中控制。

3.RPMsg框架

Remoteproc框架实现了对远程处理器生命周期的管理,RPMsg框架(Remote Processor Messaging Framework)则是实现对远程处理器信息传递。
RPMsg是基于VirtIO的消息总线,它允许内核驱动程序与系统上可用的远程处理器进行通信,同时,驱动程序可以根据需要公开适当的用户空间接口(参考linux-origin_master/Documentation/rpmsg.txt)。

STM32MP1多核通信框架如下图。

消息服务基于共享内存,使用RPMsgVirtio框架,RemoteProc框架则控制远程处理器生命周期。
信号通知(Mailbox)服务则基于内部IPCC(Inter-Processor communication controller),ST提供OpenAMP相关库。

这里列举两个示例:
第一个示例在Linux的用户态和M4通信,实现A7控制M4的灯,A7和M4的相互唤醒;
第二个示例则是在Linux的内核态创建一个简单的RPMsg客户端,实现A7和M4的大量数据传输。

3.1 用户态的通信

3.1.1 A7准备

ST官方提供的内核已经默认配置了RPMSG_TTY驱动,Linux这边就不需要做什么了。
STM32MP1多核消息通信应用接口框图如下,在RPMsgVirtio框架创建一个面向用户态的/dev/ttyRPMSG接口,M4在OpenAMP上创建虚拟串口,两者最终效果像是串口透传。

3.1.2 M4准备

创建一个STM32工程,在STM32CubeMX里,依次配置GPIO用于LED、配置UART5用于M4打印、以及配置IPCC和OPENAMP用于通信。

注意配置IPCC时,需要在NVIC Settings选项卡里,将IPCC RX1 occupied interruptIPCC TX1 free interrupt
的使能勾选上,不然后面的OPENAMP的Activated始终为灰色,无法激活。

生成初始化代码后,在USER CODE BEGIN 0USER 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控制命令,一个用来传输唤醒命令。

  • 1.初始化两个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()会保存好收到数

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值