自嵌入式系统开发以来,很长时间都采用前后台系统软件设计模式:主程序为一个无限循环,单任务顺序执行。通过设置一个或多个中断来处理异步事件。
这种系统对于简单的应用是可以的,但对于实时性要求比较高的、处理任务较多的应用,就会暴露出实时性差、系统可靠性低、稳定性差等缺点。
μC/OS-II 是一种基于优先级的抢占式多 任务实时操作系统, 包含了实时内核、任务管理、时间管理、任务间通信同步(信号量,邮箱,消息 队列)和内存管理等功能。它可以使各个任务独立工作,互不干涉,很容易实现准时而且无误执行,使实时应用程序的设计和扩展变得容易,使应用程序的设计过程大为减化。而且它内核源代码公开,可移植性强,为编程人员提供了很好的一个软件平台。通过μC/OS-II在P89V51RD2 上的移植,可以掌握移植和测试μC/OS-II 的实质内容,很容易将其移植到其它的CPU平台上。
μC/OS-II 介绍
μC /OS-II是一个完整的、可移植、可固化、可裁剪的占先式实时多任务内核。μC/OS-II绝大部分的代码是用ANSI的C语言编写的,包含一小部 分汇编代码,使之可供不同架构的微处理器使用。至今,从8位到6 4位,μC/OS-II已在超过40种不同架构上的微处理器上运行。μC/OS-II已经在世界范围内得到广泛应用,包括很 多领域, 如 手机、路由器、集线器、不间断电源、飞行器、医疗设备及工业控制 上。实际上,μC/OS-II已经通过了非常严格的 测试,并且得到了美国航空管 理局(Federal Aviation Administration)的认证,可以用在飞行器上。这说明μC/OS-II是稳定可靠的,可用于与人性命攸关的安全紧要(safety critical)系统。除此以外,μC/OS-II 的鲜明特点就是源码公开,便于移植和维护。
μC/OS-II 内核结构
多任务系统中,内核负责管理各个任务 ,或者说为每个任务分配CPU 时间 ,并且负责任务之间的通讯。内核提供的基本服务是任务切换。 μC/OS-II可以管理多达64个任务。由于它的作者占用和保留了8个任务,所以留给用户应用程序最多 可有56个任务。赋予各个任务的优先级必须是不相同的。这意味着μC/OS-II不支持时间片轮转调度法 (round-robin scheduli ng)。μC/OS-II为每个任务设置独立的 堆栈空间,可以快速实现任务切换 。μC/OS-II近似地每时每刻总是让优先级最高的就绪任务处于运行状态,为了保证这一点,它在调用系统API 函数、中断结束、定时中断结束时总是执行调度算法,μC/OS-II通过事先计算好数据简化了运算量,通过精心设计就绪表结构使得延时可预知。
P89V51RD2 微处理器介绍
P89V51RD2是Philips公司生产的一款80C51微控制器,包含64KB Flash和1024字节的数据RAM。P89V51RD2的典型特性是它的X2方式选项。利用该特性,设计者可使应用程序以传统的80C51时钟频率(每个机器周期包含12个时钟)或X2 方式(每个机器周期包含6个时钟)的时钟频率运行,选择X2方式可在相同时钟频率下获得2倍的吞吐量。从该特性获益的另一种方法是将时钟频率减半来保持特性不变,这 样可以极大地降低EMI。FLASH程序存储器支持并行和串行在系统编程(ISP),ISP允许在软件控制下对成品中的器件进行重复编程。应用固件的 产生/更新能力实现了ISP的大范围应用。 5V的工作电压,操作频率为0~40MHz。P89V51RD2的资源和ISP的功能使得它很适合用来做μC/OS-II的移植调试。并不需要购买仿真器和编程器等额外投资。
μC/OS-II 的移植
移植就是使μC/OS-II能在P89V51RD2上运行。为了方便移植,大部分的μC/OS-II的代码是用C语言编写的;但是仍需要用C语言和汇编语言编写一些处理器硬件相关的代码,这是因为μC/OS-II在读/写处理器寄存器时,只能通过汇编语言来实现。由于μC/OS-II在设计时就已经充分考虑了可移植性,所以μC/OS-II的移植相对来说是比较容易的。
硬件平台构成
由于P89V51RD2是一款80C51微控制器,片内包含了64KB的FLASH程序存储器,并且支持串行在线编程(ISP)。使它在ROM空间上很适合做μC/OS-II的移植。但是它片内RAM空间很有限,只有1KB,不能满足μC/OS-II对RAM的要求。但是由于P89V51RD2可以扩展RAM空间,使这一问题得以解决。我们为它扩展了一片32KB的RAM来构成移植μC/OS-II的硬件平台。这样P89V51RD2就满足了移植μC/OS-II的所有要求。
编译器的选择
由于μC/OS-II绝大部分代码是用标准的C语言编写的,所以C语言开发工具对于μC/OS-II是必不可少的。由于μC/OS-II是一个可剥夺行的占先式内核,所以要求C编译器可以产生可重入型代码。笔者选择Keil C51集成开发环境作为开发工具。该开发工具有C编译器,汇编器和链接定位器等工具构成。链接器用来将不同模块(编译过或汇编过的文件)链接成目标文件,定位器则允许将代码和数据放置在目标处理器的指定内存中。Keil C51 还可以生成HEX格式的编程文件用于编程EPROM或是FLASH,同时可以实现完整软件仿真支持。Keil C51支持所有8051变种的微控制器。通过设置编译控制选项,它完全可以满足编译μC/OS-II源代码的要求。
可重入函数问题
可重入函数可以被一个以上的任务调用,而不必担心数据被破坏。可重入函数任何时候都可以被中断,一段时间后又可以继续运行,而相应的数据不会丢失。由于μC/OS-II是抢占式的实时多任务内核,同一个函数可能会被不同的任务调用,也可能会被中断,因此,移植μC/OS-II要求C语言编译器可以产生可重入函数。但是正常情况下Keil C51编译器中的函数不能重入。原因是由于8051系列微控制 器的硬件堆栈很小,硬件堆栈指针SP最多只能在内部256字节的RAM内移动,不能够指向64K的外部RA M空间。所以编译器使用固定的RAM地址来存储函数的参数和局部变量,而不是使用堆栈来存储。为了在Keil C51中实现可重入函数,可以使用“reentrant”关键字声明该函数是可重入的。编译器可根据编译模式为可重入函数在内部RAM或外部RAM空间开辟一个模拟堆栈来存储可重入函数的参数和局部变量。可重入函数的返回地址仍然保存在硬件堆栈中。Cx51编译手册不推荐使用模拟堆栈,原因是受8051寻址方式的限制,模拟堆栈访问的效率很低。但是这是在Keil C51中实现可重入函数的唯一方法。可重入函数模拟堆栈拥有独立于硬件堆栈指针的模拟堆栈指针。模拟堆栈及其指针在启动代码文件“STARTUP.A51”中定义和初始化。
μC/OS-II源文件移植
在了解了P89V51RD2微处理器和Keil C51 编译器的技术细节的基础上,就可以开始μC/OS-II源文件移植的工作了。真正编写移植代码的工作就相对比较简单了。图1表示了基于μC/OS-II的应用的系统结构结构。由图1可以看出由于μC/OS-II自生的绝大部分代码是使用ANSI C编写的,而且代码的层次结构十分干净,与平台相关的移植代码仅仅存在于OS_CPU_A.ASM、OS_CPU_C.C以及OS_CPU.H这三个文件当中。下面分别解释各个文件在P89V51RD2上的移植。
图1 μC/OS-II软件体系结构和各模块之间的关系
μC/OS-II中与处理器CPU 类型无关的代码:uCOS_II.H和uCOS_II.C,其中uCOS_II.C 文件包含以下文件:OS_CORE.C OS_TASK.C OS_TIME.C OS_SEM.C OS_MBOX.C OS_MUTEX.C 和OS_FLAG.C 也就是说原则上这些文件可以直接添加不用修改。但是由于Keil C51编译器的特殊性,这些代码仍要多处改动,因为Keil C51缺省情况下编译的代码不可重入而多任务系统要求并发操作导致重入,所以要在每个C 函数及其声明后标注reentrant 关键字,另外“pdata” 和“data” 在uCOS中用做一些函数的形参,但它同时又是Keil C51 的关键字,会导致编译错误。我通过把“pdata”改成“ppdata”,“data”改成“ddata”解决了此问题。OSTCBCur、OSTCBHighRdy、OSRunning、OSPrioCur、OSPrioHighRdy 这几个变量在汇编程序中用到了,为了使用寄存器R0或R1访问而不用DPTR,应该用Keil C51扩展关键字IDATA将它们定义在内部RAM中。
OS_CPU.H的移植
OS_CPU.H包括了用#define语句定义的、与处理器相关的常数、宏及类型。因为不同的处理器有不同的字长,所以μC/OS-II的移植包括的一系列数据类型定义,以确保其可移植性。μC/OS-II代码不使用语言中的short,int,及long等数据类型,因为它们是与编译器相关的,是不可移植的。采用定义的整形数据结构等既是可移植的,又很直观。参考Cx51编译手册,可以完成OS_CPU.H里所有数据类型的定义。
与所有的实时内核一样,μC/OS-II需要先关中断,再处置临界段代码,并且在处置完毕后重新开中断。这样可以保护临界段代码免受多任务或中断服务子程序的破坏。为了隐藏不同编译器提供的不同的关中断和开中断的实现方法,增强可移植性,μC/OS-II在OS_CPU.H中定义了2个宏,来开中断和关中断:OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()。根据P89V51RD2的结构和Keil C51提供的方法,我们通过置位或清零中断允许位来实现。
代码如下:
OS_ENTER_CRITICAL() E
OS_EXIT_CRITICAL() E
MCS-51 堆栈从下往上增长(1=向下0=向上) ,OS_STK_GROWTH 定义为0。
OS_TASK_SW() OSCtxSw() ,因为P89V51RD2没有软中断指令所以用程序调用代替。在用汇编语言编写的OSCtxSw()中,模拟系统产生中断时的堆栈操作。以保证系统任务的正确切换。
OS_CPU_C.C的移植
μC/OS-II的移植要求用户在OS_CPU_C.C中编写10个简单的C函数。但唯一必要的μC/OS-II的移植要求用户在OS_CPU_C.C中编写10个简单的C函数。但唯一 必要的是OSTaskStkInit(),其他九个必须声明,但不一定要任何程序代码。
OSTaskStkInit()是在系统创建任务时用来初始化任务堆栈的,使堆栈看起来就象中断刚发生一样,所有寄存器都保存在堆栈中。由于P89V51RD2硬件堆栈很小,最多只能有在内部RAM空间的256字节。因此很难将所有任务的堆栈都用硬件堆栈来实现。为了解决这个问题,我们为每个任务在外部RAM空间都分配一段连续的存储区,用来模拟每个任务的堆栈。
在μC/OS-II进行任务切换时,首先将P89V51RD2硬件堆栈中的内容复制到要失去CPU拥有权的任务的外部模拟堆栈区,然后将要得到CPU拥有权的任务的外部模拟堆栈中的有效数据复制到P89V51RD2的硬件堆栈中。这样就实现了任务保护和切换。任务模拟堆栈和硬件的堆栈结构如图2所示。TCB 结构体中OSTCBStkPtr 总是指向用户堆栈最低地址,该地址空间内存放用户堆栈长度,其上空间存放系统堆栈映像,即:任务模拟堆栈空间大小=系统硬件堆栈空间大小+1。SP 总是先加1再存数据,因此SP初始时指向系统堆栈起始地址(OSStack)减1 处(OSStkStart)。很明显系统硬件堆栈存储空间大小=SP-OSStkStart。编写OSTaskStkInit()主要完成用户堆栈初始化,从下向上依次保存用户堆栈长度(5),PCL, PCH,PSW, AC C,B, DPL, DPH,R0,R1, R2,R3,R4,R5,R6,R7。不保存SP,任务切换时根据用户堆栈长度计算得出。紧接着的两字节保存可重入函数仿真堆栈的指针X_CP的高8位和低8位,初始化为任务模拟栈的最高地址的高8位和低8位。OSTaskStkInit()总是返回任务模拟栈的最低地址。
图2 P89V51RD2移植μC/OS-II的堆栈结构
OS_CPU_A.ASM的移植
OS_CPU_A.ASM的移植要求用户编写4个简单的汇编语言函数:
OSStartHighRdy()
OSCtxSw()
OSIntCtxSw()
OSTickISR()
μ C/OS-II的启动函数OSStart()调用OSStartHighRdy()来使就绪态任务中 优先级最高的任务开始运行,我们通过将任务模拟栈的有效长度内的数据复制到系统硬件堆栈,然后使用紧接着的两字节来改写X_CP的值。使可重入函数仿真堆栈指针指向该任务模拟栈的最高地址,这样做是因为Keil C51使用的可重入函数仿真堆栈的增长方向是向下的,和系统硬件堆栈的增长方向相反。这样就完成了OSStartHighRdy()的移植。
OSCtxSw()和OSIntCtxSw()两个汇编函数的功能主要完成任务的切换。不同的是OSCtxSw()在任务级调用,而OSIntCtxSw()是在中断推出时调用。
对于在P89V51RD2上的移植而言,这两个函数的实现基本相同。只是OSIntCtxSw()在中断调用中 由于OSIntExit()和自身对硬件堆栈的影响,需要将要保存的SP指针向下调整4个字节,以消除影响。μC/OS-II在需要任务切换时,根据CPU是否处在中断状态选择调用其中一个函数。如图2堆栈结构 所示,任务切换时先保存当前任务堆栈内容,方法是:用SP-OSStkStart 得出保存字节数。
将其写入任务模拟堆栈最低地址内。以任务模拟堆栈最低地址为起址,以OSStkStart为系统硬件堆栈起址,由系统堆栈向用户堆栈拷贝数据。循环SP-OSStkStart次,每次拷贝前先将各自栈指针增1。其次恢复最高优先级任务系统堆栈方法是:获得最高优先级任务用户堆栈最低地址,从中取出长度。以最高优先级任务用户模拟堆栈最低地址为起址,以OSStkStart 为系统堆栈起址,由任务模拟堆栈向系统堆栈拷贝数据。循环“有效长度”数值指示的次数。每次拷贝前先将各自栈指针增1。
μC/OS-II要求用户提供一个周期性的时钟源,来实现时间的延迟和超时功能。在P89V51RD2中我们通过定时器T0来提供时钟源。频率设为50Hz。T0的初始化函数在OS_CPU_C.C实现。时钟节拍中断服务子程序的编写也很简单,示意性代码如下:
void OSTickISR(void)
{
保存处理器寄存器;
调用OSIntEnter();
定时器计数器重装;
调用OSTimeTick();
调用OSIntExit();
恢复处理器寄存器;
执行中断返回指令;
}
μC/OS-II 移植代码的测试
完成μC/OS-II移植后,就要对移植的代码进行测试。测试移植的μC/OS-II是否能够完成任务调度、时间管理、任务管理与同步等功能,是否能够启动多 任务环境。在P89V51RD2的移植中,编写简单的测试程序进行多任务的测试。测试程序创建了4任务,任务AA,BB,CC和LEDFlash优先级分别为2,3,4,5。任务AA延时一秒通过串口输出一 次,任务BB延时3秒通过串口输出一次,任务CC延时6秒通过串口输出一次。
LedFlash等待信号量有效时,对P1.1口进行一次取反操作。P1.1连接LED惊醒观察。定时器中断服务子 程序定时发出信号量。这样任务LedFlash实现LED的闪烁功能。
μC/OS-II测试程序的文件结构,硬件测试结果和Keil C51的软件仿真结果如图3所示。结果表明μC/OS-II在P89V51RD2上 的移植是成功的。
图3 μC/OS-II的测试程序的文件结构,软件仿真和硬件运行测试结果
结语
通过μC/OS-II在P89V51RD2上的移植,掌握了μC/OS-II内核的工作原理和移植方法,测试程序表明移植代码可以稳定可靠的运行,实现了多任务的管理和调度。μC/OS-II实时操作系统的移入,不但可以提高系统的实时性、可靠性和稳定性,还提高了应用软件的可移植性,降低了开发人员的工作量。