µC_OS-II 实验指导书 1 目录 第一部分 实验系统简介及入门 ............................................... 5 1 实验系统的目的 ................................................................................................................................. 5 2 实验系统的构成 ................................................................................................................................. 5 3 操作系统简介 ..................................................................................................................................... 5 3.1 µC/OS-II 概述 ........................................................................................................................... 5 3.2 µC/OS-II 的特点 ....................................................................................................................... 6 3.3 µC/OS-II 主要源代码文件介绍 ............................................................................................... 7 4 LambdaTOOL 集成开发环境简介 ..................................................................................................... 7 5 µC/OS-II 实验内容简介 ...................................................................................................................... 8 5.1 任务管理实验 .......................................................................................................................... 8 5.2 优先级反转实验 ...................................................................................................................... 8 5.3 优先级继承实验 ...................................................................................................................... 9 5.4 哲学家就餐实验 ...................................................................................................................... 9 5.5 内存管理实验 .......................................................................................................................... 9 5.6 时钟中断实验 .......................................................................................................................... 9 5.7 消息队列实验 .......................................................................................................................... 9 6 预备实验:嵌入式开发环境的建立 ................................................................................................. 9 6.1 目的 .......................................................................................................................................... 9 6.2 实验步骤及说明 .................................................................................................................... 10 第二部分 µC/OS-II 实验 ...................................................... 24 实验 1 任务的基本管理 .......................................................... 24 1 实验目的 ........................................................................................................................................... 24 2 实验原理及程序结构 ....................................................................................................................... 24 2.1 实验设计 ................................................................................................................................ 24 2.2 操作系统配置 ........................................................................................................................ 25 2.3 源程序说明 ............................................................................................................................ 27 3 运行及观察应用输出信息 ............................................................................................................... 29 4 本实验中所用到的 µC/OS-II 相关函数 .......................................................................................... 31 4.1 OSTaskCreate() .................................................................................................................. 31 4.2 OSTaskSuspend() ............................................................................................................... 31 4.3 OSTaskResume() ............................................................................................................... 32 实验 2 优先级反转................................................................... 33 1 实验目的 ........................................................................................................................................... 33 2 原理及程序结构 ............................................................................................................................... 33 2.1 实验设计 ................................................................................................................................ 33 2 2.2 操作系统配置 ........................................................................................................................ 35 2.3 源程序说明 ............................................................................................................................ 36 3 运行及观察应用输出信息 ............................................................................................................... 39 4 本实验中所用到的 µC/OS-II 相关函数 .......................................................................................... 40 4.1 OSSemCreate() .................................................................................................................. 40 4.2 OSSemPend() ..................................................................................................................... 40 4.3 OSemPost() ........................................................................................................................ 41 4.4 OSTimeDly()...................................................................................................................... 41 实验 3 优先级继承 ................................................................ 42 1 实验目的 ........................................................................................................................................... 42 2 原理及程序结构 ............................................................................................................................... 42 2.1 实验设计 ....................................................................................................................... 42 2.2 操作系统配置 ........................................................................................................................ 45 3 运行及观察应用输出信息 ............................................................................................................... 46 4 本实验中所用到的 µC/OS-II 相关函数 .......................................................................................... 47 4.1 OSMutexCreate() .................................................................................................................... 47 4.2 OSMutexPend() ....................................................................................................................... 47 4.3 OSMutexPost() ........................................................................................................................ 48 5 应用调试过程 ................................................................................................................................... 49 实验 4 信号量:哲学家就餐问题的实现 .............................. 51 1 实验目的 ........................................................................................................................................... 51 2 原理及程序结构 ............................................................................................................................... 51 2.1 实验设计 ................................................................................................................................ 51 2.2 操作系统配置 ........................................................................................................................ 52 3 运行及观察应用输出信息 ................................................................................................................ 53 4 本实验中所用到的 µC/OS-II 相关函数 .......................................................................................... 55 实验 5 µC/OS-II 的内存管理 ............................................... 56 1 实验目的 ........................................................................................................................................... 56 2 原理及程序结构 ............................................................................................................................... 56 2.1 实验设计 ................................................................................................................................. 56 2.2 操作系统配置 ........................................................................................................................ 62 3 本实验中所用到的 µC/OS-II 相关函数 .......................................................................................... 63 3.1 OSMemCreate() ................................................................................................................. 63 3.2 OSMemGet()...................................................................................................................... 64 3.3 OSMemPut() ...................................................................................................................... 64 3.4 OSMemQuery() ................................................................................................................. 65 3 实验 6 时钟中断....................................................................... 66 1 实验目的 ........................................................................................................................................... 66 2 原理及程序结构 ............................................................................................................................... 66 2.1 实验设计 ................................................................................................................................ 66 2.2 操作系统配置 ........................................................................................................................ 68 3 运行及观察应用输出信息 ............................................................................................................... 70 4 本实验中所用到的 µC/OS-II 相关函数 .......................................................................................... 71 实验 7 消息队列....................................................................... 72 1 实验目的 ........................................................................................................................................... 72 2 原理及程序结构 ............................................................................................................................... 72 2.1 实验设计 ................................................................................................................................ 72 2.2 源程序说明 ............................................................................................................................ 72 2.3 操作系统配置 ........................................................................................................................ 77 3 运行及观察应用输出信息 ............................................................................................................... 78 4 本实验中所用到的 µC/OS-II 相关函数 .......................................................................................... 82 4.1 OSQCreate() ...................................................................................................................... 82 4.2 OSQPend() ......................................................................................................................... 82 4.3 OSQPostFront() ....................................................................................................................... 83 4.4 OSQPost() ............................................................................................................................... 83 4.5 OSQFlush () ............................................................................................................................ 84 4.6 OSQQuery() ............................................................................................................................ 84 4.7 OSQDel() ................................................................................................................................ 85 4.8 OSTimeDlyHMSM() ............................................................................................................... 85 4 第一部分 实验系统简介及入门 1 实验系统的目的 通过此实验系统,读者可以了解嵌入式实时操作系统 µC_OS-II 的内核机制和运行原理。本实 验系统展示了 µCOS-II 各方面的管理功能,包括信号量、队列、内存、时钟等。在各个实验中具体 介绍了 µCOS-II 的相关函数。读者在做实验的同时能够结合理论知识加以分析,了解各个函数的作 用和嵌入式应用程序的设计方法,最终对整个 µCos 和嵌入式操作系统的应用有较为清楚的认识。 2 实验系统的构成 本实验系统由以下各部分组成: 1. µCOS-II 嵌入式实时操作系统。这个操作系统是开放源代码的; 2. LambdaTOOL。一个开发嵌入式软件的集成开发环境; 3. BSP。针对特定嵌入式硬件平台的板级支持包,提供板级初始化代码和一些基本的驱动程序; 4. 实验用例程序。基于特定的嵌入式操作系统(在本实验系统中是 µCOS-II)的应用程序代码。 3 操作系统简介 3.1 µC/OS-II 概述 µC/OS-II 是一个抢占式实时多任务内核。µC/OS-II 是用 ANSI 的 C 语言编写的,包含一小部分 汇编语言代码,使之可以提供给不同架构的微处理器使用。至今,从 8 位到 64 位,µC/OS-II 已经 在 40 多种不同架构的微处理器上使用。世界上已经有数千人在各个领域中使用 µC/OS,这些领域 包括:照相机行业、航空业、医疗器械、网络设备、自动提款机以及工业机器人等。 µC/OS-II 全部以源代码的方式提供给读者,大约有 5500 行。CPU 相关的部分使用的是针对 Intel80x86 微处理器的代码。虽然 µC/OS-II 可以在 PC 机上开发和测试,但是可以很容易地移植到 不同架构的嵌入式微处理器上。 5 3.2 µC/OS-II 的特点 1、源代码:µC/OS-II 全部以源代码的方式提供给使用者(约 5500 行) 。该源码清晰易读,结 构协调,且注解详尽,组织有序; 2、可移植(portable) : µC/OS-II 的源代码绝大部分是用移植性很强的 ANSI C 写的,与微处 理器硬件相关的部分是用汇编语言写的。µC/OS-II 可以移植到许许多多不同的微处理器上,条件是: 该微处理器具有堆栈指针,具有 CPU 内部寄存器入栈、出栈指令,使用的 C 编译器必须支持内嵌 汇编,或者该 C 语言可扩展和可链接汇编模块,使得关中断和开中断能在 C 语言程序中实现; 3、可固化(ROMable) : µC/OS-II 是为嵌入式应用而设计的,意味着只要具备合适的系列软 件工具(C 编译、汇编、链接以及下载/固化)就可以将 µC/OS-II 嵌入到产品中作为产品的一部分; 4、可裁减(scalable) : 可以只使用 µC/OS-II 中应用程序需要的系统服务。可裁减性是靠条件 编译实现的,只需要在用户的应用程序中定义那些 µC/OS-II 中的功能应用程序需要的部分就可以 了; 5、可抢占性(preemptive) : µC/OS-II 是完全可抢占型的实时内核,即 µC/OS-II 总是运行就 绪条件下优先级最高的任务; 6、多任务: µC/OS-II 可以管理 64 个任务。赋予每个任务的优先级必须是不相同的,这就是 说 µC/OS-II 不支持时间片轮转调度法(该调度法适用于调度优先级平等的任务); 7、可确定性: 绝大多数 µC/OS-II 的函数调用和服务的执行时间具有可确定性。也就是说用 户能知道 µC/OS-II 的函数调用与服务执行了多长时间。进而可以说,除了函数 OSTimeTick()和 某些事件标志服务,µC/OS-II 系统服务的执行时间不依赖于用户应用程序任务数目的多少; 8、任务栈: 每个任务都有自己单独的栈。µC/OS-II 允许每个任务有不同的栈空间,以便降低 应用程序对 RAM 的需求; 9、系统服务: µC/OS-II 提供许多系统服务,比如信号量、互斥信号量、事件标志、消息邮箱、 消息队列、时间管理等等; 10、中断管理: 中断可以使正在执行的任务暂时挂起。如果优先级更高的任务被该中断唤醒, 则高优先级的任务在中断嵌套全部退出后立即执行,中断嵌套层数可以达 255 层; 11、稳定性和可靠性: µC/OS-II 的每一种功能、每一个函数以及每一行代码都经过了考验和 测试,具有足够的安全性与稳定性,能用于与人性命攸关、安全性条件极为苛刻的系统中。 6 3.3 µC/OS-II 主要源代码文件介绍 µC/OS-II 的源代码具体包括以下的文件: PC.C: 源文件 PC.H 包含了对函数和环境的一些定义。 OS_CORE.C OS_Q.C OS_FLAG.C OS_SEM.C µCOS-II.H OS_MBOX.C OS_MEM.C OS_TASK.C OS_MUTEX.C OS_TIME.C µCOS-II.C : 这些文件是 µC/OS-II 中所有与处理器类型无关部分的源代码。 OS_CPU_A.S OS_CPU_C.C OS_CPU.H : 这些文件是与处理器类型相关部分的源代码,在本实验系统中是面向 80x86 处 理器的。 INCLUDES.H 给整个内核库提供了总体的 include 文件。 OS_CFG..H: µC/OS-II 的配置文件,定义使用 µC/OS-II 中的哪些功能。 4 LambdaTOOL 集成开发环境简介 LambdaTOOL 是一个通用、统一、开放的新一代 32 位嵌入式软件集成开发环境,支持多种嵌入 式操作系统和 32 位嵌入式处理器,具备先进的新一代交叉开发环境和系统配置工具。本实验系统中 提供的 LambdaTOOL 是其面向教学的免费版本,具备支持嵌入式软件仿真开发的完整功能,具体由 下列内容组成: 编辑环境 系统配置 编译环境 目标机管理 调试环境 7 在本实验系统中, 采用 LambdaTOOL 提供的一个 PC 虚拟机作为实验项目运行的仿真目标平台。 在 LambdaTOOL 上项目的开发流程如下图所示: 创建项目 编辑源码 配置项目 构建项目 调试应用 固化应用 有关开发流程的细节请看后面的“预备实验:嵌入式开发环境的建立” 。 5 µC/OS-II 实验内容简介 5.1 任务管理实验 此实验的目的是让读者理解嵌入式操作系统中任务管理的基本原理,了解任务的各个基本状态 及其变迁过程;掌握 µC/OS-II 中任务管理的基本方法(创建、启动、挂起和解挂任务) ;熟练使用 µC/OS-II 任务管理的基本系统调用。 5.2 优先级反转实验 通过此实验读者可以了解在基于抢占式嵌入式实时操作系统并有共享资源的应用中,出现优先 级反转现象的原理。优先级反转发生在有多个任务共享资源的情况下,高优先级任务被低优先级任 务阻塞,并等待低优先级任务执行的现象。 8 5.3 优先级继承实验 通过此实验读者可以了解嵌入式实时操作系统 µC/OS-II 解决优先级反转的策略——优先级继 承的原理,以此解决低优先级任务在占用了共享资源的情况下,被高优先级任务抢占了 CPU 使用权 而导致的优先级反转的问题。 5.4 哲学家就餐实验 通过经典的哲学家就餐应用,读者可以了解如何利用嵌入式实时操作系统 µC/OS-II 的信号量 机制来对共享资源进行互斥访问。 5.5 内存管理实验 通过此实验读者可以了解嵌入式实时操作系统 µC/OS-II 中的内存管理的原理,包括对内存的 分配和回收。 5.6 时钟中断实验 通过此实验读者可以了解嵌入式实时操作系统 µC/OS-II 中,时钟中断的使用情况。 5.7 消息队列实验 通过此实验读者可以了解嵌入式实时操作系统 µC/OS-II 中的消息队列机制。读者可以了解一 个应用中的任务是如何进行通信的,如何能使它们相互协调工作。 6 预备实验:嵌入式开发环境的建立 6.1 目的 a) 掌握嵌入式软件开发环境的建立过程; b) 初步掌握嵌入式程序的编译和调试过程。 9 6.2 实验步骤及说明 1、安装集成开发环境 LambdaTOOL3: 集 成 开 发 环 境 LambdaTOOL3 的 安 装 文 件 夹 为 LambdaPRO3.1_edu , 其 中 有 一 个 名 为 “Setup.exe”的文件,直接双击该文件便可启动安装过程。具体的安装指导请看“LambdaPRO3.1_edu 安装手册.doc”文件。 (在本指导书后面的描述中,假定 LambdaTOOL 被安装在了 D 盘根目录之下, 根目录为 D:/LambdaPRO。 ) 当 LambdaTOOL3 安装完毕之后,我们看到的是一个空的界面,现在就开始一步一步地将我们 的实验项目建立并运行起来。 2、建立项目。想让我们的实验运行起来,需要建立 3 个项目:两个“静态库项目”和一个“操作系 统无关项目” 。首先,看看“静态库项目”是如何建立的: 到了这里,点击下一步: 10 在上面的窗口中,需要填写你为该项目起的名字(在这里是 ucos) ,并设定项目文件所在的目 录。对于刚开始使用 LambdaTOOL 工具的读者,最好选择缺省设置的目录位置;如果你对工具很熟 悉,可以改成自己喜欢的目录。好了,点击下一步,你看到的是如下界面: 附加名可暂时空着,由于本实验的目标运行平台是一个 PC 虚拟机,所以体系结构就选“x86” 。 这也是嵌入式软件开发与桌面应用开发的一个不同的地方,我们不可避免地需要考虑硬件方面的问 题。在“配置”栏中选择“i386_le_soft” (表示数据在内存中是以小端的方式存放,并使用软浮点库) , 然后点击“完成”按钮。 这样,你就已经建立好了一个静态库项目。我们的工程需要两个静态库项目,所以请按照上 面的方法,再建立一个叫 uc_bsp 的项目。 建立“操作系统无关项目” 。刚才在建立静态库项目的时候你就已经发现了,有一种叫做“操 作系统无关项目”的选项,这次就选择建立这样的一个项目,取名为“appTest” : 11 之后的步骤并不像静态库项目一样简单,多了些选项: 内存摸板:D:/LambdaPRO/target/deltaos/bsps/boards/pc386/mtp/tra_debug.mtp(采用目标监控器 调试时的内存模板) 启动类型:D:/LambdaPRO/target/deltaos/bsps/boards/pc386/start/tra_debug(采用目标监控器调 试时的启动方式) 12 3、拷贝项目文件 设置完成之后,该实验所需的所有项目都建立好了,不过 ucos 和 uc_bsp 的 src 目录里面都是 空的。这时候需要在系统的资源管理器中复制相关的源文件,然后粘贴到相应项目的 src 目录中就 可以了。在 uc_bsp 项目的 src 目录中粘贴 BSP(Board Support Package,板级支持包)相关的文件(包 括 driver 和 syscall 子目录) ;在 ucos 项目的 src 目录中粘贴 µC/OS-II 的内核源文件。在你所建立的 操作系统无关项目 appTest 中,已经存在了一个应用源代码文件 init.c ,请把这个文件删除,然后把 相应的实验用例程序源文件(如:任务管理.c)粘贴到 src 目录下,下图是粘贴完毕后的结果。 13 4、项目配置 uc_bsp 和 ucos 这两个静态库项目的目的是为了生成两个库文件:libuc_bsp.a(板级支持包库) 和 libucos.a(µC/OS-II 内核库) ,而 appTest 是我们最终的实验项目,它需要调用到 libuc_bsp.a 和 libucos.a 的一些功能。因此这 3 个项目的构建顺序应该是:先使用静态库项目生成应用所需的库文 件到指定路径中,然后用 appTest 项目生成最终应用。 1)对于静态库项目 uc_bsp 和 ucos,需要设置生成的库文件的名称和输出路径。 将鼠标放在项目名称上并点击右键,在弹出的快捷菜单中点击“属性”,如下图所示: 以 ucos 项目为例,在“属性”中的“构建—构建设置—输出路径”中进行如下设置: 库文件名:libucos 输出路径:D:/LambdaPRO/target/deltaos/lib/x86/i386/little 14 2)在构建项目前,我们需要设置一些编译器可能用到的路径(三个项目均需要设置) 在“属性”中的“构建—工具设置—路径”中添加路径,单击工具条( )上的 增加按钮(第一个按钮) ,在弹出的窗口中设置如下路径: 对于 ucos 项目,需设置如下路径: "D:/LambdaPRO/host/ide/workspace/ucos/src" (系统自动添加) 对于 uc_bsp 项目,需设置如下路径: "D:/LambdaPRO/host/ide/workspace/uc_bsp/src";(系统自动添加) 对于 appTest 项目,需设置如下路径: "D:/LambdaPRO /host/ide/workspace/appTest/src" (系统自动添加) "D:/LambdaPRO/host/ide/workspace/ucos/src" (需手动添加) 说明:本实验系统所用的 LambdaTOOL 会自动添加在本项目的构建过程中可能需要的相关路 径,一般不必进行额外的设置,但项目 appTest 由于要调用 src 目录下定义的一些头文件,因此必须 手动添加上面的路径。 15 3)库搜索路径的设置 项目 appTest 要用到 uc_bsp 和 ucos 两个项目产生的库文件,因此需要设置 appTest 的库搜索路 径: 在项目 appTest 的“属性”中的“构建—工具设置—库”中进行设置:添加 uc_bsp 和 ucos 两 个库,并在库搜索路径中设置库所在的路径。 (由于在前面设置库的生成路径时,设置的是系统默认 的搜索路径,因此这里可免除这一步骤。 ) 5、构建项目 到此为止,你已经将项目配置得差不多了,再稍加一些东西,就可以运行一个应用项目了。 下一个步骤就是构建项目生成可运行的程序了(这个过程包含了编译、链接等操作): 16 之后弹出的对话框显示了构建的进度,可以点击“在后台运行” ,以隐藏该对话框。 在“构建信息”视图里可以查看项目的构建信息: 在“问题”视图里可以查看项目构建的问题记录。如果在这些问题中没有出现“错误”而只 是一些“警告”或“信息” ,则表明构建是成功的。 17 注意:ucos 和 uc_bsp 项目必须先于 appTest 构建,以生成相关的库文件,这些库文件是可以 供所有的实验应用项目使用的。因此当你替换 appTest 中的应用源代码文件以构建不同的实验应用 时,不需要重新构建 ucos 和 uc_bsp 项目,只需对 appTest 重新进行构建。 6、配置虚拟机和目标机代理 本实验系统所用的目标运行平台是一个运行在桌面系统中的 PC 虚拟机,为了启动这个虚拟机, 需要首先制作它的“启动盘” 。 1)制作 X86 启动盘 在 LambdaTOOL 中选择“文件——新建——其它——Lambada——制作 X86 启动盘——制作虚 拟机 TA 启动映像”,对启动盘进行一些参数设置后(如下图所示) ,系统将自动为你生成一个 PC 虚 拟机的启动盘映像。 注意:在设置 IP 地址时,应选择你的 PC 机所在的局域网中没有被占用的一个 IP 地址。 2)配置虚拟机 18 启动盘制作完成后,选择“虚拟机——配置虚拟机”菜单,系统弹出如下对话框: 注意:如果在你的计算机上安装有 vmware 等虚拟机,应事先将其虚拟的网卡禁用,以免产生 冲突。 3)创建目标机代理 配置好虚拟机后,创建目标机代理:点击 LambdaTOOL 左下方窗口中绿色的十字符号,在弹 出的窗口中选择“基于 TA 的连接方式” ,并点击“下一步” 。 在弹出的窗口中设置连接参数,这些参数应该与你之前制作启动盘时设置的参数一致: 19 基本信息: 名字:输入目标机的名字(缺省是 default) ; 日志:选择 No; 连接参数: 连接类型:默认选择 UDP,不能更改; IP 地址:这里输入目标机(在本实验系统中是虚拟机)的 IP 地址; 端口号:输入目标机的端口(固定为 1118) ; 超时:超时时间的定义。例如,一般输入值为 100,表示如果连接超过 100ms 还未连接成 功,则算一次连接超时,重新开始连接; 重试:数据超时重试最大次数。例如,一般输入值为 100,表示如果 100 次超时重试均失 败,则退出报错。 最后点击“确定” ,在目标机管理窗口中,可以看到新增加了一个名为 default 的目标机节点。 20 7、调试应用 1)启动虚拟机 虚拟机启动后的画面如下(其中显示的 IP 地址即为步骤 6-1)中设置的 IP 地址): 2)在“目标机管理”窗口中启动目标机代理:选中之前创建的目标机代理项,并点击绿色的 按钮: 之后如果成功与目标机建立连接,则原先绿色的 按钮会变灰,其旁边红色的按钮会被点亮。 21 3)当成功构建完成 appTest 后,在该项目的“x86_i386_le_soft_elf_tra_debug”上点击鼠标右 键,在弹出的菜单中选择“调试方式-调试应用程序”,启动调试器调试生成的程序: 程序下载完成后,会弹出一个“确认透视图切换”对话框,选择“是”,切换到调试透视图。 4)调试的界面如下: 22 LambdaTOOL 中集成的调试器功能强大,比较常用的操作包括查看变量内容、设置程序的断 点、观察寄存器的状态等等。具体可参见 LambdaTOOL 的联机帮助系统。 23 第二部分 µC/OS-II 实验 实验 1 任务的基本管理 1 实验目的 理解任务管理的基本原理,了解任务的各个基本状态及其变迁过程; 掌握 µC/OS-II 中任务管理的基本方法(创建、启动、挂起、解挂任务) ; 熟练使用 µC/OS-II 任务管理的基本系统调用。 2 实验原理及程序结构 2.1 实验设计 为了展现任务的各种基本状态及其变迁过程,本实验设计了 Task0、Task1 两个任务:任务 Task0 不断地挂起自己,再被任务 Task1 解挂,两个任务不断地切换执行。通过本实验,读者可以清晰地 了解到任务在各个时刻的状态以及状态变迁的原因。 起始 任务 Task0 Task0 Task0 Task1 t0 t1 图 1-1 t2 Task1 t3 t4 Task1 t5 t6 t7 t8 图1 注意:图中的栅格并不代表严格的时间刻度,而仅仅表现各任务启动和执行的相对先后关系。 24 2.1.1 运行流程 整个应用的运行流程如图 1 所示,其描述如下: (1)系统经历一系列的初始化过程后进入 boot_card()函数,在其中调用 ucBsp_init()进行板级 初始化后,调用 main()函数; (2)main()函数调用 OSInit()函数对 µC/OS-II 内核进行初始化,调用 OSTaskCreate 创建起 始任务 TaskStart; (3)main()函数调用函数 OSStart()启动 µC/OS-II 内核的运行,开始多任务的调度,执行当 前优先级最高的就绪任务 TaskStart; (4)TaskStart 完成如下工作: a、安装时钟中断并初始化时钟,创建 2 个应用任务; b、挂起自己(不再被其它任务唤醒),系统切换到当前优先级最高的就绪任务 Task0。 之后整个系统的运行流程如下: t1 时刻,Task0 开始执行,它运行到 t2 时刻挂起自己; t2 时刻,系统调度处于就绪状态的优先级最高任务 Task1 执行,它在 t3 时刻唤醒 Task0, 后者由于优先级较高而抢占 CPU; Task0 执行到 t4 时刻又挂起自己,内核调度 Task1 执行; Task1 运行至 t5 时刻再度唤醒 Task0; …… 2.1.2 μC/OS-Ⅱ中的任务描述 一个任务通常是一个无限的循环 ,由于任务的执行是由操作系统内核调度的,因此任务是 绝不会返回的,其返回参数必须定义成 void。在 μC/OS-Ⅱ中,当一个运行着的任务使一个比它优先 级高的任务进入了就绪态,当前任务的 CPU 使用权就会被抢占,高优先级任务会立刻得到 CPU 的 控制权(在系统允许调度和任务切换的前提下)。μC/OS-Ⅱ可以管理多达 64 个任务,但目前版本的 μC/OS-Ⅱ有两个任务已经被系统占用了(即空闲任务和统计任务)。必须给每个任务赋以不同的优 先级,任务的优先级号就是任务编号(ID),优先级可以从 0 到 OS_LOWEST_PR10-2。优先级号 越低,任务的优先级越高。μC/OS-Ⅱ总是运行进入就绪态的优先级最高的任务。 2.2 操作系统配置 操作系统配置的目的在于根据应用的需要,对操作系统的功能和规模进行设置,以便优化对系 25 统存储空间的使用。配置的方法为修改 uC_OS-II 源代码目录中的 OS_CFG.h 文件: #define OS_MAX_EVENTS /*最多可以有 10 个事件*/ 10 #define OS_MAX_FLAGS 5 /*最多可以有 5 个事件标志*/ #define OS_MAX_MEM_PART 5 /*最多可以划分 5 个内存块*/ #define OS_MAX_QS 2 /*最多可以使用 2 个队列*/ #define OS_MAX_TASKS 3 #define OS_LOWEST_PRIO 14 /*最多可以创建 3 个任务*/ /*任务优先级不可以大于 14*/ #define OS_TASK_IDLE_STK_SIZE 1024 /*空闲任务堆栈大小*/ #define OS_TASK_STAT_EN 1 /*是否允许使用统计任务*/ #define OS_TASK_STAT_STK_SIZE 1024 /*统计任务堆栈大小*/ #define OS_FLAG_EN 0 /*是否允许使用事件标志功能*/ #define OS_FLAG_WAIT_CLR_EN 1 /*是否允许等待清除事件标志*/ #define OS_FLAG_ACCEPT_EN 1 /*是否允许使用 OSFlagAccept()*/ #define OS_FLAG_DEL_EN 1 /*是否允许使用 OSFlagDel()*/ #define OS_FLAG_QUERY_EN 1 /*是否允许使用 OSFlagQuery()*/ #define OS_MBOX_EN 0 #define OS_MBOX_ACCEPT_EN 1 /*是否允许使用 OSMboxAccept() */ #define OS_MBOX_DEL_EN 1 /*是否允许使用 OSMboxDel()*/ #define OS_MBOX_POST_EN 1 /*是否允许使用 OSMboxPost()*/ #define OS_MBOX_POST_OPT_EN 1 /*是否允许使用 OSMboxPostOpt() */ #define OS_MBOX_QUERY_EN 1 /*是否允许使用 OSMboxQuery()*/ #define OS_MEM_EN 0 /*是否允许使用内存管理的功能*/ #define OS_MEM_QUERY_EN 1 /*是否允许使用 OSMemQuery()*/ #define OS_MUTEX_EN 0 /*是否允许使用互斥信号量的功能*/ #define OS_MUTEX_ACCEPT_EN 1 /*是否允许使用 OSMutexAccept()*/ #define OS_MUTEX_DEL_EN 1 /*是否允许使用 OSMutexDel()*/ #define OS_MUTEX_QUERY_EN 1 /*是否允许使用 OSMutexQuery()*/ #define OS_Q_EN 0 /*是否允许使用队列功能*/ #define OS_Q_ACCEPT_EN 1 /*是否允许使用 OSQAccept()*/ #define OS_Q_DEL_EN 1 /*是否允许使用 OSQDel()*/ /*是否允许使用邮箱功能*/ 26 #define OS_Q_FLUSH_EN 1 /*是否允许使用 OSQFlush()*/ #define OS_Q_POST_EN 1 /*是否允许使用 OSQPost()*/ #define OS_Q_POST_FRONT_EN 1 /*是否允许使用 OSQPostFront()*/ #define OS_Q_POST_OPT_EN 1 /*是否允许使用 OSQPostOpt()*/ #define OS_Q_QUERY_EN 1 /*是否允许使用 OSQQuery()*/ #define OS_SEM_EN 0 /*是否允许使用信号量功能*/ #define OS_SEM_ACCEPT_EN 1 /*是否允许使用 OSSemAccept()*/ #define OS_SEM_DEL_EN 1 /*是否允许使用 OSSemDel() */ #define OS_SEM_QUERY_EN 1 /*是否允许使用 OSSemQuery()*/ #define OS_TASK_CHANGE_PRIO_EN 0 /*是否允许使用 OSTaskChangePrio()*/ #define OS_TASK_CREATE_EN 1 /*是否允许使用 OSTaskCreate()*/ #define OS_TASK_CREATE_EXT_EN 1 /*是否允许使用 OSTaskCreateExt()*/ #define OS_TASK_DEL_EN 1 /*是否允许使用 OSTaskDel()*/ #define OS_TASK_SUSPEND_EN 1 /*是否允许使用 OSTaskSuspend() and OSTaskResume()*/ #define OS_TASK_QUERY_EN 1 /*是否允许使用 OSTaskQuery()*/ #define OS_TIME_DLY_HMSM_EN 0 /*是否允许使用 OSTimeDlyHMSM()*/ #define OS_TIME_DLY_RESUME_EN 1 /*是否允许使用 OSTimeDlyResume()*/ #define OS_TIME_GET_SET_EN 1 /*是否允许使用 OSTimeGet() 和 OSTimeSet()*/ #define OS_SCHED_LOCK_EN 1 /*是否允许使用 OSSchedLock()和 OSSchedUnlock()*/ #define OS_TICKS_PER_SEC 200 /*设置每秒之内的时钟节拍数目*/ 2.3 源程序说明 系统启动后,经历一系列的初始化过程,进入 main()函数,这是我们编写实现应用程序的起点。 首先需要在 main()函数里创建起始任务 TaskStart: OSTaskCreate(TaskStart, (void *)0, &TaskStartStk[TASK_STK_SIZE - 1], 4); TaskStart 任务 TaskStart 任务负责安装操作系统的时钟中断服务例程、初始化操作系统时钟,并创建所有的应 用任务: ucos_x86_idt_set_handler(0x20,(void *)OSTickISR,0x8e00); /* Install uC/OS-II's clock tick ISR ucos_timer_init(); */ /*Timer 初始化*/ 27 TaskStartCreateTasks(); /* Create all the application tasks */ OSTaskSuspend(OS_PRIO_SELF); 具体负责应用任务创建的 TaskStartCreateTasks 函数代码如下,它创建了两个应用任务 Task0 和 Task1: static void TaskStartCreateTasks (void) { INT8U i; for (i = 0; i < N_TASKS; i++) { TaskData[i] = i; /* Create N_TASKS identical tasks */ /* Each task will display its own letter */ } OSTaskCreate(Task0, (void *)&TaskData[i], &TaskStk[i][TASK_STK_SIZE - 1], 5); OSTaskCreate(Task1, (void *)&TaskData[i], &TaskStk[1][TASK_STK_SIZE - 1], 6); } TaskStart 任务完成上述操作后将自己挂起,操作系统将调度当前优先级最高的应用任务 Task0 运行。 应用任务 应用任务 Task0 运行后将自己挂起,之后操作系统就会调度处于就绪状态的优先级最高的任务, 具体代码如下: void Task0 (void *pdata) { INT8U i; INT8U err; i=*(int *)pdata; for (;;) { …… err=OSTaskSuspend(5); /*此处为输出信息,显示任务运行的状态 /* suspend itself */ */ } } 应用任务 Task1 运行后将 Task0 唤醒,使其进入到就绪队列中: void Task1 (void *pdata) { INT8U i; INT8U err; i=*(int *)pdata; 28 for (;;) { OSTimeDly(150); …… /*此处为输出信息,显示任务运行的状态 */ OSTimeDly(150); err=OSTaskResume(5); /* resume task0 */ } } 3 运行及观察应用输出信息 按照本实验手册第一部分所描述的方法建立应用项目并完成构建,当我们在 LambdaTOOL 调 试器的控制下运行构建好的程序后,将看到在 μC/OS-Ⅱ内核的调度管理下,两个应用任务不断切换 执行的情形: T1 时刻的截图 29 T2 时刻的截图 T3 时刻的截图 30 T4 时刻的截图 4 本实验中所用到的 µC/OS-II 相关函数 4.1 OSTaskCreate() 建立一个新任务。任务的建立可以在多任务环境启动之前,也可以在正在运行的任务中建立。 中断处理程序中不能建立任务。一个任务可以为无限循环的结构。 函数原型:INT8U OSTaskCreate(void (*task)(void *pd), void *pdata, OS_STK *ptos, INT8U prio); 参数说明:task 是指向任务代码首地址的指针。 Pdata 指向一个数据结构,该结构用来在建立任务时向任务传递参数。 返回值: OSTaskCreate()的返回值为下述之一: OS_NO_ERR:函数调用成功。 OS_PRIO_EXIST:具有该优先级的任务已经存在。 OS_PRIO_INVALID:参数指定的优先级大于 OS_LOWEST_PRIO。 OS_NO_MORE_TCB:系统中没有 OS_TCB 可以分配给任务了。 4.2 OSTaskSuspend() 无条件挂起一个任务。调用此函数的任务也可以传递参数 OS_PRIO_SELF,挂起调用任务本 31 身。当前任务挂起后,只有其他任务才能唤醒被挂起的任务。任务挂起后,系统会重新进行任务调 度,运行下一个优先级最高的就绪任务。唤醒挂起任务需要调用函数 OSTaskResume()。 任务的挂起是可以叠加到其他操作上的。例如,任务被挂起时正在进行延时操作,那么任务 的唤醒就需要两个条件:延时的结束以及其他任务的唤醒操作。又如,任务被挂起时正在等待信号 量,当任务从信号量的等待对列中清除后也不能立即运行,而必须等到被唤醒后。 函数原型:INT8U OSTaskSuspend ( INT8U prio); 参数说明:prio 为指定要获取挂起的任务优先级,也可以指定参数 OS_PRIO_SELF,挂起任 务本身。此时,下一个优先级最高的就绪任务将运行。 返回值: OSTaskSuspend()的返回值为下述之一: OS_NO_ERR:函数调用成功。 OS_TASK_ SUSPEND_IDLE:试图挂起 μC/OS-II 中的空闲任务(Idle task)。此为非法操作。 OS_PRIO_INVALID:参数指定的优先级大于 OS_LOWEST_PRIO 或没有设定 OS_PRIO_SELF 的值。 OS_TASK_ SUSPEND _PRIO:要挂起的任务不存在。 4.3 OSTaskResume() 唤醒一个用 OSTaskSuspend()函数挂起的任务。OSTaskResume()也是唯一能“解挂”挂起 任务的函数。 函数原型:NT8U OSTaskResume ( INT8U prio); 参数说明:prio 指定要唤醒任务的优先级。 返回值: OSTaskResume ()的返回值为下述之一: OS_NO_ERR:函数调用成功。 OS_TASK_RESUME_PRIO:要唤醒的任务不存在。 OS_TASK_NOT_SUSPENDED:要唤醒的任务不在挂起状态。 OS_PRIO_INVALID:参数指定的优先级大于或等于 OS_LOWEST_PRIO。 32 实验 2 优先级反转 1 实验目的 掌握在基于优先级的可抢占嵌入式实时操作系统的应用中,出现优先级反转现象的原理。 2 原理及程序结构 2.1 实验设计 2.1.1 优先级反转原理 在本实验中,要体现嵌入式实时内核的优先级抢占调度的策略,并显现由于共享资源的互斥 访问而出现的优先级反转现象。 优先级反转发生在有多个任务需要使用共享资源的情况下,可能会出现高优先级任务被低优 先级任务阻塞,并等待低优先级任务执行的现象。高优先级任务需要等待低优先级任务释放资源, 而低优先级任务又正在等待中等优先级任务,这种现象就被称为优先级反转。两个任务都试图访问 共享资源是出现优先级反转最通常的情况。为了保证一致性,这种访问应该是顺序进行的。如果高 优先级任务首先访问共享资源,则会保持共享资源访问的合适的任务优先级顺序;但如果是低优先 级任务首先获得共享资源的访问,然后高优先级任务请求对共享资源的访问,则高优先级任务被阻 塞,直到低优先级任务完成对共享资源的访问。 2.1.2 设计要点 1)设计了 3 个应用任务 TA0~TA2,其优先级逐渐降低,任务 TA0 的优先级最高。 2)除任务 TA1 外,其它应用任务都要使用同一种资源,该资源必须被互斥使用。为此,创建 一个二值信号量 mutex 来模拟该资源。虽然 μC/OS-Ⅱ在创建信号量时可以选择采用防止优先级反转 的策略,但在本实验中我们不使用这种策略。 3)应用任务的执行情况如图 2-1 所示: 33 优先级 0 5 0 1 6 1 t1 2 t2 t3 任务 2: 2 t4 0 1 2 7 0 t5 1 2 t6 t7 2 t8 任务 1: 1 t9 t10 t11 t12 t13 任务 0: 0 图 2-1 注意:图中的栅格并不代表严格的时间刻度,而仅仅表现各个任务启动和执行的相对先后关 系。 2.1.3 系统的运行流程 1) 系统初始化,之后进入 main 函数; 2) 在 main 函数中,首先创建一个二值的信号量 mutex; 3) 在 main 函数中创建 TaskStart 任务,由 TaskStart 任务创建所有的应用任务(TA0、TA1、 TA2)。 优先级较高的任务 TA0、TA1 先延时若干个时钟节拍,以便低优先级任务 TA2 运行。 4) t1 时刻,任务 TA2 运行并首先申请到信号量 mutex; 5) t2 时刻,任务 TA1 延时到期,任务 TA1 的优先级高于任务 TA2 的优先级,因此任务 TA1 立刻抢占 TA2 执行,任务 TA2 由执行态转为就绪态; 6) t3 时刻,任务 TA0 延时到期,任务 TA0 的优先级高于任务 TA1 的优先级,所以任务 TA0 立刻抢占执行,任务 TA1 由执行态转为就绪态,任务 TA0 申请二值信号量 mutex 被阻赛; 7) t4 时刻,任务 TA1 由就绪态转回为执行态;此时 TA0 在等待 TA2 保持的 mutex , 而 TA2 又因为优先级低于 TA1 被阻塞。如果 TA1 一直执行而 TA2 没有机会被调度的话,那么 TA2 将一直等到 TA1 执行完后才能执行,而 TA0 更要等到 TA2 释放它所占有的信号量资源后才 能执行,这样就出现了优先级高的 TA0 任务等待优先级低的 TA1 任务的现象; 8) t5 时刻,任务 TA1 挂起自己,而 TA0 又因为申请二值信号量 mutex 而处于阻塞状态,所以 任务 TA2 由就绪态转为执行态,任务 TA2 释放信号量 mutex; 34 9) t6 时刻,TA0 获得信号量并立刻抢占执行,任务 TA2 由执行态转为就绪态; 10) t7 时刻,任务 TA0 将自己延时一段时间,而 TA1 仍然处于挂起状态,TA2 是当前最高优 先级的就绪任务,它又转为执行状态,任务 TA2 因申请二值信号量 mutex 而阻塞; 11) t8 时刻,任务 TA1 延时到期转为执行态,任务 TA1 又因等待一个事件而阻塞; 12) t9 时刻,任务 TA0 延时到,释放二值信号量 mutex,mutex 被 TA2 得到后,内核自动切换 任务; 13) t10 时刻,在就绪队列中,TA0 优先级最高,TA0 执行,又因为任务 TA0 等待一事件而阻 塞; 14) t11 时刻,任务 TA1 延时到期,立刻抢占执行,又由于任务 TA1 等待一事件而阻塞; ; 15) t12 时刻,任务 TA2 执行,保持信号量 mutex;以后系统再次出现优先级反转现象; 16) 系统如此周而复始地运行…… 2.2 操作系统配置 修改 uC_OS-II/OS_CFG.h: #define OS_MAX_EVENTS /*最多可以有 10 个事件*/ 10 #define OS_MAX_FLAGS 5 /*最多可以有 5 个事件标志*/ #define OS_MAX_MEM_PART 5 /*最多可以划分 5 个内存块*/ #define OS_MAX_QS 2 /*最多可以使用 2 个队列*/ #define OS_MAX_TASKS 9 #define OS_LOWEST_PRIO 24 /*最多可以创建 9 个任务*/ /*任务优先级不可以大于 24*/ #define OS_TASK_IDLE_STK_SIZE 1024 /*空闲任务堆栈大小*/ #define OS_TASK_STAT_EN 1 /*是否允许使用统计任务*/ #define OS_TASK_STAT_STK_SIZE 1024 /*统计任务堆栈大小*/ #define OS_FLAG_EN 1 /*是否允许使用事件标志功能*/ #define OS_FLAG_WAIT_CLR_EN 1 /*是否允许等待清除事件标志*/ #define OS_FLAG_ACCEPT_EN 1 /*是否允许使用 OSFlagAccept()*/ #define OS_FLAG_DEL_EN 1 /*是否允许使用 OSFlagDel()*/ #define OS_FLAG_QUERY_EN 1 /*是否允许使用 OSFlagQuery()*/ #define OS_MBOX_EN 0 #define OS_MEM_EN 1 /*是否允许使用内存管理的功能*/ #define OS_MEM_QUERY_EN 1 /*是否允许使用 OSMemQuery()*/ /*是否允许使用邮箱功能*/ 35 #define OS_MUTEX_EN 1 /*是否允许使用互斥信号量的功能*/ #define OS_MUTEX_ACCEPT_EN 1 /*是否允许使用 OSMutexAccept()*/ #define OS_MUTEX_DEL_EN 1 /*是否允许使用 OSMutexDel()*/ #define OS_MUTEX_QUERY_EN 1 /*是否允许使用 OSMutexQuery()*/ #define OS_Q_EN 0 /*是否允许使用队列功能*/ #define OS_SEM_EN 1 /*是否允许使用信号量功能*/ #define OS_SEM_ACCEPT_EN 1 /*是否允许使用 OSSemAccept()*/ #define OS_SEM_DEL_EN 1 /*是否允许使用 OSSemDel() */ #define OS_SEM_QUERY_EN 1 /*是否允许使用 OSSemQuery()*/ #define OS_TASK_CHANGE_PRIO_EN 1 /*是否允许使用 OSTaskChangePrio()*/ #define OS_TASK_CREATE_EN 1 /*是否允许使用 OSTaskCreate()*/ #define OS_TASK_CREATE_EXT_EN 1 /*是否允许使用 OSTaskCreateExt()*/ #define OS_TASK_DEL_EN 1 /*是否允许使用 OSTaskDel()*/ #define OS_TASK_SUSPEND_EN 1 /*是否允许使用 OSTaskSuspend() and OSTaskResume()*/ #define OS_TASK_QUERY_EN 1 /*是否允许使用 OSTaskQuery()*/ #define OS_TIME_DLY_HMSM_EN 1 /*是否允许使用 OSTimeDlyHMSM()*/ #define OS_TIME_DLY_RESUME_EN 1 /*是否允许使用 OSTimeDlyResume()*/ #define OS_TIME_GET_SET_EN 1 /*是否允许使用 OSTimeGet() 和 OSTimeSet()*/ #define OS_SCHED_LOCK_EN 1 /*是否允许使用 OSSchedLock()和 OSSchedUnlock()*/ #define OS_TICKS_PER_SEC 200 /*设置每秒之内的时钟节拍数目*/ 2.3 源程序说明 首先,在 main()函数中创建一个二值信号量: mutex=OSSemCreate(1); 然后,在 main()函数中创建 TaskStart 任务。 TaskStart 任务 在 TaskStart 任务中创建并启动所有的应用任务 TA0, TA1,TA2。 static void TaskStartCreateTasks (void) { INT8U i; 36 for (i = 0; i <N_TASKS; i++) { /* Create N_TASKS identical tasks */ /* Each task will pass its own id */ TaskData[i] = i; } OSTaskCreate(Task0, (void *)&TaskData[0], &TaskStk[0][TASK_STK_SIZE - 1], 5); OSTaskCreate(Task1, (void *)&TaskData[1], &TaskStk[1][TASK_STK_SIZE - 1], 6); OSTaskCreate(Task2, (void *)&TaskData[2], &TaskStk[2][TASK_STK_SIZE - 1], 7); } 任务 TA0 的优先级最高,它需要使用信号量 mutex: void Task0 (void *pdata) { INT8U err; INT8U id; INT16U value; id=*(int *)pdata; for (;;) { printk("Task %d is waitting a event.\n",id); OSTimeDly(200); /* Delay 200 clock tick printk("The event of Task %d come.\n",id); printk("Task %d is try to get mutex.\n",id); OSSemPend(mutex,0,&err); /* Acquire mutex */ switch(err) { case OS_NO_ERR: printk("Task %d has got the mutex.\n",id); printk("\n"); break; default: printk("Task %d is suspended.\n",id); printk("\n"); } OSTimeDly(200); printk("Task %d release mutex.\n",id); /* Delay 200 clock tick OSSemPost(mutex); /* Release mutex */ */ */ } } 任务 TA1 具有中等优先级,它不使用信号量: void Task1 (void *pdata) 37 { INT8U err; INT8U id; int i; id=*(int *)pdata; for (;;) { printk("Task %d is waitting a event.\n",id); OSTimeDly(100); printk("The event of Task %d come.\n",id); OSTimeDly(100); /* Delay 100 clock tick */ } } 任务 TA2 的优先级最低,和高优先级任务 TA0 共用信号量 mutex: void Task2 (void *pdata) { INT8U err; INT8U id; INT16U value; id=*(int *)pdata; int i; for (;;) { printk("Task %d is trying to get mutex.\n",id); OSSemPend(mutex,0,&err); /* Acquire mutex */ switch(err) { case OS_NO_ERR: { printk("\n"); printk("----------------------------------\n"); printk("Task %d has got the mutex.\n",id); OSTimeDly(200); /* Delay 100 clock tick */ break; } default : 38 { printk("Task %d is failed to get mutex.\n",id); printk("\n"); OSTimeDly(200); /* Delay 100 clock tick */ break; } } printk("Task %d release mutex.\n",id); printk("\n"); OSSemPost(mutex); /* Release mutex */ } } 3 运行及观察应用输出信息 39 4 本实验中所用到的 µC/OS-II 相关函数 4.1 OSSemCreate() 该函数建立并初始化一个信号量,信号量的作用如下: 允许一个任务和其他任务或者中断同步 取得设备的使用权 标志事件的发生 函数原型:OSSemCreate( INT16U value); 参数说明:value 参数是所建立的信号量的初始值,可以取 0 到 65535 之间的任何值。 返回值:OSSemCreate()函数返回指向分配给所建立的信号量的控制块的指针。如果没有可 用的控制块,OSSemCreate()函数返回空指针。 4.2 OSSemPend() 该函数用于任务试图取得设备的使用权、任务需要和其他任务或中断同步、任务需要等待特 定事件的发生的场合。如果任务调用 OSSemPend()函数时,信号量的值大于零,OSSemPend() 函数递减该值并返回该值。如果调用时信号量值等于零,OSSemPend()函数将任务加入该信号量 的等待队列。OSSemPend()函数挂起当前任务直到其他的任务或中断设置信号量或超出等待的预 期时间。如果在预期的时钟节拍内信号量被设置,μC/OS-Ⅱ默认让最高优先级的任务取得信号量并 回到就绪状态。一个被 OSTaskSuspend()函数挂起的任务也可以接受信号量,但这个任务将一直 保持挂起状态直到通过调用 OSTaskResume()函数恢复该任务的运行。 函数原型:Void OSSemPend ( OS_EVNNT *pevent, INT16U timeout, int8u *err ); 参数说明: pevent 是指向信号量的指针。该指针的值在建立该信号量时可以得到。(参考 OSSemCreate ()函数) 。 Timeout 允许一个任务在经过了指定数目的时钟节拍后还没有得到需要的信号量时恢复就 绪状态。如果该值为零表示任务将持续地等待信号量,最大的等待时间为 65535 个时钟节拍。这个 时间长度并不是非常严格的,可能存在一个时钟节拍的误差。 Err 是指向包含错误码的变量的指针。 返回值: OSSemPend()函数返回的错误码可能为下述几种: OS_NO_ERR :信号量不为零。 OS_TIMEOUT :信号量没有在指定数目的时钟周期内被设置。 OS_ERR_PEND_ISR :从中断调用该函数。虽然规定了不允许从中断调用该函数,但 μC/OS-Ⅱ 仍然包含了检测这种情况的功能。 OS_ERR_EVENT_TYPE :pevent 不是指向信号量的指针。 40 4.3 OSemPost() 该函数用于设置指定的信号量。如果指定的信号量是零或大于零,OSSemPost()函数递增该信 号量的值并返回。如果有任何任务在等待该信号量,则最高优先级的任务将得到信号量并进入就绪 状态。任务调度函数将进行任务调度,决定当前运行的任务是否仍然为最高优先级的就绪任务。 函数原型:INT8U OSSemPost(OS_EVENT *pevent); 参数说明:pevent 是指向信号量的指针。该指针的值在建立该信号量时可以得到。(参考 OSSemCreate()函数) 。 返回值: OSSemPost()函数的返回值为下述之一: OS_NO_ERR :信号量被成功地设置 OS_SEM_OVF :信号量的值溢出 OS_ERR_EVENT_TYPE :pevent 不是指向信号量的指针 4.4 OSTimeDly() 该函数用于将一个任务延时若干个时钟节拍。如果延时时间大于 0,系统将立即进行任务调度。 延时时间的长度可从 0 到 65535 个时钟节拍。延时时间 0 表示不进行延时,函数将立即返回调用者。 延时的具体时间依赖于系统每秒钟有多少个时钟节拍(由文件 SO_CFG.H 中的 OS_TICKS_PER_SEC 宏来设定) 。 函数原型:void OSTimeDly ( INT16U ticks); 参数说明:ticks 为要延时的时钟节拍数。 返回值:无 41 实验 3 优先级继承 1 实验目的 掌握嵌入式实时操作系统 µC/OS-II 解决优先级反转的策略——优先级继承的原理。 2 原理及程序结构 2.1 实验设计 2.1.1 优先级继承原理 优先级继承的主要思想是:当高优先级任务因申请某共享资源失败被阻塞时,把当前拥有该 资源的、且优先级较低的任务的优先级提升,提升的高度等于这个高优先级任务的优先级。在 µC/OS-II 中,在创建管理共享资源的互斥信号量时,可以指定一个 PIP(优先级继承优先级) ,之后 可以把拥有共享资源的任务优先级提升到这个高度。具体过程如下: 1. 当任务 A 申请共享资源 S 时,首先判断是否有别的任务正在占用资源 S,若无,则任务 A 获得资源 S 并继续执行; 2. 如果任务 A 申请共享资源 S 时任务 B 正在使用该资源,则任务 A 被挂起,等待任务 B 释 放该资源;同时判断任务 B 的优先级是否低于任务 A 的,若高于任务 A,则维持任务 B 的优先级不变; 3. 如果任务 B 的优先级低于任务 A 的,则提升任务 B 的优先级到 PIP,当任务 B 释放资源后, 再恢复其原来的优先级。 2.1.2 设计要点 在本实验中设计了处于不同优先级的应用任务,如下图所示: 42 优先级 8 10 1 2 2 2 2 1 11 0 2 2 1 1 1 0 12 t1 t2 t3 2 2 0 t4 任务 TASK0: 0 t5 t6 t7 t8 任务 TASK1: 1 t9 t10 t11 t12 t13 t14 任务 TASK2 : 2 图 3-1 注意:图中的栅格并不代表严格的时间刻度,而仅仅表现各个任务启动和执行的相对先后关系。 这 3 个应用任务因为要竞争同一互斥资源 mutex 而相互制约。其中,任务 TASK0 的原始优先 级最低,任务 TASK1 的原始优先级中等,任务 TASK2 的原始优先级最高。在使用 mutex 时采用优 先级继承策略,并指定各任务在使用 mutex 时的 PIP(优先级继承优先级)为 8。 2.1.3 系统的运行流程 TaskStart 任务首先运行,由它创建其他 3 个应用任务: void TaskStartCreateTasks (void) { INT8U i; for (i = 0; i <N_TASKS; i++) { TaskData[i] = i; /* Create N_TASKS identical tasks /* Each task will pass its own id */ */ OSTaskCreate(Task, (void *)&TaskData[i], &TaskStk[i][TASK_STK_SIZE - 1], 12-i); } } 每个任务的代码均如下所示: void Task (void *pdata) { INT8U err; INT8U id; id=*(int *)pdata; 43 for (;;) { printk("task %d try to get the mutex. \n", id); OSMutexPend(mutex, 0, &err); /* Acquire mutex to get continue */ printk("task %d is getting the mutex. \n", id); OSTimeDlyHMSM(0, 0, 0, 200); printk("task %d /* Wait 200 minisecond */ releases the mutex. \n", id); OSMutexPost(mutex); /* Release mutex OSTimeDlyHMSM(0, 0, 0, (3-id)*150); */ /* Wait (3-id)*150 minisecond */ } } TaskStart 任务挂起后,操作系统调度它所创建的 3 个应用任务,整个系统的运行流程如下: (1)TASK2 拥有最高优先级 10,最先开始运行,它在 t1 时刻,执行 OSMutexPend(mutex, 0, &err),成功获得互斥信号量 mutex; (2)t2 时刻,TASK2 执行延时函数 OSTimeDlyHMSM(0, 0, 0, 200)被挂起 200ms,操作系统 调度优先级次之的 TASK1 运行; (3)t3 时刻,TASK1 执行 OSMutexPend(mutex, 0, &err)申请互斥信号量失败后被阻塞,操作 系统调度优先级最低的 TASK0 运行。TASK0 执行 OSMutexPend(mutex, 0, &err)申请互斥信号量失 败后也被阻塞。在 TASK1 和 TASK0 被阻塞的时候,由于当前拥有 mutex 的 TASK2 优先级最高, 因此保持其优先级不变。当所有的应用任务都阻塞时,系统有一个空闲任务在运行,这个空闲任务 是在初始化操作系统的时候创建的,它的优先级最低; (4)t4 时刻,TASK2 延时到并运行,它执行 OSMutexPost(mutex)释放互斥信号量并由 TASK1 获得此信号量。t5 时刻 TASK2 将自己延时一段时间(150ms),任务回到运行状态; (5)TASK1 执行延时函数 OSTimeDlyHMSM(0, 0, 0, 200)后被挂起 200ms,空闲任务运行; 在 t6 时刻 TASK2 因延时时间到恢复执行,它执行 OSMutexPend(mutex, 0, &err)申请互斥信号量失败 后被阻塞,空闲任务运行。此时操作系统发现当前拥有信号量的任务 TASK1 的优先级低于 TASK2 的,因此将 TASK1 的优先级提升到 PIP,但 TASK1 此时仍处于延时状态未运行; (7)t7 时刻,TASK1 延时到,它在高优先级(PIP)下继续运行,调用 OSMutexPost(mutex) 释放互斥信号量并由 TASK2 获得此信号量,TASK1 的优先级恢复到原来的高度,而 TASK2 因优先 级较高而抢占 TASK1 运行(在 t8 时刻) ; (8)TASK2 又将自己延时一段时间(200ms),任务 TASK1 恢复执行后也将自己延时一段时 间(300ms) ,空闲任务运行;t9 时刻 TASK2 延时时间先到,它执行 OSMutexPost(mutex)释放互斥 信号量,此时 TASK0 获得此信号量; (9)t10 时刻,TASK2 延时(150ms) ,任务 TASK0 被调度运行;TASK0 在打印输出信息后, 44 将自己延时(200ms) ,空闲任务运行; (10)t11 时刻,TASK1 延时到恢复运行。TASK1 执行 OSMutexPend(mutex, 0, &err)申请互斥 信号量失败后被阻塞;此时操作系统发现当前拥有信号量的 TASK0 优先级低于 TASK1 的,因此提 升 TASK0 的优先级到 PIP,空闲任务运行; (11)t12 时刻,TASK2 延时到恢复运行,它执行 OSMutexPend(mutex, 0, &err)申请互斥信号 量失败后被阻塞;此时拥有信号量的 TASK0 的优先级已经被提升到了 PIP,且高于 TASK2 的优先 级,操作系统就没有针对 TASK0 再做优先级提升的工作。之后空闲任务运行; (12)t13 时刻,TASK0 延时到,在高优先级(PIP)下继续运行,它执行 OSMutexPost(mutex) 释放互斥信号量,其优先级恢复到原来的高度,并由 TASK2 获得此信号量,TASK2 抢占 TASK0 运 行; 系统如此周而复始地运行下去…… 2.2 操作系统配置 修改 uC_OS-II/OS_CFG.h: #define OS_MAX_EVENTS /*最多可以有 10 个事件*/ 10 #define OS_MAX_FLAGS 5 /*最多可以有 5 个事件标志*/ #define OS_MAX_MEM_PART 5 /*最多可以划分 5 个内存块*/ #define OS_MAX_QS 2 /*最多可以使用 2 个队列*/ #define OS_MAX_TASKS 6 #define OS_LOWEST_PRIO 14 /*最多可以创建 6 个任务*/ /*任务优先级不可以大于 14*/ #define OS_TASK_IDLE_STK_SIZE 1024 /*空闲任务堆栈大小*/ #define OS_TASK_STAT_EN 1 /*是否允许使用统计任务*/ #define OS_TASK_STAT_STK_SIZE 1024 /*统计任务堆栈大小*/ #define OS_FLAG_EN 0 /*是否允许使用事件标志功能*/ #define OS_MBOX_EN 0 #define OS_MEM_EN 1 /*是否允许使用内存管理的功能*/ #define OS_MEM_QUERY_EN 1 /*是否允许使用 OSMemQuery()*/ #define OS_MUTEX_EN 1 /*是否允许使用互斥信号量的功能*/ #define OS_MUTEX_ACCEPT_EN 1 /*是否允许使用 OSMutexAccept()*/ #define OS_MUTEX_DEL_EN 1 /*是否允许使用 OSMutexDel()*/ #define OS_MUTEX_QUERY_EN 1 /*是否允许使用 OSMutexQuery()*/ /*是否允许使用邮箱功能*/ 45 #define OS_Q_EN 0 /*是否允许使用队列功能*/ #define OS_SEM_EN 1 /*是否允许使用信号量功能*/ #define OS_SEM_ACCEPT_EN 1 /*是否允许使用 OSSemAccept()*/ #define OS_SEM_DEL_EN 1 /*是否允许使用 OSSemDel() */ #define OS_SEM_QUERY_EN 1 /*是否允许使用 OSSemQuery()*/ #define OS_TASK_CHANGE_PRIO_EN 1 /*是否允许使用 OSTaskChangePrio()*/ #define OS_TASK_CREATE_EN 1 /*是否允许使用 OSTaskCreate()*/ #define OS_TASK_CREATE_EXT_EN 1 /*是否允许使用 OSTaskCreateExt()*/ #define OS_TASK_DEL_EN 1 /*是否允许使用 OSTaskDel()*/ #define OS_TASK_SUSPEND_EN 1 /*是否允许使用 OSTaskSuspend() and OSTaskResume()*/ #define OS_TASK_QUERY_EN 1 /*是否允许使用 OSTaskQuery()*/ #define OS_TIME_DLY_HMSM_EN 1 /*是否允许使用 OSTimeDlyHMSM()*/ #define OS_TIME_DLY_RESUME_EN 1 /*是否允许使用 OSTimeDlyResume()*/ #define OS_TIME_GET_SET_EN 1 /*是否允许使用 OSTimeGet() 和 OSTimeSet()*/ #define OS_SCHED_LOCK_EN 1 #define OS_TICKS_PER_SEC 200 /*是否允许使用 OSSchedLock()和 OSSchedUnlock()*/ /*设置每秒之内的时钟节拍数目*/ 3 运行及观察应用输出信息 本实验运行后,从虚拟机的窗口中可以看到如下信息: 46 4 本实验中所用到的 µC/OS-II 相关函数 4.1 OSMutexCreate() 本实验通过调用 OSMutexCreate(8,&err),设置了一个互斥信号量,其中 8 为 PIP(优先级继承优 先级)的值。 4.2 OSMutexPend() 该函数用于获得互斥信号量,其具体执行过程如下(关键代码解释) : If ((INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8) == OS_MUTEX_AVAILABLE){} 判断互斥信号量是否被占用 pevent->OSEventCnt &= OS_MUTEX_KEEP_UPPER_8; /*Yes, Acquire the resource */ pevent->OSEventCnt |= OSTCBCur->OSTCBPrio; pevent->OSEventPtr = (void *)OSTCBCur; /* Save priority of owning task */ /* Point to owning task's OS_TCB */ 如果互斥信号量没有被占有,则获得互斥信号量,任务继续运行。 pip = (INT8U)(pevent->OSEventCnt >> 8); /* No, Get PIP from mutex mprio = (INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8); /* of mutex owner */ Get priority */ 47 ptcb = (OS_TCB *)(pevent->OSEventPtr); /* Point to TCB of mutex owner if (ptcb->OSTCBPrio != pip && mprio > OSTCBCur->OSTCBPrio) { /* */ Need to promote prio of owner?*/ 互斥信号量被占用,但是占有互斥信号量的任务的优先级高于请求互斥信号量的任务的优先 级,则不改变占有互斥信号量的任务的优先级。 ptcb->OSTCBPrio = pip; /* Change owner task prio to PIP */ ptcb->OSTCBY = ptcb->OSTCBPrio >> 3; ptcb->OSTCBBitY = OSMapTbl[ptcb->OSTCBY]; ptcb->OSTCBX = ptcb->OSTCBPrio & 0x07; ptcb->OSTCBBitX = OSMapTbl[ptcb->OSTCBX]; if (rdy == TRUE) { /* If task was ready at owner's priority ...*/ OSRdyGrp |= ptcb->OSTCBBitY; /* ... make it ready at new priority. */ OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX; } OSTCBPrioTbl[pip] = (OS_TCB *)ptcb; } 占有互斥信号量的任务的优先级低于请求互斥信号量的任务的优先级,改变占有互斥信号量的 任务的优先级到 PIP。 4.3 OSMutexPost() 该函数用于释放互斥信号量,具体代码说明如下: if (OSTCBCur->OSTCBPrio == pip) { /* Did we have to raise current task's priority? */ 判断任务的优先级是否在申请获得互斥信号量的过程中被改变。 if ((OSRdyTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) { OSRdyGrp &= ~OSTCBCur->OSTCBBitY; } OSTCBCur->OSTCBPrio = prio; OSTCBCur->OSTCBY = prio >> 3; OSTCBCur->OSTCBBitY = OSMapTbl[OSTCBCur->OSTCBY]; OSTCBCur->OSTCBX = prio & 0x07; OSTCBCur->OSTCBBitX = OSMapTbl[OSTCBCur->OSTCBX]; OSRdyGrp |= OSTCBCur->OSTCBBitY; OSRdyTbl[OSTCBCur->OSTCBY] |= OSTCBCur->OSTCBBitX; OSTCBPrioTbl[prio] = (OS_TCB *)OSTCBCur; } OSTCBPrioTbl[pip] = (OS_TCB *)1; /* Reserve table entry */ 如果任务的优先级在申请获得互斥信量的过程中被改变,则还原其初始优先级。 48 5 应用调试过程 在 LambdaTOOL 的调试界面中, 在 “优先级继承.c”文件中 Task 任务代码调用函数 OSMutexPend() 和 OSMutexPost()处设置断点。 当程序运行至断点处时,选择“单步跳入”运行模式,可以进入到 OS_MUTEX.c 文件中,查 看 OSMutexPend()和 OSMutexPost()函数的运行过程,深入了解 µC/OS-II 操作系统的内核代码。 49 50 实验 4 信号量:哲学家就餐问题的实现 1 实验目的 掌握在基于嵌入式实时操作系统 µC/OS-II 的应用中,任务使用信号量的一般原理。通过经典的 哲学家就餐实验,了解如何利用信号量来对共享资源进行互斥访问。 2 原理及程序结构 2.1 实验设计 五个哲学家任务(ph1、ph2、ph3、ph4、ph5)主要有两种过程:思考(即睡眠一段时间)和就 餐。每个哲学家任务在就餐前必须申请并获得一左一右两支筷子,就餐完毕后释放这两支筷子。五 个哲学家围成一圈,每两人之间有一支筷子。一共有五支筷子,在该实验中用了五个互斥信号量来 代表。如图 4-1 所示: 图 4-1 每个任务的代码都一样,如下所示: INT8U err; INT8U i; INT8U j; i=*(int *)pdata; j=(i+1) % 5; 51 for (;;) { /* 首 先 哲 学 家 处 于 TASK_Thinking_To_Hungry(i); thinking 状 态 , 然 后 执 行 TASK_Thinking_To_Hungry()函数进入 hungry 状态*/ printk("\n"); /*申请信号量,在获得两个信号量后执行 TASK_Eat()函数进入 eating 状态*/ OSSemPend(fork[i], 0, &err); OSSemPend(fork[j], 0, &err); TASK_Eat(i); printk("\n"); /* Acquire semaphores to eat */ /*释放信号量*/ OSSemPost(fork[j]); OSSemPost(fork[i]); OSTimeDly(200); /* Release semaphore /* Delay 200 clock tick */ */ } 2.2 操作系统配置 修改 uC_OS-II/OS_CFG.h: #define OS_MAX_EVENTS /*最多可以有 10 个事件*/ 10 #define OS_MAX_FLAGS 5 /*最多可以有 5 个事件标志*/ #define OS_MAX_MEM_PART 5 /*最多可以划分 5 个内存块*/ #define OS_MAX_QS 2 /*最多可以使用 2 个队列*/ #define OS_MAX_TASKS 8 #define OS_LOWEST_PRIO 14 /*最多可以创建 8 个任务*/ /*任务优先级不可以大于 14*/ #define OS_TASK_IDLE_STK_SIZE 1024 /*空闲任务堆栈大小*/ #define OS_TASK_STAT_EN 1 /*是否允许使用统计任务*/ #define OS_TASK_STAT_STK_SIZE 1024 /*统计任务堆栈大小*/ #define OS_FLAG_EN 1 /*是否允许使用事件标志功能*/ #define OS_FLAG_WAIT_CLR_EN 1 /*是否允许等待清除事件标志*/ #define OS_FLAG_ACCEPT_EN 1 /*是否允许使用 OSFlagAccept()*/ #define OS_FLAG_DEL_EN 1 /*是否允许使用 OSFlagDel()*/ #define OS_FLAG_QUERY_EN 1 /*是否允许使用 OSFlagQuery()*/ #define OS_MBOX_EN 0 #define OS_MEM_EN 0 /*是否允许使用邮箱功能*/ /*是否允许使用内存管理的功能*/ 52 #define OS_MUTEX_EN 0 /*是否允许使用互斥信号量的功能*/ #define OS_Q_EN 0 /*是否允许使用队列功能*/ #define OS_SEM_EN 1 /*是否允许使用信号量功能*/ #define OS_SEM_ACCEPT_EN 1 /*是否允许使用 OSSemAccept()*/ #define OS_SEM_DEL_EN 1 /*是否允许使用 OSSemDel() */ #define OS_SEM_QUERY_EN 1 /*是否允许使用 OSSemQuery()*/ #define OS_TASK_CHANGE_PRIO_EN 1 /*是否允许使用 OSTaskChangePrio()*/ #define OS_TASK_CREATE_EN 1 /*是否允许使用 OSTaskCreate()*/ #define OS_TASK_CREATE_EXT_EN 1 /*是否允许使用 OSTaskCreateExt()*/ #define OS_TASK_DEL_EN 1 /*是否允许使用 OSTaskDel()*/ #define OS_TASK_SUSPEND_EN 1 /*是否允许使用 OSTaskSuspend() and OSTaskResume()*/ #define OS_TASK_QUERY_EN 1 /*是否允许使用 OSTaskQuery()*/ #define OS_TIME_DLY_HMSM_EN 1 /*是否允许使用 OSTimeDlyHMSM()*/ #define OS_TIME_DLY_RESUME_EN 1 /*是否允许使用 OSTimeDlyResume()*/ #define OS_TIME_GET_SET_EN 1 /*是否允许使用 OSTimeGet() 和 OSTimeSet()*/ #define OS_SCHED_LOCK_EN 1 #define OS_TICKS_PER_SEC 200 /*是否允许使用 OSSchedLock()和 OSSchedUnlock()*/ /*设置每秒之内的时钟节拍数目*/ 3 运行及观察应用输出信息 开始,所有的哲学家先处于 thinking 状态,然后都进入 hungry 状态: 53 之后首先获得两个信号量的 1、3 号哲学家开始 eating,待他们释放相关信号量之后,哲学家 2、5、 4 获得所需的信号量并 eating: 应用如此这般地循环执行程序下去…… 54 4 本实验中所用到的 µC/OS-II 相关函数 OSSemCreate() OSSemPend() OSSemPost() OSTimeDly() 函数的具体介绍请参考实验“优先级继承”的相关内容。 55 实验 5 µC/OS-II 的内存管理 1 实验目的 掌握嵌入式实时操作系统 µC/OS-II 内存管理中内存分配和回收的功能。 2 原理及程序结构 2.1 实验设计 2.1.1 设计要点 在该实验中,需要用到 µC/OS-II 内存管理中内存分配和回收的功能。为此,设计了如下图所示 的应用: 试图申请内 存块3,失败。 内存块 试图申请内存 块3,失败。 3 2 1 t1 未分配: t2 t3 t5 t6 t4 t7 t8 t9 t10 t11 t12 时间 已分配: 图 5-1 注意:根据程序中设定的时间延迟,图 5-1 中的每个栅格对应 100 个系统时钟周期。为了防止 内存申请和释放的不合理导致的大块连续内存被分割成可用性小的小片的问题,µCos-II 将用于动态 内存分配的空间分成一些固定大小的内存块,根据应用申请的内存大小,分配适当数量的内存块。 图 5-1 的纵坐标就代表内存块。 56 2.1.2 运行流程 在 main()函数中,使用 µCos-II 的 OSMemCreate()函数创建一个用于动态内存分配的区域。 通过传递适当的调用参数,我们在该区域中划分了 2 个 128B 的内存块。如果成功创建这些内存块, µCos-II 会在内部建立并维护一个单向链表。 static void MemoryCreate() { INT8U err; //为 OSMemCreate 函数设置包含错误码的变量的指针。 CommMem = OSMemCreate(&CommBuf[0][0], 2, 128, &err); //OSMemCreate() 函数建立并初始化一块内存区。2 块,每块 128 字节。 } 以下就是刚刚初始化的内存区域的情况(为了表示方便,竖线右边表示已分配的内存块,竖线 左边表示未被分配的内存块,下同) : 2 1 此时在虚拟机中可以观察到以下的情况: 应用任务使用函数 OSMemGet(CommMem,&err)来申请内存块,根据当前内存区域的情况来判 断分配的结果: if(i==0) { CommMsg1=OSMemGet(CommMem,&err); //申请内存区中的一块。 if (err == OS_NO_ERR) { printk("First memory application HAS accept.\n"); /* 内存块已经分配 */ } else { printk("First memory alpplication NOT accept.\/n"); 57 } MemInfo(pdata); DispShow(); OSTimeDly(100); i++; } //得到内存区的信息。 //将内存区信息显示到屏幕上。 /* Wait 100tick */ 申请过后,内存区域在 t2 时刻的状态如下: 2 1 也就是说,内存块 1 已经被分配出去了,它的首地址储存在 OSMemGet()函数的返回参数中。 我们需要用一个变量(CommMsg1)记录这个返回值,以便在使用完这个内存块之后能正确地释放 它。 在虚拟机中观察到的结果如下: 在 t3 时刻,内存块 2 也被分配了出去: else if(i==1) { CommMsg2=OSMemGet(CommMem,&err); //申请内存区中的一块。 if (err == OS_NO_ERR) { printk("Second memory application HAS accept.\n"); /* 内存块已经分配 */ } else { printk("Second memory alpplication NOT accept.\n"); } 2 1 58 在虚拟机中看到的结果: 接下来在 t4 时刻,应用试图申请内存块 3,但此时系统因为没有足够的内存块可以分配,所以 失败: CommMsg3=OSMemGet(CommMem,&err); if (err == OS_NO_ERR) { printk("Third memory application HAS accept.\n"); /* 内存块已经分配 */ } else { printk("Third memory alpplication NOT accept.\n"); } 试图申请内 存块 3,失败。 2 1 在虚拟机中看到的运行结果: 在 t5、t6 和 t7 时刻,应用相继将内存块 3、2、1 归还给系统,但是由于之前申请内存块 3 的操 作是失败的,所以其归还操作是无效的: for(i=3;i>0;i--) 59 { ReleaseMem(i); MemInfo(pdata); DispShow(); OSTimeDly(10); } //释放第 i 个内存块。 //函数得到内存区的信息 //将内存区信息显示到屏幕上。 //延迟 10 个时钟周期 释放内存块的函数代码如下: void ReleaseMem(int i) { INT8U err; switch(i) { case 3:OSMemPut(CommMem,CommMsg3); if (err == OS_NO_ERR) { printk("Third memory has been released.\n"); /* 释放内存块 */ } else { printk("Third memory didn't been releasd.\n"); } break; case 2:OSMemPut(CommMem,CommMsg2); if (err == OS_NO_ERR) { printk("Second memory has been released.\n"); /* 释放内存块 */ } else { printk("Second memory didn't been releasd.\n"); } break; case 1:OSMemPut(CommMem,CommMsg1); if (err == OS_NO_ERR) { printk("First memory has been released.\n"); /* 释放内存块 */ } else 60 { printk("First memory didn't been releasd.\n"); } break; } } 过程如下: 试图归还内 存块 3,无效。 2 2 1 1 2 1 在虚拟机中看到的运行结果: 这里应该注意,µCos-II 的内存块归还函数是:OSMemPut(CommMem,CommMsg3); 它的返回值只有两种:OS_NO_ERR :成功归还内存块 OS_MEM_FULL :内存区已满,不能再接受更多释放的内存块。出现这种 情况说明用户程序出现了错误,归还了多于用 OSMemGet()函数得到的内 存块。 如果先归还内存块 1,后归还内存块 2,则是如下情况: 1 2 61 虽然内存块的地址并没有变化,但是链表的结构发生了变化。也就是说,如果进行多次内存分 配和归还的话,那么最终的链表和初始化时的链表会完全不同。 另外,在这个应用中使用了两个函数: MemInfo(pdata); DispShow(); 它们的代码如下: static void DispShow() { INT8U err; INT8U s[4][50]; //函数得到内存区的信息 //将内存区信息显示到屏幕上。 OS_MEM_DATA mem_data; OSMemQuery(CommMem, &mem_data); printk( "the pointer to the begining of memory address is: %d\n",(int)(mem_data.OSAddr)); printk( "the size of blocks in this memory area is: %d\n",(int)(mem_data.OSBlkSize)); printk( "the number of free blocks in this memory area is: %d\n", (int)(mem_data.OSNFree)); printk( "the number of using blocks in this memory area is: %d\n", (int)mem_data.OSNUsed); printk("\n\n\n"); OSTimeDlyHMSM(0,0,8,0); } void MemInfo(void *pdata) //内存空间信息。 { INT8U err; //为函数设置包含错误码的变量指针。 pdata = pdata; OS_MEM_DATA mem_data; err = OSMemQuery(CommMem, &mem_data); //OSMemQuery()函数得到内存区的信息。 } 2.2 操作系统配置 修改 uC_OS-II/OS_CFG.h: #define OS_MAX_EVENTS /*最多可以有 10 个事件*/ 10 #define OS_MAX_FLAGS 5 /*最多可以有 5 个事件标志*/ #define OS_MAX_MEM_PART 5 /*最多可以划分 5 个内存块*/ #define OS_MAX_QS 2 /*最多可以使用 2 个队列*/ #define OS_MAX_TASKS 9 #define OS_LOWEST_PRIO 24 #define OS_TASK_IDLE_STK_SIZE /*最多可以创建 9 个任务*/ /*任务优先级不可以大于 24*/ 1024 /*空闲任务堆栈大小*/ 62 #define OS_TASK_STAT_EN 1 /*是否允许使用统计任务*/ #define OS_TASK_STAT_STK_SIZE 1024 /*统计任务堆栈大小*/ #define OS_FLAG_EN 0 /*是否允许使用事件标志功能*/ #define OS_MBOX_EN 0 #define OS_MEM_EN 1 /*是否允许使用内存管理的功能*/ #define OS_MEM_QUERY_EN 1 /*是否允许使用 OSMemQuery()*/ #define OS_MUTEX_EN 1 /*是否允许使用互斥信号量的功能*/ #define OS_Q_EN 0 /*是否允许使用队列功能*/ #define OS_SEM_EN /*是否允许使用邮箱功能*/ 0 /*是否允许使用信号量功能*/ #define OS_TASK_CHANGE_PRIO_EN 0 /*是否允许使用 OSTaskChangePrio()*/ #define OS_TASK_CREATE_EN 1 /*是否允许使用 OSTaskCreate()*/ #define OS_TASK_CREATE_EXT_EN 1 /*是否允许使用 OSTaskCreateExt()*/ #define OS_TASK_DEL_EN 1 /*是否允许使用 OSTaskDel()*/ #define OS_TASK_SUSPEND_EN 1 /*是否允许使用 OSTaskSuspend() and OSTaskResume()*/ #define OS_TASK_QUERY_EN 1 /*是否允许使用 OSTaskQuery()*/ #define OS_TIME_DLY_HMSM_EN 1 /*是否允许使用 OSTimeDlyHMSM()*/ #define OS_TIME_DLY_RESUME_EN 1 /*是否允许使用 OSTimeDlyResume()*/ #define OS_TIME_GET_SET_EN 1 /*是否允许使用 OSTimeGet() 和 OSTimeSet()*/ #define OS_SCHED_LOCK_EN 1 #define OS_TICKS_PER_SEC 200 /*是否允许使用 OSSchedLock()和 OSSchedUnlock()*/ /*设置每秒之内的时钟节拍数目*/ 3 本实验中所用到的 µC/OS-II 相关函数 本实验涉及到 µC/OS-II 的 4 个相关内存管理的函数: 3.1 OSMemCreate() 该函数建立并初始化一个用于动态内存分配的区域,该内存区域包含指定数目的、大小确定 63 的内存块。应用可以动态申请这些内存块并在用完后将其释放回这个内存区域。该函数的返回值就 是指向这个内存区域控制块的指针,并作为 OSMemGet(),OSMemPut() ,OSMemQuery()等 相关调用的参数。 函数原型:OSMemCreate( void *addr, INT32U nblks ,INT32U blksize, INT8U *err); 参数说明: addr 建立的内存区域的起始地址。 可以使用静态数组或在系统初始化时使用 malloc ()函数来分配这个区域的空间。 Nblks Blksize Err 内存块的数目。每一个内存区域最少需要定义两个内存块。 每个内存块的大小,最小应该能够容纳一个指针变量。 是指向包含错误码的变量的指针。Err 可能是如下几种情况: OS_NO_ERR :成功建立内存区域。 OS_MEM_INVALID_ADDR:非法地址,即地址为空指针。 OS_MEM_INVALID_PART :没有空闲的内存区域。 OS_MEM_INVALID_BLKS :没有为内存区域建立至少两个内存块。 OS_MEM_INVALID_SIZE :内存块大小不足以容纳一个指针变量。 返回值 :OSMemCreate()函数返回指向所创建的内存区域控制块的指针。如果创建失败, 函数返回空指针。 3.2 OSMemGet() 该函数用于从内存区域分配一个内存块。用户程序必须知道所建立的内存块的大小,并必须 在使用完内存块后释放它。可以多次调用 OSMemGet()函数。它的返回值就是指向所分配内存块 的指针,并作为 OSMemPut()函数的参数。 函数原型:OSMemGet(OS_MEM *pmem, INT8U *err); 参数说明:pmem 是指向内存区域控制块的指针,可以从 OSMemCreate()函数的返回值中得 到。 是指向包含错误码的变量的指针。Err 可能是如下情况: Err OS_NO_ERR :成功得到一个内存块。 OS_MEM_NO_FREE_BLKS :内存区域中已经没有足够的内存块。 返回值:OSMemGet()函数返回指向所分配内存块的指针。如果没有可分配的内存块,OSMemGet ()函数返回空指针。 3.3 OSMemPut() 该函数用于释放一个内存块,内存块必须释放回它原先所在的内存区域,否则会造成系统错 误。 函数原型:OSMemPut( OS_MEM *pmem, void *pblk); 参数说明:pmem 是指向内存区域控制块的指针,可以从 OSMemCreate()函数的返回值中得 到。 64 Pblk 是指向将被释放的内存块的指针。 返回值 :OSMemPut()函数的返回值为下述之一: OS_NO_ERR :成功释放内存块 OS_MEM_FULL :内存区域已满,不能再接受更多释放的内存块。这种情况说 明用户程序出现了错误,释放了多于用 OSMemGet()函数得到的内存块。 3.4 OSMemQuery() 该函数用于得到内存区域的信息。 函数原型:OSMemQuery(OS_MEM *pmem, OS_MEM_DATA *pdata); 参数说明: pmem 是指向内存区域控制块的指针,可以从 OSMemCreate()函数的返回值中得到。 Pdata 是一个指向 OS_MEM_DATA 数据结构的指针,该数据结构包含了以下的域: Void OSAddr; /*指向内存区域起始地址的指针 */ Void OSFreeList; /*指向空闲内存块列表起始地址的指针 */ INT32U OSBlkSize; /*每个内存块的大小 */ INT32U OSNBlks; /*该内存区域中的内存块总数 */ INT32U OSNFree; /*空闲的内存块数目 */ INT32U OSNUsed; /*已使用的内存块数目 */ 65 实验 6 时钟中断 1 实验目的 掌握嵌入式实时操作系统 µC/OS 中中断的使用情况。 2 原理及程序结构 2.1 实验设计 在本实验中,设计了三个任务 Task1、Task2、Task3,创建了一个信号量 InterruptSem。 整个系统的运行流程如下: 1) 系统初始化,在 TaskStart 任务中,创建并启动任务 Task1、Task2、Task3,优先级分别为 12,13,14。 2) 在 TaskStart 任务中创建一个信号量 InterruptSem(初值为 1) 。 3) 任务在 TaskStart 任务中挂起自己,操作系统实施调度,进入 Task1 运行; 4) 任务 Task1 睡眠 100ticks; 5) 任务 Task2 开始执行, 任务 Task2 获得信号量 InterruptSem; 6) 任务 Task2 睡眠 500tick,任务 Task3 投入运行,打印输出语句后延时,任务 Task1 睡眠时 间到继续投入运行,它申请信号量 InterruptSem 失败被阻塞; 7) 任务 Task3 投入运行,循环地打印输出语句。期间时钟中断不断产生,在中断处理程序中 对任务 Task2 的睡眠时间进行计数; 8) Task2 睡眠时间到后恢复运行,并释放信号量 InterruptSem; 9) Task1 获得信号量 InterruptSem 后抢占 Task2 运行; 10) Task1 使用完信号量 InterruptSem 后释放该信号量; 11) 系统从步骤 4 重复执行,一直运行下去…… 程序代码如下: void Task1 (void *pdata) { INT8U err; pdata=pdata; 66 for (;;) { OSTimeDly(100); printk("\nTask1is try to get semaphore.\n\n"); /*task1 delay 100 clock ticks */ OSSemPend(InterruptSem, 0, &err); /* Acquire semaphore to get into the room */ printk("Task1 has Succeed to obtain semaphore.\n"); printk("Task1 is delayed.\n\n"); OSTimeDly(200); printk("\nThe delay of Task1 finished .\n"); printk("Task1 release semaphore.\n"); OSSemPost(InterruptSem); /* Release semaphore */ OSTimeDly(200); } } void Task2 (void *pdata) { INT8U err; pdata=pdata; for (;;) { printk( "\nTask2 is try to get semaphore.\n"); OSSemPend(InterruptSem, 0, &err); /* Acquire semaphore to get into the room */ printk( "Task2 has Succeed to obtain semaphore.\n"); printk("Task2 is delayed.\n\n"); OSTimeDly(500); /*task2 delay 500 clock ticks */ printk("\nThe delay of Task2 finished .\n"); printk("Task2 release semaphore.\n"); OSSemPost(InterruptSem); /* Release semaphore */ OSTimeDly(200); } } void Task3 (void *pdata) { pdata=pdata; for (;;) { printk("Task3 has got the CPU:|||||||||||||||||||||||||||||||||||||\n"); OSTimeDly(100); } } 67 2.2 操作系统配置 修改 uC_OS-II/OS_CFG.h: #define OS_MAX_EVENTS /*最多可以有 10 个事件*/ 10 #define OS_MAX_FLAGS 5 #define OS_MAX_MEM_PART 10 #define OS_MAX_QS 2 #define OS_MAX_TASKS 9 #define OS_LOWEST_PRIO 24 /*最多可以有 5 个事件标志*/ /*最多可以划分 5 个内存块*/ /*最多可以使用 2 个队列*/ /*最多可以创建 9 个任务*/ /*任务优先级不可以大于 24*/ #define OS_TASK_IDLE_STK_SIZE 1024 /*空闲任务堆栈大小*/ #define OS_TASK_STAT_EN 1 /*是否允许使用统计任务*/ #define OS_TASK_STAT_STK_SIZE 1024 /*统计任务堆栈大小*/ #define OS_FLAG_EN 0 /*是否允许使用事件标志功能*/ #define OS_MBOX_EN 1 #define OS_MBOX_ACCEPT_EN 1 /*是否允许使用 OSMboxAccept() */ #define OS_MBOX_DEL_EN 1 /*是否允许使用 OSMboxDel()*/ #define OS_MBOX_POST_EN 1 /*是否允许使用 OSMboxPost()*/ #define OS_MBOX_POST_OPT_EN 1 /*是否允许使用 OSMboxPostOpt() */ #define OS_MBOX_QUERY_EN 1 /*是否允许使用 OSMboxQuery()*/ #define OS_MEM_EN 1 /*是否允许使用内存管理的功能*/ #define OS_MEM_QUERY_EN 1 /*是否允许使用 OSMemQuery()*/ #define OS_MUTEX_EN 1 /*是否允许使用互斥信号量的功能*/ #define OS_MUTEX_ACCEPT_EN 1 /*是否允许使用 OSMutexAccept()*/ #define OS_MUTEX_DEL_EN 1 /*是否允许使用 OSMutexDel()*/ #define OS_MUTEX_QUERY_EN 1 /*是否允许使用 OSMutexQuery()*/ #define OS_Q_EN 1 /*是否允许使用队列功能*/ #define OS_Q_ACCEPT_EN 1 /*是否允许使用 OSQAccept()*/ #define OS_Q_DEL_EN 1 /*是否允许使用 OSQDel()*/ #define OS_Q_FLUSH_EN 1 /*是否允许使用 OSQFlush()*/ #define OS_Q_POST_EN 1 /*是否允许使用 OSQPost()*/ /*是否允许使用邮箱功能*/ 68 #define OS_Q_POST_FRONT_EN 1 /*是否允许使用 OSQPostFront()*/ #define OS_Q_POST_OPT_EN 1 /*是否允许使用 OSQPostOpt()*/ #define OS_Q_QUERY_EN 1 /*是否允许使用 OSQQuery()*/ #define OS_SEM_EN 1 /*是否允许使用信号量功能*/ #define OS_SEM_ACCEPT_EN 1 /*是否允许使用 OSSemAccept()*/ #define OS_SEM_DEL_EN 1 /*是否允许使用 OSSemDel() */ #define OS_SEM_QUERY_EN 1 /*是否允许使用 OSSemQuery()*/ #define OS_TASK_CHANGE_PRIO_EN 1 /*是否允许使用 OSTaskChangePrio()*/ #define OS_TASK_CREATE_EN 1 /*是否允许使用 OSTaskCreate()*/ #define OS_TASK_CREATE_EXT_EN 1 /*是否允许使用 OSTaskCreateExt()*/ #define OS_TASK_DEL_EN 1 /*是否允许使用 OSTaskDel()*/ #define OS_TASK_SUSPEND_EN 1 /*是否允许使用 OSTaskSuspend() and OSTaskResume()*/ #define OS_TASK_QUERY_EN 1 /*是否允许使用 OSTaskQuery()*/ #define OS_TIME_DLY_HMSM_EN 0 /*是否允许使用 OSTimeDlyHMSM()*/ #define OS_TIME_DLY_RESUME_EN 1 /*是否允许使用 OSTimeDlyResume()*/ #define OS_TIME_GET_SET_EN 1 /*是否允许使用 OSTimeGet() 和 OSTimeSet()*/ #define OS_SCHED_LOCK_EN 1 #define OS_TICKS_PER_SEC 200 /*是否允许使用 OSSchedLock()和 OSSchedUnlock()*/ /*设置每秒之内的时钟节拍数目*/ 69 3 运行及观察应用输出信息 70 4 本实验中所用到的 µC/OS-II 相关函数 OSSemCreate() OSSemPend() OSSemPost() OSTimeDly() 函数的具体介绍请参考实验“优先级继承”的相关内容。 71 实验 7 消息队列 1 实验目的 掌握嵌入式实时操作系统 µC/OS-II 中消息队列机制的基本原理和使用方法。 2 原理及程序结构 2.1 实验设计 在本实验中,设计了 6 个普通应用任务:TA0(优先级为 1) 、TA1(优先级为 2) 、TA2(优先 级为 3) 、TA3(优先级为 4) 、TA4(优先级为 5) 、TA5(优先级为 6),以及一个控制任务 TaskCon (优先级为 7) 。 µC/OS-II 中,等待消息的任务总是按照优先级的高低来决定获得消息的顺序的。 具体的设计思路为: 创建队列的功能:创建一个等待属性为 FIFO 的消息队列 1;创建一个等待属性为 LIFO 的 消息队列 2。 考察以 FIFO 方式释放消息的消息队列:由任务 TA0、TA1、TA2 等待队列 1 中的消息。 TA0、TA1、TA2 使用相同的任务代码(Taskq1 函数) 。 考察以 LIFO 方式释放消息的消息队列:由任务 TA3、TA4、TA5 等待队列 2 中的消息。 TA3、TA4、TA5 使用相同的任务代码(Taskq2 函数) 。 考察清空消息队列、查询消息队列的功能:TaskCon 任务向队列 2 中连续发送 6 条消息, 然后查询消息数;清空该队列后再查询。 考察删除消息队列的安全性:在任务 TA3、TA4、TA5 等待队列 2 中的消息的过程中,让 TaskCon 删除队列 2;当队列 2 被删除后,检查任务 TA3、TA4、TA5 调用接收消息的函数 是否返回错误码。 2.2 源程序说明 在 main()函数中通过 q1 = OSQCreate(&Msg1[0],6);q2 = OSQCreate(&Msg2[0],6); 创建两个消 息队列。 在 TaskStart 任务中创建并启动所有的应用任务。 static void TaskStartCreateTasks (void) { 72 INT8U i; for (i = 0; i <N_TASKS; i++) { TaskData1[i] = i; /* Create N_TASKS identical tasks */ /* Each task will pass its own id */ OSTaskCreate(Taskq1, (void *)&TaskData1[i], &TaskStk1[i][TASK_STK_SIZE - 1], i+1); } for (i = 0; i <N_TASKS; i++) { TaskData2[i] = i; /* Create N_TASKS identical tasks */ /* Each task will pass its own id */ OSTaskCreate(Taskq2, (void *)&TaskData2[i], &TaskStk2[i][TASK_STK_SIZE - 1], i+4); } OSTaskCreate(TaskCon, (void *)0, &TaskConStk[TASK_STK_SIZE - 1], i+4); /* Create control tasks */ } 应用任务 TA0 的代码(TA1,TA2 相同) :从队列 q1 中按 LIFO 方式取消息。 void Taskq1 (void *pdata) { INT8U err; INT8U id; char s[30]; void *mg; id=*(int *)pdata; for (;;) { OSTimeDlyHMSM(0, 0, 2, 0); mg=OSQPend(q1,0,&err); /* Wait 2 second /* apply for message */ */ switch(err){ case OS_NO_ERR:{ printk(" task %d has got the %s \n",id,(char *)mg); OSTimeDlyHMSM(0, 0, 0, 200*(4-id)); break; } /* If it is normally, just print the string.*/ default :{ 73 printk( "queue1 %d is empty. \n",id); OSTimeDlyHMSM(0, 0, 0, 200*(4-id)); break; } /* If the queue is empty or has been deleted, print another string.*/ } } } 应用任务 TA3 的代码(TA4,TA5 相同) :从队列 q2 中按 FIFO 方式取消息。 void Taskq2 (void *pdata) { INT8U err; INT8U id; char s[30]; void *mg; id=*(int *)pdata; for (;;) { OSTimeDlyHMSM(0, 0, 2, 0); mg=OSQPend(q2,0,&err); /* Wait 2 second /* apply for message */ */ switch(err){ case OS_NO_ERR:{ printk(" task %d has got the %s. \n", id+3, (char *)mg); OSTimeDlyHMSM(0, 0, 0, 200*(4-id)); break; } /* If it is normally, just print the string.*/ default :{ printk( "queue2 is empty,%d can't got the message. \n",id+3); OSTimeDlyHMSM(0, 0, 0, 200*(4-id)); break; } /* If the queue is empty or has been deleted, print another string.*/ } } } 74 控制任务 void TaskCon (void *pdata) { INT8U i,j; INT8U err; INT8U note=1; INT16U /* for flush the queue del=3; /* for delete the queue */ */ OS_EVENT *q; char ch[50]; OS_Q_DATA *data; static char *s[]={ /* queue1's message */ "message0","message1","message2","message3","message4","message5" }; static char *t[]={ /* queue2's message */ "messageA","messageB","messageC","messageD","messageE","messageF" }; pdata=pdata; for (;;) { printk("\n-------------Add message to queue1-------------\n"); for( i = 0 ; i < 6 ; i++ ) { err = OSQPostFront(q1,(void*)s[i]); /* post message to q1 LIFO */ switch(err){ case OS_NO_ERR:{ printk("the queue1 %d add %s\n",i,s[i]); OSTimeDlyHMSM(0, 0, 0, 150); break; } case OS_Q_FULL:{ printk("the queue1 is full, don't add.\n"); 75 OSTimeDlyHMSM(0, 0, 0, 150); break; } default :break; } } if(del>=0){ printk("\n-------------Add message to queue2-------------\n"); } for( j = 0 ; j < 6 ; j++ ) { err = OSQPost(q2,(void*)t[j]); /* post message to q2 FIFO */ switch(err){ case OS_NO_ERR:{ printk("the queue2 %d add %s\n",j,t[j]); OSTimeDlyHMSM(0, 0, 0, 150); break; } case OS_Q_FULL:{ printk("the queue2 is full, don't add. \n"); OSTimeDlyHMSM(0, 0, 0, 150); break; } default :break; } } if(del>=0){ if(note==1) { OSQFlush(q2); printk("\n--------------clear up the queue2--------------\n"); /* clear up the queue 2. */ note=0; } else 76 note=1; } err=OSQQuery(q2,data); /* get the information about q2 */ if(err==OS_NO_ERR){ printk("\n------------the queue2'information------------\n"); printk(" NextMsg:%s, NumMsg:%d, QSize:%d.\n",(char )data->OSMsg, data->OSNMsgs, data->OSQSize); printk("----------------------------------------------\n"); } /* print the information about q2 OSTimeDlyHMSM(0, 0, 0, 500); */ /* Wait 500 minisecond */ printk("\n------------------------------------\n",); if(del==0) { q=OSQDel(q2,OS_DEL_ALWAYS,&err); /* delete the q2 */ if(q==(OS_EVENT *)0) { printk(" already successful delete queue2 \n"); } } else { del--; printk( " not successful delete queue2 \n"); } printk("------------------------------------\n"); } } 2.3 操作系统配置 修改 uC_OS-II/OS_CFG.h: #define OS_MAX_EVENTS 10 /*最多可以有 10 个事件*/ #define OS_MAX_FLAGS 5 /*最多可以有 5 个事件标志*/ #define OS_MAX_MEM_PART 5 /*最多可以划分 5 个内存块*/ #define OS_MAX_QS 2 /*最多可以使用 2 个队列*/ 77 #define OS_MAX_TASKS 10 /*最多可以创建 10 个任务*/ #define OS_LOWEST_PRIO 14 /*任务优先级不可以大于 14*/ #define OS_TASK_IDLE_STK_SIZE 1024 /*空闲任务堆栈大小*/ #define OS_TASK_STAT_EN 1 /*是否允许使用统计任务*/ #define OS_TASK_STAT_STK_SIZE 1024 /*统计任务堆栈大小*/ #define OS_MBOX_EN 0 #define OS_MEM_EN 0 /*是否允许使用内存管理的功能*/ #define OS_MUTEX_EN 0 /*是否允许使用互斥信号量的功能*/ #define OS_Q_EN 1 /*是否允许使用队列功能*/ #define OS_Q_ACCEPT_EN 1 /*是否允许使用 OSQAccept()*/ #define OS_Q_DEL_EN 1 /*是否允许使用 OSQDel()*/ #define OS_Q_FLUSH_EN 1 /*是否允许使用 OSQFlush()*/ #define OS_Q_POST_EN 1 /*是否允许使用 OSQPost()*/ #define OS_Q_POST_FRONT_EN 1 /*是否允许使用 OSQPostFront()*/ /*是否允许使用邮箱功能*/ #define OS_Q_POST_OPT_EN 1 /*是否允许使用 OSQPostOpt()*/ #define OS_Q_QUERY_EN 1 /*是否允许使用 OSQQuery()*/ #define OS_SEM_EN 0 /*是否允许使用信号量功能*/ #define OS_TASK_CHANGE_PRIO_EN 0 /*是否允许使用 OSTaskChangePrio()*/ #define OS_TIME_DLY_HMSM_EN 1 /*是否允许使用 OSTimeDlyHMSM()*/ #define OS_TIME_DLY_RESUME_EN 1 /*是否允许使用 OSTimeDlyResume()*/ #define OS_TIME_GET_SET_EN 1 /*是否允许使用 OSTimeGet() 和 OSTimeSet()*/ #define OS_TICKS_PER_SEC 200 /*设置每秒之内的时钟节拍数目*/ 3 运行及观察应用输出信息 (1)首先,任务 TaskCon 将消息放入消息队列中: 78 (2)任务 TaskCon 清空消息队列 2,之后查询消息队列 2 的内容: (3)TA0—TA2 按照 LIFO 顺序获取消息: 79 (4)任务 TaskCon 再次将消息放入队列中,TA0—TA2 按照 LIFO 顺序获取消息,TA3—TA5 按照 FIFO 顺序获取消息。 80 (5)任务 TaskCon 将消息队列 2 删除以后,TA3—TA5 就不能获取消息了。 81 4 本实验中所用到的 µC/OS-II 相关函数 4.1 OSQCreate() 该函数用于建立一个消息队列。任务或中断可以通过消息队列向一个或多个任务发送消息。消 息的含义是和具体的应用密切相关的。 函数原型:OS_EVENT *OSQCreate( void **start, INT8U size); 参数说明: start 是消息内存区的首地址,消息内存区是一个指针数组。 Size 是消息内存区的大小。 返回值: OSQCreate()函数返回一个指向消息队列控制块的指针。如果没有空闲的控制块,OSQCreate() 函数返回空指针。 4.2 OSQPend() 该函数用于任务等待消息。消息通过中断或任务发送给需要的任务。消息是一个指针变量,在 不同的应用中消息的具体含义不同。如果调用 OSQPend()函数时队列中已经存在消息,那么该消 息被返回给 OSQPend()函数的调用者,该消息同时从队列中清除。如果调用 OSQPend()函数时 队列中没有消息,OSQPend()函数挂起调用任务直到得到消息或超出定义的超时时间。如果同时 有多个任务等待同一个消息,μC/OS-Ⅱ默认最高优先级的任务取得消息。一个由 OSTaskSuspend() 函数挂起的任务也可以接受消息,但这个任务将一直保持挂起状态直到通过调用 OSTaskResume() 函数恢复任务的运行。 函数原型:Void *OSQPend( OS_EVENT *pevent, INT16U timeout, INT8U *err); 参数: pevent 是指向消息队列的指针,该指针的值在建立该队列时可以得到。 (参考 OSMboxCreate ()函数) 。 Timeout 允许一个任务以指定数目的时钟节拍等待消息。超时后如果还没有得到消息则恢复 成就绪状态。如果该值设置成零则表示任务将持续地等待消息,最大的等待时间为 65535 个时钟节 拍。这个时间长度并不是非常严格的,可能存在一个时钟节拍的误差。 Err 是指向包含错误码的变量的指针。OSQPend()函数返回的错误码可能为下述几种: OS_NO_ERR :消息被正确地接受。 OS_TIMEOUT :消息没有在指定的时钟周期数内接收到消息。 82 OS_ERR_PEND_ISR :从中断调用该函数。虽然规定了不允许从中断中调用该函数,但 μC/OS-Ⅱ仍然包含了检测这种情况的功能。 OS_ERR_EVENT_TYPE :pevent 不是指向消息队列的指针。 返回值: OSQPend()函数返回取得的消息并将*err 置为 OS_NO_ERR。如果没有在指定数目的时钟节 拍内接受到消息,OSQPend()函数返回空指针并将*err 设置为 OS_TIMEOUT。 4.3 OSQPostFront() 该函数用于向消息队列发送消息。OSQPostFront()函数和 OSQPost()函数非常相似,不同 之处在于 OSQPostFront()函数将发送的消息插到消息队列的最前端。也就是说,OSQPostFront() 函数使得消息队列按照后入先出(LIFO)的方式工作,而不是先入先出(FIFO)。消息是一个指针 长度的变量,在不同的应用中消息的含义也可能不同。如果队列中已经存满消息,则此调用将返回 错误码。OSQPost()函数也是如此。在调用此函数时如果有任何任务在等待队列中的消息,则最 高优先级的任务将得到这个消息。如果等待消息的任务优先级比发送消息的任务优先级高,那么高 优先级的任务在得到消息后将立即抢占当前任务执行,也就是说,将发生一次任务切换。 函数原型:INT8U OSQPostFront(OS_EVENT *pevent, void *msg); 参数: pevent 是指向即将接收消息的消息队列的指针。该指针的值在建立队列时可以得到。(参考 OSQCreate()函数) 。 Msg 是即将发送的消息的指针。不允许传递一个空指针。 返回值: OSQPost()函数的返回值为下述之一: OS_NO_ERR :消息成功地放到消息队列中。 OS_MBOX_FULL :消息队列已满。 OS_ERR_EVENT_TYPE :pevent 不是指向消息队列的指针。 4.4 OSQPost() 该函数用于向消息队列发送消息。消息是一个指针长度的变量,在不同的应用中消息的含义也 可能不同。如果队列中已经存满消息,则此调用返回错误码。如果有任何任务在等待队列中的消息, 则最高优先级的任务将得到这个消息。如果等待消息的任务优先级比发送消息的任务优先级高,那 83 么高优先级的任务将在得到消息后立即抢占当前任务执行,也就是说,将发生一次任务切换。消息 是以先入先出(FIFO)方式进入队列的,即先进入队列的消息先被传递给任务。 函数原型:INT8U OSQPost(OS_EVENT *pevent, void *msg); 参数: pevent 是指向即将接受消息的消息队列的指针。该指针的值在建立队列时可以得到。(参考 OSQCreate()函数) 。 Msg 是即将发送给队列的消息。不允许传递一个空指针。 返回值: OSQPost()函数的返回值为下述之一: OS_NO_ERR :消息成功地放到消息队列中。 OS_MBOX_FULL :消息队列已满。 OS_ERR_EVENT_TYPE :pevent 不是指向消息队列的指针。 4.5 OSQFlush () 该函数用于清空消息队列。 函数原型:INT8U *OSQFlush(OS_EVENT *pevent); 参数: pevent 是指向消息队列的指针。该指针的值在建立队列时可以得到。 (参考 OSQCreate()函 数) 。 返回值: OSQFlush()函数的返回值为下述之一: OS_NO_ERR :消息队列被成功清空 OS_ERR_EVENT_TYPE :试图清除不是消息队列的对象 4.6 OSQQuery() 该函数用来取得消息队列的信息。用户程序必须建立一个 OS_Q_DATA 的数据结构,该结构用 来保存从消息队列的控制块得到的数据。通过调用该函数可以知道是否有任务在等待消息、有多少 个任务在等待消息、队列中有多少消息以及消息队列可以容纳的消息数。OSQQuery()函数还可以 得到即将被传递给任务的消息。 函数原型:INT8U OSQQuery(OS_EVENT *pevent, OS_Q_DATA *pdata); 参数: 84 pevent 是指向消息队列的指针。该指针的值在建立消息队列时可以得到。 (参考 OSQCreate() 函数) 。 Pdata 是指向 OS_Q_DATA 数据结构的指针,该数据结构包含下述成员: Void *OSMsg; /* 下一个可用的消息*/ INT16U OSNMsgs; /* 队列中的消息数目*/ INT16U OSQSize; /* 消息队列的大小 INT8U OSEventTbl[OS_EVENT_TBL_SIZE]; /* 消息队列的等待队列*/ INT8U OSEventGrp; */ 返回值: OSQQuery()函数的返回值为下述之一: OS_NO_ERR :调用成功 OS_ERR_EVENT_TYPE :pevent 不是指向消息队列的指针。 4.7 OSQDel() 该函数用于删除指定的消息队列。 4.8 OSTimeDlyHMSM() 该函数用于将一个任务延时若干时间。延时的单位是小时、分、秒、毫秒。调用 OSTimeDlyHMSM ()后,如果延时时间不为 0,系统将立即进行任务调度。 函数原型: void OSTimeDlyHMSM( INT8U hours, INT8U minutes,INT8U seconds,INT8U milli); 参数: hours 为延时小时数,范围从 0-255。 minutes 为延时分钟数,范围从 0-59。 seconds 为延时秒数,范围从 0-59 milli 为延时毫秒数,范围从 0-999。 需要说明的是,操作系统在处理延时操作时都是以时钟节拍为单位的,实际的延时时间是时钟 节拍的整数倍。如果系统时钟节拍的间隔是 10ms,而设定延时为 5ms 的话,则不会产生延时操作; 而如果设定延时为 15ms,则实际的延时是两个时钟节拍,也就是 20ms。 返回值: OSTimeDlyHMSM()的返回值为下述之一: OS_NO_ERR:函数调用成功。 OS_TIME_INVALID_MINUTES:参数错误,分钟数大于 59。 85 OS_TIME_INVALID_SECONDS:参数错误,秒数大于 59。 OS_TIME_INVALID_MILLI:参数错误,毫秒数大于 999。 OS_TIME_ZERO_DLY:四个参数全为 0。 86