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点灯,涉及到资源的分配,资源分配如下图所示。
支持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文件的更多信息。
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多核通信框架如下图。
消息服务基于共享内存,使用RPMsg和Virtio框架,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多核消息通信应用接口框图如下,在RPMsg和Virtio框架创建一个面向用户态的/dev/ttyRPMSG接口,M4在OpenAMP上创建虚拟串口,两者最终效果像是串口透传。
3.1.2 M4准备
创建一个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控制命令,一个用来传输唤醒命令。
- 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()会保存好收到数

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

被折叠的 条评论
为什么被折叠?



