FreeRTOS事件标志组
类似消息队列中的发布-订阅功能。
文章来源:
http://blog.sina.com.cn/s/blog_98ee3a930102wgev.html
https://blog.csdn.net/nippon1218/article/details/79048199
1 基础知识
事件标志组——任务间的通信和同步机制之一。
(可以在任务和中断中使用)事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。与信号量不同的是,它可以实现一对多,多对多的同步。 即一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件处理。同样,也可以是多个任务同步多个事件。
特性:
- 事件只与任务相关联,事件相互独立,一个 32 位的事件集合(EventBits_t 类型的变量,实际可用与表示事件的只有 24位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、1 表示该事件类型已经发生),一共 24 种事件类型。
- 事件仅用于同步,不提供数据传输功能。
- 事件无排队性,即多次向任务设置同一事件(如果任务还未来得及读走),等效于 只设置一次。
- 允许多个任务对同一事件进行读写操作。
- 支持事件等待超时机制。
事件标志组简介
1 、事件位( 事件标志)
事件位用来表明某个事件是否发生,事件位通常用作事件标志,比如下面的几个例子:
● 当收到一条消息并且把这条消息处理掉以后就可以将某个位(标志)置 1,当队列中没有 消息需要处理的时候就可以将这个位(标志)置 0。
● 当把队列中的消息通过网络发送输出以后就可以将某个位(标志)置 1,当没有数据需要 从网络发送出去的话就将这个位(标志)置 0。
● 现在需要向网络中发送一个心跳信息,将某个位(标志)置 1。现在不需要向网络中发送 心跳信息,这个位(标志)置 0。
2 、事件组
一个事件组就是一组的事件位,事件组中的事件位通过位编号来访问,同样,以上面列出 的三个例子为例:
● 事件标志组的 bit0 表示队列中的消息是否处理掉。
● 事件标志组的 bit1 表示是否有消息需要从网络中发送出去。
● 事件标志组的 bit2 表示现在是否需要向网络发送心跳信息。
3、 为什么要使用事件标志
事件标志组是实现多任务同步的有效机制之一。也许有不理解的初学者会问采用事件标志组多麻烦,搞个全局变量不是更简单?其实不然,在裸机编程时,使用全局变量的确比较方便,但是在加上RTOS后就是另一种情况了。使用全局变量相比事件标志组主要有如下三个问题:
1.使用事件标志组可以让RTOS内核有效地管理任务,而全局变量是无法做到的,任务的超时等机制需要用户自己去实现。
2.使用了全局变量就要防止多任务的访问冲突,而使用事件标志组则处理好了这个问题,用户无需担心。
3.使用事件标志组可以有效地解决中断服务程序和任务之间的同步问题。
1.1 事件组对比全局变量的优点
事件组对比全局变量,在RTOS内有诸多好处是全局变量所达不到的—这些好处还带我去体验
1、让RTOS内核更有效地管理任务;用全局变量时,任务超时等机制需要自己实现。
2、使用事件标志组,不用担心多任务的访问冲突;用全局变量时,为了访问共享要加锁。
3、事件标志组还可有效解决中断服务程序和任务之间的同步问题。
可以看出,事件标志组相当于RTOS给出的全局标志,特别好用的样子。
1.2 事件标志组的实现
接下去就要将任务间或任务和中断服务程序间的同步或通信问题。
这就存在一个任务处于阻塞态,等待另一任务或中断服务程序给它需要的事件标志位置1或等待超时,以使之从阻塞态转为运行态。
1.2.1 事件标志组的定义
在FreeRTOS中事件标志的配置
从ESP32的esp-idf的FreeRTOS组件分析
event_groups.h 查找EventBits_t 变量类型的定义
typedef TickType_t EventBits_t;
在 portmacro.h中分析TickType_t类型
#if( configUSE_16_BIT_TICKS == 1 )
typedef uint16_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffff
#else
typedef uint32_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
#endif
当configUSE_16_BIT_TICKS为1时,每创建一个事件组标志,其实定义了一个16位变量,仅使用低8bit;
当configUSE_16_BIT_TICKS为0时,每创建一个事件组标志,其实定义了一个32位变量,仅使用低24bit。
默认创建一个事件标志,支持24个事件标志设置。
事件组、事件标志、事件位
事件标志是一个布尔值(1或0),用于指示事件是否发生。事件组是一组事件标志,事件标志只能是1或0,允许事件标志存储在一个位中,事件组中的所有事件标志的状态存储在单个变量中,其中每个事件标志由EvenBits_t类型的变量中的一个位表示,因此,事件标志也成为事件位。如果EvenBits_t变量中的某个位为1,则表示该位表示的事件以发生。如果EvenBits_t变量中的某个位为0,则表示该位表示的事件未发生。下图显示了各个事件标志位如何映射到EvenBits_t变量中的各个位。
例如:事件组的值为0x92,即事件位1、4、7为1,因此仅发生由位1、4、7表示的事件,如下图。
其中,每位的具体意义由应用程序定义,例如:
- 位0定义为“已接受到网络消息”。
- 位1定义为“已将消息发送到网络”。
- 位2定义为“网络中断”。
- 等等。
一个事件组可表示的位数由FreeRTOSConfig.h中的configUSE_16_BIT_TICKS配置:
- configUSE_16_BIT_TICKS为1,则每个事件组可以包含8个可用事件位。
- configUSE_16_BIT_TICKS为0,则每个事件组可以包含24个可用事件位。
1.2.2 两个任务间使用EventGroups保持同步或通信
简单说明:任务1从运行态Run 调用XEventGroupWaitBits转为阻塞等待Blocked(等待需要的事件标志被置位),任务2置位事件标志。任务1在所需事件标志置位后,转为就绪态Ready,在调度器的作用下从就绪态转为运行态。
1.2.3 任务和中断使用EventGroups保持同步或通信
简单说明:任务1从运行态Run 调用XEventGroupWaitBits转为阻塞等待Blocked(等待需要的事件标志被置位),串口接收到数据进入到 串口中断服务程序,在中断服务程序中设置任务1 所需的时间标志。任务1在所需事件标志置位后,转为就绪态Ready,在调度器的作用下从就绪态转为运行态。
2 灵活运用
时间标志组相关的函数有11个,目前先关注使用最频繁的4个。
2.1 FreeRTOS 提供了两个用于创建事件标志组的函数
函数xEventGroupCreate()
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
EventGroupHandle_t xEventGroupCreate( void ) PRIVILEGED_FUNCTION;
#endif
用这个函数会创建一个事件组,自动分配事件组所需的memory block。
返回值:成功创建事件组,则返回事件组的句柄。否则,返回NULL(heap空间不足)
EventGroupHandle_t xCreatedEventGroup;
xCreatedEventGroup = xEventGroupCreate();
if( xCreatedEventGroup == NULL )
{
// The event group was not created because there was insufficient
// FreeRTOS heap available. FreeRTOS的堆空间不足
}
else
{
// The event group was created.
}
2.2 设置事件位
FreeRTOS 提供了 4 个函数用来设置事件标志组中事件位(标志),事件位(标志)的设置包括 清零和置 1 两种操作。
1 、函数 xEventGroupClearBits()
将事件标志组中的指定事件位清零,此函数只能用在任务中,不能用在中断服务函数中! 中断服务函数有其他的 API 函数。函数原型如下:
EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear );
参数:
xEventGroup : 要操作的事件标志组的句柄。
uxBitsToClear : 要清零的事件位,比如要清除 bit3 的话就设置为 0X08。可以同时清除多个 bit,如设置为 0X09 的话就是同时清除 bit3 和 bit0。
返回值:
任何值: 将指定事件位清零之前的事件组值。
2 、函数 xEventGroupClearBitsFromISR()
此函数为函数 xEventGroupClearBits()的中断级版本,也是将指定的事件位(标志)清零。此 函数用在中断服务函数中,此函数原型如下:
BaseType_t xEventGroupClearBitsFromISR( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );
参数:
xEventGroup : 要操作的事件标志组的句柄。
uxBitsToClear : 要清零的事件位,比如要清除 bit3 的话就设置为 0X08。可以同时清除多个 bit,如设置为 0X09 的话就是同时清除 bit3 和 bit0。
返回值:
pdPASS : 事件位清零成功。
pdFALSE: 事件位清零失败。
3、函数 xEventGroupSetBits()
设置指定的事件位为 1,此函数只能用在任务中,不能用于中断服务函数,此函数原型如 下:
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );
参数:
xEventGroup : 要操作的事件标志组的句柄。
uxBitsToClear : 指定要置 1 的事件位,比如要将 bit3 值 1 的话就设置为 0X08。可以同时将 多个 bit 置 1,如设置为 0X09 的话就是同时将 bit3 和 bit0 置 1。
返回值:
任何值: 在将指定事件位置 1 后的事件组值。
4、 、数 函数 xEventGroupSetBitsFromISR()
此函数也用于将指定的事件位置 1,此函数是 xEventGroupSetBits()的中断版本,用在中断 服务函数中,函数原型如下:
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t * pxHigherPriorityTaskWoken );
参数:
xEventGroup : 要操作的事件标志组的句柄。
uxBitsToClear : 指定要置 1 的事件位,比如要将 bit3 值 1 的话就设置为 0X08。可以同时将 多个 bit 置 1,如设置为 0X09 的话就是同时将 bit3 和 bit0 置 1。
pxHigherPriorityTaskWoken: :标记退出此函数以后是否进行任务切换,这个变量的值函数会自 动设置的,用户不用进行设置,用户只需要提供一个变量来保存 这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之 前一定要进行一次任务切换。
返回值:
pdPASS : 事件位置 1 成功。
pdFALSE: 事件位置 1 失败。
2.3 等待指定的事件标志位
EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait );
参数:
xEventGroup
事件组的句柄。
uxBitsToWaitFor
它是一个位掩码,指定要在事件组中测试的事件位(一个或多个)。例如,调用任务想要等待事件组中的事件位0和(或)事件位2被设置,则将uxBitsToWaitFor设置为0x05。
xClearOnExit
如果调用任务满足解除阻塞条件,并且xClearOnExit为pdTRUE,则在xEventGroupWaitBits()退出前,清除uxBitsToWaitFor指定的事件位。如果xClearOnExit为pdFALSE,则xEventGroupWaitBits()函数不会修改事件组的事件位。
xWaitForAllBits
uxBitsToWaitFor指定的是事件组中要测试的位,而当xWaitForAllBits为pdFALSE时uxBitsToWaitFor中任何位被设置就退出函数(超时后也会退出),当xWaitForAllBits为pdTRUE时,uxBitsToWaitFor中设置的所有位被设置才退出函数(超时后也会退出)。
xTicksToWait
最长的超时时间,如果在设定时间内没有达到上面的要求则会退出函数,这个参数和前面介绍的xTicksToWait使用方法一样。
返回值:
如果有事件位被设置,并且满足调用任务解除阻塞条件,则返回的值是满足调用任务退出阻塞态的事件组的值。如果是由于超时退出,则返回的值将不满足调用任务退出阻塞态的事件组的值。
调用程序用于确定任务是否将进入阻塞态以及任务何时将离开阻塞态的条件称为解除阻塞条件。解除阻塞条件由uxBitsToWaitFor和xWaitForAllBits两个参数的组合指定:
uxBitsToWaitFor指定要测试的事件组中的哪些事件位
xWaitForAllBits指定是使用按位或(任何指定的事件位被设置)测试还是按位与(所有指定的事件位都要全部被设置)测试
如果在调用xEventGroupWaitBits()时满足解除阻塞条件,则任务不会进入阻塞态。下表提供了导致任务进入或退出阻塞态的条件示例,其中只给出了4位,其余的没有给出可以假设为0.
事件组的值 | uxBitsToWaitFor | xWaitForAllBits | 行为 |
---|---|---|---|
0000 | 0101 | pdFALSE | 调用任务将进入阻塞状态,因为事件组中未设置bit 0或bit 2,并且在事件组中设置bit 0或bit 2时任务将退出阻塞态 |
0100 | 0101 | pdTRUE | 调用任务将进入阻塞状态,因为事件组中未设置bit0和bit2,并且在事件组中设置bit 0和bit 2时任务将退出阻塞态 |
0100 | 0110 | pdFALSE | 调用任务不会进入阻塞态,因为事件组中有一位(bit 2)被设置 |
0100 | 0110 | pdTRUE | 调用任务会进入阻塞态,因为事件组中只有一位被设置,这里需要两位同时被设置才能退出阻塞态 |
调用任务使用uxBitsToWaitFor参数指定要测试的位,并且可以设置调用任务在满足解除阻塞条件后将这些位清除,但也可以使用xEventGroupClearBits()函数来清除事件位,但是使用该API函数可能产生竞争条件:
- 有多个任务使用同一事件组
- 通过不同的任务或ISR设置事件组中的事件位
如果直接在xEventGroupWaitBits()函数中使用xClearOnExit,就可以避免这些潜在的竞争条件,如果xClearOnExit设置为pdTRUE,则事件位的测试和清除对于调用任务而言将是一个原子操作(不受其他任务或中断的影响)。
2.4 使用事件组同步任务引入
有时,应用程序需要两个或多个任务彼此同步。例如,任务A接收事件,将事件所需的一些处理委托给任务B、任务C、任务D三个任务,如果任务A在其他三个任务没有完成当前事件的处理时无法接收下一个事件,此时四个任务就需要彼此同步。每个任务执行到同步点后将在此等待其他任务完成处理并到达相应的同步点后才能继续执行,如此处的任务A只能在其他任务都达到同步点后才能接收另一个事件。
事件组可用于创建同步点,并同步多个任务:
- 必须为每个参与同步的任务分配唯一的事件位。
- 每个任务在到达同步点时设置自己的事件位。
- 设置自己的事件位后,事件组上的每个任务都会阻塞,以等待代表其他同步任务的事件位被设置。
但是在这个方案中不能使用xEventGroupSetBits()和xEventGroupWaitBits()函数。如果使用它们,那么设置一个bit(表示任务已达到同步点)和测试bits(确定其他任务是否已到达同步点)将会是两个单独的操作。例如:
- 任务A和任务B已到达同步点,因此它们的事件位被设置,并且它们都处于阻塞态等待任务C到达同步点。
- 任务C到达同步点,并调用xEventGroupSetBits()设置事件组中的事件位,一旦设置了任务C的事件位,任务A和任务B就会离开阻塞态,并清除这三个事件位。
- 任务C调用xEventGroupWaitBits()等待三个事件位,但那时,三个这三个事件位已被清除,任务A和任务B已经离开了各自的同步点,因此同步失败。
要成功使用事件组来创建同步点,事件位的设置以及事件位的测试必须作为单个不间断操作执行,为此,FreeRTOS提供了xEventGroupSync()函数。
xEventGroupSync()
xEventGroupSync()提供使用事件组彼此同步两个或多个任务的功能。该函数作为单一的操作允许任务在事件组中设置一个或多个事件位,然后等待事件位在同一事件组中被设置。它的原型如下:
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
const EventBits_t uxBitsToWaitFor,
TickType_t xTicksToWait );
参数:
- xEventGroup
事件组的句柄。 - uxBitsToSet
它是一个位掩码,指定在事件组中要设置为1的事件位。将uxBitsToSet的值与现有值按位或来更新事件组的值。 - uxBitsToWaitFor
它也是一个位掩码,指定要在事件组中测试的事件位。 - xTicksToWait
等待的超时时间。
返回值:
满足调用任务解除阻塞条件时,返回值是满足调用任务解除阻塞条件时(在任何位被自动清零前)的事件组的值。如果是因为超时而返回,则返回值是超时时间到达时事件组的值,此时,该值将不符合任务解除阻塞条件。
原文链接:https://blog.csdn.net/qq_25370227/article/details/86635919
3 FreeRTOS消息队列、信号量、事件组、任务通知之间的区别
4 FreeRTOS事件标志组API函数
使用如下11个函数可以实现FreeRTOS的事件标志组:
u xEventGroupCreate()
u xEventGroupCreateStatic()
u vEventGroupDelete()
u xEventGroupWaitBits()
u xEventGroupSetBits()
u xEventGroupSetBitsFromISR()
u xEventGroupClearBits()
u xEventGroupClearBitsFromISR()
u xEventGroupGetBits()
u xEventGroupGetBitsFromISR()
u xEventGroupSync()
关于这11个函数的讲解及其使用方法可以看FreeRTOS在线版手册:
另外强烈推荐用户将Cortex-M3内核的STM32F103和Cortex-M4内核的STM32F407,F429的NVIC优先级分组设置为4,即:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);这样中断优先级的管理将非常方便。
用户要在FreeRTOS多任务开启前就设置好优先级分组,一旦设置好切记不可再修改。
5 事件标志组的应用
5.1 事件组实现看门狗
在嵌入式系统中,看门狗是一个必不可少的组件。在使用了操作系统后,如何正确的实现看门狗成了一个问题!
思路: 让所有线程每隔一段时间上报一次“我还活着”事件给监视程序,当监视程序发现其中一个线程在这段时间内没有上报“我还活着”事件时就停止喂狗。
定义监视的任务事件标志组位
/**
* @brief 最多一次监测MAX_TASK_NUM个任务,如果多于该数,则需要定义多个事件标志组
*/
#define WDG_BIT_DOWN_TASK_1 (1 << 0)
#define WDG_BIT_DOWN_TASK_2 (1 << 1)
#define WDG_BIT_DOWN_TASK_3 (1 << 2)
#define WDG_BIT_DOWN_TASK_4 (1 << 3)
#define WDG_BIT_TASK_ALL ( WDG_BIT_DOWN_TASK_1 | WDG_BIT_DOWN_TASK_2 | WDG_BIT_DOWN_TASK_3 | WDG_BIT_DOWN_TASK_4 )
监视任务
监视任务负责在规定时间内检测个事件标志组位,已达到监测其他任务运行的目的。
/**
* @brief 看门狗任务
* @param argument
* @retval None
*/
void vTaskWDG(void * argument)
{
static TickType_t xTicksToWait = 100 / portTICK_PERIOD_MS*10; /* 最大延迟1s */
EventBits_t uxBits;
/* 创建事件标志组 */
xCreatedEventGroup = xEventGroupCreate();
if(xCreatedEventGroup == NULL)
{
/* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
return;
}
while(1)
{
/* 等待所有任务发来事件标志 */
uxBits = xEventGroupWaitBits(xCreatedEventGroup, /* 事件标志组句柄 */
WDG_BIT_TASK_ALL, /* 等待WDG_BIT_TASK_ALL被设置 */
pdTRUE, /* 退出前WDG_BIT_TASK_ALL被清除,这里是TASK_BIT_ALL都被设置才表示“退出”*/
pdTRUE, /* 设置为pdTRUE表示等待TASK_BIT_ALL都被设置*/
xTicksToWait); /* 等待延迟时间 */
if((uxBits & WDG_BIT_TASK_ALL) == WDG_BIT_TASK_ALL)
{
vWDG_Feed();
}
else
{
/* 通过变量uxBits简单的可以在此处检测那个任务长期没有发来运行标志 */
}
}
}
这样,在其他任务中,不断调用xEventGroupSetBits,给相应的位进行置位即可!一旦有任务没有正常置位,则该任务停止喂狗!
当然,这样就出现了一个问题:有的任务可能需要阻塞相当长的时间,这个时间已经远远超过了看门狗的时限。又或者说,用户手动挂起了一个任务,看门狗必须暂停监视该任务。
解决这个问题也很简单,只需要稍微更改一下看门狗任务即可:
- 首先,必须有函数可以同步看门狗任务,处理任务挂起、恢复问题。
- 其次,对于长时间等待任务,看门狗任务可以以固定频率喂狗,在规定的最大时间到时,检测所有事件,如果这时还有没有置位的事件,则认为出错!
原文链接:https://blog.csdn.net/ZCShouCSDN/article/details/57086718
代码:
/*
*************************************************************************
* 包含的头文件
*************************************************************************
*/
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "event_groups.h"
#include "task.h"
/* 开发板硬件bsp头文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
/**************************** 任务句柄 ********************************/
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为NULL。
*/
/* 创建任务句柄 */
static TaskHandle_t AppTaskCreate_Handle = NULL;
/* LED任务句柄 */
static TaskHandle_t LED_Task_Handle = NULL;
static TaskHandle_t Task1_Handle = NULL;
static TaskHandle_t Task2_Handle = NULL;
static TaskHandle_t TaskWDG_Handle = NULL;
EventGroupHandle_t xMyEventGroup;
#define WDG_BIT_DOWN_TASK1 (1<<0)
#define WDG_BIT_DOWN_TASK2 (1<<1)
#define WDG_BIT_TASK_ALL ( WDG_BIT_DOWN_TASK1 | WDG_BIT_DOWN_TASK2)
/********************************** 内核对象句柄 *********************************/
/*
* 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
* 们就可以通过这个句柄操作这些内核对象。
*
* 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
* 来完成的
*
*/
/******************************* 全局变量声明 ************************************/
/*
* 当我们在写应用程序的时候,可能需要用到一些全局变量。
*/
/*
*************************************************************************
* 函数声明
*************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */
static void LED_Task(void* pvParameters);/* LED_Task任务实现 */
static void vTask1(void* pvParameters);/* Task1任务实现 */
static void vTask2(void* pvParameters);/* Task2任务实现 */
static void vTaskWDG(void* pvParameters);/* TaskWDG任务实现 */
static void BSP_Init(void);/* 用于初始化板载相关资源 */
/*****************************************************************
* @brief 主函数
* @param 无
* @retval 无
* @note 第一步:开发板硬件初始化
第二步:创建APP应用任务
第三步:启动FreeRTOS,开始多任务调度
****************************************************************/
int main(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
/* 开发板硬件初始化 */
BSP_Init();
printf("这是一个[野火]-STM32全系列开发板-FreeRTOS-动态创建任务!\r\n");
/* 创建AppTaskCreate任务 */
xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
(const char* )"AppTaskCreate",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )1, /* 任务的优先级 */
(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */
/* 启动任务调度 */
if(pdPASS == xReturn)
vTaskStartScheduler(); /* 启动任务,开启调度 */
else
return -1;
while(1); /* 正常不会执行到这里 */
}
/***********************************************************************
* @ 函数名 : AppTaskCreate
* @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
* @ 参数 : 无
* @ 返回值 : 无
**********************************************************************/
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建LED_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */
(const char* )"LED_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t* )&LED_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建LED_Task任务成功!\r\n");
/* 创建Task1任务 */
xReturn = xTaskCreate((TaskFunction_t )vTask1, /* 任务入口函数 */
(const char* )"Task1",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )13, /* 任务的优先级 */
(TaskHandle_t* )&Task1_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建Task1任务成功!\r\n");
/* 创建Task2任务 */
xReturn = xTaskCreate((TaskFunction_t )vTask2, /* 任务入口函数 */
(const char* )"Task2",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )7, /* 任务的优先级 */
(TaskHandle_t* )&Task2_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建Task2任务成功!\r\n");
/* 创建TaskWDG任务 */
xReturn = xTaskCreate((TaskFunction_t )vTaskWDG, /* 任务入口函数 */
(const char* )"TaskWDG",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )7, /* 任务的优先级 */
(TaskHandle_t* )&TaskWDG_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建TaskWDG任务成功!\r\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
/**********************************************************************
* @ 函数名 : LED_Task
* @ 功能说明: LED_Task任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void LED_Task(void* parameter)
{
while (1)
{
LED1_ON;
vTaskDelay(500); /* 延时500个tick */
taskENTER_CRITICAL();
printf("LED_Task Running,LED1_ON\r\n");
taskEXIT_CRITICAL();
LED1_OFF;
vTaskDelay(500); /* 延时500个tick */
taskENTER_CRITICAL();
printf("LED_Task Running,LED1_OFF\r\n");
taskEXIT_CRITICAL();
}
}
static void vTask1(void* pvParameters) /* Task1任务实现 */
{
uint32_t count = 0;
while (1)
{
vTaskDelay(100);
xEventGroupSetBits(xMyEventGroup, WDG_BIT_DOWN_TASK1); //设置任务1标志位
taskENTER_CRITICAL();
printf("WDG_BIT_DOWN_TASK1\r\n");
taskEXIT_CRITICAL();
count++;
if(count > 10 && count < 20)
vTaskDelay(1500);
}
static void vTask2(void* pvParameters) /* Task2任务实现 */
{
while (1)
{
vTaskDelay(100);
xEventGroupSetBits(xMyEventGroup, WDG_BIT_DOWN_TASK2); //设置任务1标志位
taskENTER_CRITICAL();
printf("WDG_BIT_DOWN_TASK2\r\n");
taskEXIT_CRITICAL();
}
}
static void vTaskWDG(void* pvParameters) /* TaskWDG任务实现 */
{
EventBits_t uxBits;
//创建事件标志组
xMyEventGroup = xEventGroupCreate();
if(NULL == xMyEventGroup)
{
//xMyEventGroup没有创建成功,用户可以在这里加入创建失败的机制
printf("创建事件标志组xMyEventGroup失败!");
return ;
}
while (1)
{
//等待所有任务发来事件标志
uxBits = xEventGroupWaitBits(xMyEventGroup, //事件标志组句柄
WDG_BIT_TASK_ALL, //等待WDG_BIT_TASK_ALL被设置
pdTRUE, //退出前WDG_BIT_TASK_ALL被清楚,这里是WDG_BIT_TASK_ALL都被设置才表示“退出”
pdTRUE, //设置为pdTRUE表示等待WDG_BIT_TASK_ALL都被设置
1000); //等待延迟时间为1000ticks,即1秒钟
if((uxBits & WDG_BIT_TASK_ALL) == WDG_BIT_TASK_ALL)
{
//vWDG_Feed(); 喂狗操作
taskENTER_CRITICAL();
printf("==========vWDG_Feed==========\r\n");
taskEXIT_CRITICAL();
}
else
{
//通过变量uxBits简单的可以在此处检测到哪个任务长期没有发来运行标志
}
}
}
/***********************************************************************
* @ 函数名 : BSP_Init
* @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
* @ 参数 :
* @ 返回值 : 无
*********************************************************************/
static void BSP_Init(void)
{
/*
* STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
* 都统一用这个优先级分组,千万不要再分组,切忌。
*/
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
/* LED 初始化 */
LED_GPIO_Config();
/* 串口初始化 */
USART_Config();
}
/********************************END OF FILE****************************/
5.2 中断中应用注意事项
实际应用中,中断方式的消息机制要注意以
下四个问题:
中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。
实际应用中,建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在
任务中实现消息处理,这样可以有效地保证中断服务程序的实时响应。同时此任务也需要设置为高优
先级,以便退出中断函数后任务可以得到及时执行。
中断服务程序中一定要调用专用于中断的事件标志设置函数,即以 FromISR 结尾的函数。
在操作系统中实现中断服务程序与裸机编程的区别。
如果 FreeRTOS 工程的中断函数中没有调用 FreeRTOS 的事件标志组 API 函数,与裸机编程是
一样的。
如果 FreeRTOS 工程的中断函数中调用了 FreeRTOS 的事件标志组的 API 函数,退出的时候要
检测是否有高优先级任务就绪,如果有就绪的,需要在退出中断后进行任务切换,这点跟裸机编
程稍有区别。
事件标志组(对应一个变量),本质就是状态机,每一位的0和1代表不同的意义,从而可以被我们根据每位状态,做出相应动作。