μC/OS-Ⅱ在 AT89S51单片机上的应用

2012-02-27 03:10刘洪利
上海电力大学学报 2012年4期
关键词:编译器堆栈寄存器

刘洪利,赵 萍

(上海电力学院计算机与信息工程学院,上海 200090)

近年来,随着科技的飞速发展,嵌入式应用已经渗透到生产和生活的各个领域.对于国内中小型系统的设计,免费软件和开放代码是最佳选择[1].μC/OS-Ⅱ是目前源码开放的嵌入式系统之一,它提供了操作系统最基本的功能,其核心代码短小精悍,易于移植,受到人们的青睐.μC/OS-Ⅱ已通过联邦航空局商用航行器认证,成功移植到40多种CPU上,但其数据和程序存储器的开销很大,至少要达到8 k字节.μC/OS-Ⅱ是源码完全公开的嵌入式实时操作系统,最多可分配64个任务,其中系统任务占用8个,其余56个任务用户可以自由分配.

1 μC/OS-Ⅱ系统原理

μC/OS-Ⅱ的代码90%都是用ANSI C写的,可移植性好,安全性高,代码的容量至少为8 kB.由于AT89S51单片机的ROM只有4 kB,所以要扩展的外部程序存储器容量要大于8 kB;每个任务都有自己的硬件栈和仿真栈,硬件栈用于保存任务运行时系统栈内的数据.用户栈中保存的仿真栈与硬件栈相向生长,中间为空闲间隔.硬件栈的保存恢复是通过拷贝实现的.而对于仿真堆栈的保存,μC/OS-Ⅱ只提供堆栈空间和只操作堆栈指针,不进行内存拷贝,因此其效率相对较高.

尽管μC/OS-Ⅱ将不同任务使用不同空间看成是优点,但为了在51单片机上有效实现任务重入,建议用户使用统一的固定大小的堆栈空间.用户堆栈空间的大小是可以精确计算出来的,用户堆栈空间=硬件堆栈空间+仿真堆栈空间.硬件栈占用内部RAM,内部RAM执行效率高、速度快.如果堆栈空间过大,会影响KEIL编译的程序性能,如果堆栈空间小,在中断嵌套和程序调用时会造成系统崩溃,因此综合考虑,可将硬件堆栈空间大小确定为64 B,用户可以根据实际情况自行设定.仿真堆栈大小取决于形参和局部变量的类型及数量,并可以精确算出.因为所有用户栈使用相同空间,所以取占用空间最大的任务函数的空间大小为仿真堆栈空间大小,这样用户堆栈空间大小就唯一确定了.将用户堆栈空间大小用宏定义在OS_CFG.H文件中,宏名为MaxStkSize.

由于AT89S51片内只有128个数据存储器单元和4 k个ROM单元,因此需要根据系统中任务个数的多少来外扩程序存储器和数据存储器[2].为了便于调试,系统还添加了液晶显示模块和按键输入模块.其中,数据RAM采用静态数据存储器6164(8 k×8位);程序存储器采用EPROM27128(16 k×8位);液晶显示器采用金鹏公司的C系列OCMJ4X8C显示模块,可以显示字母、数字、汉字及图形等,也可用于显示键盘输入值.键盘采用矩阵式键盘,可以节省单片机接口.其硬件结构如图1所示.

图1 系统原理示意

处理器和编译器需要满足下列要求[3]:

(1)所用的C编译器可产生可重入型代码;

(2)用C语言就可以打开和关闭中断;

(3)处理器能产生中断,通常在10~100 Hz;

(4)处理器带有能容纳一定数量数据的硬件堆栈.

(5)处理器有将堆栈指针和其他的CPU寄存器从内存中读出和存到堆栈或内存中的指令.

AT89S51单片机和KEIL C51编译器完全可以满足上述要求,因此可以将μC/OS-Ⅱ移植到AT89S51单片机上.

2 μC/OS-Ⅱ中核心代码的编程

μC/OS-Ⅱ操作系统的软件结构如图2所示[4],其中“核心代码(处理器无关)”部分从网上直接下载无需修改;“设置代码(应用相关)”部分在移植时根据项目要求仅作少许修改即可;OS_CPU.H,OS_CPU_A.ASM,OS_CPU_C.C 是与处理器紧密相关的代码,移植的主要工作就是根据处理器编写这3个函数.

图2 μC/OS-Ⅱ的软件结构

2.1 OS_CPU.H的设计思路

OS_CPU.H主要定义与KEIL C51编译器相关的数据类型、宏和常量.在μC/OS-Ⅱ执行临界段代码前要先关中断,执行完后又要开中断.在KEIL C51编译器中,可以用C语言直接开/关中断.用 EA=0可以关中断,则有#define OS_ENTER_CRITICAL EA=0;用EA=1可以开中断,则有#define OS_EXIT_CRITICAL EA=1.开/关中断的方法有3种,在这里采用最简单的方法,即进入临界段代码前先关闭中断,执行完临界段代码后再打开中断,则有#define OS_CRITICAL_METHOD 1.

AT89S51单片机的堆栈增长方向都是从低地址向高地址增长的,所以有#define OS_STK_GROWTH 0.在μC/OS-Ⅱ中任务间进行切换时要求采用软中断实现,但AT89S51单片机没有软中断指令,且函数调用与中断的堆栈结构相同,因此可以用函数调用来实现入栈,即#define OS_TASK_SW()OSCtxSw(),用中断返回指令RETI实现出栈.

2.2 OS_CPU_C.C的设计思路

在OS_CPU_C.C中主要是编写函数OSTaskStkInit(),该函数的主要任务是初始化新建任务的私有堆栈.μC/OS-Ⅱ处于就绪态的任务堆栈看起来像刚刚发生过中断一样,所有CPU寄存器中的数据都保存在任务的私有堆栈中.AT89S51单片机在函数调用时只将程序计数器PC的值(16位数据)自动压入系统栈中,PSW,ACC,B,DPL,DPH,R0,R1,R2,R3,R4,R5,R6,R7,SP则需要手动依次保存到系统栈.在任务切换时,为了实现任务的私有堆栈和系统栈之间的复制,任务私有堆栈的结构和系统栈结构应该基本相同,主要区别是在任务私有堆栈中还要保存仿真栈的地址,每个任务都有自己的仿真栈,仿真栈由编译器自动分配.任务私有堆栈不是真正的堆栈,是外部RAM,所以CPU寄存器的值都要手动保存,而系统栈只需手动保存除PC寄存器之外的其他寄存器.任务私有堆栈和系统栈的结构如图3所示.

图3 任务私有堆栈和系统栈的结构

2.3 OS_CPU_A.ASM的设计思路

OS_CPU_A.ASM包含了与处理器AT89S51紧密相关的汇编代码,由 OSStartHighRdy(),OSCtxSw(),OSIntCtxSw(),OSTickISR()4 个函数组成.

(1)OSStartHighRdy() 用来查找就绪表中优先级最高的任务并加以运行.首先根据最高优先级任务的任务控制块指针,找到该任务的私有堆栈指针,然后找到私有堆栈的长度,并将私有堆栈中的数据复制到系统栈,再通过POPALL将除PC之外的寄存器值从系统栈弹到CPU寄存器后,用RETI指令将系统栈中PC的值恢复到PC寄存器[5].

(2)OSCtxSw() 主要任务是保存当前任务的上下文,然后将就绪表中优先级最高任务的私有堆栈中的数据恢复到CPU的各个寄存器中.先将CPU寄存器(除PC外)中的数据用PUSHALL指令保存到系统栈中,并从系统栈复制到任务的私有堆栈中,然后将新的栈顶指针保存到当前任务的任务控制块中,由此当前任务的上下文就保存完毕.将就绪表中优先级最高的任务设置为当前任务,堆栈指针指向该任务的私有堆栈,将私有堆栈中的数据复制到系统栈,然后用POPALL指令将系统栈中的数据弹至CPU寄存器中,最后用RETI指令将PC的值弹出,CPU转去执行就绪表中优先级最高的任务.

(3)OSIntCtxSw() 由于当前执行的是中断服务程序,不需要返回,只需将就绪表中优先级最高的任务设置为当前任务,然后恢复其寄存器即可.它与OSCtxSw()的区别在于不用保存当前任务的上下文.

(4)OSTickISR() 主要任务是安装时钟,并设置时钟节拍.在AT89S51中,采用定时器零.在执行OSTickISR()前,先关中断,保存CPU寄存器的值到系统栈,设置时钟频率,启动定时器零,调用时钟服务函数,调度一次,恢复CPU寄存器的值,然后中断返回.

原则上,与处理器无关的代码不用修改.由于KEIL编译器在缺省情况下编译的代码不可重入,所以要在每个C函数及其声明后标注reentrant关键字.另外,“pdata”和“data”在 uCOS中用作一些函数的形参,但它同时又是KEIL的关键字,容易导致编译错误.文献[6]将“pdata”改成“ppdata”,“data”改成“ddata’,以避免此类错误的发生.

2.4 系统测试

在系统运行前,必须设置时钟节拍发生器定时器零的初值,定时器零工作模式设置为1,16位定时器,其初值的计算公式为:

式中:f——时钟节拍的频率,此处设置为50;

F0SC——晶振频率,设置为12 MHz;

x——定时器初值,x=B1E0H.

设置好时钟节拍发生器定时器零后,再配置μC/OS-Ⅱ,这样代码可以在仿真环境中通过断点和跟踪等手段进行调试.为了更直观地看到程序运行结果,在此设置两个任务:Mytask和Yourtask.Mytask在屏上显示为Mytask,优先级为5;Yourtask在屏上显示为Yourtask发送的消息,优先级为6.

3 结语

本文主要讨论了 μC/OS-Ⅱ操作系统在AT89S51单片机上的应用方法及其应注意的问题.实践证明,该方法切实可行,简单易掌握,具有较强的实用性.但在使用过程还应注意如下几个问题.一是μC/OS-Ⅱ是一个基于优先级的实时操作系统,优先级是任务唯一的标识,每个任务的优先级必须不同.为避免优先级反转,需要使用互斥型信号量来管理共享资源.二是μC/OS-Ⅱ是多任务操作系统,需要为每个任务分配私有堆栈(私有堆栈中包括中断堆栈,且中断可以嵌套达255层),但由于AT89S51单片机的内部RAM只有128 B,且AT89S51的硬件堆栈不能放在片外,所以任务的私有堆栈只能放在外部RAM中.三是由于μC/OS-Ⅱ操作系统本身有大量的代码,引入OS需要占用CPU10% ~20%的负荷能力.此外频率决定了CPU的耗费,频率越高耗费越大,至一定程度时需更换更强的CPU.

[1]腾凌巧,刘常春,戴琨.嵌入式操作系统的移植与测试[J].平顶山工学院学报,2003,12(4):33-35.

[2]LABROSSE Jean.μC/OS-Ⅱ源码公开的实时嵌入式操作系统[M].邵贝贝,译.北京:中国电力出版社,2001:56-67.

[3]田志鑫,张雷,赵明扬.在51单片机上移植μC/OS-Ⅱ关键问题的解决[J].微计算机信息,2007,23(12):45-48.

[4]任哲,潘树林,房红征.嵌入式操作系统基础UCOS-Ⅱ和Linux[M].北京:北京航空航天大学出版社,2001:65-68.

[5]姚念龙,尹航,姜久春.μC/OS-Ⅱ在MC9S12A64上的移植和应用[J].微计算机信息,2006,22(8):12-15.

[6]孟庆峰.实时内核μC/OS-Ⅱ在S3C44B0X上移植的研究与实现[J].安徽电子信息职业技术学院学报,2008,7(7):34-36.

猜你喜欢
编译器堆栈寄存器
基于行为监测的嵌入式操作系统堆栈溢出测试*
STM32和51单片机寄存器映射原理异同分析
Lite寄存器模型的设计与实现
基于相异编译器的安全计算机平台交叉编译环境设计
基于堆栈自编码降维的武器装备体系效能预测
通用NC代码编译器的设计与实现
高速数模转换器AD9779/AD9788的应用
一种用于分析MCS-51目标码堆栈深度的方法
一种可重构线性反馈移位寄存器设计
编译器无关性编码在微控制器中的优势