正点原子FreeRTOS(下)

Posted 行稳方能走远

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了正点原子FreeRTOS(下)相关的知识,希望对你有一定的参考价值。

目录

第十六章FreeRTOS 事件标志组

前面我们学习了使用信号量来完成同步,但是使用信号量来同步的话任务只能与单个的事
件或任务进行同步。有时候某个任务可能会需要与多个事件或任务进行同步,此时信号量就无
能为力了。FreeRTOS 为此提供了一个可选的解决方法,那就是事件标志组。本章我们就来学习
一下FreeRTOS 中事件标志组的使用,本章分为如下几部分:
16.1 事件标志组简介
16.2 创建事件标志组
16.3 设置事件位
16.4 获取事件标志组值
16.5 等待指定的事件位
16.6 事件标志组实验

16.1 事件标志组简介

1、事件位(事件标志)
事件位用来表明某个事件是否发生,事件位通常用作事件标志,比如下面的几个例子:
●当收到一条消息并且把这条消息处理掉以后就可以将某个位(标志)置1,当队列中没有
消息需要处理的时候就可以将这个位(标志)置0。
●当把队列中的消息通过网络发送输出以后就可以将某个位(标志)置1,当没有数据需要
从网络发送出去的话就将这个位(标志)置0。
●现在需要向网络中发送一个心跳信息,将某个位(标志)置1。现在不需要向网络中发送
心跳信息,这个位(标志)置0。
2、事件组
一个事件组就是一组的事件位,事件组中的事件位通过位编号来访问,同样,以上面列出
的三个例子为例:
●事件标志组的bit0 表示队列中的消息是否处理掉。
●事件标志组的bit1 表示是否有消息需要从网络中发送出去。
●事件标志组的bit2 表示现在是否需要向网络发送心跳信息。
3、事件标志组和事件位的数据类型
事件标志组的数据类型为EventGroupHandle_t,当configUSE_16_BIT_TICKS 为1 的时候
事件标志组可以存储8 个事件位,当configUSE_16_BIT_TICKS 为0 的时候事件标志组存储24
个事件位。
事件标志组中的所有事件位都存储在一个无符号的EventBits_t 类型的变量中,EventBits_t
在event_groups.h 中有如下定义:

typedef TickType_t EventBits_t;

数据类型TickType_t 在文件portmacro.h 中有如下定义:

#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
	#define portTICK_TYPE_IS_ATOMIC 1
#endif

可以看出当configUSE_16_BIT_TICKS 为0 的时候TickType_t 是个32 位的数据类型,因
此EventBits_t 也是个32 位的数据类型。EventBits_t 类型的变量可以存储24 个事件位,另外的
那高8 位有其他用。事件位0 存放在这个变量的bit0 上,变量的bit1 就是事件位1,以此类推。
对于STM32 来说一个事件标志组最多可以存储24 个事件位,如图16.1.1 所示:

16.2 创建事件标志组

FreeRTOS 提供了两个用于创建事件标志组的函数,如表16.2.1 所示:

1、函数xEventGroupCreate()
此函数用于创建一个事件标志组,所需要的内存通过动态内存管理方法分配。由于内部处
理的原因,事件标志组可用的bit 数取决于configUSE_16_BIT_TICKS ,当
configUSE_16_BIT_TICKS1 为1 的时候事件标志组有8 个可用的位(bit0~bit7) ,当
configUSE_16_BIT_TICKS 为0 的时候事件标志组有24 个可用的位(bit0~bit23)。EventBits_t 类
型的变量用来存储事件标志组中的各个事件位,函数原型如下:

EventGroupHandle_t xEventGroupCreate( void )

参数:
无。
返回值:
NULL: 事件标志组创建失败。
其他值: 创建成功的事件标志组句柄。
2、函数xEventGroupCreateStatic()
此函数用于创建一个事件标志组定时器,所需要的内存需要用户自行分配,此函数原型如
下:

EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t *pxEventGroupBuffer )

参数:
pxEventGroupBuffer:参数指向一个StaticEventGroup_t 类型的变量,用来保存事件标志组结
构体。
返回值:
NULL: 事件标志组创建失败。
其他值: 创建成功的事件标志组句柄。

16.3 设置事件位

FreeRTOS 提供了4 个函数用来设置事件标志组中事件位(标志),事件位(标志)的设置包括
清零和置1 两种操作,这4 个函数如表16.3.1 所示:

函数描述
xEventGroupClearBits()将指定的事件位清零,用在任务中。
xEventGroupClearBitsFromISR()将指定的事件位清零,用在中断服务函数中
xEventGroupSetBits()将指定的事件位置1,用在任务中。
xEventGroupSetBitsFromISR()将指定的事件位置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: 事件位清零失败。
2、函数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 后的事件组值。
3、函数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 失败。

16.4 获取事件标志组值

我们可以通过FreeRTOS 提供的API 函数来查询事件标准组值,FreeRTOS 一共提供了两个
这样的API 函数,如表16.4.1 所示:


1、函数xEventGroupGetBits()
此函数用于获取当前事件标志组的值,也就是各个事件位的值。此函数用在任务中,不能
用在中断服务函数中。此函数是个宏,真正执行的是函数xEventGroupClearBits(),函数原型如
下:

EventBits_t xEventGroupGetBits( EventGroupHandle_t xEventGroup )

参数:
xEventGroup:要获取的事件标志组的句柄。
返回值:
任何值:当前事件标志组的值。
2、函数xEventGroupGetBitsFromISR()
获取当前事件标志组的值,此函数是xEventGroupGetBits()的中断版本,函数原型如下:

EventBits_t xEventGroupGetBitsFromISR( EventGroupHandle_t xEventGroup )

参数:
xEventGroup:要获取的事件标志组的句柄。
返回值:
任何值:当前事件标志组的值。

16.5 等待指定的事件位

某个任务可能需要与多个事件进行同步,那么这个任务就需要等待并判断多个事件位(标
志),使用函数xEventGroupWaitBits()可以完成这个功能。调用函数以后如果任务要等待的事件
位还没有准备好(置1 或清零)的话任务就会进入阻塞态,直到阻塞时间到达或者所等待的事件
位准备好。函数原型如下:

EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
const TickType_t xTicksToWait );

参数:
xEventGroup:指定要等待的事件标志组。
uxBitsToWaitFord:指定要等待的事件位,比如要等待bit0 和(或)bit2 的时候此参数就是0X05,
如果要等待bit0 和(或)bit1 和(或)bit2 的时候此参数就是0X07,以此类推。
xClearOnExit:此参数要是为pdTRUE 的话,那么在退出此函数之前由参数uxBitsToWaitFor
所设置的这些事件位就会清零。如果设置位pdFALSE 的话这些事件位就
不会改变。
xWaitForAllBits:此参数如果设置为pdTRUE 的话,当uxBitsToWaitFor 所设置的这些事件
位都置1,或者指定的阻塞时间到的时候函数xEventGroupWaitBits()才会
返回。当此函数为pdFALSE 的话,只要uxBitsToWaitFor 所设置的这些事
件位其中的任意一个置1 ,或者指定的阻塞时间到的话函数
xEventGroupWaitBits()就会返回。
xTicksToWait:设置阻塞时间,单位为节拍数。
返回值:
任何值:返回当所等待的事件位置1 以后的事件标志组的值,或者阻塞时间到。根
据这个值我们就知道哪些事件位置1 了。如果函数因为阻塞时间到而返回的话那么这个返回值就不代表任何的含义。

16.6 事件标志组实验

16.6.1 实验程序设计

1、实验目的
学习FreeROTS 事件标志组的使用,包括创建事件标志组、将相应的事件位置1、等待相应
的事件位置1 等操作。
2、实验设计
本实验设计四个任务:start_task、eventsetbit_task、eventgroup_task 和eventquery_task 这四
个任务的任务功能如下:
start_task:用来创建其他三个任务和事件标志组。
eventsetbit_task:读取按键值,根据不同的按键值将事件标志组中相应的事件位置1,用来
模拟事件的发生。
eventgroup_task:同时等待事件标志组中的多个事件位,当这些事件位都置1 的话就执行
相应的处理,例程中是刷新LCD 指定区域的背景色。
eventquery_task:查询事件组的值,也就是各个事件位的值。获取到事件组值以后就将其显
示到LCD 上,并且也通过串口打印出来。
实验中还创建了一个事件标志组:EventGroupHandler,实验中用到了这个事件标志组的三
个事件位,分别位bit0,bit1 和bit2。
实验中会用到3 个按键:KEY0、KEY1 和KEY2,其中按键KEY1 和KEY2 为普通的输入
模式。按键KEY0 为中断输入模式,KEY0 用来演示如何在中断服务程序调用事件标志组的API
函数。
3、实验工程
FreeRTOS 实验16-1 FreeRTOS 事件标志组实验。
4、实验程序与分析
●任务设置

#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 256 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
#define EVENTSETBIT_TASK_PRIO 2 //任务优先级
#define EVENTSETBIT_STK_SIZE 256 //任务堆栈大小
TaskHandle_t EventSetBit_Handler; //任务句柄
void eventsetbit_task(void *pvParameters); //任务函数
#define EVENTGROUP_TASK_PRIO 3 //任务优先级
#define EVENTGROUP_STK_SIZE 256 //任务堆栈大小
TaskHandle_t EventGroupTask_Handler; //任务句柄
void eventgroup_task(void *pvParameters); //任务函数
#define EVENTQUERY_TASK_PRIO 4 //任务优先级
#define EVENTQUERY_STK_SIZE 256 //任务堆栈大小
TaskHandle_t EventQueryTask_Handler; //任务句柄
void eventquery_task(void *pvParameters); //任务函数

EventGroupHandle_t EventGroupHandler; //事件标志组句柄
#define EVENTBIT_0 (1<<0) //事件位
#define EVENTBIT_1 (1<<1)
#define EVENTBIT_2 (1<<2)
#define EVENTBIT_ALL (EVENTBIT_0|EVENTBIT_1|EVENTBIT_2)
//LCD 刷屏时使用的颜色
int lcd_discolor[14]= WHITE, BLACK, BLUE, BRED,
        GRED, GBLUE, RED, MAGENTA,
        GREEN, CYAN, YELLOW, BROWN,
        BRRED, GRAY ;

●main()函数

int main(void)

        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
        delay_init(); //延时函数初始化
        uart_init(115200); //初始化串口
        LED_Init(); //初始化LED
        KEY_Init(); //初始化按键
        EXTIX_Init(); //初始化外部中断
        BEEP_Init(); //初始化蜂鸣器
        LCD_Init(); //初始化LCD
        my_mem_init(SRAMIN); //初始化内部内存池
        POINT_COLOR = RED;
        LCD_ShowString(30,10,200,16,16,"ATK STM32F103/407");
        LCD_ShowString(30,30,200,16,16,"FreeRTOS Examp 16-1");
        LCD_ShowString(30,50,200,16,16,"Event Group");
        LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
        LCD_ShowString(30,90,200,16,16,"2016/11/25");
        POINT_COLOR = BLACK;
        LCD_DrawRectangle(5,130,234,314); //画矩形
        POINT_COLOR = BLUE;
        LCD_ShowString(30,110,220,16,16,"Event Group Value:0");
        //创建开始任务
        xTaskCreate((TaskFunction_t )start_task, //任务函数
                        (const char* )"start_task", //任务名称
                        (uint16_t )START_STK_SIZE, //任务堆栈大小
                        (void* )NULL, //传递给任务函数的参数
                        (UBaseType_t )START_TASK_PRIO, //任务优先级
                        (TaskHandle_t* )&StartTask_Handler); //任务句柄
        vTaskStartScheduler(); //开启任务调度

●任务函数

//开始任务任务函数
void start_task(void *pvParameters)

        taskENTER_CRITICAL(); //进入临界区
        //创建事件标志组
        EventGroupHandler=xEventGroupCreate(); //创建事件标志组(1)
        //创建设置事件位的任务
        xTaskCreate((TaskFunction_t )eventsetbit_task,
                        (const char* )"eventsetbit_task",
                        (uint16_t )EVENTSETBIT_STK_SIZE,
                        (void* )NULL,
                        (UBaseType_t )EVENTSETBIT_TASK_PRIO,
                        (TaskHandle_t* )&EventSetBit_Handler);
        //创建事件标志组处理任务
        xTaskCreate((TaskFunction_t )eventgroup_task,
                        (const char* )"eventgroup_task",
                        (uint16_t )EVENTGROUP_STK_SIZE,
                        (void* )NULL,
                        (UBaseType_t )EVENTGROUP_TASK_PRIO,
                        (TaskHandle_t* )&EventGroupTask_Handler);
        //创建事件标志组查询任务
        xTaskCreate((TaskFunction_t )eventquery_task,
                        (const char* )"eventquery_task",
                        (uint16_t )EVENTQUERY_STK_SIZE,
                        (void* )NULL,
                        (UBaseType_t )EVENTQUERY_TASK_PRIO,
                        (TaskHandle_t* )&EventQueryTask_Handler);
        vTaskDelete(StartTask_Handler); //删除开始任务
        taskEXIT_CRITICAL(); //退出临界区

//设置事件位的任务
void eventsetbit_task(void *pvParameters)

        u8 key;
        while(1)
        
                if(EventGroupHandler!=NULL)
                
                        key=KEY_Scan(0);
                        switch(key)
                        
                                case KEY1_PRES:
                                        xEventGroupSetBits(EventGroupHandler,EVENTBIT_1); (2)
                                                break;
                                case KEY2_PRES:
                                        xEventGroupSetBits(EventGroupHandler,EVENTBIT_2); (3)
                                                break;
                        
                
                vTaskDelay(10); //延时10ms,也就是10 个时钟节拍
        

//事件标志组处理任务
void eventgroup_task(void *pvParameters)

        u8 num;
        EventBits_t EventValue;
        while(1)
        
                if(EventGroupHandler!=NULL)
                
                        //等待事件组中的相应事件位
                        EventValue=xEventGroupWaitBits((EventGroupHandle_t )EventGroupHandler, (4)
                                        (EventBits_t ) EVENTBIT_ALL,
                                        (BaseType_t )pdTRUE,
                                        (BaseType_t )pdTRUE,
                                        (TickType_t )portMAX_DELAY);
                        printf("事件标志组的值:%d\\r\\n",EventValue);
                        LCD_ShowxNum(174,110,EventValue,1,16,0);
                        num++;
                        LED1=!LED1;
                        LCD_Fill(6,131,233,313,lcd_discolor[num%14]);
                
                else
                
                        vTaskDelay(10); //延时10ms,也就是10 个时钟节拍
                
        

//事件查询任务
void eventquery_task(void *pvParameters)

        u8 num=0;
        EventBits_t NewValue,LastValue;
        while(1)
        
                if(EventGroupHandler!=NULL)
                
                        NewValue=xEventGroupGetBits(EventGroupHandler); //获取事件组的(5)
                        if(NewValue!=LastValue)
                        
                                LastValue=NewValue;
                                printf("事件标志组的值:%d\\r\\n",NewValue);
                                LCD_ShowxNum(174,110,NewValue,1,16,0);
                        
                
                num++;
                if(num==0) //每500msLED0 闪烁一次
                
                        num=0;
                        LED0=!LED0;
                
                vTaskDelay(50); //延时50ms,也就是50 个时钟节拍
        

(1)、首先调用函数xEventGroupCreate()创建一个事件标志组EventGroupHandler。
(2)、按下KEY1 键的时候就调用函数xEventGroupSetBits()将事件标志组的bit1 置1。
(3)、按下KEY2 键的时候调用函数xEventGroupSetBits()将事件标志组的bit2 值1。
(4)、调用函数xEventGroupWaitBits()同时等待事件标志组的bit0,bit1 和bit2,只有当这三
个事件都置1 的时候才会执行任务中的其他代码。
(5)、调用函数xEventGroupGetBits()查询事件标志组EventGroupHandler 的值变化,通过查看这些值的变化就可以分析出当前哪个事件位置1 了。
●中断初始化及处理过程
事件标志组EventGroupHandler 的事件位bit0是通过KEY0 的外部中断服务函数来设置的,
注意中断优先级的设置!本例程的中断优先级设置如下:

NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x06; //抢占优先级6
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; //子优先级0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //初始化外设NVIC 寄存器

KEY0 的外部中断服务函数如下:

//事件标志组句柄        
extern EventGroupHandle_t EventGroupHandler;
//中断服务函数
void EXTI4_IRQHandler (void)

        BaseType_t Result,xHigherPriorityTaskWoken;
        delay_xms(50); //消抖
        if(KEY0==0)
        
                Result=xEventGroupSetBitsFromISR(EventGroupHandler,EVENTBIT_0,\\ (1)
                                &xHigherPriorityTaskWoken);
                if(Result!=pdFAIL)
                
                        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
                
        
        EXTI_ClearITPendingBit(EXTI_Line4);//清除LINE4 上的中断标志位


(1)、在中断服务函数中通过调用xEventGroupSetBitsFromISR()来将事件标志组的事件位
bit0 置1。

16.6.2 程序运行结果分析

编译并下载实验代码到开发板中,打开串口调试助手,默认情况下LCD 显示如图16.6.2.1
所示:


通过按下KEY0、KEY1 和KEY2 来观察LCD 的变化和串口调试助手收到的信息,当KEY0、
KEY1 和KEY2 都有按下的时候LCD 指定区域的背景颜色就会刷新,因为三个事件位都置1
了,LCD 显示如图16.6.2.2 所示:


注意观察,当LCD 背景颜色刷新完成以后事件标志组就会清零,通过串口调试助手可以很
清楚的观察到事件标志组值的变化情况,如图16.6.2.3 所示:

从图16.6.2.3 中可以看出,每次事件标志组的值为7 的话就会紧接着将事件标志组的值清
零。这是因为当事件标志组的值为7 的话说明事件位bit0,bit1 和bit2 都置1 了,任务
eventgroup_task()所等待的事件满足了,然后

以上是关于正点原子FreeRTOS(下)的主要内容,如果未能解决你的问题,请参考以下文章

正点原子FreeRTOS(下)

正点原子FreeRTOS(下)

正点原子FreeRTOS

使用HAL对STM32F407ZGT6单片机移植FreeRTOS(参考正点原子)

FreeRTOS 系统时钟节拍和时间管理

FreeRTOS-06任务运行时间信息统计