ARM上移植实时操作系统大家可能比较熟悉,且例程较多,对于RISC-V内核的单片机,可能相对比较陌生。下面结合WCH沁恒微电子的赤菟V103(CH32V103)和赤菟V307(CH32V307)两款RISC-V内核芯片来详细说下针对RISC-V平台,移植实时操作系统的注意点。
之所以选择赤菟V103和赤菟V307两个芯片主要是其极具代表性:首先,直观上其外设的使用方法和我们之前熟悉的F103,F107等是兼容的,这样降低了我们使用和移植时的难度,基于WCH提供的外设库,我们以前上层的代码甚至于不用修改可直接使用。其次,赤菟V103是WCH RISC-V青稞内核家族中的青稞V3A内核,V307为青稞V4F内核,青稞V3内核支持RV32IMAC指令集,即除支持RISC-V基本的32位整数指令集外,还支持硬件乘除法,原子指令,压缩指令。青稞V4F在青稞V3A的基础上增加了单精度硬件浮点,并且其性能也比青稞V3A高。除此所有的青稞V4内核还支持自定义压缩指令-XW扩展,包括以下指令c.lbu/c.lhu/c.sb/c.sh/c.lbusp/c.lhusp/c.sbsp/c.shsp。
这里需要注意的是一般情况下,移植RTOS的时需要关闭硬件压栈,因为在切换任务时,我们希望自己控制出栈入栈的内容。但是针对青稞V4F核,CSR 0x804中有增加控制位(bit5)GIHWSTKNEN(全局中断和硬件压栈关闭使能),可以进中断时置位该位,关闭中断和硬件压栈,我们手动保存当前线程栈,恢复新线程栈,中断mret返回后,硬件自动清除该位,恢复中断和硬件压栈使能。这样即可保证RTOS下,硬件压栈可正常使用,保证RTOS下的中断响应速度。
今天聊下需要移植RTOS时RISC-V内核单片机需要保存的寄存器。
RISC-V寄存器如下图1所示,其中x0-x31为整形寄存器,f0-f31为浮点寄存器(青稞V3没有浮点寄存器)。所有带caller的寄存器,当发生中断时需要保存,值得注意的是,WCH的硬件压栈保存的寄存器仅仅保存整数的16个caller saved 寄存器。正常一个中断函数的寄存器保存我们不用关心,编译器会帮我们做的很好。但是当我们从一个汇编入口进中断函数的时候这些过程就不得不由我们自己来实现。寄存器中几个相对特殊的x0恒为0,x1是返回地址寄存器ra,函数调用时用来存放返回地址,x2为堆栈指针sp,x3为gp全局指针,用来寻址全局变量。
图1 RISC-V寄存器
RISC-V内核进中断需要保存caller saved(顾名思义,调用者需要保存)的寄存器。当不开启硬件浮点时,编译器会把16个寄存器在中断函数开始时存入堆栈,中断返回前恢复,如下图2和图3所示。我们内核支持硬件压栈,硬件保存和恢复的也正是这16个寄存器。使用硬件压栈时需要使能硬件功能,即硬件压栈使能(不同芯片配置位置不同,详见手册中断章节),同时也需要通知编译器不自动生成图2和图3中的软件出入栈的代码,即在MRS声明中断函数时由__attribute__((interrupt("WCH-Interrupt-fast")))方式定义编译器不自动添加软件出入栈代码,由__attribute__((interrupt()))方式定义编译器添加软件出入栈的代码。
图2 整形寄存器入栈
图3 整形寄存器出栈
当开启硬件压栈并且编译器中声明使用硬件压栈后,中断函数汇编代码如下图4所示。可见进入中断后直接执行的中断代码,形如图2和图3中的16个寄存器的入栈和出栈由硬件在中断开始和结束时自动完成。同时也可以看出整个中断函数可以减少34条指令。
图4 开启硬件压栈后中断函数汇编代码
由此也可知道前文说的一般中断切换上下文时不开启硬件压栈的原因:开启后中断返回时硬件会复写16个caller saved寄存器。
当开启硬件浮点时,除了上述16个整形还会增加20个浮点寄存器,如下图5所示。由此也可以看出,硬件压栈只对整形的寄存器生效。
图5 浮点寄存器出入栈