一、什么是事件组
之前在FreeRTOS中学了队列和信号量,利用队列和信号量来传达信息,但是我们仔细思考一下,
1、如果单单只依靠队列或者信号量是不是只能唤醒一个任务?但是我需要同时唤醒达到条件的所有任务呢?
2、如果使用队列或者信号量是不是在使用之后就需要释放消息?但是我们希望使用之后还要唤醒其他事件呢?
所以这个时候我们就引入了事件组这个概念,那么什么是事件组呢?
- 事件组可以看成一个整数,他的每一位bit可以看成一个事件,bit1表示事件发生了,bit0表示事件没有发生。
- 一个或多个任务都可以去写这些位,也可以去读这些位。
- 可以等待其中的某一位,也可以等待所有位
如果configUSE_16_BIT_TICK是1,那么就表示整数是16位,低8位是表示事件
如果configUSE_32_BIT_TICK是0,那么就表示整数是32位,低24位是表示事件
1、事件组函数
事件组的创建
EventGroupHandle_t xEventGroupCreateStatic( EventGroupHandle_t * pxEventGroupBuffer );
可以看到xEventGroupCreate(void)事件组创建函数,非NULL即为创建成功
设置事件
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet );
其中参数是指哪个事件组?哪些位?
等待事件
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait ) PRIVILEGED_FUNCTION;
通过官方给的解释可以看到各个参数的根本含义
- xEventGroup:等待哪个事件组
- uxBitsToWaitFor:等待那些位?
- xClearOnExit:函数提出前是否需要清楚事件,如果是pdTRUE那就会清楚uxBitsToWaitFor指定的位,如果是pdFALSE就不会清除。
- xTicksToWait:你希望阻塞的时间,可以设置为0,这里我们设置为成功才返回。
二、事件组的使用
1、 事件组之等待事件
任务函数
void Task1Function(void * param)
{
volatile int i = 0;
while (1)
{
for (i = 0; i < 100000; i++)
sum++;
xQueueSend(xQueueCalcHandle, &sum, 0);
/* 设置事件0 */
xEventGroupSetBits(xEventGroupCalc, (1<<0));
}
}
void Task2Function(void * param)
{
volatile int i = 0;
while (1)
{
for (i = 0; i < 100000; 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);
}
}
主函数
xEventGroupCalc = xEventGroupCreate();
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
xTaskCreate(Task3Function, "Task3", 100, NULL, 1, NULL);
将程序跑起来,我们可以观察到现象
2、事件组之同步点
我们举一个生活例子,做一道菜,是不是需要洗菜,煮饭,炒菜等等一系列步骤,只有等全部做完之后我们才能吃到香喷喷的白米饭。事件组之同步点就是这一概念
我们需要引入xEventGroupSync() 函数
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
const EventBits_t uxBitsToWaitFor,
TickType_t xTicksToWait )
- xEventGroup:哪一个事件组
- uxBitsToSet:需要设置哪些位
- uxBitsToWaitFor:等待哪些位
- xTicksToWait:需要等待的时间
在此直接引用官方提供的源程序进行分析
/* bit0: 洗菜
* bit1: 生火
* bit2: 炒菜
*/
#define TABLE (1<<0)
#define BUYING (1<<1)
#define COOKING (1<<2)
#define ALL (TABLE | BUYING | COOKING)
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);
}
}
可以看到在任务中,分别写入自己的位,并且等待所有的位然后再执行任务。程序运行起来的结果和所说无差别。
总结
本文讲述了FreeRTOS中的事件组,以及事件组基本的使用。通过这两天的学习感觉FreeRTOS就是在调用别人写好的API函数,但是里面内核是如何实现的我一无所知,等将这门课过一遍之后再回过头来仔细研究一下实现的原理和过程。