本实验过程中所显示的优惠价格及费用报销等相关信息仅在【Arm AI 开发体验创造营】体验活动过程中有效,逾期无效,请根据实时价格自行购买和体验。同时,感谢本次体验活动 Arm 导师 Liliya 对于本博客的指导。
详见活动地址:https://marketing.csdn.net/p/a11ba7c4ee98e52253c8608a085424be
1 写在前面
在常规的嵌入式软件开发中,通常需要在用于开发的电脑主机上提前把应用程序编译好,生成可在嵌入式芯片上运行的文件代码,再通过相应的烧录调试工具,把该代码烧录至开发板中,才能查看验证所编写应用程序的正确性。
传统的嵌入式开发流程中,往往需要用到物理开发板才能进行相应的软件开发。但是,没有拿到物理开发板或对于一些新推出的处理器产品(例如:Arm® Cortex®-M55,Cortex-M85, Ethos™-U 系列 NPU 等)市场上硬件的资源较为稀缺且需要较长的时间才能获取到物理开发板的情况下,是否有办法在相应的平台上进行软件开发呢?
答案自然是有的,这就是我们本期实验手册要给大家介绍的一个非常强大的开发工具:**Arm 虚拟硬件(Arm Virtual Hardware)。**借助 Arm 虚拟硬件平台,我们可以做一些非常实用有趣的工具和案例,达到辅助我们日常开发的目的。
另一方面,在当下越来越复杂的物联网比如一些对实时性要求比较高,且应用逻辑复杂的智能家居应用场景,常见的Wi-Fi等通讯芯片底层都需要跑RTOS操作系统;否则,如此庞大的业务逻辑没法很好地把代码组织起来,实现底层技术与上层业务的解耦。
由此可见,RTOS的重要性,而RT-Thread作为一款国内拥有自主知识产权的优秀国产物联网操作系统,因其高效、代码风格良好、开源生态很强,越来越多的物联网人开始使用它开发一些商业级别的产品。
本期文章给大家介绍的 如何基于 Arm 的虚拟硬件移植RT-Thread物联网操作系统,下文会对本移植过程做详细介绍。
2 Arm虚拟硬件简介
Arm 虚拟硬件(Arm Virtual Hardware)提供了一个 Ubuntu Linux 镜像,包括用于 物联网、机器学习和嵌入式应用程序的 Arm 开发工具:例如,Arm 编译器、 FVP 模型和其他针对 Cortex-M 系列处理器的开发工具帮助开发者快速入门。Arm 虚拟硬件限时免费提供用于评估用途,例如,评估 CI/CD、MLOps 和 DevOps 工作流中的自动化测试工作流等。订阅访问和使用此版本的 Arm 虚拟硬件,您需同意产品最终用户许可协议中与免费测试版许可相关的条款和协议。
Arm 虚拟硬件产品的技术概览示意图如下所示。开发者也可访问 Arm 虚拟硬件产品介绍页和产品技术文档了解更多关于 Arm 虚拟硬件产品知识。
图1. Arm 虚拟硬件产品概览
3 RT-Thread简介
3.1 RT-Thread简介
RT-Thread 诞生于2006年,是国内以开源中立、 社区化发展起来的一款高可靠实时操作系统 ,由睿赛德科技负责开发维护和运营 。因其十五年的沉淀积累, 专业化的运营推广,其高可靠性、安全、高可伸缩性和中间组件丰富易用等特性极大地满足了市场需求。目前已经成为市面上装机量最大(超20亿台)、开发者数量最多(超20万)软硬件生态最好的操作系统之一,被广泛应用于航空、电力、轨道交通、车载、工业自动化、消费电子等众多行业领域。
3.2 核心软件架构图
核心架构图:
源码目录展示:
软架构与硬架构的类比:
4 移植过程介绍
4.1 核心的移植步骤
在整个移植过程中,我基本是参考以下步骤进行的:
- 获取最新的 RT-Thread 工程源码
- 基于 Arm 虚拟硬件搭建一个能跑通裸机基础功能(能开机打印hello world)的工程
- 新建一个bsp目录,例如 bsp/arm/AVH-FVP_MPS2_Cortex-M85,把step2的基础工程放到这个bsp目录
- 寻找一个RT-Thread的配置模版,可直接从 bsp/qemu-vexpress-a9 拷贝一个rtconfig.h
- 尝试编译构建qemu-vexpress-a9,找出除bsp层和libcpu层外的所有参与编译的文件列表
- 将step5的文件列表添加到bsp目录的工程文件中,并加上bsp相关实现文件和libpcu的相关文件(这个使用的是Cortex-M85),以及增加一些头文件检索目录,必要时加入一些宏定义
- 实现bsp中的相关接口,例如系统tick定时器的初始化、console串口的初始化等操作
- 重新构建工程,理论上,跑在 Arm 硬件平台的 axf 文件就可以生成了!
主体上,内核移植 = libcpu内核移植 + bsp板极移植,详见下文。
4.2 移植libcpu部分
移植libcpu就是跟处理器架构相关的代码,通常是汇编级别的代码。
这里我们直接复用RT-Thread的工程附带的libcpu/Cortex-M85:
4.3 移植bsp部分
bsp级别的一致,只要完成以下工作:
参考实现如下:
4.4 遇到的难题
这期间遇到的难度有2个:
- 理解原生工程的cbuild构建流程,包括其工程管理文件*.cprj的格式与含义;这个花了很多时间去学习;
- 在仿真时,遇到了串口无法输入的问题,导致RT-Thread的finsh只有输出没有输入,非常尴尬;后面是通过socket的方向巧妙解决,可以参考下文的核心代码展示。
5 核心代码实现
5.1 libcpu/cortex-m85的汇编代码实现
可以参考这里,核心内容就是实现上一章节提及的CPU架构级别需要实现的几个核心函数:
/*
* Copyright (c) 2006-2018, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2009-10-11 Bernard first version
* 2012-01-01 aozima support context switch load/store FPU register.
* 2013-06-18 aozima add restore MSP feature.
* 2013-06-23 aozima support lazy stack optimized.
* 2018-07-24 aozima enhancement hard fault exception handler.
*/
/**
* @addtogroup cortex-m85
*/
/*@{*/
.cpu cortex-m85
.syntax unified
.thumb
.text
.equ SCB_VTOR, 0xE000ED08 /* Vector Table Offset Register */
.equ NVIC_INT_CTRL, 0xE000ED04 /* interrupt control state register */
.equ NVIC_SYSPRI2, 0xE000ED20 /* system priority register (2) */
.equ NVIC_PENDSV_PRI, 0xFFFF0000 /* PendSV and SysTick priority value (lowest) */
.equ NVIC_PENDSVSET, 0x10000000 /* value to trigger PendSV exception */
/*
* rt_base_t rt_hw_interrupt_disable();
*/
.global rt_hw_interrupt_disable
.type rt_hw_interrupt_disable, %function
rt_hw_interrupt_disable:
MRS r0, PRIMASK
CPSID I
BX LR
/*
* void rt_hw_interrupt_enable(rt_base_t level);
*/
.global rt_hw_interrupt_enable
.type rt_hw_interrupt_enable, %function
rt_hw_interrupt_enable:
MSR PRIMASK, r0
BX LR
/*
* void rt_hw_context_switch(rt_uint32 from, rt_uint32 to);
* r0 --> from
* r1 --> to
*/
.global rt_hw_context_switch_interrupt
.type rt_hw_context_switch_interrupt, %function
.global rt_hw_context_switch
.type rt_hw_context_switch, %function
rt_hw_context_switch_interrupt:
rt_hw_context_switch:
/* set rt_thread_switch_interrupt_flag to 1 */
LDR r2, =rt_thread_switch_interrupt_flag
LDR r3, [r2]
CMP r3, #1
BEQ _reswitch
MOV r3, #1
STR r3, [r2]
LDR r2, =rt_interrupt_from_thread /* set rt_interrupt_from_thread */
STR r0, [r2]
_reswitch:
LDR r2, =rt_interrupt_to_thread /* set rt_interrupt_to_thread */
STR r1, [r2]
LDR r0, =NVIC_INT_CTRL /* trigger the PendSV exception (causes context switch) */
LDR r1, =NVIC_PENDSVSET
STR r1, [r0]
BX LR
/* r0 --> switch from thread stack
* r1 --> switch to thread stack
* psr, pc, lr, r12, r3, r2, r1, r0 are pushed into [from] stack
*/
.global PendSV_Handler
.type PendSV_Handler, %function
PendSV_Handler:
/* disable interrupt to protect context switch */
MRS r2, PRIMASK
CPSID I
/* get rt_thread_switch_interrupt_flag */
LDR r0, =rt_thread_switch_interrupt_flag
LDR r1, [r0]
CBZ r1, pendsv_exit /* pendsv already handled */
/* clear rt_thread_switch_interrupt_flag to 0 */
MOV r1, #0x00
STR r1, [r0]
LDR r0, =rt_interrupt_from_thread
LDR r1, [r0]
CBZ r1, switch_to_thread /* skip register save at the first time */
MRS r1, psp /* get from thread stack pointer */
#if defined (__VFP_FP__) && !defined(__SOFTFP__)
TST lr, #0x10 /* if(!EXC_RETURN[4]) */
IT EQ
VSTMDBEQ r1!, {d8 - d15} /* push FPU register s16~s31 */
#endif
STMFD r1!, {r4 - r11} /* push r4 - r11 register */
#if defined (__VFP_FP__) && !defined(__SOFTFP__)
MOV r4, #0x00 /* flag = 0 */
TST lr, #0x10 /* if(!EXC_RETURN[4]) */
IT EQ
MOVEQ r4, #0x01 /* flag = 1 */
STMFD r1!, {r4} /* push flag */
#endif
LDR r0, [r0]
STR r1, [r0] /* update from thread stack pointer */
switch_to_thread:
/* set PSPLIM register */
PUSH {LR}
bl TaskSwitch_StackCheck
POP {LR}
LDR r1, =rt_interrupt_to_thread
LDR r1, [r1]
LDR r1, [r1] /* load thread stack pointer */
#if defined (__VFP_FP__) && !defined(__SOFTFP__)
LDMFD r1!, {r3} /* pop flag */
#endif
LDMFD r1!, {r4 - r11} /* pop r4 - r11 register */
#if defined (__VFP_FP__) && !defined(__SOFTFP__)
CMP r3, #0 /* if(flag_r3 != 0) */
IT NE
VLDMIANE r1!, {d8 - d15} /* pop FPU register s16~s31 */
#endif
MSR psp, r1 /* update stack pointer */
#if defined (__VFP_FP__) && !defined(__SOFTFP__)
ORR lr, lr, #0x10 /* lr |= (1 << 4), clean FPCA. */
CMP r3, #0 /* if(flag_r3 != 0) */
IT NE
BICNE lr, lr, #0x10 /* lr &= ~(1 << 4), set FPCA. */
#endif
pendsv_exit:
/* restore interrupt */
MSR PRIMASK, r2
ORR lr, lr, #0x04
BX lr
/*
* void rt_hw_context_switch_to(rt_uint32 to);
* r0 --> to
*/
.global rt_hw_context_switch_to
.type rt_hw_context_switch_to, %function
rt_hw_context_switch_to:
LDR r1, =rt_interrupt_to_thread
STR r0, [r1]
#if defined (__VFP_FP__) && !defined(__SOFTFP__)
/* CLEAR CONTROL.FPCA */
MRS r2, CONTROL /* read */
BIC r2, #0x04 /* modify */
MSR CONTROL, r2 /* write-back */
#endif
/* set from thread to 0 */
LDR r1, =rt_interrupt_from_thread
MOV r0, #0x0
STR r0, [r1]
/* set interrupt flag to 1 */
LDR r1, =rt_thread_switch_interrupt_flag
MOV r0, #1
STR r0, [r1]
/* set the PendSV and SysTick exception priority */
LDR r0, =NVIC_SYSPRI2
LDR r1, =NVIC_PENDSV_PRI
LDR.W r2, [r0,#0x00] /* read */
ORR r1,r1,r2 /* modify */
STR r1, [r0] /* write-back */
LDR r0, =NVIC_INT_CTRL /* trigger the PendSV exception (causes context switch) */
LDR r1, =NVIC_PENDSVSET
STR r1, [r0]
/* restore MSP */
LDR r0, =SCB_VTOR
LDR r0, [r0]
LDR r0, [r0]
NOP
MSR msp, r0
/* enable interrupts at processor level */
CPSIE F
CPSIE I
/* ensure PendSV exception taken place before subsequent operation */
DSB
ISB
/* never reach here! */
/* compatible with old version */
.global rt_hw_interrupt_thread_switch
.type rt_hw_interrupt_thread_switch, %function
rt_hw_interrupt_thread_switch:
BX lr
NOP
.global HardFault_Handler
.type HardFault_Handler, %function
HardFault_Handler:
/* get current context */
MRS r0, msp /* get fault context from handler. */
TST lr, #0x04 /* if(!EXC_RETURN[2]) */
BEQ _get_sp_done
MRS r0, psp /* get fault context from thread. */
_get_sp_done:
STMFD r0!, {r4 - r11} /* push r4 - r11 register */
#if defined (__VFP_FP__) && !defined(__SOFTFP__)
STMFD r0!, {lr} /* push dummy for flag */
#endif
STMFD r0!, {lr} /* push exec_return register */
TST lr, #0x04 /* if(!EXC_RETURN[2]) */
BEQ _update_msp
MSR psp, r0 /* update stack pointer to PSP. */
B _update_done
_update_msp:
MSR msp, r0 /* update stack pointer to MSP. */
_update_done:
PUSH {LR}
BL rt_hw_hard_fault_exception
POP {LR}
ORR lr, lr, #0x04
BX lr
5.2 bsp的移植代码
实现板极的代码移植相对较简单,board.c的初始化代码:
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <stdint.h>
#include <stdio.h>
#include "rtthread.h"
#include "RTE_Components.h"
#include CMSIS_device_header
//#include "cmsis_os2.h"
#ifdef RTE_Compiler_EventRecorder
#include "EventRecorder.h"
#endif
extern int rt_hw_usart_init(void);
/* SysTick configuration */
void rt_hw_systick_init(void)
{
extern uint32_t SystemCoreClock;
uint32_t ret = SysTick_Config(SystemCoreClock / 1000);
NVIC_SetPriority(SysTick_IRQn, 0xFF);
}
/**
* This is the timer interrupt service routine.
*
*/
void SysTick_Handler(void)
{
/* enter interrupt */
rt_interrupt_enter();
rt_tick_increase();
/* leave interrupt */
rt_interrupt_leave();
}
extern int Image$$ARM_LIB_HEAP$$ZI$$Base;
extern int Image$$ARM_LIB_STACK$$ZI$$Base;
#define HEAP_BEGIN ((void *)&Image$$ARM_LIB_HEAP$$ZI$$Base)
#define HEAP_END ((void *)&Image$$ARM_LIB_STACK$$ZI$$Base)
void rt_hw_board_init(void)
{
rt_hw_systick_init();
/* Heap initialization */
#if defined(RT_USING_HEAP)
rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END);
#endif
/* Pin driver initialization is open by default */
#ifdef RT_USING_PIN
//rt_hw_pin_init();
#endif
/* USART driver initialization is open by default */
#ifdef RT_USING_SERIAL
rt_hw_usart_init();
#endif
/* Set the shell console output device */
#if defined(RT_USING_CONSOLE) && defined(RT_USING_DEVICE)
rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif
/* Board underlying hardware initialization */
#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif
}
stdio的重定向代码:
/*---------------------------------------------------------------------------
* Copyright (c) 2021 Arm Limited (or its affiliates). All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the License); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an AS IS BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Name: retarget_stdio.c
* Purpose: Retarget stdio to USART
*
*---------------------------------------------------------------------------*/
#include <stdio.h>
#include "drv_usart.h"
#if (ARM_AVH_UART_PRINTF_ENABLE)
/**
Initialize stdio
\return 0 on success, or -1 on error.
*/
int stdio_init (void)
{
int32_t status;
status = ptrUSART->Initialize(NULL);
if (status != ARM_DRIVER_OK) {
return (-1);
}
status = ptrUSART->PowerControl(ARM_POWER_FULL);
if (status != ARM_DRIVER_OK) {
return (-1);
}
status = ptrUSART->Control(ARM_USART_MODE_ASYNCHRONOUS |
ARM_USART_DATA_BITS_8 |
ARM_USART_PARITY_NONE |
ARM_USART_STOP_BITS_1 |
ARM_USART_FLOW_CONTROL_NONE,
USART_BAUDRATE);
if (status != ARM_DRIVER_OK) {
return (-1);
}
// 设置USART中断的优先级
NVIC_SetPriority(UART0RX_IRQn, 0); // 0是最高优先级,数值越大优先级越低
// 使能USART中断
NVIC_EnableIRQ(UART0RX_IRQn);
// 使能接收中断
status = ptrUSART->Control(ARM_USART_CONTROL_RX, 1);
if (status != ARM_DRIVER_OK) {
return (-1);
}
// 使能USART中断
status = ptrUSART->Control(ARM_USART_CONTROL_TX, 1); // 如果需要发送,也使能发送中断
if (status != ARM_DRIVER_OK) {
return (-1);
}
return (0);
}
/**
Put a character to the stderr
\param[in] ch Character to output
\return The character written, or -1 on write error.
*/
int stderr_putchar (int ch)
{
uint8_t buf[1];
buf[0] = ch;
if (ptrUSART->Send(buf, 1) != ARM_DRIVER_OK) {
return (-1);
}
if (ptrUSART->Send(buf, 1) != ARM_DRIVER_OK) {
return (-1);
}
while (ptrUSART->GetTxCount() != 1);
return (ch);
}
/**
Put a character to the stdout
\param[in] ch Character to output
\return The character written, or -1 on write error.
*/
int stdout_putchar (int ch)
{
uint8_t buf[1];
buf[0] = ch;
if (ptrUSART->Send(buf, 1) != ARM_DRIVER_OK) {
return (-1);
}
if (ptrUSART->Send(buf, 1) != ARM_DRIVER_OK) {
return (-1);
}
while (ptrUSART->GetTxCount() != 1);
return (ch);
}
/**
Get a character from the stdio
\return The next character from the input, or -1 on read error.
*/
int stdin_getchar (void)
{
uint8_t buf[1];
if (ptrUSART->Receive(buf, 1) != ARM_DRIVER_OK) {
return (-1);
}
while (ptrUSART->GetRxCount() != 1);
return (buf[0]);
}
#else
int stdio_init(void)
{
return 0;
}
int stderr_putchar(int ch)
{
printf("%c", ch);
return 0;
}
int stdout_putchar(int ch)
{
printf("%c", ch);
return 0;
}
int stdin_getchar(void)
{
return getchar();
}
#endif
int stdout_puts(const char *s)
{
while(*s != '\0') {
stderr_putchar(*s);
s++;
}
return 0;
}
5.3 使用vsocket代替传统finsh中串口输入的核心实现
可以参考这张示意图:
它是利用了Arm虚拟硬件的VSocket接口,详见下的架构示意图:
值得注意的是,VIO/VSI/VSocket是Arm虚拟硬件中FVP模型仿真独有的功能,非常有利于开发者做功能仿真。
具体的代码体现如下:
Python脚本端的代码:
import hashlib
import sys
import json
import socket
import os
import shutil
# 服务器地址和端口号
SERVER_HOST = '127.0.0.1'
SERVER_PORT0 = 12345
SERVER_PORT1 = 12346
def avh_rtt_debug_loop():
received_data = None
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
print("Conncting debug server ...", end=" ")
client_socket.connect((SERVER_HOST, SERVER_PORT0))
except Exception as e:
print("Catch exception:", e)
client_socket.close()
try:
print("Conncting debug server ...", end=" ")
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((SERVER_HOST, SERVER_PORT1))
except Exception as e:
print("Catch exception:", e)
return None
client_socket.settimeout(0.1)
try:
print("Conncted debug server ok ...")
while True:
while True:
print("msh >", end=' ')
user_input = input("")
if user_input != "":
break
client_socket.sendall(user_input.encode())
received_data = b""
try:
while True:
part_data = client_socket.recv(4096)
if part_data is not None:
received_data += part_data
else:
print("none")
break
except socket.timeout:
print("", end='')
print(received_data.decode())
except Exception as e:
print("Catch exception:", e)
return None
finally:
client_socket.close()
return received_data
def help():
print("Usage: python " + sys.argv[0] + " [debug]")
if __name__ == "__main__":
if len(sys.argv) < 2:
help()
sys.exit(1)
if len(sys.argv) > 2:
SERVER_PORT = int(sys.argv[2])
operation = sys.argv[1]
if operation == "debug":
avh_rtt_debug_loop()
else:
help()
sys.exit(1)
sys.exit(0)
Arm 虚拟硬件端的代码:
while(1) {
char buf[1024] = {"hello"};
int ret = 5;
#if 1
memset(buf,'\0',sizeof(buf));
ret = read(client_fd, buf, sizeof(buf)); /* 读取客户端发送的请求数据 */
if (ret <= 0) {
printf("read fail(%d) %d\n", client_fd, ret);
break; /* 接收出错,跳出循环 */
}
#endif
//hexdump("server recv:", buf, ret);
extern void serial_out_to_socket_start(void *putc);
extern void serial_out_to_socket_stop();
g_client_fd = client_fd;
serial_out_to_socket_start(cm_socket_putc);
msh_exec(buf, ret);
serial_out_to_socket_stop();
//break;
}
static struct rt_uart_ops cm_uart_ops =
{
.configure = cm_uart_configure,
.control = cm_uart_control,
.putc = cm_uart_putc,
.getc = cm_uart_getc,
.transmit = cm_uart_transmit
};
typedef int (*seiral_putc)(struct rt_serial_device *serial, char c);
void serial_out_to_socket_start(void *putc)
{
rt_interrupt_enter();
cm_uart_ops.putc = (seiral_putc)putc;
rt_interrupt_leave();
}
void serial_out_to_socket_stop(void)
{
rt_interrupt_enter();
cm_uart_ops.putc = cm_uart_putc;
rt_interrupt_leave();
}
对源码感兴趣的,可以参考文末给出的仓库地址。
6 功能测试
6.1 工程的编译构建
本工程项目采用Makefile+cbuild来进行构建,最外层,我们使用的事Makefile进行操作的封装。
输入make help
可以获取帮助信息,一眼可以看出如何进行输入命令:
$ make help
make help -> Show this help msg.
make source -> Install env parameters.
make build -> Build thie demo.
make clean -> Clean object files.
make run -> Run this demo.
make debug -> Build & run this demo.
make all -> Source & clean & build & run all together.
make stop -> Stop to run this demo.
输入make clean
清除编译生成的中间文件,make build
可以进行代码的编译构建:
ubuntu@arm-43ecd4886e664dffa086d3a69133a5e5:~/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85$ make clean
Clean ...
rm -rf ./Objects/
rm -rf out
ubuntu@arm-43ecd4886e664dffa086d3a69133a5e5:~/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85$
ubuntu@arm-43ecd4886e664dffa086d3a69133a5e5:~/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85$ make build
Building ...
cbuild --packs ./AVH-FVP_MPS2_Cortex-M85.cprj
info cbuild: Build Invocation 1.2.0 (C) 2022 ARM
(cbuildgen): Build Process Manager 1.3.0 (C) 2022 Arm Ltd. and Contributors
M650: Command completed successfully.
(cbuildgen): Build Process Manager 1.3.0 (C) 2022 Arm Ltd. and Contributors
M652: Generated file for project build: '/home/ubuntu/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85/Objects/CMakeLists.txt'
-- The ASM compiler identification is ARMClang
-- Found assembler: /opt/armcompiler/bin/armclang
-- Configuring done
-- Generating done
-- Build files have been written to: /home/ubuntu/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85/Objects
[55/55] Linking C executable image.axf
"/home/ubuntu/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85/RTE/Device/ARMCM85/ARMCM85_ac6.sct", line 22 (column 7): Warning: L6314W: No section matches pattern *(.bss.noinit).
Program Size: Code=37976 RO-data=5560 RW-data=468 ZI-data=52776
Finished: 0 information, 1 warning and 0 error messages.
info cbuild: build finished successfully!
至此,我们就完成了Arm平台的axf文件的编译生成。
6.2 验证RT-Thread系统的运行
输入make run
即可把编译生成好的axf文件跑起来:
$ make run
Running ...
/opt/VHT/bin/FVP_MPS2_Cortex-M85 --stat --simlimit 80000000 -f ./vht_config.txt out/image.axf &
telnetterminal2: Listening for serial connection on port 5000
telnetterminal1: Listening for serial connection on port 5001
telnetterminal0: Listening for serial connection on port 5002
\ | /
- RT - Thread Operating System
/ | \ 5.2.0 build Jun 6 2024 00:06:06
2006 - 2024 Copyright by RT-Thread team
Debug server create success, accepting clients @ port 12345 ...
Conncting debug server ... Conncted debug server ok ...
msh > help
RT-Thread shell commands:
pin - pin [option]
clear - clear the terminal screen
version - show RT-Thread version information
list - list objects
help - RT-Thread shell help
ps - List threads in the system
free - Show the memory usage in the system
msh > ps
thread pri status sp stack size max used left tick error tcb addr
------------ --- ------- ---------- ---------- ------ ---------- ------- ----------
debug-clien 10 running 0x00000044 0x00000800 76% 0x00000013 OK 0x20002978
tshell 20 suspend 0x0000007c 0x00001000 03% 0x0000000a EINTRPT 0x200018a8
tidle0 31 ready 0x00000044 0x00000100 26% 0x00000002 OK 0x20000768
timer 4 suspend 0x00000074 0x00000200 22% 0x0000000a EINTRPT 0x2000042c
main 10 suspend 0x00000234 0x00000800 30% 0x00000013 EINTRPT 0x20000da0
msh >
核心的命令就是 /opt/VHT/bin/FVP_MPS2_Cortex-M85 --stat --simlimit 80000000 -f ./vht_config.txt out/image.axf
为便于开发者理解,此处简单的展开说明,该命令调用了 Arm 虚拟硬件镜像中的 Cortex-M85 处理器的 FVP 模型 FVP_MPS2_Cortex-M85 模拟一个硬件开发板,我们直接在云服务器上就把本来应该跑在ARM开发板上的可执行文件给跑起来了。其中,该命令部分参数解读如下:
FVP_MPS2_Cortex-M85
即为所调用的 Cortex-M85 的 FVP 模型的名称。--stat
表示停止模拟时,打印相关的运行状态信息。--simlimit 80000000
表示模拟运行的时间上限为 80000000s,即若用户未手动退出,则80000000s 后程序会自动退出运行。out/image.axf
即为移植好RT-Thread操作系统的应用程序,格式为标准的axf文件。-f vht_config.txt
即指定了 FVP 模型运行时的所依据的配置文件。可以通过FVP_MPS2_Cortex-M85 -l
命令获取 Cortex-M85 的 FVP 模型所有可配置的参数及其默认值(初始值)信息。用户可根据自身需求进行参数调整,获得不同的应用执行效果。
6.3 运行示例图展示
参考下面的运行图:
7 更多思考
7.1 关于 Arm 虚拟硬件的几点优势
我想补充几点:
- Arm 虚拟硬件平台给了非常便利的开发、编译、调试、运行验证的操作体验,无论是在开发阶段还是在生产阶段,都能给开发者及企业带来很大的便利。
- 相对于其他孤立的芯片开发平台,Arm 虚拟硬件平台在成套的软件包上还是比较完毕的,比如RTOS相关的、网络相关的、安全相关的等等软件包,都可以通过快速配置得到比较好的复用,这一点在开发流程上,也得到了很大的改善。
- 借助 Arm 虚拟硬件的网络通讯平台,其具备公网通讯的能力,这一点可以在适当的功能扩展上做成很多基于网络通讯的应用工程,是一个值得期待的开发亮点。
7.2 移植过程的心得体会分享
- 独乐乐,不如众乐乐!
- 纸上得来终觉浅,绝知此事要躬行!
- 先从“小事”做起!
- 善用你的“知识库”,把问题问好!
- 保持空杯心态!
每一个字都是自己亲身经历过才总结出来的,这些都是宝贵的项目经验。
7.3 项目的后续迭代与展望
当然,就当前的实现案例来说,也还存在一些不同需要后续补充改进,比如:
- 移植RT-Thread过程中,UART的输入问题,始终没有很好地解决;目前用socket来模拟,也只是调试阶段可以这么玩;
- RT-Thread强大的软件包生态,如何接入进来,也是一个需要思考的问题;
- RT-Thread中基于kconfig和Scons的可配置化系统,如何结合现有的工程,发挥其最大的价值,也是后期需要努力的一个方向。
除此之外,基于Arm虚拟硬件,我们还可以借助RT-Thread在下面这些方面做更多的探索:
- 开发和测试:在AVH环境中编译和运行RT-Thread,确保其在Arm架构上的正确性和性能。
- CI/CD集成:AVH可以集成到持续集成/持续交付(CI/CD)流程中,提高开发效率。
- 性能评估:使用AVH进行性能评估和基准测试,评估不同Arm虚拟硬件配置下的性能表现。
- 物联网应用开发:AVH可以与RT-Thread配合,为物联网设备提供快速原型开发和测试环境。
- 机器学习集成:AVH提供的虚拟硬件,结合RT-Thread作为开发和测试机器学习模型的平台。
- 教育和培训:AVH作为教学工具,帮助学习者理解RT-Thread在Arm架构上的行为。
- 硬件兼容性测试:AVH可以用于测试RT-Thread在不同硬件配置下的兼容性。
- 资源优化:帮助评估RT-Thread在不同虚拟硬件配置下的资源使用情况,优化系统资源占用。
整体来说,本案例很好地完成了基于AVH-FVP_MPS2_Cortex-M85模型移植了RT-Thread,并做了系统调度及其他功能演示的展示,也在一定程度上展示了Arm 虚拟硬件平台的开发优势,但具体到真正的生产环境落地,还需要比较长的规划和设计要走,剩余的就交给开发者朋友吧。
温馨提示:
本次基于Arm 虚拟硬件平台,我不光把 RT-Thread 操作系统在 M85 平台上跑起来了,也同样在 M7 平台也跑起来了,一样的纵享丝滑,感兴趣可以参考文末的仓库地址,找到对应的bsp代码及工程目录去体验。
8 参考资料
- 本案例的主代码仓库地址:https://github.com/recan-li/AVH-RTTHREAD-DEMO-RECAN 在对应的 rt-thread/bsp/arm 目录下即可找到对应的FVP适配工程及对应的代码。
- Arm 虚拟硬件产品简介
- Arm 虚拟硬件帮助文档
- Arm 虚拟硬件开发者资源
- 【中文技术指南】Arm 虚拟硬件实践专题一:产品订阅指南(百度智能云版)
- 【中文技术指南】Arm 虚拟硬件实践专题二:Arm 虚拟硬件 FVP 模型入门指南
- 【中文视频直播课】加速AI开发,1小时快速入门Arm虚拟硬件
- Arm 社区微信公众号