STM32
直播中

lique

13年用户 884经验值
擅长:模拟与电源
私信 关注
[问答]

怎样将常用的RTOS移植到低的RAM芯片上去

RTOS能移植到低的RAM芯片上去吗?
怎样将常用的RTOS移植到低的RAM芯片上去?有哪些移植过程?

回帖(1)

徐静怡

2021-11-30 11:01:38
终于说到了正事上来了。
基于以上选择,我两个芯片都买来试了一下,体验真的不一样。
航顺芯片做的是真比APT好太多,以前在STM32/8运行的程序,几乎可以无缝移植,连库函数的名字都差不多。
APT芯片在使用上真的让人难受,首先是产品手册写的烂,还有使用demo程序让人不能恭维。真的是人比人,气死人。再看库函数,我的内心要崩溃了,还不如我自己直接在代码里封装函数控制寄存器舒服。一个函数需要传递的参数可以超过10个。我都怀疑该公司技术团队是不是半路出家的。APT的芯片基于平头哥的自主内核架构设计,采用RISC命令集,CPU的各个方面功能都还不错,尤其是ROM是真良心,有64K之多,可是RAM就小气了,只有4K,这么低的RAM,是很难移植常用的RTOS的。而且咨询了官方,官方的回复是,不支持操作系统,都是跑裸机。这感觉是我弱我有理啊。
笔者用习惯了操作系统,突然写裸机程序,真是要了命了,虽然折腾了半个月把裸机的代码敲出来了,但是自己看着这个程序就是一坨屎,恶心的很。想不通为啥64K的flash还不能用操作系统,网上找了相关资料,低RAM的芯片大多都是裸机,其中主要原因是功能实时性要求不高,也没有太多需求,很多高端的嵌入式工程师看不起这么低端芯片,几乎没人在这里投入精力。
在网上找到了Atomthreads的相关资料,这个是一个极小的RTOS,网上能找到的资料几乎都是2016年之前的,非常少,还都是国内的兄弟们移植到STM8上的验证资料。去官网查看了,这个RTOS本身支持STM8,源代码都是现成的。
没办法,只能去Atomthreads的官网查看了,读完了官网资料,下载了源代码,花了一周业余时间,读完了所有源码,包括支持STM8和cortex-M、AVR等的移植源码,发现这个系统移植到APT上是可行的。而航顺采用的是Cortex-M架构,天然支持该芯片。此时,基于航顺的所有芯片都可以使用操作系统了。航顺的大容量芯片可以使用Freertos,小RAM芯片可以使用Atomthreads,此处不多说航顺芯片了。对于APT芯片的移植开始了。
寻找资料,APT的芯片是基于平头哥的CK801内核设计,所以找APT的官方人员要相关资料,唉,回复没有。我也不奢求他们能给出啥有用的回复。自己去平头哥官网找吧,官方资料竟然只有英文版,好吧,我也忍了。下载了一堆资料,最后比较有用的就是《C-SKY+ABIV2+Standards+Manual.pdf》和《T-HEAD_800_Series_ABI_Standards_Manual.pdf》、《CSKY Architecture user_guide.pdf》、《C-SKY_V2_CPU_Applications_Binary_Interface_Standards_Manual.pdf》。
其实就是看下这个架构下各个通用寄存器的功能还有几个指令,这里可以给大家推荐一个视频,视频有讲RSIC-V汇编指令的内容。

移植经验

以下是个人移植过程中的经验。
英文不错的朋友建议读一下官网移植指南

先说这个RTOS的核心,基本就是任务切换和现场保护。每个任务有独立的栈,任务切换的时候,将需要保存的寄存器压栈,然后将任务指针切换到其他任务。

按照官网给出的建议,需要实现4个函数就可以完成移植:
任务初始化函数

archThreadContextInit()

void archThreadContextInit (ATOM_TCB *tcb_ptr, void *stack_top, void (*entry_point)(UINT32), UINT32 entry_param)
void archThreadContextInit (ATOM_TCB *tcb_ptr, void *stack_top, void (*entry_point)(uint32_t), uint32_t entry_param)
{
    uint32_t *stack_ptr;


    /** Start at stack top */
    tcb_ptr->sp_save_ptr = stack_ptr = stack_top;




    *stack_ptr-- = (uint32_t)thread_shell;


    /**
     * Store starting register values for R4-R8
     */


   
    *stack_ptr-- = (uint32_t) 0x00000808;   /* R8 */
    *stack_ptr-- = (uint32_t) 0x00000707;   /* R7 */
    *stack_ptr-- = (uint32_t) 0x00000606;   /* R6 */
    *stack_ptr-- = (uint32_t) 0x00000505;   /* R5 */
    *stack_ptr   = (uint32_t) 0x00000404;   /* R4 */


    /**
     * All thread context has now been initialised. Save the current
     * stack pointer to the thread's TCB so it knows where to start
     * looking when the thread is started.
     */
    tcb_ptr->sp_save_ptr = stack_ptr;
}
这个函数主要是完成task创建的初始化,尤其是指针的运用尤其重要,这个函数移植不难,参考其他架构的移植样例,几乎可以照搬。使用C语言编程即可。

任务初始化的时候,需要模拟该任务已经有保存了上下文,所以有模拟压栈操作。因为在任务启动的时候,内核会调用上下文切换函数,将栈内的值出栈,其实任务在刚运行的时候,栈内的值都是无效的。但是在移植的时候,还是建议设置一些值,这些值在任务刚启动会出栈到对应寄存器中。

上下文切换函数

archContextSwitch()

archContextSwitch(ATOM_TCB *old_tcb, ATOM_TCB *new_tcb)
archContextSwitch:
    push       r4-r8, r15             /* Save registers */


    st.w        r14, (r0)              /* Save old SP in old_tcb_ptr->sp_save_ptr (first TCB element) */
    ld.w        r14, (r1)              /* Load new SP from new_tcb_ptr->sp_save_ptr (first TCB element) */


    pop r4-r8,r15             /* Load new registers */
简单的说,就是用来保存当前任务的上下文和寄存器里的值到栈里,俗称压栈。官网建议使用汇编指令编写。
这里官网给出了建议:



  • 线程因进入暂停状态而自行切换(延时/信号量/队列)
  • 来自中断处理程序,因为新线程已准备好运行(其他任务抢占式切换)
这两个状态都会触发任务切换,这个时候需要保存任务上下文。也就是当前的寄存器值和SP栈指针。查看了C-SKY的手册,R0~R3是函数的参数寄存器和返回值寄存器,R4~R11是函数的本地寄存器,R12~R13是临时寄存器,R14是栈指针(SP),R15是lr寄存器。也就是说,官方编译器在编译C程序的时候,会自动保存R0~R3,其他的寄存器是我们需要压栈的。所以只需要保存R4~R13即可。
这里APT来作妖了,该芯片只有16个32位通用寄存器,通过IDE在线debug发现APT没有R9~R12寄存器,在压栈的时候,也只需要保存R4~R8、R15就可以了。
SP的值是来自函数的参数。第一个参数是当前task的栈指针,需要保存该值。第二个参数是新task的栈指针,需要加载到SP中。
这个函数就是旧任务压栈,新任务出栈操作。

OS第一个任务运行函数

archFirstThreadRestore()

archFirstThreadRestore(ATOM_TCB *new_tcb)
_archFirstThreadRestore:


ld.w        r14, (r0)              /* Get SP (sp_save_ptr is conveniently first element of TCB) */
    pop r4-r8, r15             /* Load new registers */
这个函数就是在OS启动的时候,第一个task启动的时候使用,后续的都不需要使用了。
按照官方的推荐,这个函数就是完成archContextSwitch()的后半段操作,新任务出栈操作即可。

系统中断函数

最后是OS的系统中断,为系统提供一个滴答定时器,这个在每个RTOS中都有,就不详细说了。
APT的芯片我采用了CORET定时器作为OS的定时器。相关资料请参考APT的产品手册(真的写的烂)

最后的个人建议

最后,有个容易忽略的地方,每个系统都有一个临界段,在Atomthreads官网中没有提到这个问题,但是看源代码,每个架构的临界段命令都不一样,所以这也是在移植过程中需要注意的地方。CK8xx系列全局中断的指令是在PSR寄存器里ie位设置的,相关资料参考《CSKY Architecture user_guide.pdf》中3.2.2节关于PSR寄存器的介绍(全英文),所以移植的时候,需要修改临界段进出的宏定义。这个在我移植好的代码中atomport.h有体现
#define CRITICAL_STORE          //uint32_t __atom_critical
#define CRITICAL_START()        asm  ("psrclr ie");
#define CRITICAL_END()          asm  ("psrset ie");

CRITICAL_STORE不能删除,内核有使用该函数。


性能测试


完成了移植,需要来测试一下性能了。

下图是使用APT官方给的最简单工程编译后的截图。该工程就只是初始化了时钟和看门狗,main函数就一直while中喂狗。编译后代码大概为4K。RAM的使用主要还是APT的库函数使用的。个人感觉APT的库函数写的是真烂,如果对RAM使用要求很高的,可以考虑删减不需要的库函数或者不用库函数,自己封装库函数更好。



下图是移植了Atomthreads后,并创建了2个任务使用了信号量/队列/定时器后的代码量,代码大概为9K,其中有1k是2个任务的代码,实际RTOS使用了约4k的ROM。这个在64K的ROM中是可以接受的。
内存增大的原因是我给每个任务分配了512字节的内存,同时idle任务的内存也是201个字节。也就是说bss段中有1225字节都是预分给任务使用的,RTOS实际使用了约100字节。APT有4k的内存,足够分配很多任务了。而且任务需要的内存可以再降低,我尝试使用128字节为任务的内存,在比较复杂的任务逻辑也可以运行,这个需要在编程过程中考虑内存分配,这里不再详述。



以下是串口截图,可以看到任务之间的切换是正常的。




相关demo工程和移植好的代码已经上传到github上,请移步下载

这才是本文核心

我估计会有人说,低端芯片实际应用使用裸机跑程序已经可以了,没必要用操作系统,代码和逻辑会变的更复杂。这句话说的我无言以对。使用操作系统后,代码和逻辑的确会变的复杂,而且非常考验攻城狮的水平。但是因为低端芯片就不用操作系统这思维是不对的,这对个人的发展非常不好,一个没有全局思维的编程者就是流水线的钉子,做的所有东西都是重复劳动。使用操作系统的好处是将问题分解并快速处理,这样在后续如果需要添加新功能,可以避免和减少对原有功能的影响。这个是裸机编程很难避免的。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分