tag 标签: 鸿蒙OS

相关帖子
相关博文
  • 热度 12
    2021-10-28 20:22
    13519 次阅读|
    0 个评论
    RISC-V MCU开发实战 (三):移植鸿蒙OS项目
    软件平台 :MounRiver Studio( MRS); 硬件平台 : CH32V307 开发板 先去码云上将源码克隆下来: https://gitee.com/openharmony/kernel_liteos_m 新建一个 CH32V307 的工程,将源码直接拖到工程中,就添加进来了,然后去添加头文件路径即可 源码中包含比较全面,我们可以选择不需要的部分将其排除在编译之外, 操作方法为右键目录或文件,点击 Include/Exclude From Build 菜单项恢复编译,同样的方法再选一遍即可。 下面说些移植操作系统的注意事项 ARM 上移植实时操作系统大家可能比较熟悉,对于 RISC-V 内核的 MCU ,可能相对比较陌生。下面结合 WCH 的 CH32V103 和 CH32V307 两款芯片来详细说下针对 RISC-V 平台,移植实时操作系统的注意点。 在移植前,有必要对 RISC-V 的一些基本知识点有一定的了解,这里对 RISC-V 的概况,发展,指令集,特权模式等不作详述,仅结合 WCH 的 RISC-V 内核的 MCU ,简单介绍我们移植实时操作系统有可能遇到的关键点做一下描述。这里之所以选取 V103 和 V307 两款芯片,主要其极具代表性: 首先,直观上其外设的使用方法和我们之前熟悉的 F103 , F107 等是兼容的,这样降低了我们使用和移植时的难度,基于 WCH 提供的外设库,我们以前上层的代码甚至于不用修改可直接使用。其次, V103 是 WCH RISC-V 内核家族中的 V3 内核, V307 为 V4 内核, V3 内核支持 RV32IMAC 指令集,即除支持 RISC-V 基本的 32 位整数指令集外,还支持硬件乘除法,原子指令,压缩指令。 V4 在 V3 的基础上增加了单精度硬件浮点,并且其性能也比 V3 高。 除上述之外,虽然两者的中断控制器( PFIC )相较于现行的 PLIC 均不同,均不是统一入口,而是采用中断向量表寻址的方式,但是 V3 的中断向量表处存放是一条指令,而 V4 的向量表既可以存放指令,也可以存放中断处理函数的地址。两者均支持中断嵌套和硬件压栈,区别在于 V3 最大嵌套两级, V4 最大可达八级,同时 V3 的硬件压栈深度两级, V4 的硬件压栈深度为三级。这里需要注意的是,移植实时操作系统时需要关闭硬件压栈,在切换任务时所有寄存器,我们希望是由我们自己控制其压栈和出栈的内容。 RISC-V 寄存器如下图所示,其中 x0-x31 为整形寄存器, f0-f31 为浮点寄存器( V3 没有浮点寄存器)。所有带 caller 的寄存器,当发生中断时需要保存,值得注意的是, WCH 的硬件压栈保存的寄存器仅仅保存整数的 16 个 caller saved 寄存器。正常一个中断函数的寄存器保存我们不用关心,编译器会帮我们做的很好。但是当我们从一个汇编入口进中断函数的时候这些过程就不得不由我们自己来实现。寄存器中几个相对特殊的 x0 恒为 0 , x1 是返回地址寄存器 ra ,函数调用时用来存放返回地址, x2 为堆栈指针 sp , x3 为 gp 全局指针,用来寻址全局变量。对于一个正常运行的程序,除了 x0 , gp 两个初始值固定的外,其余的均会是不确定的,所有在进行上下文保护时,均需要保存。用到硬件浮点的时候,更是要保存 32 个浮点寄存器。 除了上述的寄存器,移植还要关心的是几个 csr 寄存器 mstatus , mepc 。正常情况下大部分 csr 只能在机器模式下操作( WCH 的 v3 和 v4 内核支持机器模式和用户模式)。 mstatus 中, MIE 为中断使能,当进中断时 MPIE 更新为 MIE, 返回时 MIE 更新为 MPIE 。 MPP 用于保存进中断之前的特权模式,如果我们设置其为 MPP=0b11 ,那么将一直处于机器模式,其 mret 返回后还是处于机器模式。 mepc 是机器模式下异常程序指针,其只会在发生异常是被更新(中断也是一类异常),进异常时我们可以从另外两个 csr 寄存器 mcause 来看引起异常原因通过 mtval 查看引起异常时的值。当从异常返回时 mepc 的值被更新给 pc 。我们正是通过进中断修改 mepc 来实现任务的切换的,后面会详细说明这个过程。 实时操作系统大家应该不陌生,常见的 uCOS , FreeRTOS , RT-Thread , LiteOS-M 等等,其基本的思路都是一样的,需要一个定时器用于系统时间片的实现,一个中断用于任务切换。想要其能够在一个 MCU 上成功的跑起来,需要弄清除一下几个事情: (1) 进中断需要保存哪些内容。 从之前的描述中,应该知道,对于 risc-v 内核来说其进中断压栈的是 caller saved 的寄存器。从下图一可以看出,进 Systick 中断函数,先进行寄存器保存,退出中断时进行寄存器恢复,如果开启硬件浮点,同时还会对浮点寄存器进行保存和恢复。这个过程是编译器帮我们实现,有一点需要注意的是我们移植的代码里面进中断后获取了中断的堆栈“ csrrw sp,mscratch,sp ”,返回时恢复了线程的堆栈指针“ csrrw sp,mscratch,sp ”中断堆栈指针初始值是在任务开始时存入 mscratch 寄存器的,如果采用 C 形式中断函数,中断堆栈的获取会在压栈操作之后,中断压入的堆栈是当前运行任务的任务堆栈区域,如果想要中断函数压栈时压入的自己的堆栈区域,可以使用汇编入口,进中断后先修改 sp ,然后压栈,再调用中断处理函数,如图二所示。 图 1 图 2 (2) 任务栈需要保存哪些内容。 前文说过对于一个正常运行的程序,切换任务前,除了 x0 恒 0 , x3 gp 指针外,其余的寄存器均需要保存,每个 RTOS 中都会定义一个上下文保存相关的结构体,这里我们以华为鸿蒙 LiteOS_M 为例,看一下这个结构体: 图 3 在创建任务的时候均会为一个任务分配一个 id 和堆栈大小并对这个堆栈做初始化: 图 4 图 5 任务创建好了后会关联一个根据任务 id 关联一个任务控制块 taskCB ,总的任务个数是在头文件中配置的( target_config.h )总的任务块的初始化也是在 LOS_KernelInit 被初始化。 图 6 从上面可以看出来, sp 指针 memory 这样的路线,而这片 memory 开始位置用于上下文保存。 这样的方式在其他 RTOS 中也可以看到,例如 RT-Thread 中用于上下文保存的结构体 rt_hw_stack_frame ,和 taskCB 类似的结构体 rt_thread 等。 图 7 图 8 (3) 如何开启任务调度。 前面看了每个任务上下文保存位置,注意到堆栈初始化的时候把任务的入口地址给了 epc 。同时 LiteOS_M 源码中定义了一个 LosTask 类型的全局变量 g_losTask ,其内部只有两个任务控制块指针,一个指向当前运行的任务,一个指向新任务,即要切换至的任务。 图 9 当做好一系列初始化后, LiteOS 会调用 HalStartSchedule 来初始化系统节拍定时器,并注册系统定时器的中断处理函数,然后开始转向执行第一个任务,如下图所示: 图 10 其中 OsSchedStart 函数从任务列表中获取第一个任务,并赋值给 g_losTask 里面的 runTask 和 newTask 。然后调用 HalStartToRun 转向执行 runTask 所指示的任务。 HalStartToRun 是一段汇编代码,下面就具体看其如何切换至 runTask ,具体如下图的注释: 图 11 这样 mret 之后就转向去执行第一个任务,并且不会再有返回,因为每个任务本身会是个循环,这里也就能理解其源码注释 never return 的含义。 图 12 其他操作系统中也有类似的操作,例如 RT-Thread 中有个 rt_hw_context_switch_to 函数,其也是汇编代码实现,它是一个带参数的函数,其传入的参数为 sp) ,如下图: 图 13 从名字就可以看出,传递的参数为启动执行的第一个线程的控制块的堆栈指针 sp 的值,后面赋值 mepc , mstatus ,其他寄存器等等都是和 LiteOS_M 一致的。 ( 4 )如何进行任务切换。 了解了如何切换至第一个任务,那么如何实现不同任务之间的切换呢,在这之前我们应该都有了解, RTOS 是根据任务的优先级和时间片进行轮转的,每个任务执行一段时间,然后切换至下一个任务执行。每次切换前我们需要把当前任务的运行状态进行保存,然后切换至新任务,对其运行状态进行恢复,如此循环反复,实现任务调度。时间片实现使用的是内核的 SysTick 定时器, LiteOS_M 是在 los_timer.c 中实现的,这个只需要根据实际硬件的进行初始化就行。其他操作系统也是类似,像 RT-Thread 源码中我们根据硬件完成 board.c 。对于任务切换,我们利用内核的软中断,只要使能该中断,并且当需要切换任务时,把中断控制器的对应的 pendset 位置 1 ,即可触发该中断进行任务切换。下图是 liteOS_M 切换过程: 图 14 图 15 其他操作系统也是大同小异,具体的区别仅仅是在切换新任务时,新任务如何获取的问题,上图可以看到 LiteOS_M 是通过 g_losTask 来管理, RT-Thread 中定义了 from_thread , to_thread ,顾名思义从一个线程切换至另外一个线程。 弄清楚以上的问题,对于某一个 RTOS 的基本移植来说应该就比较明了。 最后移植好的鸿蒙 os , RT-Thread 等实时操作系统的代码均已在 MRS 上线,可以直接创建,开发相关应用 v\:* {behavior:url(#default#VML);} o\:* {behavior:url(#default#VML);} w\:* {behavior:url(#default#VML);} .shape {behavior:url(#default#VML);} Normal 0 false 7.8 磅 0 2 false false false EN-US ZH-CN X-NONE /* Style Definitions */ table.MsoNormalTable {mso-style-name:普通表格; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-priority:99; mso-style-parent:""; mso-padding-alt:0cm 5.4pt 0cm 5.4pt; mso-para-margin:0cm; mso-pagination:widow-orphan; font-size:10.0pt; "Calibri",sans-serif; mso-ascii-Calibri; mso-ascii-theme-font:minor-latin; mso-hansi-Calibri; mso-hansi-theme-font:minor-latin; mso-bidi-"Times New Roman"; mso-bidi-theme-font:minor-bidi;}