logo资料库

uCOSⅡ中文教程(邵贝贝).pdf

第1页 / 共344页
第2页 / 共344页
第3页 / 共344页
第4页 / 共344页
第5页 / 共344页
第6页 / 共344页
第7页 / 共344页
第8页 / 共344页
资料共344页,剩余部分请下载后查看
第2章 实时系统概念
2.0 前后台系统 (Foreground/Background System)
2.1 代码的临界段
2.2 资源
2.3 共享资源
2.4 多任务
2.5 任务
2.6 任务切换(Context Switch or Task Switch)
2.7 内核(Kernel)
2.8 调度(Scheduler)
2.9 不可剥夺型内核 (Non-Preemptive Kernel)
2.10 可剥夺型内核
2.11 可重入性(Reentrancy)
2.12 时间片轮番调度法
2.13 任务优先级
2.14 2.14静态优先级
2.15 动态优先级
2.16 优先级反转
2.17 任务优先级分配
2.18 互斥条件
2.18.1 关中断和开中断
2.18.2 测试并置位
2.18.3 禁止,然后允许任务切换
2.18.4 信号量(Semaphores)
2.19 死锁(或抱死)(Deadlock (or Deadly Embrace))
2.20 同步
2.21 事件标志(Event Flags)
2.22 任务间的通讯(Intertask Communication)
2.23 消息邮箱(Message Mail boxes)
2.24 消息队列(Message Queue)
2.25 中断
2.26 中断延迟
2.27 中断响应
2.28 中断恢复时间(Interrupt Recovery)
2.29 中断延迟、响应和恢复
2.30 中断处理时间
2.31 非屏蔽中断(NMI)
2.32 时钟节拍(Clock Tick)
2.33 对存储器的需求
2.34 使用实时内核的优缺点
2.35 实时系统小结
第3章 内核结构
3.0 临界段(Critical Sections)
3.1 任务
3.2 任务状态
3.3 任务控制块(Task Control Blocks, OS _TCBs)
3.4 就绪表(Ready List)
3.5 任务调度(Task Scheduling)
3.6 给调度器上锁和开锁(Locking and UnLocking the Scheduler)
3.7 空闲任务(Idle Task)
3.8 统计任务
3.9 μC/OS中的中断处理
3.10 时钟节拍
3.11 μC/OS-Ⅱ初始化
3.12 μC/OS-Ⅱ的启动
3.13 获取当前μC/OS-Ⅱ的版本号
3.14 OSEvent???()函数
第4章 任务管理
4.0 建立任务,OSTaskCreate()
4.1 建立任务,OSTaskCreateExt()
4.2 任务堆栈
4.3 堆栈检验,OSTaskStkChk()
4.4 删除任务,OSTaskDel()
4.5 请求删除任务,OSTaskDelReq()
4.6 改变任务的优先级,OSTaskChangePrio()
4.7 挂起任务,OSTaskSuspend()
4.8 恢复任务,OSTaskResume()
4.9 获得有关任务的信息,OSTaskQuery()
第5章 时间管理
5.0 任务延时函数,OSTimeDly()
5.1 按时分秒延时函数 OSTimeDlyHMSM()
5.2 让处在延时期的任务结束延时,OSTimeDlyResume()
5.3 系统时间,OSTimeGet()和OSTimeSet()
第6章 任务之间的通讯与同步
6.0 事件控制块ECB
6.1 初始化一个事件控制块,OSEventWaitListInit()
6.2 使一个任务进入就绪态,OSEventTaskRdy()
6.3 使一个任务进入等待某事件发生状态, OSEventTaskWait()
6.4 由于等待超时而将任务置为就绪态, OSEventTO()
6.5 信号量
6.5.1 建立一个信号量, OSSemCreate()
6.5.2 等待一个信号量, OSSemPend()
6.5.3 发送一个信号量, OSSemPost()
6.5.4 无等待地请求一个信号量, OSSemAccept()
6.5.5 查询一个信号量的当前状态, OSSemQuery()
6.6 邮箱
6.6.1 建立一个邮箱,OSMboxCreate()
6.6.2 等待一个邮箱中的消息,OSMboxPend()
6.6.3 发送一个消息到邮箱中,OSMboxPost()
6.6.4 无等待地从邮箱中得到一个消息, OSMboxAccept()
6.6.5 查询一个邮箱的状态, OSMboxQuery()
6.6.6 用邮箱作二值信号量
6.6.7 用邮箱实现延时,而不使用OSTimeDly()
6.7 消息队列
6.7.1 建立一个消息队列,OSQCreate()
6.7.2 等待一个消息队列中的消息,OSQPend()
6.7.3 向消息队列发送一个消息(FIFO),OSQPost()
6.7.4 向消息队列发送一个消息(后进先出LIFO),OSQPostFront()
6.7.5 无等待地从一个消息队列中取得消息, OSQAccept()
6.7.6 清空一个消息队列, OSQFlush()
6.7.7 查询一个消息队列的状态,OSQQuery()
6.7.8 使用消息队列读取模拟量的值
6.7.9 使用一个消息队列作为计数信号量
第7章 内存管理
7.0 内存控制块
7.1 建立一个内存分区,OSMemCreate()
7.2 分配一个内存块,OSMemGet()
7.3 释放一个内存块,OSMemPut()
7.4 查询一个内存分区的状态,OSMemQuery()
7.5 Using Memory Partitions
7.6 等待一个内存块
第8章 9.03.02 代码临界区
8.0 方法1
8.1 方法2
8.2 目录和文件
8.3 INCLUDES.H
8.4 OS_CPU.H
8.4.1 与编译有关的数据类型s
8.4.2 OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()
8.4.3 OS_STK_GROWTH
8.4.4 OS_TASK_SW()
8.4.5 OS_FAR
8.5 OS_CPU_A.ASM
8.5.1 OSStartHighRdy()
8.5.2 OSCtxSw()
8.5.3 OSIntCtxSw()
8.5.4 OSTickISR()
8.6 OS_CPU_C.C
8.6.1 OSTaskStkInit()
8.6.2 OSTaskCreateHook()
8.6.3 OSTaskDelHook()
8.6.4 OSTaskSwHook()
8.6.5 OSTaskStatHook()
8.6.6 OSTimeTickHook()
8.7 总结
第一章:范例 在这一章里将提供三个范例来说明如何使用 µ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 公司的 80x86 CPU 兼容。 1.00 安装 µC/OS-II 本书附带一张软盘包括了所有我们讨论的源代码。是假定读者在 80x86,Pentium,或 者 Pentium-II 处理器上运行 DOS 或 Windows95。至少需要 5Mb 硬盘空间来安装 uC/OS-II。 请按照以下步骤安装: 1.进入到 DOS(或在 Windows 95 下打开 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(或 Windows 95 下的 DOS 窗口)下运行。 \SOFTWARE\uCOS-II\EX2_x86L 这个目录里包括例 2 的源代码(参见 1.08, 例 2),可以在 DOS(或 Windows 95 下的 DOS 窗口)下运行。 \SOFTWARE\uCOS-II\EX3_x86L 这个目录里包括例 3 的源代码(参见 1.09, 例 3),可以在 DOS(或 Windows 95 下的 DOS 窗口)下运行。 \SOFTWARE\uCOS-II\Ix86L 这个目录下包括依赖于处理器类型的代码。此时是为在 80x86 处理器上运行 uC/OS-II 而必须的一些代码,实模式,在大模式下编译。 \SOFTWARE\uCOS-II\SOURCE 这个目录里包括与处理器类型无关的源代码。这些代码完全可移植到其它架构的处理器 上。 1.01 INCLUDES.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 可移植型数据类型。 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++编译器为 例,为 80x86 提供的定义语句。为了和 µC/OS 兼容,还定义了 BYTE,WORD,LONG 以及相应的 无符号变量。这使得用户可以不作任何修改就能将 µC/OS 的代码移植到 µC/OS-II 中。之所 以这样做是因为笔者觉得这种新的数据类型定义有更多的灵活性,也更加易读易懂。对一些 人来说,WORD 意味着 32 位数,而此处却意味着 16 位数。这些新的数据类型应该能够消除 此类含混不请 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() PC_DispClrLine() PC_DispChar() PC_DispStr() Clear the screen Clear a single row (or line) Display a single ASCII character anywhere on the screen 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 窗口崩溃。 调用 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.06 应用 µC/OS-II 的范例 本章中的例子都用 Borland C/C++编译器编译通过,是在 Windows95 的 DOS 窗口下编译 的。可执行代码可以在每个范例的 OBJ 子目录下找到。实际上这些代码是在 Borland IDE (Integrated Development Environment)下编译的,编译时的选项如表 1.1 所示: 表 T1.1 IDE中编译选项。 Code generation Model Options Assume SS Equals DS : Large : Treat enums as ints : Default for memory model
分享到:
收藏