关键词 C8051F120;µC/OS-II;移植
Taiyuan University of Technology Tian Juan
Abstract The paper introduces the characteristic of real-time operation system µC/OS-II, and discusses the necessity of porting µC/OS-II on 51 series MCU. Then the specific process of porting µC/OS-II on C8051F120 which is selected as porting target is analyzed. The paper ends with designing test program to prove the success of the porting.
Keywords C8051F120; µC/OS-II; porting
引言
传统的嵌入式系统设计往往采用前/后台系统。应用程序是一个无限的循环,在循环中调用相应的函数完成对应的操作,这部分可以看成后台行为。中断服务程序处理异步事件,这部分可以看成前台行为[1]。在大多数对实时性要求较高的场合中,这种系统结构就无法满足应用的要求,实时内核逐步成为嵌入式系统设计的主流。µC/OS-II是一个完整的,可移植、固化、裁减的占先式实时多任务内核,不仅结构简单,绝大部分采用C语言,而且可移植性好,很容易被移植到各种微处理器上,在移植过程中,只需要做少量的修改工作即可。目前µC/OS-II最多支持64个任务,总是在执行处于就绪态的优先级最高的任务。并且因其源代码的完全公开和优越性能而得到了广泛的应用。
51系列单片机是美国Intel公司在1980年推出的高性能8位单片机,目前仍然是我国使用最广泛的单片机系列之一,有非常大的应用环境与前景。如果开发一套基于51系列单片机的操作系统,那么用户只需要编写各个任务的程序,不必同时将所有任务运行的各种情况记在心中,不但大大减少了程序编写的工作量,而且减少了出错的可能性[2]。
所以,在51系列单片机上移植实时操作系统µC/OS-II是很有必要的。
1 µC/OS-II的移植条件
要使µC/OS-II移植到处理器后能正常运行,处理器必须满足以下条件:
● 处理器的C编译器能产生可重入代码。µC/OS-II是可剥夺型内核,总是让就绪态的高优先级的任务先运行,中断服务程序可以抢占CPU,所以只能通过C编译器产生可重入代码。可重入函数可以被一个以上的任务调用,而不必担心数据被破坏。
● 处理器的C编译器能在C语言中开中断和关中断。因为µC/OS-II在处理临界段的时候,须关中断,处理完毕后,再开中断。
● 处理器支持中断,并且能产生定时中断。因为µC/OS-II是通过硬件中断来实现系统时钟,并在时钟中断服务程序中来处理与时间相关的问题,实现任务之间的调度。
● 处理器具有一定数量的硬件堆栈,并且有将堆栈指针和其他CPU寄存器内容读出、并存储到堆栈或内存中的指令。因为µC/OS-II为每一个任务分配任务堆栈,在任务切换时,需要先保存当前任务堆栈内容,再恢复最高优先级任务堆栈内容。
2 CPU芯片的选择
随着现代通信技术的发展,智能化系统对DSP需求的增长要求不断提高单片机运算速度[3]。C8051F系列单片机就是在这样的情况下由Silabs公司推出的,它是完全集成的混合信号系统级芯片,具有与8051兼容的微控制器内核,在不扩展8位数据总线的情况下,使单周期指令速度提高到原8051的12倍。而C8051F120作为C8051F系列中的高端产品就被选择为系统移植的对象。
C8051F120的内部资源有:64个I/O引脚,5个16位通用计数器/定时器,6个捕获/比较模块,硬件实现的SPI,SMBus/IIC和两个UART串行接口,片内看门狗定时器,2个比较器,真正12位100ksps的8通道ADC,8位500ksps的8通道ADC,128KB的FLASH存储器,8448B的内部数据RAM[4]。
由于C8051F120处理器和使用的Keil编译器都能够很好的满足上述移植条件,所以可以把µC/OS-II移植到C8051F120上。在实际开发系统中,使用外部晶体振荡器,晶振频率为22.1184MHZ,并外扩256KB的RAM。
3 移植过程
µC/OS-II的移植可以看作是对µC/OS-II代码的修改。µC/OS-II的代码分为与处理器无关的代码,与处理器相关的代码和与应用相关的代码。与处理器无关的代码原则上是不用修改可以直接添加,但由于Keil编译器的特殊性,必须在需要可重入的函数声明的后面标注reentant关键字,即加上重入属性;又因为pdata既是Keil的关键字又是µC/OS-II的一些函数的形参,会导致编译错误,所以把pdata改为pdat。与处理器相关的代码包括3个文件:OS_CPU.H、OS_CPU_C.C、OS_CPU_A.ASM,需要大量的修改后才能添加,这是移植工作的重点。另外,与应用相关的代码包括2个头文件:INCLUDES.H和OS_CFG.H。INCLUDES.H是一个主头文件,出现在每个.C文件的第一行。INCLUDES.H文件使得工程项目中的每个.C文件无需分别考虑它实际上需要哪些头文件。还可以在头文件列表的最后添加自己的头文件。OS_CFG.H是系统配置文件,µC/OS-II的裁减过程是通过对OS_CFG.H中的相关常量进行设置来完成[5]。可以分为任务管理功能的裁减,节省代码存储空间;数据结构的裁减,节省数据存储空间;系统节拍频率设置和任务堆栈大小设置。
3.1 OS_CPU.H
OS_CPU.H包括了用#define语句定义的、与处理器相关的常数、宏以及类型。
数据类型的定义是为了保证可移植性。而且必须把任务堆栈的数据类型告诉µC/OS-II,通过为OS_STK声明恰当的C数据类型来实现的。C8051F120的堆栈是8位的,所以声明OS_STK:
typedef INT8U OS_STK; //堆栈的宽度为8位
µC/OS-II为了处理临界段代码,须关中断,处理完毕后,再开中断。所以定义2个宏来关中断和开中断:OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()。这2个宏可以用3种方法实现,具体用哪种方法,取决于使用的处理器类型和C编译器。C8051F120使用方法1来处理临界段,即直接开关中断。这样定义既减少了程序行数,又避免了退出临界区后关中断造成的死机。根据中断允许寄存器 IE的第7位EA为中断允许总控制位,EA=0屏蔽所有中断,EA=1允许所有中断,所以定义语句为:
#define OS_ENTER_CRITICAL() EA=0 //关中断
#define OS_EXIT_CRITICAL() EA=1 //开中断
C8051F120堆栈从下向上增长(0=向上,1=向下),通过配置常数OS_STK_GROWTH指定堆栈的方向。所以定义语句为:
#define OS_STK_GROWTH 0 //堆栈从下向上增长,1=向下增长
µC/OS-II的任务切换是通过模仿中断动作完成的,但C8051F120没有软中断指令,所以用函数调用的方式实现任务切换,定义语句为:
#define OS_TASK_SW() OSCtxSw() // OSCtxSw()是用于实现任务切换的函数
3.2 OS_CPU_C.C
OS_CPU_C.C包括10个C函数:OSTaskStkInit()函数和9个Hook函数。OSTaskStkInit()函数在任务创建时被调用,用来初始化任务堆栈。Hook函数用来扩展µC/OS-II的功能,可以不包含任何代码,但必须声明。最后添加Timer0初始化函数,包括选择工作模式,设定初值和使能中断,因为要使用C8051F120的Timer0实现时钟中断。
在修改OSTaskStkInit()函数之前,需要先知道任务堆栈的结构。µC/OS-II为每一个任务都分配了任务堆栈,任务堆栈由系统堆栈和仿真堆栈两部分组成。由于C8051F120要求堆栈设置在片内RAM中,而片内RAM空间又非常有限,因此所有任务的任务堆栈设置在片内RAM中是几乎不可能的。只能把任务堆栈存放在片外RAM,并在片内RAM设置一个公共堆栈,即系统堆栈,栈底地址为?STACK。在任务切换时,需先保存当前任务堆栈内容,再恢复最高优先级任务堆栈内容,即进行任务堆栈和系统堆栈的复制。仿真堆栈是用来为可重入函数完成参数传递和存放局部变量的,设置在片外RAM,增长方向由上向下,栈指针为?C_XBP。
所以,任务堆栈初始化可以看作是把处于就绪态的最高优先级任务的任务堆栈内容复制到系统堆栈的过程。首先要获得任务堆栈最低地址和长度,因为需要从任务堆栈中恢复15个寄存器内容到系统堆栈,所以堆栈长度为15。然后从下向上依次复制寄存器内容,复制顺序是:PCL,PCH,PSW,ACC,B,DPL,DPH,R0,R1,R2,R3,R4,R5,R6,R7。最后保存仿真堆栈地址,并返回任务堆栈最低地址。这样就完成了任务堆栈初始化。
3.3 OS_CPU_A.ASM
OS_CPU_A.ASM包括4个汇编语言函数,这4个函数都是不可重入的,并且定义了系统堆栈空间大小。
OSStartHighRdy ()函数,用来使就绪态任务中优先级最高的任务开始运行。OSCtxSw()函数,实现CPU正常运行时任务间的切换,即对当前任务堆栈的保存和对最高优先级任务堆栈的弹出,使最高优先级任务获取CPU的控制权。OSIntCtxSw()函数,作用是在中断服务程序中执行中断级别任务切换。它的绝大多数代码和OSCtxSw()函数是一样的,区别在于中断服务函数已经保存了寄存器内容,则不需要再在OSIntCtxSw()函数中保存。OSTickISR()函数,是系统时钟的中断服务程序,主要功能是检查是否有由于延时而被挂起的任务成为就绪任务。如果有,就调用OSIntCtxSw()函数进行任务切换,从而运行最高优先级任务。因为µC/OS-II在每一个节拍都要检查有没有更高优先级的任务在等待执行,如果有,就要进行任务切换。所以,时钟节拍率越高,系统的额外负荷就越重。
4 系统测试
按照上述移植步骤,作者在Keil编译环境下实现了µC/OS-II在C8051F120上的具体移植。为了防止在编译时出现段过大的错误,需要选择内存模式为大模式,并相应的在STARTUP.A51 文件中设置XBPSTACK=1。创建2个任务来验证µC/OS-II移植的成功:
OSTaskCreate (TaskLed, (void *)0, TaskStartStkLed,2);
OSTaskCreate (TaskSmg, (void *)0, TaskStartStkSmg,3);
程序流程图如图1所示。任务TaskLed闪烁1次后,向任务TaskSmg发送消息并等待回复,任务TaskSmg得到消息后显示闪烁次数,并进行回复。最终效果为P4.0连接的LED闪烁1次,则P5口连接的数码管显示数字加1,最大显示为9,之后自动清零。经过4小时的连续实验,一切运行正常,这就验证了移植代码的正确性。
图1 程序流程图
需要注意的是,C8051F120的使用涉及到SFRPAGR的保护,因为C8051F120拥有太多的模拟和数字资源,它们都需要相应的SFR控制,而标准8051保留的SFR空间不能满足所需的SFR寄存器,所以C8051F120另外安排了SFRPAGE来扩展更多的SFR寄存器空间。同一个SFR地址配合不同的SFRPAGE值,控制不同的资源。但如果是非中断情况下发生任务切换,并且在新任务中改变了SFRPAGE,那么回到以前的任务后很可能SFRPAGE已经改变,从而无法控制正确的资源,还有可能使程序跑飞。因为不同的资源往往需要不同的SFRPAGE,为了解决这一问题,在所有出现SFRPAGE赋值的地方都应当作临界代码保护起来,这样就可以完全避免因SFRPAGE值的错误而引出的问题[6]。
5 结论
µC/OS-II是一种实时性好、代码量小的多任务实时操作系统,具有很好的稳定性与可靠性,可广泛移植到不同构架的微处理器上。本文完整的阐述了µC/OS-II的移植过程,并在C8051F120上实现了双任务同步通信。论文对51系列单片机的µC/OS-II移植具有普遍的指导意义。