1)实验平台:Alienek NANO STM32F411 V1开发板
2)摘自《正点原子STM32F4 开发指南(HAL 库版》:更多信息请关注我们的官方微信:正点原子
第36 章UCOSII 实验1 - 任务调度
到目前为止所有例程都是裸机程序(裸奔)。接下来的三章将向您介绍UCOSII(实时多任务操作系统内核)的使用。本章介绍UCOSII最基本、最重要的应用:任务调度。本章分为以下几个部分:
36.1 UCOSII 概述
36.2 硬件设计
36.3 软件设计
36.4 下载验证
36.1 UCOSII 概述
UCOSII的前身是UCOS,源于1992年美国嵌入式系统专家Jean J. Labrosse在《嵌入式系统编程》杂志五月和六月号连载的一篇文章,UCOS的源代码发表在该杂志的发布。最新版本:UCOSIII已经发布,但UCOSII是目前应用最广泛的。本章主要介绍UCOSII。 UCOSII是一个可扩展、抢占式、实时多任务内核,运行在ROM上,是一个高度可移植的实时操作系统,性能可与许多商业操作系统相媲美。 (实时操作系统)。为了提供最佳的可移植性性能,UCOSII 采用完整的ANSI C 语言开发,兼容近40 个处理器系统,涵盖从8 位到64 位的各种CPU(包括DSP)。 UCOSII是专门为嵌入式计算机应用而设计的,大部分代码是用C语言编写的。 CPU中与硬件相关的部分是用汇编语言编写的,为了方便移植到其他CPU上,汇编语言部分总共被压缩到最少约200行。只要用户拥有标准的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个任务等)。
修复了许多已知的错误。然而,有两个文件,os_dbg_r.c和os_dbg.c,没有在上图中列出。
这两个主要用于UCOS内核调试支持,使用相对较少。
从上图可以看出,移植UCOSII只需要更改os_cpu.h、os_cpu_a.asm和os_cpu.c。
只需等待三个文件:定义数据类型的os_cpu.h、与处理器相关的代码和一些函数原语。
type; os_cpu_a.asm是移植过程中需要编译的一些函数,主要是任务切换函数。
定义一些用户HOOK函数。
图中定时器的作用是向UCOSII提供系统时钟节拍,并实现任务切换、任务延迟等功能。这
每个时钟周期由OS_TICKS_PER_SEC 设置(在os_cfg.h 中定义)。通常,您会配置UCOSII 系统。
时钟节拍在1 毫秒到100 毫秒之间,根据您拥有的处理器和使用需求进行设置。本章使用STM32
SYSTICK 定时器提供UCOSII 时钟周期。
UCOSII到STM32F4的详细移植请参考光盘资料(《ALIENTEK STM32F4 UCOS》)。
开发手册.pdf)这里不再详细介绍。
UCOSII的早期版本仅支持64个任务,但从2.80版本开始,支持的任务数量增加到255个。
不过,一般来说64个任务就足够了,而且一般很难使用这么多任务。 UCOSII保持最高排名
共有8 个任务,具有4 个优先级和4 个最低优先级,用于扩展使用。实际上,UCOSII只是正常被占用。
优先级较低的两个用于空闲任务(倒数第二个)和统计任务(倒数第二个),剩下的交给我了。
最多使用255-2=253 个任务(V2.91)。
UCOS如何实现同时多任务处理,相信大家都熟悉外部中断。 CPU正在运行
在编写用户代码时,此时当外部中断发生时,先进行现场保护,然后再转入中断服务程序。
执行完成后,场景将恢复,原始用户代码将从中断点开始运行。 Ucos的原理本质上是一样的
这样,如果在任务A 运行时释放CPU 控制权,任务A 将首先受到现场保护。
然后从任务准备表中找到其他准备好的任务并运行。如果任务A的等待时间过去,则任务A可以被重新获取。
CPU控制,此时恢复任务A的场景并继续运行任务A,所以看起来像两个任务
同时执行。事实上,任何时候只有一个任务可以获得CPU的控制权。这个过程非常负责而且场景
有很多,但这里是一个简单的例子。
所谓任务,其实就是一个实现特定功能的无限循环函数。一个项目可以包含许多这样的功能。
此类任务(最多255 个)由UCOSII 进行调度和管理,以确保这些任务可以并发运行(注)
他们不应该一起工作。并发是指每个任务按顺序占用CPU,而不是同时占用CPU。永远只有一个。
每个任务可以占用一个CPU。这是UCOSII最基本的特征。 Ucos任务的一般格式为:
无效我的任务(无效*pdata)
{
为任务做准备.
while(1)//死循环
{Task MyTask实体代码;
OSTimeDlyHMSM(x,x,x,x);//调用任务延时函数释放CPU控制权,
}
}
如果我们创建两个新任务,MyTask 和YourTask,我们将忽略任务优先级的概念。
任务无限循环的延迟时间为1s。如果任务MyTask在特定时刻运行,则延迟函数将被执行。
当OSTimeDlyHMSM 运行时,CPU 控制被释放,此时任务YourTask 获得CPU 控制并启动。
当任务YourTask 运行时,它还会调用一个延迟函数,延迟1 秒以释放此过程中的CPU 控制权。
任务A延迟1秒到达,重新获得CPU的控制权,并在无限循环中恢复执行任务实体代码。请遵循此
响,这种现象就是两个任务交替执行,就好像CPU同时在做两件事一样。
如果有大量等待任务,就会出现一个问题:应该先执行哪个任务?如果任务正在进行中,
停止后我可以做其他工作吗?这包括任务优先级和任务状态的任务控制。
后面我会讲解一些知识。如果想了解更多,推荐阅读任哲老师的书《ucosII 实时操作系统》。
以下是您需要了解的一些UCOSII 相关概念。任务优先级、任务堆栈、任务控制块、任务就绪表、任务调度器。
任务优先级。这个概念在ucos中是比较容易理解的。每个任务都有自己的优先级。优先级是
任务的唯一标识符。使用CPU 时,UCOSII 使用较高优先级(较低值)的任务,而不是较低优先级的任务。
任务有优先使用权。这意味着任务就绪列表中优先级最高的任务总是获得CPU的使用权,只有高优先级的任务才能获得CPU的使用权。
低优先级任务只有在放弃CPU 权限(例如,通过延迟)时才能获得CPU 权限。 UCOSII不支持
将多个任务保持相同的优先级。也就是说,每个任务必须有不同的优先级。
任务栈是内存中连续的存储区域。 CPU寄存器被保存来响应任务切换和中断。
每个任务都有自己的堆栈,具体取决于处理器上的内容以及调用其他函数时任务的需求。创建任务时,
任务栈是任务创建的重要入口参数。
任务控制块OS_TCB用于记录任务堆栈指针、任务当前状态、任务优先级和其他任务属性。
一旦创建任务,UCOSII 任务就通过任务控制块(TCB) 进行控制。
块OS_TCB 被分配一个值。每个任务管理块都有三个最重要的参数: 1.任务函数指针。
堆栈指针;3、任务优先级;任务控制块是系统中任务的识别卡(UCOSII按优先级来识别)。
有关任务控制块的更多信息,请参阅任哲的《嵌入式实时操作》。
《系统UCOSII原理与应用》一书的第2章。
换句话说,任务就绪表用于记录系统中所有处于就绪状态的任务。它是位图,系统
系统中的每个任务都占用该位图中的一个二进制位。该位置的状态(1 或0)指示任务是否正在运行。
就绪状态。
任务调度的作用是找到任务就绪表中优先级最高的就绪任务,并实现任务切换。
例如,当任务放弃对CPU的控制来执行任务调度时,任务调度器首先继续执行任意任务。
查询任务就绪表,查找优先级最高的就绪任务,找到后进行任务切换,执行下一个任务。
有关计划任务的更多信息,请参阅书籍《嵌入式实时操作系统 UCOSII 原理及应用》 的第3 章。
相关内容。
UCOSII 中的每个任务都是一个无限循环。每个任务可以处于以下五种状态之一:
这三种状态是睡眠状态、就绪状态、运行状态、等待状态(等待事件发生)和中断服务状态。
睡眠状态。未配备任务控制块或任务控制块被占用时任务的状态。
在就绪状态下,系统具有用于在任务就绪表中注册的任务的任务控制块。
已经准备好了,但是此时该任务暂时无法运行,因为它的优先级低于正在运行的任务。
这种状态称为就绪状态。
执行状态是任务获得CPU使用权并正在运行的状态。
等待状态:当一个正在运行的任务需要等待一定的时间或者等待某个事件发生才能执行时,该任务
该任务将CPU 使用权让给另一个任务,并进入等待状态。
中断服务状态。当正在运行的任务响应中断请求时,它停止执行并执行中断服务程序。
此时任务的状态称为中断服务状态。
UCOSII任务的五种状态转换关系如图36.1.2所示。
图36.1.2 UCOSII任务状态转换关系
接下来我们看一下UCOSII中一些与任务相关的函数。
1)创建任务函数
如果要在UCOSII中管理用户任务,必须首先创建任务。 UCOSII提供了两个建立任务
任务函数:OSTaskCreat 和OSTaskCreateExt。任务通常使用OSTaskCreat 函数创建。
函数原型为:
OSTaskCreate(void(*任务)(void * pd),void * pdata,OS_STK * ptos,INTU prio)。
该函数有四个参数:task:是指向任务代码的指针,pdata:是任务开始执行时传递的数据。
指向传递给任务的参数的指针ptos:指向分配给任务的堆栈顶部的指针。 prio 是分配给任务的优先级。
优先事项。
每个任务都有自己的堆栈,该堆栈必须声明为OS_STK 类型并由连续的内存空间组成。
变得。堆栈空间可以静态或动态分配。
OSTaskCreateExt也可以用来创建任务,它是OSTaskCreate的增强版本,并提供了一些附件。
功能。有关详细信息,请参阅《嵌入式实时操作系统 UCOSII 原理及应用》 第3.5.2 节。
2)任务删除功能
所谓任务删除,就是让任务进入休眠状态,而不是删除任务代码。宇越
提供的任务删除函数原型为:
INT8U OSTaskDel(INT8U 优先级);
参数prio是要删除的任务的优先级。可以看到这个功能是通过任务优先级来实现的。
该任务将被删除。
特别注意:任务不能轻易删除。只有在确定被删除任务的资源已被释放后,才应将其删除。
消除!
3)任务删除请求功能
前面提到,在删除已删除任务之前,需要确保已删除任务的资源已释放,所以
删除任务前,向被删除的任务发送删除请求,释放该任务占用的资源。请从UCOSII提供
任务删除函数的原型为:
INT8U OSTaskDelReq(INT8U 优先级);
请求的删除任务也由优先级确定。
4)更改任务优先级功能
UCOSII在创建任务时会为任务分配一个优先级,但这个优先级不是静态的。
相反,可以通过调用UCOSII 提供的函数来修改它们。 UCOSII提供的任务优先级改变函数原型如下。
INT8U OSTaskChangePrio(INT8U oldprio,INT8U newprio);
5)任务暂停功能
暂停任务与删除任务类似,但也有区别。暂停任务仅表明暂停任务的准备情况。
任务控制块被移除并记录任务中止记录。任务控制块不会从任务控制块链表中删除,因此不需要释放它们。
释放资源。要删除任务,必须先释放被删除任务的资源。您还必须释放已删除任务的任务控制块。
它被删除了。暂停的任务在恢复(取消暂停)后可以继续运行。 UCOSII提供的任务暂停功能
原型是:
INT8U OSTaskSuspend(INT8U 优先);
6)任务恢复功能
它具有任务挂起和任务恢复功能,允许调度程序恢复挂起的任务。
这足以重新安排该功能。 UCOSII提供的任务恢复功能原型如下。
INT8U OSTaskResume(INT8U 优先级);
7)查询任务信息
应用程序通常需要了解任务信息。任务信息查询函数的原型为:
INT8U OSTaskQuery(INT8U prio,OS_TCB *pdata);
该函数检索相应任务的OS_TCB 内容的副本。
从上面的函数我们可以看出,每个任务都有一个非常重要的参数:任务优先级priority。
在UCOS中,任务优先级对于任务来说是唯一的,因为它可以用作任务的唯一标识符。
而且它是不可重现的。
引入了大量UCOSII任务相关的函数。最后我们来看看在STM32上运行UCOSII。
下一步:
1)移植UCOS
当然,如果想让UCOSII在STM32F4上正常工作,需要先移植UCOSII。这已经完成了。
大家都在做(移植过程参见光盘:STM32F4 UCOS开发手册.pdf)。
需要特别注意的地方是ALIENTEK提供的SYSTEM文件夹中的系统函数。
要直接支持UCOSII,只需将sys.h文件中的SYSTEM_SUPPORT_OS宏定义更改为1即可。
UCOSII的系统时钟节拍可以通过lay_init函数进行初始化,为UCOSII提供时钟节拍。
2)编写任务函数并设置堆栈大小、优先级等参数。
编写UCOSII调用的任务函数。
设置函数的堆栈大小。如果您的任务函数有许多局部变量,则应根据函数的需要进行设置。
如果嵌套层数较多,则相应的栈也必须较大。如果您的堆栈配置较小,则可能会耗尽CPU。
如果进入HardFault并且发生这种情况,则必须将堆栈设置得更大。也有一些地方需要你小心。
如果您在运行任务时遇到神秘错误(例如使用sprintf 时出现错误),请注意堆栈字节对齐问题。
考虑是否是咬合对齐问题。
为了确定任务的优先级,每个人都应该根据任务的重要性和实时性来设置优先级,并记住哪些任务具有更高的优先级。
优先考虑CPU。
3)初始化UCOSII并在UCOSII中创建任务
调用OSInit初始化所有UCOSII变量和数据结构,然后调用OSTaskCreate函数。
创建的任务数。
4)启动UCOSII
通过调用OSStart 启动UCOSII。
以上四步将启动UCOSII在STM32F4上运行。这里有一些事情你需要小心。
os_cfg.h 已部分配置以满足您自己的需求。
36.2 硬件设计
本节实验功能介绍:本章我们将在UCOSII中创建三个任务:启动任务、LED0任务和LED1。
任务。启动的任务用于创建其他(LED0 和LED1)任务,LED0 任务用于控制DS0。
DS0 每秒亮起80 毫秒。 LED1任务用于控制DS1的开启和关闭。 DS1 将开启300ms,关闭300ms。
按顺序骑车。
使用的硬件资源如下。
1)指示灯DS0、DS1
36.3 软件设计
本章我们修改了第六章(实验一)中的实验,在项目源码下添加了一个UCOSII文件。
保存UCOSII源代码的文件夹(我把UCOSII源代码分为三个文件夹:CORE、PORT、CONFIG)。
打开您的项目并创建并添加三个新组:UCOSII-CORE、UCOSII-PORT 和UCOSII-CONFIG。
将源代码存放在三个UCOSII文件夹中,将这三个文件夹添加到头文件的include路径中,最终得到如图所示的工程。
36.3.1 表示:
图36.3.1 添加UCOSII源代码后的项目
UCOSII-CORE Group 下面是UCOSII核心源代码。无需更改。
UCOSII-PORT Group 移植UCOSII时需要修改以下三段代码。这将在移植期间完成。
UCOSII-CONFIG组下是UCOSII的配置部分,主要用于用户根据需要配置UCOSII。
配置修剪等设置。
本章我们定义os_cfg.h中OS_TICKS_PER_SEC的值为200,并配置UCOSII。
时钟周期为5 ms,OS_MAX_TASKS 设置为10,即最多10 个任务(包括空闲任务)。
(例如统计任务),其他配置不再详细介绍。请参阅本实验的源代码。
如前所述,要支持UCOSII,必须在sys.h中将SYSTEM_SUPPORT_OS设置为1。
此配置允许您使用delay_init来初始化SYSTICK并生成UCOSII系统时钟,以及
支持beats并允许使用UCOSII中常用的delay_us和delay_ms函数(实现原理见5.1节)。
这使您可以轻松地将以前的代码移植到UCOSII。 UCOSII还提供了延迟函数,它们是:
OSTimeDly 和OSTimeDLyHMSM,但这两个函数的最小延迟单位仅为1 个UCOSII 时钟节拍。
在本章中,很明显您无法实现US 电平延迟,即5ms,但US 电平延迟在很多情况下(例如IIC)非常有用。
模拟时序,对DS18B20等单总线器件的操作并且通过我们提供的delay_us和delay_ms,您可以:
这提供了比UCOSII本身提供的延迟功能更方便的US和MS延迟服务。
将SYSTEM_SUPPORT_OS设置为1后,UCOSII时钟节拍由SYSTICK中断服务函数决定。
指定了一个数字,这部分的代码是:
//使用OS时使用的systick中断服务函数
无效SysTick_Handler(无效)
{
if(delay_osrunning==1)
//操作系统在执行正常调度处理之前启动
{
OSIntEnter();
//中断输入
OSTimeTick();
//调用ucos时钟服务程序
OSIntExit();
//触发任务切换软中断
}
}
上述代码中,OSIntEnter为入口中断服务函数,用于记录中断嵌套层数(OSIntNesting)。
新增1); OSTimeTick是系统时钟节拍服务函数,掌握每个时钟节拍下各个任务的延迟状态。
当达到其延迟时间限制时,使无中断任务进入就绪状态。 OSIntExit是退出中断服务函数。
触发任务切换(如果OSIntNesting==0,调度器不会锁定就绪表中最高优先级的任务!=挂起)
任务优先级),否则继续返回原来的任务执行代码(如果OSIntNesting不为0则减1)。
请注意,这仅适用于操作系统
开始运行以后(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 被重新创建,所以蜂鸣器恢复鸣叫。 版权声明:本文转载于今日头条,版权归作者所有,如果侵权,请联系本站编辑删除