事件标志组定义
事件标志位:用一个为来表示事件是否发生
事件标志组是事件标志位的集合可以简单的理解事件标志组,就是一个整数
事件标志组的特点:
-
1:它的每一个位表示一个事件(高8位不算)
-
2:每一个事件的含义,由用户自己决定,如:bit0表示按键是否按下,bit表示是否接收到消息......(这些位的值为1:表示事件发生了,值为0:表示事件未发生)
-
3:任意任务或中断都可以读写这些位
-
4:可以等待某一位成立,或者诸多位同事成立
事件标志组简介
一个事件组就包含了一个EventBits_t 数据类型的变量,变量类型EventBits_t的定义如下所示。
typdef TickType_t EventBits_t ;
#if (configUSE_16_BIT_TICKS == 1)
typedef uint16_t TickType_t; // 1 表示的是无符号16位的数据类型
#else
typedef uint32_t TickType_t; // 0 表示的是无符号32位的数据类型
#endif
#define configUSE_16_BIT_TICKS 0
虽然使用了32位无符号的数据类型变量来存储事件标志,但其中的高8位用作存储事件标志组的控制信息,低24位用作存储事件标志,所以说一个事件组最多可以存储24个事件标志!
事件标志组与队列,信号量的区别?
功能 | 唤醒对象 | 事件清除 |
队列,信号量 | 事件发生时,只能唤醒一个任务 | 是消耗型资源,队列的数据被读走就没有了,信号量被获取后就减少了 |
事件标志组 | 事件发生时会唤醒所有符合条件的任务,可以理解为广播的作用 | 被唤醒的任务有两个选择,可以让事件保留不动,也可以清除事件 |
事件标志组API函数
函数 | 描述 |
xEventGroupCreate() | 使用动态方式创建事件标志组 |
xEventGroupCreateStatic() | 使用静态方式创建事件标志组 |
xEventGroupClearBits() | 清零事件标志位 |
xEventGroupClearBitsFromISR() | 在中断中清零事件标志位 |
xEventGroupSetBits() | 设置事件标志位 |
xEventGroupSetBitsFromISR() | 在中断中设置事件标志位 |
xEventGroupWaitBits() | 等待事件标志位 |
xEventGroupSync() | 设置事件标志位并等待事件标志位 |
创建标志组
使用动态的方式创建事件标志组
使用事件组之前,要先创建,得到一个句柄;
使用事件组时,要使用句柄来表明使用哪个事件组。
有两种创建方法:动态分配内存、静态分配内存。
函数原型如下:
EventGroupHandle_t xEventGroupCreate ( void ) ;
返回值 | 描述 |
NULL | 事件标志组创建失败 |
其他值 | 事件标志组创建成功,返回句柄 |
创建示例:
清除事件标志位API函数
EventBits_t xEventGroupClearBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear
);
EventBits_t xEventGroupClearBits
(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear
);
形参 | 描述 |
xEventGroup | 待操作的事件标志组句柄 |
uxBitsToSet | 待清零的事件标志位 |
返回值 | 描述 |
整数 | 清零事件标志位之前事件组中事件标志位的值 |
设置事件标志位API函数
EventBits_t xEventGroupSetBits
(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet
);
形参 | 描述 |
xEventGroup | 待操作的事件标志组句柄 |
uxBitsToSet | 待设置的事件标志位 |
返回值 | 描述 |
整数 | 事件组中的事件标志位值 |
可以设置事件组的某个位、某些位,使用的函数有2个:
-
在任务中使用 xEventGroupSetBits()
-
在ISR中使用 xEventGroupSetBitsFromISR()
有一个或多个任务在等待事件,如果这些事件符合这些任务的期望,那么任务还会被唤醒。函数原型如下:
注:
值得注意的是,ISR中的函数,比如队列函数 xQueueSendToBackFromISR、信号量函数xSemaphoreGiveFromISR,它们会唤醒某个任务,最多只会唤醒1个任务。
但是设置事件组时,有可能导致多个任务被唤醒,这会带来很大的不确定性。
所以xEventGroupSetBitsFromISR函数不是直接去设置事件组,而是给一个FreeRTOS后台任务(daemon task)发送队列数据,由这个任务来设置事件组。
如果后台任务的优先级比当前被中断的任务优先级高, xEventGroupSetBitsFromISR会设置 *pxHigherPriorityTaskWoken为pdTRUE。
如果daemon task成功地把队列数据发送给了后台任务,那么 xEventGroupSetBitsFromISR的返回值就是pdPASS。
等待事件标志位的API函数
形参 | 描述 |
xEventGroup | 等待事件标志组句柄 |
uxBitsToWaitFor | 等待事件标志位,可以用逻辑或等待多个事件标志位 |
xClearOnExit | 成功等待事件标志位后,清除事件组中对应的事件标志位, pdTRUE: 清除uxBitsToWaitFor指定位 pdFALSE: 不清除 |
xWaitForAllBits | 等待uxBitsToWaitFor中的所有事件标志位(逻辑与) pdTRUE:等待的位,全部为1 pdFALSE:等待的位,某个为1 |
xTicksToWait | 等待的阻塞时间 |
返回值 | 描述 |
等待事件标志位值 | 等待事件标志位成功,返回等待到的事件标志位 |
其他值 | 等待事件标志位失败,返回事件组中的事件标志位 |
特点:可以等待某一位也可以等待多位,等到期望事件后,还可以清除某些位
同步函数
同步点
EventBits_t xEventGroupSysc(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
const EventBits_t uxBitsToWaitFor,
TickType_t xTickToWait
)
形参 | 描述 |
xEventGroup | 等待事件标志所在事件组 |
uxBitsToSet | 达到同步点后,要设置的事件标志 |
uxBitsToWaitFor | 等待事件标志 |
xTickToWait | 等待阻塞时间 |
同步点函数形参与返回值
返回值 | 描述 |
等待的事件标志位值 | 等待事件标志位成功,返回等待到的事件标志位 |
其他值 | 等待事件标志位失败,返回事件组中的事件标志位 |
例子:
Task 1 : 做饭
Task 2 : 做菜
Task1做好自己的事情之后, 需要等到菜也做好后,大家再一起吃饭
事件组的使用:
等待事件
本节程序:20_freertos_example_event_group
。
-
创建3个任务
-
任务1:累加10000000次,然后设置事件bit 0
-
任务2:累减5000000次,然后设置事件bit 1
-
任务3:等待
-
事件0和事件1
-
事件0或事件1
-
step1:使用动态创建任务的方式创建三个任务
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
xTaskCreate(Task3Function, "Task3", 100, NULL, 1, NULL);
step2:实现累加功能,累加
void Task1Function(void * param)
{
// 使用volatile 不给程序代码做优化
volatile int i = 0;
// 使用while循环
while (1)
{
// i的初始值为1,循环10000次
for (i = 0; i < 10000; i++)
sum++;
/*
1:读取队列中的信息,第一个参数是队列的句柄,要读取的队列是哪一个
2:第二个参数是buffer的指针,队列的数据会被复制到这个buffer中
3:第三个是阻塞函数,没有读取到之后的操作0表示没有读取到数据立刻返回,
portMAX_DELAY,则会一直阻塞直到有数据可写
*/
xQueueSend(xQueueCalcHandle, &sum, 0);
/* 设置事件0 设置事件标志位*/
xEventGroupSetBits(xEventGroupCalc, (1<<0));
}
}
step3: 实现功能的累减,bit1
void Task2Function(void * param)
{
volatile int i = 0;
while (1)
{
for (i = 0; i < 10000; i++)
dec--;
xQueueSend(xQueueCalcHandle, &dec, 0);
/* 设置事件1 */
xEventGroupSetBits(xEventGroupCalc, (1<<1));
}
}
step4:等待事件
使用 xEventGroupWaitBits来等待事件,可以等待某一位、某些位中的任意一个,也可以等待多位;等到期望的事件后,还可以清除某些位。
函数原型如下:
注:
1:第一个参数表示等待哪些事件组
2:第二个参数表示等待哪些位,哪些位要被测试
3:第三个参数表示函数退出前是否清除事件
- 返回值pdTRUE表示清除uxBitsToWaitFor指定的位
- pdFALSE: 不清除
4:第四个参数表示如何测试是AND还是OR
- pdTRUE: 等待的位,全部为1;
- pdFALSE: 等待的位,某一个为1即可
5:最后一个参数表示阻塞等待时间是立即返回还是死等
void Task3Function(void * param)
{
int val1, val2;
while (1)
{
/*等待事件 */
xEventGroupWaitBits(xEventGroupCalc, (1<<0)|(1<<1), pdTRUE, pdTRUE, portMAX_DELAY);
xQueueReceive(xQueueCalcHandle, &val1, 0);
xQueueReceive(xQueueCalcHandle, &val2, 0);
printf("val1 = %d, val2 = %d\r\n", val1, val2);
}
}
创建事件组
/* 创建事件组 */
xEventGroupCalc = xEventGroupCreate();
// 创建队列,队列项2个,大小是一个int 位4个byte
xQueueCalcHandle = xQueueCreate(2, sizeof(int));
// 判断是否创建成功
if (xQueueCalcHandle == NULL)
{
printf("can not create queue\r\n");
}
全局变量及句柄定义
static int sum = 0; // 参数++ 的值
static int dec = 0; // 参数-- 的值
static volatile int flagCalcEnd = 0;
static volatile int flagUARTused = 0;
static QueueHandle_t xQueueCalcHandle; // 队列的句柄
static EventGroupHandle_t xEventGroupCalc; //事件组的句柄
完整程序代码
static int sum = 0; // 参数++ 的值
static int dec = 0; // 参数-- 的值
static volatile int flagCalcEnd = 0;
static volatile int flagUARTused = 0;
static QueueHandle_t xQueueCalcHandle; // 队列的句柄
static EventGroupHandle_t xEventGroupCalc; //事件组的句柄
void Task1Function(void * param)
{
// 使用volatile 不给程序代码做优化
volatile int i = 0;
// 使用while循环
while (1)
{
// i的初始值为1,循环10000次
for (i = 0; i < 10000; i++)
sum++;
/*
1:读取队列中的信息,第一个参数是队列的句柄,要读取的队列是哪一个
2:第二个参数是buffer的指针,队列的数据会被复制到这个buffer中
3:第三个是阻塞函数,没有读取到之后的操作0表示没有读取到数据立刻返回,
portMAX_DELAY,则会一直阻塞直到有数据可写
*/
xQueueSend(xQueueCalcHandle, &sum, 0);
/* 设置事件0 设置事件标志位*/
xEventGroupSetBits(xEventGroupCalc, (1<<0));
}
}
void Task2Function(void * param)
{
volatile int i = 0;
while (1)
{
for (i = 0; i < 10000; i++)
dec--;
xQueueSend(xQueueCalcHandle, &dec, 0);
/* 设置事件1 */
xEventGroupSetBits(xEventGroupCalc, (1<<1));
}
}
void Task3Function(void * param)
{
int val1, val2;
while (1)
{
/*等待事件 */
xEventGroupWaitBits(xEventGroupCalc, (1<<0)|(1<<1), pdTRUE, pdTRUE, portMAX_DELAY);
xQueueReceive(xQueueCalcHandle, &val1, 0);
xQueueReceive(xQueueCalcHandle, &val2, 0);
printf("val1 = %d, val2 = %d\r\n", val1, val2);
}
}
/*-----------------------------------------------------------*/
int main( void )
{
TaskHandle_t xHandleTask1;
#ifdef DEBUG
debug();
#endif
prvSetupHardware();
printf("Hello, world!\r\n");
/* 创建事件组 */
xEventGroupCalc = xEventGroupCreate();
// 创建队列,队列项2个,大小是一个int 位4个byte
xQueueCalcHandle = xQueueCreate(2, sizeof(int));
// 判断是否创建成功
if (xQueueCalcHandle == NULL)
{
printf("can not create queue\r\n");
}
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
xTaskCreate(Task3Function, "Task3", 100, NULL, 1, NULL);
/* Start the scheduler. */
vTaskStartScheduler();
/* Will only get here if there was not enough heap space to create the
idle task. */
return 0;
}
运行结果
同步点案例
本节程序:
-
21_freertos_example_event_group_task_sync
-
就是参考书配套源码
FreeRTOS_21_event_group_task_sync
-
过于简单,就不现场写了。
xEventGroupSync退出后,会清除事件吗?
使用xEventGroupSync函数时,在参数uxBisToWaitF-or中指定了等待哪些事件,如果函数成功返回,则会清除uxBitsToWaitFor表示的位。
/* Standard includes. */
#include <stdio.h>
/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
/* Library includes. */
#include "stm32f10x_it.h"
extern void UART_Init(unsigned long ulWantedBaud);
/* Demo app includes. */
static void prvSetupHardware( void );
/*-----------------------------------------------------------*/
static void vCookingTask( void *pvParameters );
static void vBuyingTask( void *pvParameters );
static void vTableTask( void *pvParameters );
/*-----------------------------------------------------------*/
/* 事件组句柄 */
EventGroupHandle_t xEventGroup;
/* bit0: 洗菜
* bit1: 生火
* bit2: 炒菜
*/
#define TABLE (1<<0)
#define BUYING (1<<1)
#define COOKING (1<<2)
#define ALL (TABLE | BUYING | COOKING)
int main( void )
{
prvSetupHardware();
/* 创建递归锁 */
xEventGroup = xEventGroupCreate( );
if( xEventGroup != NULL )
{
/* 创建3个任务: 洗菜/生火/炒菜
*/
xTaskCreate( vCookingTask, "task1", 1000, "A", 1, NULL );
xTaskCreate( vBuyingTask, "task2", 1000, "B", 2, NULL );
xTaskCreate( vTableTask, "task3", 1000, "C", 3, NULL );
/* 启动调度器 */
vTaskStartScheduler();
}
else
{
/* 无法创建事件组 */
}
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
/*-----------------------------------------------------------*/
/*-----------------------------------------------------------*/
static void vCookingTask( void *pvParameters )
{
const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
int i = 0;
/* 无限循环 */
for( ;; )
{
/* 做自己的事 */
printf("%s is cooking %d time....\r\n", (char *)pvParameters, i);
/* 表示我做好了, 还要等别人都做好 */
xEventGroupSync(xEventGroup, COOKING, ALL, portMAX_DELAY);
/* 别人也做好了, 开饭 */
printf("%s is eating %d time....\r\n", (char *)pvParameters, i++);
vTaskDelay(xTicksToWait);
}
}
static void vBuyingTask( void *pvParameters )
{
const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
int i = 0;
/* 无限循环 */
for( ;; )
{
/* 做自己的事 */
printf("%s is buying %d time....\r\n", (char *)pvParameters, i);
/* 表示我做好了, 还要等别人都做好 */
xEventGroupSync(xEventGroup, BUYING, ALL, portMAX_DELAY);
/* 别人也做好了, 开饭 */
printf("%s is eating %d time....\r\n", (char *)pvParameters, i++);
vTaskDelay(xTicksToWait);
}
}
static void vTableTask( void *pvParameters )
{
const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );
int i = 0;
/* 无限循环 */
for( ;; )
{
/* 做自己的事 */
printf("%s is do the table %d time....\r\n", (char *)pvParameters, i);
/* 表示我做好了, 还要等别人都做好 */
xEventGroupSync(xEventGroup, TABLE, ALL, portMAX_DELAY);
/* 别人也做好了, 开饭 */
printf("%s is eating %d time....\r\n", (char *)pvParameters, i++);
vTaskDelay(xTicksToWait);
}
}
程序运行结果
【参考正点原子教程与韦东山文档撰写】