嵌入式实时操作系统uCOS-II(高清).pdf
第一章:范例第一章:范例在这一章里将提供三个范例来说明如何使用C/OS-II。笔者之所以在本书一开始就写这一章是为了让读者尽快开始使用C/OS-II。在开始讲述这些例子之前,笔者想先说明一些在这本书里的约定。这些例子曾经用 Borland C/C+ 编译器(V3.1)编译过,用选择项产生Intel/AMD80186处理器(大模式下编译)的代码。这些代码实际上是在 Intel Pentium II PC (300MHz)上运行和测试过,Intel Pentium II PC 可以看成是特别快的 80186。笔者选择 PC 做为目标系统是由于以下几个原因:首先也是最为重要的,以 PC 做为目标系统比起以其他嵌入式环境,如评估板,仿真器等,更容易进行代码的测试,不用不断地烧写 EPROM,不断地向 EPROM 仿真器中下载程序等等。用户只需要简单地编译、链接和执行。其次,使用 Borland C/C+产生的 80186 的目标代码(实模式,在大模式下编译)与所有 Intel、AMD、Cyrix 公司的 80 x86CPU 兼容。1.00 安装1.00 安装C/OS-IIC/OS-II本书附带一张软盘包括了所有我们讨论的源代码。是假定读者在80 x86,Pentium,或者Pentium-II 处理器上运行 DOS 或 Windows95。至少需要 5Mb 硬盘空间来安装 uC/OS-II。请按照以下步骤安装:1.进入到 DOS(或在 Windows 95 下打开 DOS 窗口)并且指定 C:为默认驱动器。2.将磁盘插入到 A:驱动器。3.键入 A:INSTALL 【drive】注意drive是读者想要将 C/OSII 安装的目标磁盘的盘符。INSTALL.BAT 是一个 DOS 的批处理文件,位于磁盘的根目录下。它会自动在读者指定的目标驱动器中建立SOFTWARE 目录并且将 uCOS-II.EXE 文件从 A: 驱动器复制到SOFTWARE 并且运行。 C/OSII 将在SOFTWARE 目录下添加所有的目录和文件。完成之后 INSTALL.BAT将删除 uCOS-II.EXE 并且将目录改为SOFTWAREuCOS-IIEX1_x86L,第一个例子就存放在这里。在安装之前请一定阅读一下 READ.ME 文件。当 INSTALL.BAT 已经完成时,用户的目标目录下应该有一下子目录:zSOFTWARESOFTWARE这是根目录,是所有软件相关的文件都放在这个目录下。zSOFTWAREBLOCKSSOFTWAREBLOCKS子程序模块目录。笔者将例子中 C/OS-II 用到的与 PC 相关的函数模块编译以后放在这个目录下。zSOFTWAREHPLISTCSOFTWAREHPLISTC这个目录中存放的是与范例 HPLIST 相关的文件 (请看附录 D, HPLISTC 和 TO) 。 HPLIST.C存放在SOFTWAREHPLISTCSOURCE 目录下。DOS下的可执行文件(HPLIST.EXE)存放在SOFTWARETOEXE 中。zSOFTWARETOSOFTWARETO这个目录中存放的是和范例 TO 相关的文件(请看附录 D,HPLISTC 和 TO) 。源文件 TO.C存放在SOFTWARETOSOURCE 中,DOS 下的可执行文件(TO.EXE)存放在SOFTWARETOEXE中。注意TO 需要一个 TO.TBL 文件,它必须放在根目录下。用户可以在SOFTWARETOEXE目录下找到 TO.TBL 文件。如果要运行 TO.EXE,必须将 TO.TBL 复制到根目录下。zSOFTWAREuCOS-IISOFTWAREuCOS-II与 C/OS-II 相关的文件都放在这个目录下。zSOFTWAREuCOS-IIEX1_x86LSOFTWAREuCOS-IIEX1_x86L这个目录里包括例 1 的源代码(参见 1.07, 例 1),可以在 DOS(或 Windows 95 下的 DOS窗口)下运行。zSOFTWAREuCOS-IIEX2_x86LSOFTWAREuCOS-IIEX2_x86L这个目录里包括例 2 的源代码(参见 1.08, 例 2),可以在 DOS(或 Windows 95 下的 DOS窗口)下运行。zSOFTWAREuCOS-IIEX3_x86LSOFTWAREuCOS-IIEX3_x86L这个目录里包括例 3 的源代码(参见 1.09, 例 3),可以在 DOS(或 Windows 95 下的 DOS窗口)下运行。zSOFTWAREuCOS-IIIx86LSOFTWAREuCOS-IIIx86L这个目录下包括依赖于处理器类型的代码。此时是为在 80 x86 处理器上运行 uC/OS-II而必须的一些代码,实模式,在大模式下编译。zSOFTWAREuCOS-IISOURCESOFTWAREuCOS-IISOURCE这个目录里包括与处理器类型无关的源代码。这些代码完全可移植到其它架构的处理器上。1.01 INCLUDES.H1.01 INCLUDES.HINCLUDE.H 可以使用户不必在工程项目中每个*.C 文件中都考虑需要什么样的头文件。 换句话说,INCLUDE.H 是主头文件。这样做唯一的缺点是 INCLUDES.H 中许多头文件在一些*.C文件的编译中是不需要的。这意味着逐个编译这些文件要花费额外的时间。这虽有些不便,但代码的可移植性却增加了。本书中所有的例子使用一个共同的头文件 INCLUDES.H,3 个副本 分 别 存 放 在 SOFTWAREuCOS-IIEX1_x86L , SOFTWAREuCOS-IIEX2_x86L , 以 及SOFTWAREuCOS-IIEX3_x86L 中。 当然可以重新编辑 INCLUDES.H 以添加用户自己的头文件。用户将注意到本书中所有的 *.C 文件都包括了以下定义:#include includes.h1.021.02不依赖于编译的数据类型不依赖于编译的数据类型因为不同的微处理器有不同的字长, C/OS-II 的移植文件包括很多类型定义以确保可移植性(参见SOFTWAREuCOS-IIIx86LOS_CPU.H,它是针对 80 x86 的实模式,在大模式下编译) 。 COS-II 不使用 C 语言中的 short,int,long 等数据类型的定义,因为它们与处理器类型有关,隐含着不可移植性。笔者代之以移植性强的整数数据类型,这样,既直观又可移植,如表 L1.1 所示。为了方便起见,还定义了浮点数数据类型,虽然 C/OS-II 中没有使用浮点数。程序清单 L程序清单 L1.11.1 可移植型数据类型。可移植型数据类型。Typedef unsigned char BOOLEAN;Typedef unsigned char INT8U;Typedef signed char INT8S;Typedef unsigned int INT16U;Typedef signed int INT16S;Typedef unsigned long INT32U;Typedef signed long INT32S;Typedef float FP32;Typedef double FP64;#define BYTE INT8S#define UBYTE INT8U#define WORD INT16S#define UWORD INT16U#define LONG INT32S#define ULONG INT32U以 INT16U 数据类型为例,它代表 16 位无符号整数数据类型。 C/OS-II 和用户的应用代码可以定义这种类型的数据,范围从 0 到 65,535。如果将 CO/S-II 移植到 32 位处理器中,那就意味着 INT16U 不再不是一个无符号整型数据, 而是一个无符号短整型数据。 然而将无论C/OS-II 用到哪里, 都会当作 INT16U 处理。 表 1.1 是以 Borland C/C+编译器为例, 为 80 x86提供的定义语句。为了和 C/OS 兼容,还定义了BYTE,WORD,LONG 以及相应的无符号变量。这使得用户可以不作任何修改就能将 C/OS 的代码移植到 C/OS-II 中。之所以这样做是因为笔者觉得这种新的数据类型定义有更多的灵活性,也更加易读易懂。对一些人来说,WORD意味着 32 位数,而此处却意味着 16 位数。这些新的数据类型应该能够消除此类含混不请1.031.03 全局变量全局变量以下是如何定义全局变量。众所周知,全局变量应该是得到内存分配且可以被其他模块通过 C 语言中 extern 关键字调用的变量。因此,必须在 .C 和 .H 文件中定义。这种重复的定义很容易导致错误。 以下讨论的方法只需用在头文件中定义一次。 虽然有点不易懂,但用户一旦掌握,使用起来却很灵活。表 1.2 中的定义出现在定义所有全局变量的.H 头文件中。程序清单 L程序清单 L 1.2 1.2定义全局宏。定义全局宏。#ifdef xxx_GLOBALS#define xxx_EXT#else#define xxx_EXT extern#endif.H 文件中每个全局变量都加上了 xxx_EXT 的前缀。 xxx 代表模块的名字。 该模块的.C 文件中有以下定义:#define xxx_GLOBALS#include includes.h当编译器处理.C文件时, 它强制xxx_EXT (在相应.H文件中可以找到) 为空,(因为xxx_GLOBALS已经定义) 。所以编译器给每个全局变量分配内存空间,而当编译器处理其他.C文件时,xxx_GLOBAL 没有定义,xxx_EXT被定义为 extern,这样用户就可以调用外部全局变量。为了说明这个概念,可以参见 uC/OS_II.H,其中包括以下定义:#ifdef OS_GLOBALS#define OS_EXT#else#define OS_EXT extern#endifOS_EXT INT32U OSIdleCtr;OS_EXT INT32U OSIdleCtrRun;OS_EXT INT32U OSIdleCtrMax;同时,uCOS_II.H 有中以下定义:#define OS_GLOBALS#include “includes.h”当编译器处理 uCOS_II.C 时,它使得头文件变成如下所示,因为 OS_EXT 被设置为空。INT32U OSIdleCtr;INT32U OSIdleCtrRun;INT32U OSIdleCtrMax;这样编译器就会将这些全局变量分配在内存中。当编译器处理其他.C 文件时,头文件变成了如下的样子,因为 OS_GLOBAL 没有定义,所以 OS_EXT 被定义为 extern。extern INT32U OSIdleCtr;extern INT32U OSIdleCtrRun;extern INT32U OSIdleCtrMax;在这种情况下,不产生内存分配,而任何 .C文件都可以使用这些变量。这样的就只需在 .H文件中定义一次就可以了。1.041.04OS_ENTER_CRITICAL() 和OS_ENTER_CRITICAL() 和OS_EXIT_CRITICAL()OS_EXIT_CRITICAL()用户会看到,调用 OS_ENTER_CRITICAL()和 OS_EXIT_CRITICAL()两个宏,贯穿本书的所有源代码。OS_ENTER_CRITICAL() 关中断;而OS_EXIT_CRITICAL()开中断。关中断和开中断是为了保护临界段代码。 这些代码很显然与处理器有关。 关于宏的定义可以在 OS_CPU.H 中找到。9.03.02 节详细讨论定义这些宏的两种方法。程序清单 L程序清单 L 1.3 1.3进入正确部分的宏。进入正确部分的宏。#define OS_CRITICAL_METHOD 2#if OS_CRITICAL_METHOD = 1#define OS_ENTER_CRITICAL() asm CLI#define OS_EXIT_CRITICAL() asm STI#endif#if OS_CRITICAL_METHOD = 2#define OS_ENTER_CRITICAL() asm PUSHF; CLI#define OS_EXIT_CRITICAL() asm POPF#endif用户的应用代码可以使用这两个宏来开中断和关中断。 很明显, 关中断会影响中断延迟,所以要特别小心。用户还可以用信号量来保护林阶段代码。1.051.05基于基于PCPC的服务的服务PC.C 文件和 PC.H 文件(在SOFTWAREBLOCKSPCSOURCE 目录下)是笔者在范例中使用到的一些基于 PC 的服务程序。与C/OS-II 以前的版本(即C/OS)不同,笔者希望集中这些函数以避免在各个例子中都重复定义,也更容易适应不同的编译器。PC.C 包括字符显示,时间度量和其他各种服务。所有的函数都以 PC_为前缀。1.05.011.05.01字符显示字符显示为了性能更好,显示函数直接向显示内存区中写数据。在VGA显示器中,显示内存从绝对地址 0 x000B8000 开始(或用段、偏移量表示则为B800:0000) 。在单色显示器中,用户可以把#define constant DISP_BASE从 0 xB800 改为 0 xB000。PC.C 中的显示函数用 x 和 y 坐标来直接向显示内存中写 ASCII 字符。 PC 的显示可以达到25 行 80 列一共 2,000 个字符。每个字符需要两个字节来显示。第一个字节是用户想要显示的字符,第二个字节用来确定前景色和背景色。前景色用低四位来表示,背景色用第 4 位到6 位来表示。最高位表示这个字符是否闪烁, (1)表示闪烁, (0)表示不闪烁。 用 PC.H 中#defien constants 定义前景和背景色,PC.C 包括以下四个函数:PC_DispClrScr()Clear the screenPC_DispClrLine()Clear a single row (or line)PC_DispChar()Display a single ASCII character anywhere on the screenPC_DispStr()Display an ASCII string anywhere on the screen1.05.021.05.02 花费时间的测量花费时间的测量时间测量函数主要用于测试一个函数的运行花了多少时间。测量时间是用 PC 的 82C54定时器 2。 被测的程序代码是放在函数 PC_ElapsedStart()和 PC_ElapsedStop()之间来测量的。在用这两个函数之前,应该调用PC_ElapsedInit()来初始化,它主要是计算运行这两个函数本身所附加的的时间。这样,PC_ElapsedStop()函数中返回的数值就是准确的测量结果了。注意,这两个函数都不具备可重入性,所以,必须小心,不要有多个任务同时调用这两个函数。表 1.4 说明了如何测量 PC_DisplayChar()的执行时间。注意,时间是以 uS 为单位的。程序清单 L程序清单 L 1.4 1.4测量代码执行时间。测量代码执行时间。INT16U time;PC_ElapsedInit();.PC_ElapsedStart();PC_DispChar(40, 24, A, DISP_FGND_WHITE);time = PC_ElapsedStop();1.05.031.05.03 其他函数其他函数C/OS-II 的应用程序和其他 DOS 应用程序是一样的,换句话说,用户可以像在 DOS 下编译其他单线程的程序一样编译和链接用户程序。 所生成的.EXE程序可以在DOS下装载和运行,当然应用程序应该从 main()函数开始。因为 C/OS-II 是多任务,而且为每个任务开辟一个堆栈,所以单线程的DOS 环境应该保存,在退出C/OS-II 程序时返回到DOS。调用PC_DOSSaveReturn()可以保存当前 DOS 环境, 而调用 PC_DOSReturn()可以返回到 DOS。 PC.C中使用 ANSI C 的 setjmp(),longjmp()函数来分别保存和恢复 DOS 环境。Borland C/C+编译库提供这些函数,多数其它的编译程序也应有这类函数。应该注意到无论是应用程序的错误还是只调用 exit(0)而没有调用 PC_DOSReturn()函数都会使 DOS 环境被破坏,从而导致 DOS 或 WINDOWS95 下的 DOS 窗口崩溃。调用 PC_GetDateTime()函数可得到 PC 中的日期和时间, 并且以 SACII 字符串形式返回。格式是MM-DD-YY HH:MM:SS, 用户需要19个字符来存放这些数据。 该函数使用了Borland C/C+的 gettime()和 getdate()函数,其它 DOS 环境下的 C 编译应该也有类似函数。PC_GetKey() 函数检查是否有按键被按下。如果有按键被按下,函数返回其值。这个函数使用了 Borland C/C+的 kbhit()和 getch()函数,其它 DOS 环境下的 C 编译应该也有类似函数。函数 PC_SetTickRate()允许用户为C /OS-II 定义频率,以改变钟节拍的速率。在DOS下,每秒产生 18.20648 次时钟节拍,或每隔 54.925ms 一次。这是因为 82C54 定时器芯片没有初始化,而使用默认值 65,535 的结果。如果初始化为 58,659,那么时钟节拍的速率就会精确地为20.000Hz。笔者决定将时钟节拍设得更快一些,用的是200Hz(实际是上是199.9966Hz)。注意 OS_CPU_A.ASM 中的 OSTickISR()函数将会每 11 个时钟节拍调用一次 DOS中的时钟节拍处理,这是为了保证在 DOS 下时钟的准确性。如果用户希望将时钟节拍的速度设置为 20HZ,就必须这样做。在返回 DOS 以前,要调用 PC_SetTickRate(),并设置 18 为目标频率,PC_SetTickRate()就会知道用户要设置为 18.2Hz,并且会正确设置 82C54。PC.C 中最后两个函数是得到和设置中断向量,笔者是用 Borland C/C+中的库函数来完成的,但是 PC_VectGet()和 PC_VectSet()很容易改写,以适用于其它编译器。1.01.06 应用6 应用C/OS-II 的范例C/OS-II 的范例本章中的例子都用 Borland C/C+编译器编译通过,是在 Windows95 的 DOS 窗口下编译的。可执行代码可以在每个范例的 OBJ 子目录下找到。实际上这些代码是在 Borland IDE(Integrated Development Environment)下编译的,编译时的选项如表 1.1 所示:表表 T1.1 T1.1IDEIDE中编译选项。中编译选项。Code generationCode generationModel :LargeOptions :Treatenums asintsAssumeSSEqualsDSAdvanced code generationAdvanced code generationFloating pointInstruction set: Default for memory model: Emulation: 80186Options :GenerateunderbarsOptimizationsOptimizationsOptimizationsOptimizationsDebug info in OBJsFast floating pointGlobal register allocationInvariant code motionRegister variablesRegister variablesCommon subexpressionsCommon subexpressionsOptimize forOptimize forInduction variablesLoop optimizationSuppress redundant loadsCopy propagationDead code eliminationJump optimizationIn-line intrinsic functionsAutomaticOptimize globallySpeed笔者的 Borland C/C+编译器安装在 C:CPP 目录下,如果用户的编译器是在不同的目录下,可以在 Options/Directories 的提示下改变 IDE 的路径。C/OS-II 是一个可裁剪的操作系统,这意味着用户可以去掉不需要的服务。代码的削减可以通过设置 OS_CFG.H 中的#defines OS_?_EN 为 0 来实现。用户不需要的服务代码就不生成。本章的范例就用这种功能,所以每个例子都定义了不同的 OS_?_EN。1.071.07 例例 1 1第一个范例可以在SOFTWAREuCOS_IIEX1_x86L目录下找到,它有13 个任务(包括C/OS-II 的空闲任务)。 C/OS-II 增加了两个内部任务:空闲任务和一个计算 CPU 利用率的任务。例1 建立了 11 个其它任务。TaskStart()任务是在函数main()中建立的;它的功能是建立其它任务并且在屏幕上显示如下统计信息:z每秒钟任务切换次数;zCPU 利用百分率;z寄存器切换次数;z目前日期和时间;zC/OS-II 的版本号; TaskStart()还检查是否按下 ESC 键,以决定是否返回到 DOS。其余 10 个任务基于相同的代码Task();每个任务在屏幕上随机的位置显示一个 0 到9 的数字。1.07.01 main()1.07.01 main()例 1 基本上和最初C/OS中的第一个例子做一样的事,但是笔者整理了其中的代码,并且在屏幕上加了彩色显示。同时笔者使用原来的数据类型(UBYTE,UWORD等)来说明C/OS-II向下兼容。main()程序从清整 个屏 幕开始 ,为 的是保 证屏 幕上不 留有 以前的DOS 下的显示L1.5(1)。注意,笔者定义了白色的字符和黑色的背景色。既然要请屏幕,所以可以只定义背景色而不定义前景色,但是这样在退回 DOS 之后,用户就什么也看不见了。这也是为什么总要定义一个可见的前景色。C/OS-II要用户在使用任何服务之前先调用OSInit()L1.5(2)。它会建立两个任务:空闲任务和统计任务,前者在没有其它任务处于就绪态时运行;后者计算CPU的利用率。程序清单 L程序清单 L 1.5 1.5main()main() . .void main (void) PC_DispClrScr(DISP_FGND_WHITE + DISP_BGND_BLACK); OSInit(); PC_DOSSaveReturn(); PC_VectSet(uCOS, OSCtxSw); RandomSem = OSSemCreate(1); OSTaskCreate(TaskStart, (void *)0, (void *)&TaskStartStkTASK_STK_SIZE-1, 0); OSStart(); (7) (1) (2) (3) (4) (5) (6)当前DOS环境是通过调用PC_DOSSaveReturn()L1.5(3)来保存的。这使得用户可以返回到没有运行 C/OS-II以前的DOS环境。跟随清单L1.6 中的程序可以看到PC_DOSSaveReturn()做了很多事情。PC_DOSSaveReturn()首先设置PC_ExitFlag为FALSEL1.6(1),说明用户不是要返回DOS,然后初始化OSTickDOSCtr为 1L1.6(2),因为这个变量将在OSTickISR()中递减,而0 将使得这个变量在OSTickISR()中减 1 后变为 255。然后,PC_DOSSaveReturn()将DOS 的时钟节拍处理(tick handler)存入一个自由向量表入口中L1.6(3)-(4),以便为C/OS-II的时钟节拍处理所调用。接着PC_DOSSaveReturn()调用jmp()L1.6(5),它将处理器状态(即所有寄存器的值)存入被称为PC_JumpBuf的结构之中。保存处理器的全部寄存器使得程序返回到PC_DOSSaveReturn()并且在调用setjmp () 之后立即执行。 因为PC_ExitFlag被初始化为FALSEL1.6(1)。PC_DOSSaveReturn()跳过if状态语句 L1.6(6) (9) 回到main()函数。如果用户想要返回到DOS,可以调用 PC_DOSReturn()(程序清单 L 1.7),它设置PC_ExitFlag为TRUE,并且执行longjmp()语句L1.7(2),这时处理器将跳回PC_DOSSaveReturn()在调用setjmp()之后 L1.6(5),此时PC_ExitFlag为TRUE,故if语句以后的代码将得以执行。 PC_DOSSaveReturn()将时钟节拍改为 18.2HzL1.6(6), 恢复PC 时钟节拍中断服务L1.6(7),清屏幕L1.6(8),通过exit(0)返回DOS L1.6(9)。程序清单 L程序清单 L 1.6 1.6保存保存DOSDOS环境。环境。. .void PC_DOSSaveReturn (void) PC_ExitFlag = FALSE; OSTickDOSCtr = 8; (1) (2) PC_TickISR = PC_VectGet(VECT_TICK); (3) OS_ENTER_CRITICAL(); PC_VectSet(VECT_DOS_CHAIN, PC_TickISR); OS_EXIT_CRITICAL(); (4) Setjmp(PC_JumpBuf); if (PC_ExitFlag = TRUE) OS_ENTER_CRITICAL(); PC_SetTickRate(18); (6) (7) (5) PC_VectSet(VECT_TICK, PC_TickISR); OS_EXIT_CRITICAL(); PC_DispClrScr(DISP_FGND_WHITE + DISP_BGND_BLACK); exit(0); (8) (9)程序清单 L程序清单 L 1.7 1.7设置返回设置返回DOSDOS。void PC_DOSReturn (void) PC_ExitFlag = TRUE; longjmp(PC_JumpBuf, 1); (1) (2)现在回到 main()这个函数,在程序清单 L 1.5 中,main()调用 PC_VectSet()来设置 COS-II 中的 CPU 寄存器切换。任务级的 CPU 寄存器切换由 80 x86 INT指令来分配向量地址。笔者使用向量 0 x80(即 128) ,因为它未被 DOS 和 BIOS 使用。这里用了一个信号量来保护 Borland C/C+库中的产生随机数的函数L1.5(5),之所以使用信号量保护一下,是因为笔者不知道这个函数是否具备可重入性,笔者假设其不具备,初始化将信号量设置为 1,意思是在某一时刻只有一个任务可以调用随机数产生函数。在开始多任务之前,笔者建立了一个叫做TaskStart()的任务L1.5(6),在启动多任务OSStart()之前用户至少要先建立一个任务,这一点非常重要L1.5(7)。 不这样做用户的应用程序将会崩溃。实际上,如果用户要计算CPU的利用率时,也需要先 建立一个任务。 COS-II的统计任务要求在整个一秒钟内没有任何其它任务运行。如果用户在启动多任务之前要建立其它任务,必须保证用户的任务代码监控全局变量OSStatRdy和延时程序 即调用OSTimeDly()的执行,直到这个变量变成TRUE。这表明 C/OS-II的CPU利用率统计函数已经采集到了数据。1.07.02 TaskStart()1.07.02 TaskStart()例 1 中的主要工作由 TaskStart()来完成。TaskStart()函数的示意代码如程序清单 L1.8 所示。TaskStart()首先在屏幕顶端显示一个标识,说明这是例 1 L1.8(1)。然后关中断,以改变中断向量,让其指向 C/OS-II 的时钟节拍处理,而后,改变时钟节拍率,从DOS的 18.2Hz 变为 200Hz L1.8(3)。在处理器改变中断向量时以及系统没有完全初始化前,当然不希望有中断打入!注意 main()这个函数(见程序清单 L 1.5)在系统初始化的时候并没有将中断向量设置成 C/OS-II 的时钟节拍处理程序,做嵌入式应用时,用户必须在第一个任务中打开时钟节拍中断。程序清单 L程序清单 L 1.8 1.8建立其它任务的任务。建立其它任务的任务。void TaskStart (void *data) Prevent compiler warning by assigning data to itself; Display banner identifying this as EXAMPLE #1; OS_ENTER_CRITICAL(); PC_VectSet(0 x08, OSTickISR); PC_SetTickRate(200); OS_EXIT_CRITICAL(); (2) (3) (1) Initialize the statistic task by calling OSStatInit(); (4) Create 10 identical tasks; (5) for (;) Display the number of tasks created; Display the % of CPU used; Display the number of task switches in 1 second; Display uC/OS-IIs version number If (key was pressed) if (key pressed was the ESCAPE key) PC_DOSReturn(); Delay for 1 Second; 在建立其他任务之前, 必须调用OSStatInit()L1.8(4)来确定用户的PC有多快, 如程序清单L1.9 所示。在一开始,OSStatInit()就将自身延时了两个时钟节拍,这样它就可以与时钟节拍中断同步L1.9(1)。因此,OSStatInit()必须在时钟节拍启动之后调用;否则,用户的应用程序就会崩溃。当 C/OS-II调用OSStatInit()时,一个32 位的计数器OSIdleCtr被清为 0 L1.9(2),并产生另一个延时,这个延时使OSStatInit()挂起。此时,uCOS-II没有别的任务可以执行,它只能执行空闲任务(C/OS-II的内部任务) 。空闲任务是一个无线的循环,它不断的递增OSIdleCtrL1.9(3)。1秒以后,uCOS-II重新开始OSStatInit(),并且将OSIdleCtr保存在OSIdleMax中L1.9(4)。所以OSIdleMax是OSIdleCtr所能达到的最大值。而当用户再增加其他应用代码时,空闲任务就不会占用那样多的CPU时间。OSIdleCtr不可能达到那样多的记数, (如果拥护程序每秒复位一次OSIdleCtr)CPU利用率的计算由 C/OS-II中的OSStatTask()函数来完成, 这个任务每秒执行一次。 而当OSStatRdy置为TRUEL1.9(5),表示 C/OS-II将统计CPU的利用率。程序清单 L程序清单 L 1.9 1.9测试测试CPUCPU速度。速度。void OSStatInit (void) OSTimeDly(2); OS_ENTER_CRITICAL(); OSIdleCtr = 0L; OS_EXIT_CRITICAL(); OSTimeDly(OS_TICKS_PER_SEC); OS_ENTER_CRITICAL(); OSIdleCtrMax = OSIdleCtr; OSStatRdy = TRUE; OS_EXIT_CRITICAL(); (4) (5) (3) (2) (1)1.07.031.07.03TaskN()TaskN()OSStatInit()将返回到 TaskStart()。现在,用户可以建立10 个同样的任务(所有任务共享同一段代码) 。所有任务都由 TaskStart()中建立,由于 TaskStart()的优先级为 0(最高) ,新任务建立后不进行任务调度。当所有任务都建立完成后,TaskStart()将进入无限循环之中,在屏幕上显示统计信息,并检测是否有 ESC 键按下,如果没有按键输入,则延时一秒开始下一次循环;如果在这期间用户按下了 ESC 键,TaskStart()将调用 PC_DOSReturn()返回 DOS 系统。程序清单L1.10给出了任务的代码。 任务一开始, 调用OSSemPend()获取信号量RandomSem程序清单 L1.10(1) (也就是禁止其他任务运行这段代码 译者注) , 然后调用 Borland C/C+的库函数 random()来获得一个随机数程序清单 L1.10(2),此处设 random()函数是不可重入的,所以 10 个任务将轮流获得信号量,并调用该函数。当计算出 x 和 y 坐标后程序清单 L1.10(3),任务释放信号量。随后任务在计算的坐标处显示其任务号(0-9,任务建立时的标识)程序清单 L1.10(4)。最后,任务延时一个时钟节拍程序清单 L1.10(5),等待进入下一次循环。系统中每个任务每秒执行 200 次,10 个任务每秒钟将切换 2000 次。程序清单 L程序清单 L 1.10 1.10在屏幕上显示随机位置显示数字的任务。在屏幕上显示随机位置显示数字的任务。void Task (void *data) UBYTE x; UBYTE y; UBYTE err; for (;) OSSemPend(RandomSem, 0, &err); x = random(80); y = random(16); OSSemPost(RandomSem); (3) (4) (1) (2) PC_DispChar(x, y + 5, *(char *)data, DISP_FGND_LIGHT_GRAY); OSTimeDly(1); (5)1.081.08例例 2 2例 2 使用了带扩展功能的任务建立函数 OSTaskCreateExt()和 uCOS-II 的堆栈检查操作(要使用堆栈检查操作必须用 OSTaskCreateExt()建立任务 译者注) 。当用户不知道应该给任务分配多少堆栈空间时,堆栈检查功能是很有用的。在这个例子里,先分配足够的堆栈空间给任务,然后用堆栈检查操作看看任务到底需要多少堆栈空间。显然,任务要运行足够长时间,并要考虑各种情况才能得到正确数据。最后决定的堆栈大小还要考虑系统今后的扩展,一般多分配 10,25或者更多。如果系统对稳定性要求高,则应该多一倍以上。uCOS-II 的堆栈检查功能要求任务建立时堆栈清零。 OSTaskCreateExt()可以执行此项操作 (设置选项 OS_TASK_OPT_STK_CHK 和 OS_TASK_OPT_STK_CLR 打开此项操作) 。 如果任务运行过程中要进行建立、删除任务的操作,应该设置好上述的选项,确保任务建立后堆栈是清空的。同时要意识到 OSTaskCreate