实时嵌入式系统ucosii第1章.docx
《实时嵌入式系统ucosii第1章.docx》由会员分享,可在线阅读,更多相关《实时嵌入式系统ucosii第1章.docx(35页珍藏版)》请在冰豆网上搜索。
实时嵌入式系统ucosii第1章
第一章:
范例
在这一章里将提供三个范例来说明如何使用µC/OS-II。
笔者之所以在本书一开始就写这一章是为了让读者尽快开始使用µC/OS-II。
在开始讲述这些例子之前,笔者想先说明一些在这本书里的约定。
这些例子曾经用BorlandC/C++编译器(V3.1)编译过,用选择项产生Intel/AMD80186处理器(大模式下编译)的代码。
这些代码实际上是在IntelPentiumIIPC(300MHz)上运行和测试过,IntelPentiumIIPC可以看成是特别快的80186。
笔者选择PC做为目标系统是由于以下几个原因:
首先也是最为重要的,以PC做为目标系统比起以其他嵌入式环境,如评估板,仿真器等,更容易进行代码的测试,不用不断地烧写EPROM,不断地向EPROM仿真器中下载程序等等。
用户只需要简单地编译、链接和执行。
其次,使用BorlandC/C++产生的80186的目标代码(实模式,在大模式下编译)与所有Intel、AMD、Cyrix公司的80x86CPU兼容。
1.00安装µC/OS-II
本书附带一张软盘包括了所有我们讨论的源代码。
是假定读者在80x86,Pentium,或者Pentium-II处理器上运行DOS或Windows95。
至少需要5Mb硬盘空间来安装uC/OS-II。
请按照以下步骤安装:
1.进入到DOS(或在Windows95下打开DOS窗口)并且指定C:
为默认驱动器。
2.将磁盘插入到A:
驱动器。
3.键入A:
INSTALL【drive】
注意『drive』是读者想要将µC/OS-II安装的目标磁盘的盘符。
INSTALL.BAT是一个DOS的批处理文件,位于磁盘的根目录下。
它会自动在读者指定的目标驱动器中建立\SOFTWARE目录并且将uCOS-II.EXE文件从A:
驱动器复制到\SOFTWARE并且运行。
µC/OS-II将在\SOFTWARE目录下添加所有的目录和文件。
完成之后INSTALL.BAT将删除uCOS-II.EXE并且将目录改为\SOFTWARE\uCOS-II\EX1_x86L,第一个例子就存放在这里。
在安装之前请一定阅读一下READ.ME文件。
当INSTALL.BAT已经完成时,用户的目标目录下应该有一下子目录:
●\SOFTWARE
这是根目录,是所有软件相关的文件都放在这个目录下。
●\SOFTWARE\BLOCKS
子程序模块目录。
笔者将例子中µC/OS-II用到的与PC相关的函数模块编译以后放在这个目录下。
●\SOFTWARE\HPLISTC
这个目录中存放的是与范例HPLIST相关的文件(请看附录D,HPLISTC和TO)。
HPLIST.C存放在\SOFTWARE\HPLISTC\SOURCE目录下。
DOS下的可执行文件(HPLIST.EXE)存放在\SOFTWARE\TO\EXE中。
●\SOFTWARE\TO
这个目录中存放的是和范例TO相关的文件(请看附录D,HPLISTC和TO)。
源文件TO.C存放在\SOFTWARE\TO\SOURCE中,DOS下的可执行文件(TO.EXE)存放在\SOFTWARE\TO\EXE中。
注意TO需要一个TO.TBL文件,它必须放在根目录下。
用户可以在\SOFTWARE\TO\EXE目录下找到TO.TBL文件。
如果要运行TO.EXE,必须将TO.TBL复制到根目录下。
●\SOFTWARE\uCOS-II
与µC/OS-II相关的文件都放在这个目录下。
●\SOFTWARE\uCOS-II\EX1_x86L
这个目录里包括例1的源代码(参见1.07,例1),可以在DOS(或Windows95下的DOS窗口)下运行。
●\SOFTWARE\uCOS-II\EX2_x86L
这个目录里包括例2的源代码(参见1.08,例2),可以在DOS(或Windows95下的DOS窗口)下运行。
●\SOFTWARE\uCOS-II\EX3_x86L
这个目录里包括例3的源代码(参见1.09,例3),可以在DOS(或Windows95下的DOS窗口)下运行。
●\SOFTWARE\uCOS-II\Ix86L
这个目录下包括依赖于处理器类型的代码。
此时是为在80x86处理器上运行uC/OS-II而必须的一些代码,实模式,在大模式下编译。
●\SOFTWARE\uCOS-II\SOURCE
这个目录里包括与处理器类型无关的源代码。
这些代码完全可移植到其它架构的处理器上。
1.01INCLUDES.H
用户将注意到本书中所有的*.C文件都包括了以下定义:
#include"includes.h"
INCLUDE.H可以使用户不必在工程项目中每个*.C文件中都考虑需要什么样的头文件。
换句话说,INCLUDE.H是主头文件。
这样做唯一的缺点是INCLUDES.H中许多头文件在一些*.C文件的编译中是不需要的。
这意味着逐个编译这些文件要花费额外的时间。
这虽有些不便,但代码的可移植性却增加了。
本书中所有的例子使用一个共同的头文件INCLUDES.H,3个副本分别存放在\SOFTWARE\uCOS-II\EX1_x86L,\SOFTWARE\uCOS-II\EX2_x86L,以及\SOFTWARE\uCOS-II\EX3_x86L中。
当然可以重新编辑INCLUDES.H以添加用户自己的头文件。
1.02不依赖于编译的数据类型
因为不同的微处理器有不同的字长,µC/OS-II的移植文件包括很多类型定义以确保可移植性(参见\SOFTWARE\uCOS-II\Ix86L\OS_CPU.H,它是针对80x86的实模式,在大模式下编译)。
µCOS-II不使用C语言中的short,int,long等数据类型的定义,因为它们与处理器类型有关,隐含着不可移植性。
笔者代之以移植性强的整数数据类型,这样,既直观又可移植,如表L1.1所示。
为了方便起见,还定义了浮点数数据类型,虽然µC/OS-II中没有使用浮点数。
程序清单L1.1可移植型数据类型。
TypedefunsignedcharBOOLEAN;
TypedefunsignedcharINT8U;
TypedefsignedcharINT8S;
TypedefunsignedintINT16U;
TypedefsignedintINT16S;
TypedefunsignedlongINT32U;
TypedefsignedlongINT32S;
TypedeffloatFP32;
TypedefdoubleFP64;
#defineBYTEINT8S
#defineUBYTEINT8U
#defineWORDINT16S
#defineUWORDINT16U
#defineLONGINT32S
#defineULONGINT32U
以INT16U数据类型为例,它代表16位无符号整数数据类型。
µC/OS-II和用户的应用代码可以定义这种类型的数据,范围从0到65,535。
如果将µCO/S-II移植到32位处理器中,那就意味着INT16U不再不是一个无符号整型数据,而是一个无符号短整型数据。
然而将无论µC/OS-II用到哪里,都会当作INT16U处理。
表1.1是以BorlandC/C++编译器为例,为80x86提供的定义语句。
为了和µC/OS兼容,还定义了BYTE,WORD,LONG以及相应的无符号变量。
这使得用户可以不作任何修改就能将µC/OS的代码移植到µC/OS-II中。
之所以这样做是因为笔者觉得这种新的数据类型定义有更多的灵活性,也更加易读易懂。
对一些人来说,WORD意味着32位数,而此处却意味着16位数。
这些新的数据类型应该能够消除此类含混不请
1.03全局变量
以下是如何定义全局变量。
众所周知,全局变量应该是得到内存分配且可以被其他模块通过C语言中extern关键字调用的变量。
因此,必须在.C和.H文件中定义。
这种重复的定义很容易导致错误。
以下讨论的方法只需用在头文件中定义一次。
虽然有点不易懂,但用户一旦掌握,使用起来却很灵活。
表1.2中的定义出现在定义所有全局变量的.H头文件中。
程序清单L1.2定义全局宏。
#ifdefxxx_GLOBALS
#definexxx_EXT
#else
#definexxx_EXTextern
#endif
.H文件中每个全局变量都加上了xxx_EXT的前缀。
xxx代表模块的名字。
该模块的.C文件中有以下定义:
#definexxx_GLOBALS
#include"includes.h"
当编译器处理.C文件时,它强制xxx_EXT(在相应.H文件中可以找到)为空,(因为xxx_GLOBALS已经定义)。
所以编译器给每个全局变量分配内存空间,而当编译器处理其他.C文件时,xxx_GLOBAL没有定义,xxx_EXT被定义为extern,这样用户就可以调用外部全局变量。
为了说明这个概念,可以参见uC/OS_II.H,其中包括以下定义:
#ifdefOS_GLOBALS
#defineOS_EXT
#else
#defineOS_EXTextern
#endif
OS_EXTINT32UOSIdleCtr;
OS_EXTINT32UOSIdleCtrRun;
OS_EXTINT32UOSIdleCtrMax;
同时,uCOS_II.H有中以下定义:
#defineOS_GLOBALS
#include“includes.h”
当编译器处理uCOS_II.C时,它使得头文件变成如下所示,因为OS_EXT被设置为空。
INT32UOSIdleCtr;
INT32UOSIdleCtrRun;
INT32UOSIdleCtrMax;
这样编译器就会将这些全局变量分配在内存中。
当编译器处理其他.C文件时,头文件变成了如下的样子,因为OS_GLOBAL没有定义,所以OS_EXT被定义为extern。
externINT32UOSIdleCtr;
externINT32UOSIdleCtrRun;
externINT32UOSIdleCtrMax;
在这种情况下,不产生内存分配,而任何.C文件都可以使用这些变量。
这样的就只需在.H文件中定义一次就可以了。
1.04OS_ENTER_CRITICAL()和
OS_EXIT_CRITICAL()
用户会看到,调用OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()两个宏,贯穿本书的所有源代码。
OS_ENTER_CRITICAL()关中断;而OS_EXIT_CRITICAL()开中断。
关中断和开中断是为了保护临界段代码。
这些代码很显然与处理器有关。
关于宏的定义可以在OS_CPU.H中找到。
9.03.02节详细讨论定义这些宏的两种方法。
程序清单L1.3进入正确部分的宏。
#defineOS_CRITICAL_METHOD2
#ifOS_CRITICAL_METHOD==1
#defineOS_ENTER_CRITICAL()asmCLI
#defineOS_EXIT_CRITICAL()asmSTI
#endif
#ifOS_CRITICAL_METHOD==2
#defineOS_ENTER_CRITICAL()asm{PUSHF;CLI}
#defineOS_EXIT_CRITICAL()asmPOPF
#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)。
在单色显示器中,用户可以把#defineconstantDISP_BASE从0xB800改为0xB000。
PC.C中的显示函数用x和y坐标来直接向显示内存中写ASCII字符。
PC的显示可以达到25行80列一共2,000个字符。
每个字符需要两个字节来显示。
第一个字节是用户想要显示的字符,第二个字节用来确定前景色和背景色。
前景色用低四位来表示,背景色用第4位到6位来表示。
最高位表示这个字符是否闪烁,
(1)表示闪烁,(0)表示不闪烁。
用PC.H中#defienconstants定义前景和背景色,PC.C包括以下四个函数:
PC_DispClrScr()Clearthescreen
PC_DispClrLine()Clearasinglerow(orline)
PC_DispChar()DisplayasingleASCIIcharacteranywhereonthescreen
PC_DispStr()DisplayanASCIIstringanywhereonthescreen
1.05.02花费时间的测量
时间测量函数主要用于测试一个函数的运行花了多少时间。
测量时间是用PC的82C54定时器2。
被测的程序代码是放在函数PC_ElapsedStart()和PC_ElapsedStop()之间来测量的。
在用这两个函数之前,应该调用PC_ElapsedInit()来初始化,它主要是计算运行这两个函数本身所附加的的时间。
这样,PC_ElapsedStop()函数中返回的数值就是准确的测量结果了。
注意,这两个函数都不具备可重入性,所以,必须小心,不要有多个任务同时调用这两个函数。
表1.4说明了如何测量PC_DisplayChar()的执行时间。
注意,时间是以uS为单位的。
程序清单L1.4测量代码执行时间。
INT16Utime;
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中使用ANSIC的setjmp(),longjmp()函数来分别保存和恢复DOS环境。
BorlandC/C++编译库提供这些函数,多数其它的编译程序也应有这类函数。
应该注意到无论是应用程序的错误还是只调用exit(0)而没有调用PC_DOSReturn()函数都会使DOS环境被破坏,从而导致DOS或WINDOWS95下的DOS窗口崩溃。
调用PC_GetDateTime()函数可得到PC中的日期和时间,并且以SACII字符串形式返回。
格式是MM-DD-YYHH:
MM:
SS,用户需要19个字符来存放这些数据。
该函数使用了BorlandC/C++的gettime()和getdate()函数,其它DOS环境下的C编译应该也有类似函数。
PC_GetKey()函数检查是否有按键被按下。
如果有按键被按下,函数返回其值。
这个函数使用了BorlandC/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中最后两个函数是得到和设置中断向量,笔者是用BorlandC/C++中的库函数来完成的,但是PC_VectGet()和PC_VectSet()很容易改写,以适用于其它编译器。
1.06应用µC/OS-II的范例
本章中的例子都用BorlandC/C++编译器编译通过,是在Windows95的DOS窗口下编译的。
可执行代码可以在每个范例的OBJ子目录下找到。
实际上这些代码是在BorlandIDE(IntegratedDevelopmentEnvironment)下编译的,编译时的选项如表1.1所示:
表T1.1IDE中编译选项。
Codegeneration
Model
:
Large
Options
:
Treatenumsasints
AssumeSSEqualsDS
:
Defaultformemorymodel
Advancedcodegeneration
Floatingpoint
:
Emulation
Instructionset
:
80186
Options
:
Generateunderbars
DebuginfoinOBJs
Fastfloatingpoint
Optimizations
Optimizations
Globalregisterallocation
Invariantcodemotion
Inductionvariables
Loopoptimization
Suppressredundantloads
Copypropagation
Deadcodeelimination
Jumpoptimization
In-lineintrinsicfunctions
Registervariables
Automatic
Commonsubexpressions
Optimizeglobally
Optimizefor
Speed
笔者的BorlandC/C++编译器安装在C:
\CPP目录下,如果用户的编译器是在不同的目录下,可以在Options/Directories的提示下改变IDE的路径。
µC/OS-II是一个可裁剪的操作系统,这意味着用户可以去掉不需要的服务。
代码的削减可以通过设置OS_CFG.H中的#definesOS_?
?
?
_EN为0来实现。
用户不需要的服务代码就不生成。
本章的范例就用这种功能,所以每个例子都定义了不同的OS_?
?
?
_EN。
1.07例1
第一个范例可以在\SOFTWARE\uCOS_II\EX1_x86L目录下找到,它有13个任务(包括µC/OS-II的空闲任务)。
µC/OS-II增加了两个内部任务:
空闲任务和一个计算CPU利用率的任务。
例1建立了11个其它任务。
TaskStart()任务是在函数main()中建立的;它的功能是建立其它任务并且在屏幕上显示如下统计信息:
●每秒钟任务切换次数;
●CPU利用百分率;
●寄存器切换次数;
●目前日期和时间;
●µC/OS-II的版本号;
TaskStart()还检查是否按下ESC键,以决定是否返回到DOS。
其余10个任务基于相同的代码——Task();每个任务在屏幕上随机的位置显示一个0到9的数字。
1.07.01main()
例1基本上和最初µC/OS中的第一个例子做一样的事,但是笔者整理了其中的代码,并且在屏幕上加了彩色显示。
同时笔者使用原来的数据类型(UBYTE,UWORD等)来说明µC/OS-II向下兼容。
main()程序从清整个屏幕开始,为的是保证屏幕上不留有以前的DOS下的显示[L1.5
(1)]。
注意,笔者定义了白色的字符和黑色的背景色。
既然要请屏幕,所以可以只定义背景色而不定义前景色,但是这样在退回DOS之后,用户就什么也看不见了。
这也是为什么总要定义一个可见的前景色。
µC/OS-II要用户在使用任何服务之前先调用OSInit()[L1.5
(2)]。
它会建立两个任务:
空闲任务和统计任务,前者在没有其它任务处于就绪态时运行;后者计算CPU的利用率。
程序清单L1.5main().
voidmain(void)
{
PC_DispClrScr(DISP_FGND_WHITE+DISP_BGND_BLACK);
(1)
OSInit();
(2)
PC_DOSSaveReturn();(3)
PC_VectSet(uCOS,OSCtxSw);(4)
RandomSem=OSSemCreate
(1);(5)
OSTaskCreate(TaskStart,(6)
(void*)0,
(void*)&TaskStartStk[TASK_STK_SIZE-1],
0);
OSStart();(7)
}
当前DOS环境是通过调用PC_DOSSaveReturn()[L1.5(3)]来保存的。
这使得用户可以返回到没有运行µC/OS-II以前的DOS环境。
跟随清单L1.6中的程序可以看到PC_DOSSaveReturn()做了很多事情。
PC_DOSSaveReturn()首先设置PC_ExitFlag为FALSE[L1.6
(1)],说明用户不是要返回DOS,然后初始化OSTickDOSCtr为1[L1.6
(2)],因为这个变量将在OSTickISR()中递减,而0将使得这个变量在OSTickISR()中减1后变为255。
然后,PC_DOSSaveReturn()将DOS的时钟节拍处理(tickhandler)存入一个自由向量表入口中[L1.6(3)-(4)],以便为µC/OS-II的时钟节拍处理所调用。
接着PC_DOSSaveReturn()调用jmp()[L1.6(5)],它将处理器状态(即所有寄存器的值)存入被称为PC_JumpBuf的结构之中。
保存处理器的全部寄存器使得程序返回到PC_DOSSaveRetur