嵌入式操作系统是嵌入式应用的基础和核心。随着应用系统的不断复杂化和系统实时性需求的不断提高,对相应软件的逻辑结构、稳定性、实时性也提出了更高的要求,以传统的前后台编程模式编制软件将更加困难,而且容易出错,因此,嵌入式实时操作系统(简称RTOS)就成为一个比较好的解决方法。使用RTOS作为应用程序的运行平台,它将应用程序分解为多个任务,负责各个任务调度、资源管理、任务通信等,可使系统更加稳定可靠,程序易于扩展、移植和维护。[1]
μC/OS-Ⅱ是一个源码公开的、非商业用途时免费的嵌入式实时操作系统,它有许多方面可与商业的实时内核相媲美。它是一个基于优先级的多任务实时操作系统,包含了实时内核、任务管理、时间管理、任务间通信和内存管理等功能,具有很强的可裁剪性和可移植性,可以适应不同系统的要求灵活地进行配置。本文以Silicon Laboratories公司高性能单片机C8051F060为硬件平台,在成功移植了μC/OS-Ⅱ操作系统的基础上,并针对实际的工程项目建立了多任务操作系统。该系统已应用于智能碳硫分析仪控制器中,通过现场的调试,系统运行准确可靠。
2. μC/OS-Ⅱ的工作原理
μC/OS-Ⅱ是一个基于优先级的多任务实时操作系统,即系统的调度是以优先级为基础的,每一个任务根据其重要性分配有一个优先级,系统总是运行处于就绪态下的优先级最高的任务。每个任务通常都是一个无限的循环,都会处在以下5种状态之一:睡眠态、就绪态、运行态、等待态和中断服务态,任务的状态转换图如图1所示。[2]
在μC/OS-II 中把睡眠态中的任务交给内核管理是通过调用函数OSTaskCreat()或者OSTaskCreatExt()来实现的。任务建立时,用户可以给任务分配优先级和栈空间等。任务一旦建立,这个任务就进入就绪状态准备运行。内核通过任务调度,使得就绪态中优先级最高的任务开始运行,任何时刻只能有一个任务处于运行态。正在运行的任务可以通过延时或等待某个事件的发生进入等待状态,这时该任务的CPU使用权被剥夺,只有当延时时间到或者收到等待的消息,任务才可以进入就绪态,等待下一次的任务调度。处于运行态、等待态和就绪态的任务可以调用内核函数将自己删除,即该任务重返睡眠态。正在运行的任务可以被中断而挂起,这时中断服务子程序控制了CPU的使用权,中断结束时内核会进行任务调度。当μC/OS-II 决定运行另外的任务时,它就会将当前任务的运行状态,即处理器所有寄存器的内容,保存在该任务堆栈或系统中断堆栈之中,然后把下一个将要运行的任务当前状态从该任务栈区中重新装入处理器的寄存器,并开始运行。
3. C8051F060功能简介
C8051FXXX系列单片机是完全集成的混合信号系统级芯片,其指令集与MCS-51系列单片机完全兼容。除了具有标准8051的数字外设部件外,片内还集成了数据采集和控制系统中常用的模拟部件和其它数字外设及功能部件。与传统的MCS-51相比,该单片机的CPU采用流水线结构的CIP-51内核,最高系统时钟频率可达25MHz,70%的指令执行是在一个或两个系统时钟周期内完成。[3]本系统中使用C8051F060单片机作为现场控制器件,该器件采用100引脚TQFP封装,具有丰富的I/O接口资源,各个I/O端口的功能可通过片内的优先权交叉开关译码器进行配置;内含两个16 位分辨率的A/D转换器,转换速率可编程,最大为1Mbps;同时含有4352×8位数据RAM和64K×8位FLASH存储器,可在系统编程。
具体移植
4.1 编译工具及运行环境
实现μC/OS-Ⅱ的移植,要求所使用的C编译器支持混合编程。KEIL C51可以为众多的8051派生器件编程,我们选用的是KEIL7.06,运行环境是Silicon Laboratories IDE Version 2.4,也可以在Keil uVision2 IDE 中调试C8051F 系列单片机,必须安装动态链接库。适配器使用Silicon Lab 公司的EC2,通过JTAG接口,可以在线编程调试。μC/OS- II 内核使用了V2.52 版本。
4.2移植过程
移植过程中需要修改与处理器相关的四个文件:启动代码STARTUP.A51、头文件OS_CPU.H 、C语言文件OS_CPU_C.C及汇编语言文件OS_CPU_A.ASM。
启动代码STARTUP.A51
由于选择KEIL C51 在大模式下编译,所以设置XBPSTACK EQU 1,而设置IBPSTACK EQU 0。C8051F060MCU自带4KRAM,故设置XBPSTACKTOP EQU 0FFFH+1。
头文件OS_CPU.H
这个文件包括了用#define语句定义的、与处理器相关的常数、宏以及类型。其中需改动的部分如下:
typedef unsigned char OS_STK /* 堆栈入口宽度为8位*/
#define OS_STK_GROWTH 0 /*堆栈增长方向*/
#define OS_ENTER_CRITICAI () EA=0 /*关中断*7
#define OS_EXIT_CRITICAI () EA=1 /*开中断*/
#define OS_TASK_Sw() OSCtxSw() /*任务调度*/
在C8051F060MCU中,数据宽度和堆栈宽度都是8位,所以将OS_STK定义成unsigned char型,它的堆栈是从下向上递增的,定义OS_STK_GROWTH为0。μC/OS-Ⅱ在进入系统临界代码区之前要关中断,等到退出临界区后再打开,以保护核心数据不被多任务环境下的其它任务或中断破坏。内核提供了3 种处理临界代码的方法,这里使用第一种,即通过对寄存器EA 开关中断。由于51指令集中没有软中断指令,所以任务切换用函数0SCtxSw()代替。
C语言文件OS_CPU_C.C
当某个任务建立时,用户都要为任务分配一个堆栈空间,用来存放任务的起始地址、处理器状态、中断返回地址和一些参数。一般函数调用时函数堆栈的操作过程是编译器自动完成的,而任务切换时需要模拟一个和编译器类似的任务堆栈的操作过程。实际上,μC/OS-Ⅱ的移植工作主要就是解决这个问题,OSTaskStkInit ( ) 是完成任务堆栈初始化工作,后面介绍的OS_CPU_A..ASM文件完成3 种不同状态下的任务切换操作。
在文件OS_CPU_C.C中,主要要求用户编写OSTaskStkInit()函数,下面列出了该函数编写情况。
void *OSTaskStkInit(void (*task)(void*pd), void *ppdata, void *ptos, INT16U opt) REENTRANT
{ OS_STK *stk;
ppdata = ppdata; /*防止警告 */
opt= opt;
stk= (OS_STK *)ptos;/*栈底 */
*stk++ = (0xFF + 1); /*仿真堆栈 */
*stk++ = 2 + 14; /* 2字节返回地址和14个字节寄存器*/
*stk++ = (INT16U)task & 0xFF;/*返回地址地位 */
*stk++ = (INT16U)task >> 8; /*返回地址高位 */
*stk++ = 0x84; /* SFRPAGE */
*stk++ = 0xE0; /* ACC */
*stk++ = 0xF0; /* B */
*stk++ = 0x83; /* DPH */
*stk++ = 0x82; /* DPL */
*stk++ = 0xD0; /* PSW */
*stk++ = 0x00; /* R0 */
*stk++ = 0x01; /* R1 */
*stk++ = 0x02; /* R2 */
*stk++ = 0x03; /* R3 */
*stk++ = 0x04; /* R4 */
*stk++ = 0x05; /* R5 */
*stk++ = 0x06; /* R6 */
*stk++ = 0x07; /* R7 */
return ((void *)ptos); /*返回栈顶地址*/
}
在OSTaskStkInit()函数中,处理器的入栈地址一定要正确无误,否则移植后的程序在运行时会出现一些未知错误。这里,需要指出寄存器SFRPAGE的用途,特殊功能寄存器SFR的地址在片内内存0x80~0xFF,而C8051F060具有超过128个特殊寄存器,两者是矛盾的。为了解决这个问题,C8051F060采用了分页技术,将寄存器放于不同的页,所以SFRPAGE这个寄存器必须在用户堆栈中保存。
汇编语言文件OS_CPU_A.ASM
这个文件要编写4个的汇编语言函数:OSStartHighRdy()、OSCtxSw()、OSIntCtxSw()和OSTickISR()。除了OSTickISR(),每一个函数都需要用PUBLIC声明为可以被外部模块调用。在大模式下,函数声明的固定格式为?PR?函数名?模块名 SEGMENT CODE,如?PR?OSStartHighRdy? OS_CPU_A SEGMENT CODE。模块中还需要引用4个外部可重入函数:OSIntEnter()、OSTaskSwHook()、OSIntExit()、OSTimeTick(),声明格式为EXTRN CODE (_?OSTaskSwHook)等;5个外部全局变量:OSTCBCur、OSTCBHighRdy、OSRunning、OSPrioCur、OSPrioHighRdy,申明格式为EXTRN IDATA (OSTCBCur)等。
另外,由于任务切换需要寄存器频繁的进栈和出栈;因此,将进栈、出栈操作定义为宏以减少代码编写量。入栈是将寄存器PSW、ACC、B、DPL 、DPH、R0~R7、SFRPAGE依次压入堆栈,出栈的顺序则刚好相反。
OSStartHighRdy()是由任务启动函数OS_Start()调用来使就绪态任务中优先级最高的任务开始运行。任务级的切换由函数OSCtxSw()完成,而中断级的任务切换用OSIntCtxSw()实现。当中断调用结束后,系统将判断是否有更高优先级的任务处于就绪态,如果有比被中断的任务优先级更高的任务,则系统将进行任务调度,以运行处于就绪态的优先级最高的任务,否则就返回被中断的任务。OSIntCtxSw()的代码与OSCtxSw()非常相似,唯一的区别是OSIntCtxSw()不需再保存寄存器,保存寄存器的工作已由中断服务程序完成。μC/OS-Ⅱ运行需要一个周期性的时钟源,用于时间延迟和确认超时。本文使用定时器T0来产生时钟节拍,定时中断服务程序由OSTickISR()完成[1]。
5应用
本文在C8O51F060的硬件平台上实现了μC/OS-Ⅱ的移植,并将其应用在碳硫分析仪的控制器中。碳硫分析仪主要应用在冶金钢铁行业,用来分析钢铁等金属中的碳、硫含量。针对应用系统所要实现的功能,将整个系统划分为五个并行执行的任务,按其优先级从高到低顺序排列依次是:系统监视任务、通讯显示任务、过程控制任务、采集处理任务和打印结果任务。[4]当系统上电复位后,首先进行C8O51F060及外设初始化;接着进行嵌入式操纵系统μC/OS-Ⅱ初始化;上面准备工作做好后,开始创建多任务,并且分配任务的优先级,建立任务间通信的信号量、消息邮箱;启动多任务调度,系统程序从优先级最高的任务开始执行。主程序流程图如图2所示。
多任务启动后,系统监视任务优先权最高,所以最先进入运行态。它是用来监视系统状态的,用于判断是否有升炉信号到,如果没有则直接调用函数OSTaskSuspend()将自身挂起,如果收到信号则发送消息给过程控制任务后将自身挂起。按优先权级别顺序,与触摸屏的通讯显示任务将由就绪态转为运行态,该任务用于实时刷新系统当前所处的状态。按手动触摸按键反应时间而定,约为120 ms,所以任务每次执行完都会调用函数OSTimeDlyHMSM()将自身延时100ms,暂时退出就绪态。接着过程控制任务进入运行态,它首先判断有没有收到升炉信号,如果有则按照分析过程要求依次执行准备、通氧、对零、吸收和回复动作。该任务会在执行相应动作时发送消息给数据采集处理任务和打印任务,然后自身延时,交出CPU 使用权。数据采集处理任务和打印任务在收到过程控制任务的消息后执行相应动作,在调用OSSemPend()后将自身挂起。过程控制任务在整个动作结束时调用OSTaskResume()将系统监视任务唤醒。由于C8051F060的处理器速度足够快,所有任务可以在满足时序的前提下顺利执行。
结论
本文作者的创新点:在C8O51F060的硬件平台上实现了μC/OS-Ⅱ的移植,并将其应用在智能碳硫分析仪的控制器中。与传统前后编程模式相比,嵌入式实时操作系统的应用大大提高嵌入式系统开发的效率,改变以往嵌入式软件设计只能针对具体的应用从头做起的状况。在实时操作系统上进行开发减少了系统开发的工作量,增强了软件的可移植性,提高了系统的实时性,使嵌入式系统的开发方法更具科学性。
参考文献
[1] 郭雨梅,闫磊著.嵌入式实时操作系统μC/OS-II在C8051F020上的移植[J].沈阳工业大学学报,2005, 27(1):59-62
[2] Jean J.Labrosse著,邵贝贝译.嵌入式实时操作系统μC/OS-Ⅱ[M].北京:北京航空航天大学出版,2003.
[3] 潘琢金,施围君著.C8051FXXX高速SOC单片机原理及应用[M],第1版.北京:北京航空航天大学出版,2002
[4] 杨华,殷承良. μC/OS-Ⅱ在混合动力汽车总控制中的应用[J].微计算机信息,2007,23(5):262-264