μC/OS-II是一种揭露源代码、结构细巧、具有可掠夺实时内核的嵌入式开发体系,代码简略、条理清晰、实时性及安全功用很高,绝大部分代码用C编写,现已被移植到多种处理器的构架中。跟着51单片机片内资源的日益丰厚,在51单片机上移植μC/OS-II已成为或许,植入体系后,由体系来办理软件与硬件资源,简化运用程序的规划,而且使运用体系功用愈加完善。因此在51单片机上移植μC/OS-II具有十分重要的含义。
1 μC/OS实时操作体系概述
μC/OS-II实时操作体系是一种可移植、可固化、可裁剪即可掠夺型的多使命实时内核,适用于各种微处理器和微控制器。μC/OS-II首要包含使命调度、时刻办理、内存办理、事情办理(信号量、邮箱、音讯行列)4大部分。它的移植与4个文件相关:汇编文件(OS_CPU_A.A SM)、处理器相关C文件(OS_CPU.H、OS_CPU_C.C)和配置文件(OS_CFG.H)。有64个优先级,体系占用8个,用户可创立56使命,不支持时刻片轮转。
它的基本思路便是“近似地每时每刻总是让优先级最高的安排妥当使命处于运转状况”。为了确保这一点,它在调用体系函数、中止完毕、守时中止完毕时总是履行调度算法。原作者经过事前核算好数据,简化了运算量,经过精心规划安排妥当表结构,使得延时可预知。使命的切换是经过模仿一次中止完成的。
2 使命调度的完成原理
使命调度是μC/OS-II的重要部分,和详细的微处理器联系严密。有必要移植的5个函数有4个都和使命有关。使命调度便是保存当时使命的寄存器和PC指针(即当时使命的断点),然后把即将履行的使命的寄存器值回来给寄存器并把PC指向即将履行使命的断点。这些的完成要借助于仓库和中止,为了简洁起见,先看函数调用时仓库的运用状况。在函数调用时,仓库的一个重要功用便是保存被调函数的断点地址。若有4个函数,Fun1调用Fun2,Fun2调用Fun3,Fun3调用Fun4,Fun4为叶子程序(无子程序调用)。
假定现在从Fun1一向运转到Fun4,此刻仓库结构如图1所示,中心的ADD_A到ADD_D为仓库中的数据,左面的SP到SP-7为仓库指针,右边的Fun1到Fun4为对应的调用函数。运转Fun4时,此刻SP与SP-1所存的值为ADD_D,而ADD_D为Fun3中子函数Fun4的下一行的地址,即Fun3中3-2行的地址,以此类推,ADD_C为2-2行地址,ADD_B 图1函数运转及仓库结构图为1-2行地址。
当函数A调用函数B时,进入函数B时就会把函数A的断点地址压栈,而当函数B运转完毕时则把仓库中函数A的断点地址弹出到PC指针,程序接着从函数A的断点开端运转。假如在函数B中更改SP及SP-1中的数据,则函数B运转完毕时就不会再回来函数A中,而回来到SP及SP-1更改后的数据所代表的地址。
以上是函数调用时的基本状况,假如是中止则仓库不只保存断点地址还会主动保存寄存器的值。使命调度便是靠中止来完成,中止中所保存的断点地址便是使命的断点地址,当本使命要再次履行时就把断点地址赋给PC就能够接着使命被中止时地址次序履行。
3 头文件移植
与移植相关的4个文件中有2个头文件,这2个头文件的移植比较简略,能够参阅其它的移植程序。其间OS_CPU.H中首要是数据类型的界说、仓库成长方向的界说、开关中止的界说以及函数级使命切换的宏界说。OS_CFG.H中首要是使命数、优先级数、事情数、每秒中止节拍数以及各种体系函数的使能界说。
4 汇编与C文件的移植
在要移植的汇编与C的两个文件中有14个函数,其间9个是接口函数,可依据实践需求来决议,有5个是有必要写的。这5个函数分别是:OS_CPU_C.C文件中的OSTaskStkInit()和OS_CPU_A.ASM文件中的OSStartHighRdy()、OSCtxSw()、OSINTCtxSw()与OSTICkISR()。下面就这5个函数来做详细分析。
4.1 使命仓库初始化函数OSTaskStkInit()
此函数是在使命创立函数OSTaskCreat()或OSTaskCreatExt()中调用的。由于体系为每个使命申请了一个数组作为栈,当一个使命运转时,就把仓库指针指向本使命的栈,使命仓库初始化函数便是在使命创立时即将创立使命的仓库进行初始化。但C51的仓库指针SP是8位的,只能在片内RAM的256个字节内寻址。因其寻址空间有限且SP仅有,不能像DSP或ARM那样为每一段程序或每一种形式界说仓库,需当心办理仓库空间。为了习惯上述状况,需求换一种思路,不是让SP去指向各使命仓库空间,而是把各使命仓库空间的内容复制到体系栈中。至于仓库数组空间要有多大以及仓库数组空间里放些什么内容,能够学习keil中中止函数的压栈状况,当中止函数不指定寄存器组时,编译器一般将PC、ACC、B、DPTR、PSW、R0~R7寄存器入栈,其间PC和DPTR是双字节的,其它都是单字节的,总共15个字节,所以把仓库数组规划成至少15个字节的,以确保使命所用的寄存器都在仓库数组中包含着。由于每个数组里放的是寄存器的值,在此就把这每个使命的仓库数组叫做寄存器数组,暂时把寄存器数组规划成15个字节,顺次寄存PC、ACC、B、DPTR、PSW、R0~R7。
函数OSTaskStkInit()传递4个参数,第1个参数task是所创立使命的开端地址,这个参数须保存到PC在寄存器数组的对应方位,第2个参数ppdata是所创立使命的参数,C51规矩顶用R1~R3来传递参数指针,这个参数须寄存到R1~R3在寄存器数组中的对应方位。第3个参数ptos是栈底指针,从当时地址开端初始化仓库指针,第4个参数opt是附加参数,一般不必。
4.2 运转等候使命中优先级最高使命函数OSStartHighRdy()
此函数在发动操作体系函数OSStart()的最终一行调用,且此函数不回来,经过此函数后μC/OS接收体系。OSStartHighRdy()不是去调用用户使命函数,而是让PC指针指向使命函数首地址。且使命函数的传递参数只要一个,若此参数正确,则可确保使命函数运转正确。在调用OSStartHighRdy()之前OSStart()现已把最高优先级使命的使命表预备好了,只要把最高优先级使命表的数据康复到仓库中,再履行回来指令即可,以上最要害的是怎么让其回来到最高优先级使命中而不是回来到被调函数中。
当函数OSStart()调用函数OSStartHighRdy()时,断点地址入栈;当OSStartHighRdy()履行完之后,回来断点。在OSStartHighRdy()中把SP及SP-1的值改为最高优先级使命的地址,这样OSStartHighRdy()就会回来到最高优先级使命中去运转。
4.3 使命级的使命切换函数OSCtxSw()
此函数是保存当时使命的状况,然后运转处于安排妥当态中的最高优先级使命。前面介绍过不是更改SP去指向寄存器数组,而是把寄存器数组的数复制到仓库中。先看下一般的状况,在用户使命MyTask(void*ppdtat)中调用TImeDly(),TImeDly()中调用OSSched(),在OSSched()中有一个宏OS_TASK_SW(),这个宏的意图是让程序进人函数OSCtxSw()。参看图1,就如Fun4为OSCtxSw(),Fun3为OSSched(),Fun2为TImeDly(),Fun1为MyTask()。ADD_D存的是OSSched()的断点,ADD_C为TimeDly()的断点,ADD_B为MyTask()的断点。假如进行使命切换,应该把高优先级使命的地址值赋给ADD_B(即SP-4与SP-5)。
以上考虑的是最简略的状况,当使命比较复杂时,或许更改了ACC、PSW、DPTR或R0~R7的值,在进入高优先使命时,寄存器并不是此使命的寄存器值,运转的成果或许不正确。
在上述状况下怎么确保CPU寄存器的值正确,要分两个阶段。第一个阶段是把CPU寄存器值保存到要挂起使命的寄存器数组中,当刚进入OSCtxSw()时,CPU寄存器的值是要挂起使命的寄存器值,所以一开端就要确定CPU寄存器的值。假如OS_TASK_SW()界说为中止的话,在进入OSCtxSw()时,CPU寄存器的值被主动压栈;假如把OS_TASK_SW()界说为函数时,在进入函数时运用内嵌汇编的办法把CPU寄存器入栈。这时仓库中又压入了13个字节,就如在图1的ADD_D上又压入了13个字节的数据,然后从仓库中把值取出来放到相应使命的寄存器数组中。第二个阶段是把即将履行使命的寄存器数组的值复制到仓库中。此刻PC指针在仓库中对应的方位是SP-17与SP-18,SP到SP-12的13个字节对应ACC、B、DPTR、PSW、R0~R7。
4.4 中止级的使命切换函数OSINTCtxSw()
此函数和上一个函数基本思想共同,都要保存当时使命的状况,运转处于安排妥当态中的优先级最高的使命。二者的不同在于,上个函数的仓库中SP-17与SP-18是PC值的方位,SP到SP-12是13个寄存器的方位。当中止来时,在中止中调用函数OSIntExit(),函数OSIntExit()调用函数OSIntCtxSw(),在OSIntCtxSw()中完成使命切换。在进入函数OSIntExit()之前寄存器的值现已入栈,所以运转到本函数时仓库中SP-17与SP-18是PC值的方位。SP-4到SP-16是13个寄存器的方位。在图1上,上个函数的13个寄存器的值被压入ADD_D上面的13个字节中,而本函数是在ADD_B于ADD_C之间压入的这13个寄存器。
4.5周期节拍中止函数OSTICkISR()
这个函数是给体系供给一个节拍,一般每秒10~100次。假如节拍频率太高,μC/OS体系会占用很多硬件资源;假如太低,使命间的切换又会很慢。
此函数首先要确保发生一个周期性的中止,能够运用硬件守时器,也能够从交流电中取得50/60Hz的时钟频率。这个函数至少要做3件事:1)进入中止时,把中止嵌套层数计数器加1,阐明又进入一次中止,也能够直接调用OSIntEnter()函数;2)调用时钟节拍函数OSTimeTick(),奉告体系又经过了一个节拍;3)调用OSIntExit()函数,阐明要退出中止了,此函数会主动处理。
5 完毕语
文中论述了在仓库空间有限的51单片机上运转μC/OS-II体系的移植进程,使用体系栈SP作为数据交换的纽带。在实践运用中,假如用体系栈来移植,只需依据文中的基本思想进行恰当的改写,即可运转于其他处理器上。假如处理器的仓库指针寻址空间足够大,也能够为每个使命拓荒一个栈,经过改动仓库指针指向不同使命的栈空间,来完成使命调度。
经过在51单片机上的运转,能够看出μC/OS-II也能在仓库空间比较少的CPU上运转。