1)实验平台:alientek NANO STM32F411 V1开发板2)摘自《正点原子STM32F4 开发指南(HAL 库版》关注官方微信号公众号,获取更多资料:正点原子
第三十六章 UCOSII 实验 1-任务调度
前面我们所有的例程都是跑的裸机程序(裸奔),从本章开始,我们将分 3 个章节向大家介绍 UCOSII(实时多任务操作系统内核)的使用。本章,我们将向大家介绍 UCOSII 最基本也是最重要的应用:任务调度。本章分为如下几个部分:
36.1 UCOSII 简介
36.2 硬件设计
36.3 软件设计
36.4 下载验证
36.1 UCOSII 简介
UCOSII 的前身是 UCOS,最早出自于 1992 年美国嵌入式系统专家 Jean J.Labrosse 在《嵌入式系统编程》杂志的 5 月和 6 月刊上刊登的文章连载,并把 UCOS 的源码发布在该杂志的BBS 上。目前最新的版本:UCOSIII 已经出来,但是现在使用最为广泛的还是 UCOSII,本章我们主要针对 UCOSII 进行介绍。UCOSII 是一个可以基于 ROM 运行的、可裁减的、抢占式、实时多任务内核,具有高度可移植性,特别适合于微处理器和控制器,是和很多商业操作系统性能相当的实时操作系统(RTOS)。为了提供最好的移植性能,UCOSII 最大程度上使用 ANSI C 语言进行开发,并且已经移植到近 40 多种处理器体系上,涵盖了从 8 位到 64 位各种 CPU(包括 DSP)。UCOSII 是专门为计算机的嵌入式应用设计的, 绝大部分代码是用 C 语言编写的。CPU 硬件相关部分是用汇编语言编写的、总量约 200 行的汇编语言部分被压缩到最低限度,为的是便于移植到任何一种其它的 CPU 上。用户只要有标准的 ANSI 的 C 交叉编译器,有汇编器、连接器等软件工具,就可以将 UCOSII 嵌人到开发的产品中。UCOSII 具有执行效率高、占用空间小、实时性能优良和可扩展性强等特点, 最小内核可编译至 2KB 。UCOSII 已经移植到了几乎所有知名的 CPU 上。UCOSII 构思巧妙。结构简洁精练,可读性强,同时又具备了实时操作系统的全部功能,虽然它只是一个内核,但非常适合初次接触嵌入式实时操作系统的朋友,可以说是麻雀虽小,五脏俱全。UCOSII(V2.91 版本)体系结构如图 36.1.1 所示:
图 36.1.1 UCOSII 体系结构图
注意本章我们使用的是 UCOSII 的最新版本:V2.91 版本,该版本 UCOSII 比早期的 UCOSII
(如 V2.52)多了很多功能(比如多了软件定时器,支持任务数最大达到 255 个等),而且修
正了很多已知 BUG。不过,有两个文件:os_dbg_r.c 和 os_dbg.c,我们没有在上图列出,也不
将其加入到我们的工程中,这两个主要用于对 UCOS 内核进行调试支持,比较少用到。
从上图可以看出,UCOSII 的移植,我们只需要修改:os_cpu.h、os_cpu_a.asm 和 os_cpu.c
等三个文件即可,其中:os_cpu.h,进行数据类型的定义,以及处理器相关代码和几个函数原
型;os_cpu_a.asm,是移植过程中需要汇编完成的一些函数,主要就是任务切换函数;os_cpu.c,
定义一些用户 HOOK 函数。
图中定时器的作用是为 UCOSII 提供系统时钟节拍,实现任务切换和任务延时等功能。这
个时钟节拍由 OS_TICKS_PER_SEC(在 os_cfg.h 中定义)设置,一般我们设置 UCOSII 的系统
时钟节拍为 1ms~100ms,具体根据你所用处理器和使用需要来设置。本章,我们利用 STM32
的 SYSTICK 定时器来提供 UCOSII 时钟节拍。
关于 UCOSII 在 STM32F4 的详细移植,请参考光盘资料(《ALIENTEK STM32F4 UCOS
开发手册.pdf》),这里我们就不详细介绍了。
UCOSII 早期版本只支持 64 个任务,但是从 2.80 版本开始,支持任务数提高到 255 个,不
过对我们来说一般 64 个任务都是足够多了,一般很难用到这么多个任务。UCOSII 保留了最高
4 个优先级和最低 4 个优先级的总共 8 个任务,用于拓展使用,单实际上,UCOSII 一般只占用
了最低 2 个优先级,分别用于空闲任务(倒数第一)和统计任务(倒数第二),所以剩下给我
们使用的任务最多可达 255-2=253 个(V2.91)。
UCOS 是怎样实现多任务并发工作的呢? 外部中断相信大家都比较熟悉了。CPU 在执行
一段用户代码的时候,如果此时发生了外部中断,那么先进行现场保护,之后转向中断服务程
序执行,执行完成后恢复现场,从中断处开始执行原来的用户代码。Ucos 的原理本质上也是这
样的,当一个任务 A 正在执行的时候,如果他释放了 cpu 控制权,先对任务 A 进行现场保护,
然后从任务就绪表中查找其他就绪任务去执行,等到任务 A 的等待时间到了,它可能重新获得
cpu 控制权,这个时候恢复任务 A 的现场,从而继续执行任务 A,这样看起来就好像两个任务
同时执行了。实际上,任何时候,只有一个任务可以获得 cpu 控制权。这个过程很负责,场景
也多样,这里只是举个简单的例子说明。
所谓的任务,其实就是一个死循环函数,该函数实现一定的功能,一个工程可以有很多这
样的任务(最多 255 个),UCOSII 对这些任务进行调度管理,让这些任务可以并发工作(注
意不是同时工作!!,并发只是各任务轮流占用 CPU,而不是同时占用,任何时候还是只有 1
个任务能够占用 CPU),这就是 UCOSII 最基本的功能。Ucos 任务的一般格式为:
void MyTask (void *pdata)
{
任务准备工作…
While(1)//死循环
{ 任务 MyTask 实体代码;
OSTimeDlyHMSM(x,x,x,x);//调用任务延时函数,释放 cpu 控制权,
}
}
假如我们新建了 2 个任务为 MyTask 和 YourTask,这里我们先忽略任务优先级的概念,两个
任务死循环中延时时间为 1s。如果某个时刻,任务 MyTask 在执行中,当它执行到延时函数
OSTimeDlyHMSM 的时候,它释放 cpu 控制权,这个时候,任务 YourTask 获得 cpu 控制权开
始执行,任务 YourTask 执行过程中,也会调用延时函数延时 1s 释放 CPU 控制权,这个过程中
任务 A 延时 1s 到达,重新获得 CPU 控制权,重新开始执行死循环中的任务实体代码。如此循
环,现象就是两个任务交替运行,就好像 CPU 在同时做两件事情一样。
疑问来了,如果有很多任务都在等待,那么先执行那个任务呢?如果任务在执行过程中,
想停止之后去执行其他任务是否可行呢?这里就涉及到任务优先级以及任务状态任务控制的一
些知识,我们在后面会有所提到。如果要详细的学习,建议看任哲老师的《ucosII 实时操作系统》一书。
这里有几个 UCOSII 相关的概念需要大家了解一下。任务优先级,任务堆栈,任务控制块,任务就绪表和任务调度器。
任务优先级,这个概念比较好理解,ucos 中,每个任务都有唯一的一个优先级。优先级是
任务的唯一标识。在 UCOSII 中,使用 CPU 的时候,优先级高(数值小)的任务比优先级低的
任务具有优先使用权,即任务就绪表中总是优先级最高的任务获得 CPU 使用权,只有高优先级
的任务让出 CPU 使用权(比如延时)时,低优先级的任务才能获得 CPU 使用权。UCOSII 不支
持多个任务优先级相同,也就是每个任务的优先级必须不一样。
任务堆栈,就是存储器中的连续存储空间。为了满足任务切换和响应中断时保存 CPU 寄存
器中的内容以及任务调用其他函数时的需要,每个任务都有自己的堆栈。在创建任务的时候,
任务堆栈是任务创建的一个重要入口参数。
任务控制块OS_TCB,用来记录任务堆栈指针,任务当前状态以及任务优先级等任务属性。
UCOSII 的任何任务都是通过任务控制块(TCB)的东西来控制的,一旦任务创建了,任务控
制块 OS_TCB 就会被赋值。每个任务管理块有 3 个最重要的参数:1,任务函数指针;2,任务
堆栈指针;3,任务优先级;任务控制块就是任务在系统里面的身份证(UCOSII 通过优先级识
别任务),任务控制块我们就不再详细介绍了,详细介绍请参考任哲老师的《嵌入式实时操作
系统 UCOSII 原理及应用》一书第二章。
任务就绪表,简而言之就是用来记录系统中所有处于就绪状态的任务。它是一个位图,系
统中每个任务都在这个位图中占据一个进制位,该位置的状态(1 或者 0)就表示任务是否处于
就绪状态。
任务调度的作用一是在任务就绪表中查找优先级最高的就绪任务,二是实现任务的切换。
比如说,当一个任务释放 cpu 控制权后,进行一次任务调度,这个时候任务调度器首先要去任
务就绪表查询优先级最高的就绪任务,查到之后,进行一次任务切换,转而去执行下一个任务。
关于任务调度的详细介绍,请参考《嵌入式实时操作系统 UCOSII 原理及应用》一书第三章相
关内容。
UCOSII 的每个任务都是一个死循环。每个任务都处在以下 5 种状态之一的状态下,这 5
种状态是:睡眠状态、 就绪状态、 运行状态、 等待状态(等待某一事件发生)和中断服务状态。
睡眠状态,任务在没有被配备任务控制块或被剥夺了任务控制块时的状态。
就绪状态,系统为任务配备了任务控制块且在任务就绪表中进行了就绪登记,任务已经准
备好了,但由于该任务的优先级比正在运行的任务的优先级低, 还暂时不能运行,这时任务的
状态叫做就绪状态。
运行状态,该任务获得 CPU 使用权,并正在运行中,此时的任务状态叫做运行状态。
等待状态,正在运行的任务,需要等待一段时间或需要等待一个事件发生再运行时,该任
务就会把 CPU 的使用权让给别的任务而使任务进入等待状态。
中断服务状态,一个正在运行的任务一旦响应中断申请就会中止运行而去执行中断服务程
序,这时任务的状态叫做中断服务状态。
UCOSII 任务的 5 个状态转换关系如图 36.1.2 所示:
图 36.1.2 UCOSII 任务状态转换关系
接下来,我们看看在 UCOSII 中,与任务相关的几个函数:
1) 建立任务函数
如果想让 UCOSII 管理用户的任务,必须先建立任务。UCOSII 提供了我们 2 个建立任
务的函数:OSTaskCreat 和 OSTaskCreateExt,我们一般用 OSTaskCreat 函数来创建任务,
该函数原型为:
OSTaskCreate(void(*task)(void*pd),void*pdata,OS_STK*ptos,INTU prio)。
该函数包括 4 个参数:task:是指向任务代码的指针;pdata:是任务开始执行时,传
递给任务的参数的指针;ptos:是分配给任务的堆栈的栈顶指针;prio 是分配给任务的优
先级。
每个任务都有自己的堆栈,堆栈必须申明为 OS_STK 类型,并且由连续的内存空间组
成。可以静态分配堆栈空间,也可以动态分配堆栈空间。
OSTaskCreateExt 也可以用来创建任务,是 OSTaskCreate 的扩展版本,提供一些附件
功能。详细介绍请参考《嵌入式实时操作系统 UCOSII 原理及应用》3.5.2 节。
2) 任务删除函数
所谓的任务删除,其实就是把任务置于睡眠状态,并不是把任务代码给删除了。UCOSII
提供的任务删除函数原型为:
INT8U OSTaskDel(INT8U prio);
其中参数 prio 就是我们要删除的任务的优先级,可见该函数是通过任务优先级来实现
任务删除的。
特别注意:任务不能随便删除,必须在确保被删除任务的资源被释放的前提下才能删
除!
3) 请求任务删除函数
前面提到,必须确保被删除任务的资源被释放的前提下才能将其删除,所以我们通过
向被删除任务发送删除请求,来实现任务释放自身占用资源后再删除。UCOSII 提供的请
求删除任务函数原型为:
INT8U OSTaskDelReq(INT8U prio);
同样还是通过优先级来确定被请求删除任务。
4) 改变任务的优先级函数
UCOSII 在建立任务时,会分配给任务一个优先级,但是这个优先级并不是一成不变的,
而是可以通过调用 UCOSII 提供的函数修改。UCOSII 提供的任务优先级修改函数原型为:
INT8U OSTaskChangePrio(INT8U oldprio,INT8U newprio);
5) 任务挂起函数
任务挂起和任务删除有点类似,但是又有区别,任务挂起只是将被挂起任务的就绪标
志删除,并做任务挂起记录,并没有将任务控制块任务控制块链表里面删除,也不需要释
放其资源,而任务删除则必须先释放被删除任务的资源,并将被删除任务的任务控制块也
给删了。被挂起的任务,在恢复(解挂)后可以继续运行。UCOSII 提供的任务挂起函数
原型为:
INT8U OSTaskSuspend(INT8U prio);
6) 任务恢复函数
有任务挂起函数,就有任务恢复函数,通过该函数将被挂起的任务恢复,让调度器能
够重新调度该函数。UCOSII 提供的任务恢复函数原型为:
INT8U OSTaskResume(INT8U prio);
7) 任务信息查询
在应用程序中我们经常要了解任务信息,查询任务信息函数原型为:
INT8U OSTaskQuery(INT8U prio,OS_TCB *pdata);
这个函数获得的是对应任务的 OS_TCB 中内容的拷贝。
从上面这些函数我们可以看出,对于每个任务,有一个非常关键的参数就是任务优先级 prio,在
UCOS 中,任务优先级可以用来作为任务的唯一标识,所以任务优先级对任务而言是唯一的,
而且是不可重复的。
UCOSII与任务相关的函数我们就介绍这么多。最后,我们来看看在STM32上面运行UCOSII
的步骤:
1) 移植 UCOSII
要想 UCOSII 在 STM32F4 正常运行,当然首先是需要移植 UCOSII,这部分我们已经为
大家做好了(移植过程参考光盘:STM32F4 UCOS 开发手册.pdf)。
这里我们要特别注意一个地方,ALIENTEK 提供的 SYSTEM 文件夹里面的系统函数直
接支持 UCOSII,只需要在 sys.h 文件里面将:SYSTEM_SUPPORT_OS 宏定义改为 1,即
可通过 delay_init 函数初始化 UCOSII 的系统时钟节拍,为 UCOSII 提供时钟节拍。
2) 编写任务函数并设置其堆栈大小和优先级等参数。
编写任务函数,以便 UCOSII 调用。
设置函数堆栈大小,这个需要根据函数的需求来设置,如果任务函数的局部变量多,嵌
套层数多,那么相应的堆栈就得大一些,如果堆栈设置小了,很可能出现的结果就是 CPU
进入 HardFault,遇到这种情况,你就必须把堆栈设置大一点了。另外,有些地方还需要注
意堆栈字节对齐的问题,如果任务运行出现莫名其妙的错误(比如用到 sprintf 出错),请
考虑是不是字节对齐的问题。
设置任务优先级,这个需要大家根据任务的重要性和实时性设置,记住高优先级的任务
有优先使用 CPU 的权利。
3) 初始化 UCOSII,并在 UCOSII 中创建任务
调用 OSInit,初始化 UCOSII 的所有变量和数据结构,然后通过调用 OSTaskCreate 函
数创建我们的任务。
4) 启动 UCOSII
调用 OSStart,启动 UCOSII。
通过以上 4 个步骤,UCOSII 就开始在 STM32F4 上面运行了,这里还需要注意我们必须对
os_cfg.h 进行部分配置,以满足我们自己的需要。
36.2 硬件设计
本节实验功能简介:本章我们在 UCOSII 里面创建 3 个任务:开始任务、LED0 任务和 LED1
任务,开始任务用于创建其他(LED0 和 LED1)任务,之后挂起;LED0 任务用于控制 DS0
的亮灭,DS0 每秒钟亮 80ms;LED1 任务用于控制 DS1 的亮灭,DS1 亮 300ms,灭 300ms,
依次循环。
所要用到的硬件资源如下:
1) 指示灯 DS0 、DS1
36.3 软件设计
本章,我们在第六章实验 (实验 1 )的基础上修改,在该工程源码下面加入 UCOSII 文
件夹,存放 UCOSII 源码(我们已经将 UCOSII 源码分为三个文件夹:CORE、PORT 和 CONFIG)。
打开工程,新建 UCOSII-CORE、UCOSII-PORT 和 UCOSII-CONFIG 三个分组,分别添加
UCOSII 三个文件夹下的源码,并将这三个文件夹加入头文件包含路径,最后得到工程如图
36.3.1 所示:
图 36.3.1 添加 UCOSII 源码后的工程
UCOSII-CORE 分组下面是 UCOSII 的核心源码,我们不需要做任何变动。
UCOSII-PORT 分组下面是我们移植 UCOSII 要修改的 3 个代码,这个在移植的时候完成。
UCOSII-CONFIG 分组下面是 UCOSII 的配置部分,主要由用户根据自己的需要对 UCOSII
进行裁剪或其他设置。
本章,我们对 os_cfg.h 里面定义 OS_TICKS_PER_SEC 的值为 200,也就是设置 UCOSII
的时钟节拍为 5ms,同时设置 OS_MAX_TASKS 为 10,也就是最多 10 个任务(包括空闲任务
和统计任务在内),其他配置我们就不详细介绍了,请参考本实验源码。
前面提到,我们需要在 sys.h 里面设置 SYSTEM_SUPPORT_OS 为 1,以支持 UCOSII,通
过这个设置,我们不仅可以实现利用 delay_init 来初始化 SYSTICK,产生 UCOSII 的系统时钟
节拍,还可以让 delay_us 和 delay_ms 函数在 UCOSII 下能够正常使用(实现原理请参考 5.1 节),
这使得我们之前的代码,可以十分方便的移植到 UCOSII 下。虽然 UCOSII 也提供了延时函数:
OSTimeDly 和 OSTimeDLyHMSM,但是这两个函数的最少延时单位只能是 1 个 UCOSII 时钟节拍,
在本章,即 5ms,显然不能实现 us 级的延时,而 us 级的延时在很多时候非常有用:比如 IIC
模拟时序,DS18B20 等单总线器件操作等。而通过我们提供的 delay_us 和 delay_ms,则可以方
便的提供 us 和 ms 的延时服务,这比 UCOSII 本身提供的延时函数更好用。
在设置 SYSTEM_SUPPORT_OS 为 1 之后,UCOSII 的时钟节拍由 SYSTICK 的中断服务函
数提供,该部分代码如下:
//systick 中断服务函数,使用 OS 时用到
void SysTick_Handler(void)
{
if(delay_osrunning==1)
//OS 开始跑了,才执行正常的调度处理
{
OSIntEnter();
//进入中断
OSTimeTick();
//调用 ucos 的时钟服务程序
OSIntExit();
//触发任务切换软中断
}
}
以上代码,其中 OSIntEnter 是进入中断服务函数,用来记录中断嵌套层数(OSIntNesting
增加 1);OSTimeTick 是系统时钟节拍服务函数,在每个时钟节拍了解每个任务的延时状态,
使已经到达延时时限的非挂起任务进入就绪状态;OSIntExit 是退出中断服务函数,该函数可能
触发一次任务切换(当 OSIntNesting==0&&调度器未上锁&&就绪表最高优先级任务!=被中断
的任务优先级时),否则继续返回原来的任务执行代码(如果 OSIntNesting 不为 0,则减 1),
注意,这里只有在 OS 开始运行以后(delay_osrunning 为 1),才开始调用 OSTimeTick 等函数。
事实上,任何中断服务函数,我们都应该加上 OSIntEnter 和 OSIntExit 函数,这是因为
UCOSII 是一个可剥夺型的内核,中断服务子程序运行之后,系统会根据情况进行一次任务调
度去运行优先级别最高的就绪任务,而并不一定接着运行被中断的任务!
最后,我们打开 main.c,代码如下:
/UCOSII 任务设置///
//START 任务
//设置任务优先级
#define START_TASK_PRIO
10 //开始任务的优先级设置为最低
//设置任务堆栈大小
#define START_STK_SIZE
64
//任务堆栈
OS_STK START_TASK_STK[START_STK_SIZE];
//任务函数
void start_task(void *pdata);
//LED0 任务
//设置任务优先级
#define LED0_TASK_PRIO
7
//设置任务堆栈大小
#define LED0_STK_SIZE
64
//任务堆栈
OS_STK LED0_TASK_STK[LED0_STK_SIZE];
//任务函数
void led0_task(void *pdata);
//LED1 任务
//设置任务优先级
#define LED1_TASK_PRIO
6
//设置任务堆栈大小
#define LED1_STK_SIZE
64
//任务堆栈
OS_STK LED1_TASK_STK[LED1_STK_SIZE];
//任务函数
void led1_task(void *pdata);
int main(void)
{
HAL_Init();
//初始化 HAL 库
Stm32_Clock_Init(96,4,2,4);
//设置时钟,96Mhz
delay_init(96);
//初始化延时函数
LED_Init();
//初始化与 LED 连接的硬件接口
OSInit();
OSTaskCreate(start_task,(void *)0,
(OS_STK *)&START_TASK_STK[START_STK_SIZE-1],
START_TASK_PRIO );//创建起始任务
OSStart();
}
//开始任务
void start_task(void *pdata)
{
OS_CPU_SR cpu_sr=0;
pdata = pdata;
OS_ENTER_CRITICAL(); //进入临界区(无法被中断打断)
OSTaskCreate(led0_task,(void *)0,(OS_STK*)&LED0_TASK_STK[LED0_STK_SIZE-1],
LED0_TASK_PRIO);
OSTaskCreate(led1_task,(void *)0,(OS_STK*)&LED1_TASK_STK[LED1_STK_SIZE-1],
LED1_TASK_PRIO);
OSTaskSuspend(START_TASK_PRIO);//挂起起始任务.
OS_EXIT_CRITICAL(); //退出临界区(可以被中断打断)
}
//LED0 任务
void led0_task(void *pdata)
{
while(1)
{
LED0=0;
delay_ms(80);
LED0=1;
delay_ms(920);
};
}
//LED1 任务
void led1_task(void *pdata)
{
while(1)
{
LED1=0;
delay_ms(300);
LED1=1;
delay_ms(300);
};
}
可以看到,我们在创建 start_task 之前首先调用 ucos 初始化函数 OSInit(),该函数的作用是
初始化 ucos 的所有变量和数据结构,该函数必须在调用其他任何 ucos 函数之前调用。在
start_task 创建之后,我们调用 ucos 多任务启动函数 OSStart(),调用这个函数之后,任务才真正
开始运行。在这段代码中我们创建了 3 个任务:start_task、led0_task 和 led1_task,优先级分别
是 10、
7 和 6,堆栈大小都是 64(注意 OS_STK 为 32 位数据)。我们在 main 函数只创建了 start_task
一个任务,然后在 start_task 再创建另外两个任务,在创建之后将自身(start_task)挂起。这里,
我们单独创建 start_task,是为了提供一个单一任务,实现应用程序开始运行之前的准备工作(比
如:外设初始化、创建信号量、创建邮箱、创建消息队列、创建信号量集、创建任务、初始化
统计任务等等)。
在应用程序中经常有一些代码段必须不受任何干扰地连续运行,这样的代码段叫做临界段
(或临界区)。因此,为了使临界段在运行时不受中断所打断,在临界段代码前必须用关中断
指令使 CPU 屏蔽中断请求,而在临界段代码后必须用开中断指令解除屏蔽使得 CPU 可以响应
中断请求。UCOSII 提供 OS_ENTER_CRITICAL 和 OS_EXIT_CRITICAL 两个宏来实现,这两
个宏需要我们在移植 UCOSII 的时候实现,本章我们采用方法 3(即 OS_CRITICAL_METHOD
为 3)来实现这两个宏。因为临界段代码不能被中断打断,将严重影响系统的实时性,所以临
界段代码越短越好!
在 start_task 任务中,我们在创建 led0_task 和 led1_task 的时候,不希望中断打断,故使用
了临界区。其他两个任务,就十分简单了,我们就不细说了,注意我们这里使用的延时函数还
是 delay_ms,而不是直接使用的 OSTimeDly。
另外,一个任务里面一般是必须有延时函数的,以释放 CPU 使用权,否则可能导致低优先
级的任务因高优先级的任务不释放 CPU 使用权而一直无法得到 CPU 使用权,从而无法运行。
软件设计部分就为大家介绍到这里。
36.4 下载验证
在代码编译成功之后,我们通过下载代码到 NANO STM32F4 开发板上,可以看到 DS0 一
秒钟闪一次,而 DS1 则以固定的频率闪烁,说明两个任务(led0_task 和 led1_task)都已经正常
运行了,符合我们预期的设计。
36.5 任务删除,挂起和恢复测试
前面我们简单的建立了两个任务,主要是让大家了解 UCOSII 怎么运行以及怎样创建任务。
下面我们在这一节补充一个实验测试任务的删除,挂起和恢复。为了和寄存器版本手册章节保
持一致,我们这里不另起一章。实验代码在我们光盘的“实验 28 UCOSII 实验 1-2-任务创建删
除挂起恢复”中,主函数文件 main.c 源码如下:
/UCOSII 任务设置///
//START 任务
//设置任务优先级
#define START_TASK_PRIO
10 //开始任务的优先级设置为最低
//设置任务堆栈大小
#define START_STK_SIZE 64
//任务堆栈
OS_STK START_TASK_STK[START_STK_SIZE];
//任务函数
void start_task(void *pdata);
//LED 任务
//设置任务优先级
#define LED_TASK_PRIO 7
//设置任务堆栈大小
#define LED_STK_SIZE 64
//创建任务堆栈空间
OS_STK LED_TASK_STK[LED_STK_SIZE];
//任务函数接口
void led_task(void *pdata);
//蜂鸣器任务
//设置任务优先级
#define BEEP_TASK_PRIO 5
//设置任务堆栈大小
#define BEEP_STK_SIZE 64
//创建任务堆栈空间
OS_STK BEEP_TASK_STK[BEEP_STK_SIZE];
//任务函数接口
void beep_task(void *pdata);
//按键扫描任务
//设置任务优先级
#define KEY_TASK_PRIO 3
//设置任务堆栈大小
#define KEY_STK_SIZE 64
//创建任务堆栈空间
OS_STK KEY_TASK_STK[KEY_STK_SIZE];
//任务函数接口
void key_task(void *pdata);
int main(void)
{
HAL_Init();
//初始化 HAL 库
Stm32_Clock_Init(96,4,2,4);
//设置时钟,96Mhz
delay_init(96);
//初始化延时函数
LED_Init();
//初始化与 LED 连接的硬件接口
BEEP_Init();
//蜂鸣器初始化
KEY_Init();
//按键初始化
OSInit();
OSTaskCreate(start_task,(void *)0,(OS_STK *)&START_TASK_STK
[START_STK_SIZE-1],
START_TASK_PRIO );//创建起始任务
OSStart();
}
//开始任务
void start_task(void *pdata)
{
OS_CPU_SR cpu_sr=0;
pdata = pdata;
OS_ENTER_CRITICAL();
//进入临界区(无法被中断打断)
OSTaskCreate(led_task,(void *)0,(OS_STK*)&LED_TASK_STK
[LED_STK_SIZE-1],LED_TASK_PRIO);
OSTaskCreate(beep_task,(void *)0,(OS_STK*)&BEEP_TASK_STK
[BEEP_STK_SIZE-1],BEEP_TASK_PRIO);
OSTaskCreate(key_task,(void *)0,(OS_STK*)&KEY_TASK_STK
[KEY_STK_SIZE-1],KEY_TASK_PRIO);
OSTaskSuspend(START_TASK_PRIO);//挂起起始任务.
OS_EXIT_CRITICAL(); //退出临界区(可以被中断打断)
}
//LED 任务
void led_task(void *pdata)
{
while(1)
{
LED0=!LED0;
LED1=!LED1;
delay_ms(500);
}
}
//蜂鸣器任务
void beep_task(void *pdata)
{
while(1)
{
if(OSTaskDelReq(OS_PRIO_SELF)==OS_ERR_TASK_DEL_REQ)
//判断是否有删除请求
{
OSTaskDel(OS_PRIO_SELF); //删除任务本身 TaskLed
}
BEEP=0;
delay_ms(940);
BEEP=1;
delay_ms(60);
}
}
//按键扫描任务
void key_task(void *pdata)
{
u8 key;
while(1)
{
key=KEY_Scan(0);
if(key==KEY0_PRES)
{
OSTaskSuspend(LED_TASK_PRIO);//挂起 LED 任务,LED 停止闪烁
}
else if (key==KEY2_PRES)
{
OSTaskResume(LED_TASK_PRIO);//恢复 LED 任务,LED 恢复闪烁
}
else if (key==WKUP_PRES)
{
OSTaskDelReq(BEEP_TASK_PRIO);
//发送删除 BEEP 任务请求,任务睡眠,无法恢复
}
else if(key==KEY1_PRES)
{
OSTaskCreate(beep_task,(void *)0,(OS_STK*)&BEEP_TASK_STK
[BEEP_STK_SIZE-1],BEEP_TASK_PRIO);//重新创建任务 beep
}
delay_ms(10);
}
}
该代码在 start_task 中创建了 3 个任务分别为 led_task, beep_task 和 key_task。led_task 是
LED0 和 LED1 每隔 500ms 翻转一次。beep_task 在没有收到删除请求的时候是隔一段时间蜂鸣
器鸣叫一次,key_task 是进行按键扫描。当 KEY0 按键按下的时候挂起任务 led_task,这时 LED0
和 LED1 停止闪烁。当 KEY2 按键按下的时候,如果 led_task 被挂起则恢复之,如果没有挂起
则没有影响。当 KEY_UP 按键按下的时候删除任务 beep_task。当 KEY1 按键按下的时候,重
新创建任务 beep_task。
我们的测试顺序为:首先下载代码之后可以看到 LED0 和 LED1 不断闪烁,同时蜂鸣器不
断鸣叫。这个时候我们按下 KEY0 之后 led_task 任务被挂起,我们可以看到 LED 不再闪烁。接
着我们按下 KEY2,led_task 任务重新恢复,可以看到 LED 恢复闪烁。然后我们按下 KEY_UP,
任务 beep_task 被删除,所以蜂鸣器不再鸣叫。这个时候我们再按下按键 KEY1,任务 beep_task
被重新创建,所以蜂鸣器恢复鸣叫。
嵌入式实时操作系统ucos-ii_「正点原子NANO STM32开发板资料连载」第三十六章 UCOSII 实验 1任务调度...