1.03全局变量
以下是如何定义全局变量。众所周知,全局变量应该是得到内存分配且可以被其他模块通过C语言中extern关键字调用的变量。因此,必须在 .C 和 .H 文件中定义。这种重复的定义很容易导致错误。以下讨论的方法只需用在头文件中定义一次。虽然有点不易懂,但用户一旦掌握,使用起来却很灵活。表1.2中的定义出现在定义所有全局变量的.H头文件中。
程序清单 L 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 #endif
OS_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.04 OS_ENTER_CRITICAL() 和
OS_EXIT_CRITICAL()
用户会看到,调用OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()两个宏,贯穿本书的所有源代码。OS_ENTER_CRITICAL() 关中断;而OS_EXIT_CRITICAL()开中断。关中断和开中断是为了保护临界段代码。这些代码很显然与处理器有关。关于宏的定义可以在OS_CPU.H中找到。9.03.02节详细讨论定义这些宏的两种方法。
程序清单 L 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.05基于PC的服务
PC.C 文件和 PC.H 文件(在\SOFTWARE\BLOCKS\PC\SOURCE目录下)是笔者在范例中使用到的一些基于PC的服务程序。与 µC/OS-II 以前的版本(即 µC/OS)不同,笔者希望集中这些函数以避免在各个例子中都重复定义,也更容易适应不同的编译器。PC.C包括字符显示,时间度量和其他各种服务。所有的函数都以PC_为前缀。
1.05.01字符显示
为了性能更好,显示函数直接向显示内存区中写数据。在VGA显示器中,显示内存从绝对
地址0x000B8000开始(或用段、偏移量表示则为B800:0000)。在单色显示器中,用户可以把#define constant DISP_BASE从0xB800改为0xB000。
PC.C中的显示函数用x和y坐标来直接向显示内存中写ASCII字符。PC的显示可以达到25行80列一共2,000个字符。每个字符需要两个字节来显示。第一个字节是用户想要显示的字符,第二个字节用来确定前景色和背景色。前景色用低四位来表示,背景色用第4位到6位来表示。最高位表示这个字符是否闪烁,(1)表示闪烁,(0)表示不闪烁。 用PC.H中 #defien constants定义前景和背景色,PC.C包括以下四个函数:
PC_DispClrScr() Clear the screen PC_DispClrLine() Clear a single row (or line) PC_DispChar() Display a single ASCII character anywhere on the screen PC_DispStr() Display an ASCII string anywhere on the screen
1.05.02花费时间的测量
时间测量函数主要用于测试一个函数的运行花了多少时间。测量时间是用PC的82C54定时器2。 被测的程序代码是放在函数PC_ElapsedStart()和PC_ElapsedStop()之间来测量的。在用这两个函数之前,应该调用PC_ElapsedInit()来初始化,它主要是计算运行这两个函数本身所附加的的时间。这样,PC_ElapsedStop()函数中返回的数值就是准确的测量结果了。注意,这两个函数都不具备可重入性,所以,必须小心,不要有多个任务同时调用这两个函数。表1.4说明了如何测量PC_DisplayChar()的执行时间。注意,时间是以uS为单位的。
程序清单 L 1.4 测量代码执行时间。
INT16U time;
PC_ElapsedInit(); . .
PC_ElapsedStart();
PC_DispChar(40, 24, ‘A’, DISP_FGND_WHITE); time = PC_ElapsedStop();
1.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窗口崩溃。