嵌入式实时操作系统uCOS-II-邵贝贝
嵌入式实时操作系统uCOS-II-邵贝贝数程序清单LL.1可移植型数据类型Typedef unsigned char BOOLEANTypedef unsigned char INT8U;Typedef signed char TNT8S;Typedef unsigned int INT16JiTypedef signed int INT16STypedef unsigned long INT32U;yredef signed long TNT32S;Typedef floatFP32Typedef CoubleFP64;#define BYTFNTBStdcfinc UBYTEinT8#define WORDINT16S#define WOrdINT16U#Define loNg工NT325cc三 inc ULONGINT32U以INT16U数据类型为例,它代表16位无符号整数数据类型。1C/0s-I和用户的应用代码可以定义这种类型的数据,范围从0到65,535。如果将μC0/S-II移植到32位处理器中,那就意味着INT16U不再不是一个无符号整型数据,而是一个无符号短整型数据。然而将无论pC/0sI用到哪里,都会当作INT6处理。表1.1是以 Borland C/CH编译器为例,为80x8提供的定义语句。为∫和1nC/0S兼容,还定义了BYTE,WORD,LONG以及相应的无符号变量。这使得用户可以不作仟何修改就能将uC/OS的代码移植到υC/0S-I中。之所以这样做是因为笔者觉得这种新的数据类型定义有更多的灵活性,也更加易读易懂。对些人来说,W意味着32位数,而此处却意味着16位数。这些新的数据类型应该能够消除此类含混不请1.03全局变量以下是如何定义全局变量。众所周知,全局变量应该是得到内存分配且可以被其他模块通过C语言中 extern关键宇调用的变量。因此,必须在.C和.H文件中定义。这种重复的定义很容易导致错误。以下讨论的方法只需用在头文件中定义一次。虽然有点不易懂,但用户旦掌握,使用起来却很灵活。表1.2屮的定义出现在定义所有全局变量的.H头文件中。程序清单L1.2定全局宏。#i fdef xXX GLOBALScc三 inc XXX EXT#else# CeLine XX× FXT extern# endifH文件中每个全局变量都加上了ⅹ XX EXT的前缀。ⅹⅹx代表模块的名字。该模块的.C文件中有以下定义:# Cene XX× GLOBA-Sinclude Includes. h'当编译器处理.C文件吋,它强制 XXX EXT在相应.H文件中可以找到)为空,(因为 XXX GLOBALS已绎定义)。所以编译器给每个全局变量分配内存空间,而当编译器处理其他.C文件时,XXX GLOBAL没有定义, XXX EXT被定义为 extern,这样用户就可以调用外部全局变量。为了说明这个概念,可以参见uC/OSII.H,其中包括以卜定义:#ifdef CS GLOBALS#define OS EXT#el sedesine OS EXT externt cdif0sEXT工NT32UoSIdlectrOS EXT INT32UoSIdlectrEunOS EXT INT32U○ SIdlectrMax;同时,uC0sII.H有中以下定义:#define OS GLOBALS#include includes. h'当编译器处理 uCOS II.C时,它使得头文件变成如下所示,因为 OS EXT被设置为空。INT32UOSIdlectriINT32UoSIdlectrruniINT32USIdlectrMax这样编译器就会将这些全局变量分配在内存中。当编译器处理其他.C文件时,头文件变成了如下的样了,因为 OS GLOBAL没有定义,所以0SEⅪ1被定义为 externex-ern tnt32UoSTdlectrexcern Int32UoSIdlectrrunexcern INT32UosIdlectrMax在这种情况下,不产生内存分配,而任何C文件都可以使用这些变量。这样的就只需在.H文件中定义一次就可以了。1.040 S ENTER CRITIC0和OS EXIT CRITICAL O用户会看到,调用0 S ENTER CRITICAL0和0S' CRITICAL两个宏,贯穿本书的所有溴代们。0 S ENTER CRITICAL0关中断;而0 S EXIT CRITICAL0开中断。关中断和开中断是为了保护临界段代码。这些代码很显然与处理器有关。关于宏的定义可以在0SCPU.H屮找到。9.03.02节详绌讨论定义这些宏的两种方法程序情单L1.3进入正确部分的宏。井Ce三 ine OS CRITICAL METHODtifOS CRITICAL METHOD#ce三 ine OS ENTER CR工 TICAL()as:mCLI#define os EXIT CRITICAL( asm STI#endif#ifOS CRITICAL ME THOD#define os ENtER CRTTTCAt.o) asm( PUSHF; CL.#define OS EXIT CRITICAL(t endif用户的应用代码可以使用这两个宏米开中断和关中断。很明显,关中断会影响中断延迟,所以要特别小心。用户还可以用信号量来侏护林阶段代码。1.0基于FC的服务PC.C文件和PC.H文件(在\ SOFTWARE\ BLOCKS\PC\ SOURCE日录下)是笔者在范例中使用到的一些基于PC的服务程序。与1C/0s-I以前的版本(即pC0s)不同,笔者希望集中这些函数以避免在各个例了中都重复定义,也更谷易适应不同的编译器。PC.C包括子符显示,时间度量和其他各种服务。所有的函数都以PC为前缀1.05.01字符显示为了性能更好,显示数直接向显示内存区中写数据。在VGA显示器中,显示内存从绝对地址0x000B8000开始(或用段、偏移量表示则为B800:000)。在单色显示器中,用户可以把# define constant DISP BASE从0xB800改为OxB000PC.C中的显示函数用x和y坐标来直接向显示內存中写 ASCII字符。PC的显示可以达到5行80列共2,00个字符。每个字符需要两个字节来显示。第个字节是用户想要显示的字符,第二个字节用来确定前景色和背景色。前景色用低四位来表小,背景色用第4位到6位来表示。最高位表示这个字符是否闪烁,(1)表示闪烁,(0)表示不闪烁。用PC.H中# defies constants定义前景和背景色,PC.C包括以下四个函数PC DispCirScroClear the screenPC DispClrLine( Clear a single row (or linePC DispChar oDisplay a single ascii character anywhere on the screenPC DispStr oDisplay an ascii string anywhere on the screen1.05.02花费时间的测量时间测量函数主要用于测试一个函数的运行花了多少时间。测量时间是用PC的82C54定时器2。被测的程序代码是放在函数 PC ElapsedStart o和 PC ElapsedStopo之间来测量的。在用这两个函数之前,应该调用 PC Elapsedlnit(来初始化,它主要是计算运行这两个函数本身所附加的的时间。这样, PC ElapsedStop(函数中返回的数佰就是准确的测量结果。注意,这两个函数都不具各可重入性,所以,必须小心,不要有多个任务同时调用这两个函数。表1.4说明了如何测量 PC Displaychar(的执行时问。注意,时间是以uS为单位的程序清单L14测量代码执行时间inClu timePC FlapsedTnit.();PC Elapsedstart()PC Di spChar(40, 24,'A', DTSP FGND NETtE)timc- PC E_apscastop(1.05.03其他函数icC/OSII的应用程序和其他D0s应用程序是一样的,换句话说,用户可以像在DoS下编译其他单线程的程序一样编译和链接用户程序。所生成的.EXE程序可以在D0S下装载和运行,当然应用程序应该从main()函数开始。因为pC/0s-I是多任务,而且为每个任务开辟个堆栈,所以单线程的ⅣOs环境应该保存,在退出μC(OsII程序时返回到DS。调用PC DOSSave Return(可以保存当前DOS环境,而调用 PC DOSReturn可以返回到DOS。PC.C中使用 ANSI C的 sct jmp(, long jmp(函数来分别保存和恢复D0sS环境。 Borland o/C+编译库提供这些函数,多数其它的编译程序也应有这类函数应该注意到尢论是应用程序的错误还是只调用exit(0)而没有调用 PC DOSReturn O函数都会使DS环境被破坏,从而导致DS或WNWS95下的D0S窗口崩溃调用 PC GetDatelime(函数可得到PC中的日期和时间,并且以SACl字符串形式返回格式是M-DD-YHH:MM:SS,用户需要19个字符来存放这些数据。该函数使用∫ Borland c/C++的 gettime o和 getdate o函数,其它DS环境下的C编译应该也有类似函数。PC GetKey函数检查是否有按键被按下。如果有按键被按下,函数返回其值。这个函数使用了 Borland c/C+的 khit()和 getch0函数,其它D0S环境下的C编译应该也有类似函数。哟数 PC SetTickRate(允许用户为nC0S-I定义频率,以改变钟节拍的速率。在DOS下,每秒产生18.20618次时钟节拍,或每隔54.925ms一次。这是因为82C54定时器芯片没有初始化,而使用默认值65,535的结果。如果初始化为58,659,那么时钟节拍的速率就会精确地为20.0。笔者决定将时钟节拍设得史快一些,用的是200Hz(实际是上是199.9966Hz)。注意0s_CPUA.ASM中的0 STickIsr(函数将会每11个时钟节拍调用一次Ds中的吋钟节拍处理,这是为了保证在DOS下时钟的准确性。如果用户希望将吋钟节拍的速度设置为20HZ,就必须这样做。在返回DOS以前,婁调用 PC SetTickRate(,并设置18为目标频率, PC SetTickRate就会知道用户要设置为18.2Hz,并且会正确设置82C54PC.C中最后两个函数是得到和设置中断向量,笔者是用 Borland c/c+中的库涵数来完成的,但是 PC VectGet(和 PC VectSet(很容易改写,以适用于其它编译器。1.06应用pC的s-1的落例本章中的例子都用 Borland c/C+-编译器编译通过,是在 Windows95的D0S窗口下编译的。可执行代码可以在每个范例的0BJ子目录下找到。实际上这些代码是在 Borland ide( Integrated Development Environment)下编译的,编详时的选项如表1.1所小表T1.1ⅠDE中编译选项。Code generationModelLargeOptionsTreat enums as intsAssume ss equals DsDefault for memory modelcone gFloating pointEmulationInstruction set80186OptionsGenerate underbarsDebug info in OB IsFast floating pointOptimizationOptimizationsGlobal register allocationInvariant code motionInduction variableLoop optimizationSuppress redundant loadsCopy propagationDead code climinationJump optimizationIn-line intrinsic functionsRegister variablesAutomaticCommon subexpressionsOptimize globallyOptimize forSpeed笔者的 Borland c/C+-编译器安装在C:CPP目录下,如果用户的编译器是在不同的目录下,可以在 Options/ Directories的提示下改变IDE的路径C/OS-II是一个可裁剪的操作系统,这意味着用户可以去掉不需要的服务。代码的削减可以通过设置0sCFG.H中的# defines0s???FN为0米实现。用户不需要的服务代码就不生成。本章的范例就用这种功能,所以每个例子都定义了个同的0S???EN。1.07例1第一个范例可以在\ SOFTWARE \uCOS IIEX1x86L日录下找到,它有13个任务(包括1C/(0s-TI的空闲任务)。COS-I增加了两个内部任务:空闲任务和一个计算CPU利用率的任务。例1建立了11个其它任务。1 ashTart o任务是在函数 maino中建立的;它的功能是建立其它任务并且在屏幕上显示如下统计信息每秒钟任务切换次数●CPU利用百分率;寄存器切换次数;目前日期和时间;●C/OS-I的版本号TaskStart O还检查是按下ESC键,以决定是否返回到DS其余10个任务基于相同的代码—Task();每个任务在屏幕上随机的位置显示一个0到9的数字。1.07. 01 maino例1基本上和最初uCOS中的第个例了做样的事,但是笔者整理了其中的代码,并∏在屏幕上加了彩色显示。同时笔者使用原来的数据类型( UBYTE,J○RD等)来说明1C/OS-II向下兼容。nain(程序从清整个屏幕廾始,为的是保让屏幕上不留有以前的D0S下的显示L1.5(1)1。注意,笔者定义了白色的字符和黑色的背景色。既然要请屏幕,所以可以只定义背景色而不定义前景色,但是这样在退回DS之后,用户就什么也看不见了。这也是为什么总要定义一个可见的前景色。1C/OS-II要用户在使用任何服务之前先调用 OSInit()LL1.5(2)]。它会建立两个仟务空闲任务和统计任务,前者在没有其它仁务处于就绪态时运行;后者计算CPU的利用率程序单Ll5main(oid main (void)PC DispClrScr(DISP FGND WHITE DISP BGND BLACK)iOSInit(PC DOSSavereturn()iPc vectsct (ucos, cSctxsw(4)RancomSem OSSemCreate((5)osTaskCreae(Taskstart(6)(void *)o(void*)∝ Taskstartstk「 TASK STK SIZE-11,当前D0S环境是通过调用 PC DOSSaveReturn0[L.5(3)]来侏存的。这使得用户可以返冋到没有运行μC/0S-I以前的DOS环境。跟随清单1.6中的程序可以看到PCD0 SSaveReturn o做了很多事情。 PC DOSSavereturn(首先设置 PC ExitFlag为FALSE L1.6(1),说明用户不是要返回DOS,然后初始化0 STickdosctr为1L1.6(2),因为这个变量将在0 STickIER o中递减,而0将使得这个变量在0 STickIEr(中减1后变为25然后, PC DOSSaveReturn(将D0S的时钟节拍处理( tick handler)存入个自由向量衣入口中1.6(3)-(4)1,以便为COS-Ⅰ的时钟节拍处理所调用。接着 PC DOSSaveReturn(调用jmp()[L1.6(5)],它将处理器状态(即所有寄存器的值)存入被称为 PC JumpBuf的结构之中。俣存处理器的全部寄存器使得程序返回到PCD0 SSaveReturn并且在调用 set ]mp)之后立即执行。因为 PC ExitFlag被初始化为 FALSE LL1.6(1)]。 PC DOSSavcReturn(跳过i状态语句L1.6(6)-(⑨9)]回到main()函数。如果用户想要返回到DOs,可以调用PC DOSReturn((程序清单L1.7),它设置 PC ExitFlag为TRUE,并且执行 long jmp()语句[1.7(2)],这时处理器将跳回 PC DOSSaveReturn([在调用 set]mp()之后][1.6(5)]此时 PC ExitFlag为TRUE,故ⅱ语句以后的代码将得以执行。 PC DOSSaveReturnO将时钟节拍改为18.2Hz[Ll.6(6)],恢复PC时钟节拍中断服务[1.6(⑦)],清屏幕[L1.6(8)],通过exit(0)返回DOs[L1.6(9)]程序情单L,6保存DOS环境。void Pc DoSSaveReturn (void)PC ExitFlag- FALSE;OSTickDOSC-(2)C TicKISRPC VectGe-(VECT TICK)i(3OS ENTER CRITICAL(C Vcctset(vECT DCS CHAIN, PC TickIS3)iOS EXIT CRITICAL ()iSetjmp(PC JumpBuf)(5)if (PC Exi-Flag = TRUE)[OS ENTER CRITICAL (PC Setr⊥ crAte(18);(6)PC Vcctset(vect TICK, PC TickISR)(7)OS EXIT CRITICAL (PC Di spClrScr(DISP FGND WHITE DISP BCND BLACKexit(o)i程序情单L17设置返回DOS。void PC DoSReturn (voic)PC ExitFlag- TRUEi(1)longjmp(PC JumpBufr 1(2)}现在回到main()这个函数,在程序清单L1.5中,main()调用 PC Vectset来设置μ COS-II中的CP寄存器切换。仁务级的CPU寄存器切换由80x86IN指令米分配向量地址。笔者使用向量0x80(即128),因为它耒被DOS和BI0S使用。这里用了一个信号量来保护 Borland o/C++库军中的产生随机数的数L1.5(5)],之所以使用信号量保护·下,是因为笔者不知道这个函数是否具备可重入性,笔者假设其不具备初始化将信号量设置为1,意思是在某一时刻只有一个任务可以调用随机数产生函数在开始多任务之前,笔者建立了一个叫做 TaskStart(的任务[1.5(6)],在启动多任务OSStart o之前用户至少要先建立个任务,这点非常重要[L1.5(7)]。不这样做用户的应用程序将会崩溃。实际上,如果用户要计算CPU的利用率时,也需要先建立一个任务。μCOS-II的统计任务要求在整个一秒钟内没有任何其它任务运行。如果用户在启动多任务之前要建立其它任务,必须保证用户的任务代码监控全局变量 OSStatRdy和延时程序[即调用OSTimeDly()1的执行,直到这个变量变成TRUE。这表明C/Os-II的CPU利用率统计函数已经采集到了数据。
下载地址
用户评论