【起航】OpenHarmony远征03轻量系统移植
轻量级系统芯片移植
目前轻量级系统的典型架构有cortex-m和risc-v系列,这里顺便说一下常见的架构
CISC(复杂指令合集):
隐式总线访问
- x86 --> 用于PC 常见的芯片core i7 (intel)
RISC(精简指令合集):
显式总线访问
- ARM -->用于Mobile & 便携设备
- MIPS -->机顶盒 & 网关
- RISC-V -->智能穿戴设备
由于openharmony的整体功能较为复杂,如果没有特殊的需求,移植过程中需要关注的目录主要如下;
目录名称 | 描述 |
---|---|
/build/lite | OpenHarmony基础编译构建框架 |
/kernel/liteos_m | 基础内核,其中芯片架构相关实现在arch目录下 |
/device | 板级相关实现,各个三方厂商按照OpenHarmony规范适配实现 |
/vendor | 产品级相关实现,主要由华为或者产品厂商贡献 |
OpenHarmony的device目录是基础芯片的适配目录,如果在三方芯片应用过程中发现此目录下已经有完整的芯片适配,则不需要再额外移植,直接跳过移植过程进行系统应用开发即可,如果该目录下无对应的芯片移植实现,则根据本文完成移植过程。OpenHarmony三方芯片移植主要过程如下: | |
![]() |
编译构建适配流程
首先需要创建开发板目录,以芯片解决方案厂商cat为例,创建一下目录device/cat/test
1.编译工具链与编译选项的配置
构建系统默认使用ohos-clang编译工具链,也支持芯片解决方案厂商按开发板自定义配置。开发板编译配置文件编译相关的变量如下:
- kernel_type: 开发板使用的内核类型,例如:“liteos_a”, “liteos_m”, “linux”
- kernel_version: 开发使用的内核版本,例如:“4.19”
- board_cpu: 开发板CPU类型,例如:“cortex-a7”, “riscv32”
- board_arch: 开发芯片arch, 例如: “armv7-a”, “rv32imac”
- board_toolchain: 开发板自定义的编译工具链名称,例如:“gcc-arm-none-eabi”。若为空,则使用默认为ohos-clang
【arch】 【os】 【嵌入式应用的二进制接口】
“gcc-arm-linux-gnueabihf” 基于arm64的Linux系统,并支持硬件浮点单元 - board_toolchain_prefix:编译工具链前缀,例如:“gcc-arm-none-eabi”
- board_toolchain_type:编译工具链类型,目前支持gcc和clang。例如:“gcc” ,“clang”
- board_cflags:开发板配置的c文件编译选项
-o 生成目标文件
-E 只执行预处理
-c 取消ld链接
-w 不生成任何的告警
-g 包含gdb调试信息
-I 指定头文件路径
-L 指定库文件路径 - board_flags: 开发板配置的c文件编译选项
- board_ld_flags: 开发板配置的链接选项
配置好的编译选项如下:
device/cat/test/liteos_m/config.gni
# Kernel type, e.g. "linux", "liteos_a", "liteos_m".kernel_type = "liteos_m"# Kernel version.kernel_version = "3.0.0"# Board CPU type, e.g. "cortex-a7", "riscv32".board_cpu = "real-m300"# Board arch, e.g. "armv7-a", "rv32imac".board_arch = ""# Toolchain name used for system compiling.# E.g. gcc-arm-none-eabi, arm-linux-harmonyeabi-gcc, ohos-clang, riscv32-unknown-elf.# Note: The default toolchain is "ohos-clang". It's not mandatory if you use the default toochain.board_toolchain = "gcc-arm-none-eabi"# The toolchain path instatlled, it's not mandatory if you have added toolchian path to your ~/.bashrc.board_toolchain_path = rebase_path("//prebuilts/gcc/linux-x86/arm/gcc-arm-none-eabi/bin", root_build_dir)# Compiler prefix.board_toolchain_prefix = "gcc-arm-none-eabi-"# Compiler type, "gcc" or "clang".board_toolchain_type = "gcc"# Board related common compile flags.board_cflags = []board_cxx_flags = []board_ld_flags = []
内核移植
芯片架构的适配式可选的过程,若liteos_m/arch已经支持的芯片架构,则不需要进行芯片的架构适配
Liteos_m的内核主要分为KAL,Components,Kernel和Utils这四个模块
- KAL是整个内核对外的接口, 依赖Components模块和kernel模块
- Components模块是可插拔的,依赖kernel模块
- 在kernel模块中,硬件相关的代码放在kernel的arch目录中,其余的都是硬件不相关的代码,内核的相关功能集(task,sem)的实现依赖的是kernel中arch相关的代码。如任务上下文切换,原子写操作等
- Utils是基础代码块,kernel与Components都需要依赖Utils模块
.├── arch --- 内核指令架构层代码│ ├── arm --- arm32架构的代码│ │ ├── cortex-m3--- cortex-m3架构的代码│ │ │ ├── iar --- iar编译工具链实现│ │ │ ├── keil --- keil编译工具链实现│ │ │ └── xxx --- xxx编译工具链实现│ │ └── cortex-m4--- cortex-m4架构的代码│ │ ├── iar --- iar编译工具链实现 │ │ ├── keil--- keil编译工具链实现│ │ └── xxx --- xxx编译工具链实现│ ├── include --- 所有的arch需要实现的函数定义,内核依赖│ └── risc-v--- risk-v架构│ └── gcc --- gcc编译工具链实现├── components--- 移植可选组件,依赖内核,单独对外提供头文件├── kal--- 内核抽象层,提供内核对外接口,当前支持cmsis接口和部分posix接口├── kernel --- 内核最小功能集代码│ ├── include --- 内核最小功能集代码│ └── src --- 内核最小功能集代码 └──utils --- 基础代码,作为依赖的最底层,被系统依赖
芯片架构的适配点
内核目录结构如图所示,arch/inclue定义了所有的arch需要实现的函数定义,芯片相关的一些代码也会有部分汇编代码,这部分代码会分局编译工具链的不同而不同,因此在具体的芯片架构下还会包含具体的工具链的实现
内核基础适配
在完成芯片架构适配之后,liteos-m提供系统运行所需的系统初始化流程和定制化配置选项。在移植时需要去关注初始化流程中与硬件配置相关的函数
1.启动文件startup.S和相应链接配置文件
2.main.c中的串口初始化和tick中断注册
启动文件startup.S需要确保中断向量表的入口函数放在RAM的首地址,通常是由链接配置文件进行指定的,如果startup.S能够正常完成系统时钟的初始化,并且能够引导到main函数,则启动文件不需要进行修改,采用厂商自带的startup.S即可
main.c文件中,需要去关注串口的舒适化UartInit和系统的Tick handler函数注册。
-
UartInit函数表示单板串口的初始化,具体的函数名根据单板自行定义。这个函数是可选的,用户可以根据硬件单板是否支持串口来自行选择调用该函数。如果硬件单板支持串口,则该函数需要完成使能串口TX和RX通道,设置波特率。
-
HalTickStart设置tick中断的handler函数OsTickHandler。
对于中断向量表不可重定向的芯片,需要关闭LOSCFG_PLATFORM_HWI宏,并且在startup.S中新增tick中断的handler函数。
特性配置项
liteos_m的完整能力以及默认配置在los_config.h定义,该头文件中的配置项可以适配不同的单板进行裁剪配置。
针对这些配置项需要进行不同的板级配置,将对应的配置项,则可将对应的配置项直接定义到对应单板的device/xxxx/target_config.h文件中,其他未定义的配置项就采用los_config.h中的默认值
内核典型配置项说明
配置项 | 说明 |
---|---|
LOSCFG_BASE_CORE_SWTMR | 软件定时器特性开关,1表示打开,0表示关闭 |
LOSCFG_BASE_CORE_SETMR_ALIGN | 对齐软件定时器特性开,1表示打开,依赖软件定时器特性打开,0表示关闭 |
LOSCFG_BASE_IPC_QUEUE | 队列功能开关,1表示打开,0表示关闭 |
LOSCFG_BASE_CORE_TSK_LIMIT | 除idle task之外,总的可用task的个数限制 |
LOSCFG_KERNEL_PRINTF | 打印特性开关,1表示打开,0表示关闭 |
内核移植验证
由于xts依赖的框架比较多,无法支撑内核单独跑xts,只需要去验证最小系统的核心流程基本OK。
官方测试case如下:
VOID TaskSampleEntry2(VOID) // 任务2的入口函数{ while(1) { LOS_TaskDelay(10000); printf("taskSampleEntry2 running...\n"); }}VOID TaskSampleEntry1(VOID) // 任务1的入口函数{ while(1) { LOS_TaskDelay(2000); printf("taskSampleEntry1 running...\n"); }}UINT32 TaskSample(VOID){ UINT32 uwRet; UINT32 taskID1,taskID2; TSK_INIT_PARAM_S stTask1={0}; stTask1.pfnTaskEntry = (TSK_ENTRY_FUNC)TaskSampleEntry1; stTask1.uwStackSize = 0X1000; stTask1.pcName= "taskSampleEntry1"; stTask1.usTaskPrio = 6; //stTask1的任务优先级设定,不同于stTask2 uwRet = LOS_TaskCreate(&taskID1, &stTask1); stTask1.pfnTaskEntry = (TSK_ENTRY_FUNC)TaskSampleEntry2; stTask1.uwStackSize = 0X1000; stTask1.pcName= "taskSampleEntry2"; stTask1.usTaskPrio = 7; uwRet = LOS_TaskCreate(&taskID2, &stTask1); return LOS_OK;}LITE_OS_SEC_TEXT_INIT int main(void){ UINT32 ret; UartInit(); // 硬件串口配置,通过串口输出调试日志,实际函数名根据单板实现不一样而不一样。 printf("\n\rhello world!!\n\r"); ret = LOS_KernelInit(); TaskSample(); if (ret == LOS_OK) { LOS_Start(); // 开始系统调度,循环执行stTask1/stTask2任务,串口输出任务日志 } while (1) { __asm volatile("wfi"); }}