使用了全局变量就要防止多任务的访问冲突, 而使用事件标志组则处理好了这个问题,用户无需担心。使用事件标志组可以有效地解决中断服务程序和任务之间的同步问题
下面我们来说说 FreeRTOS 中事件标志的实现, 根据用户在 FreeRTOSConfig.h 文件中的配置:
#define configUSE_16_BIT_TICKS 0/1
配置宏定义 configUSE_16_BIT_TICKS 为 0 时,每创建一个事件标志组, 用户可以使用的事件标志是24 个,而configUSE_16_BIT_TICKS 为 1时事件标志是24 个 。
上面说的 8 个和 24 个事件标志应该怎么理解呢? 其实就是定义了一个 16 位变量,仅使用了低 8bit或者定义了一个 32 位变量,仅使用了低 24bit。每一个 bit 用 0 和 1 两种状态来代表事件标志。
#include "FreeRTOS.h"
#include "task.h"
#include<stdio.h>
#include "timers.h"
#include "event_groups.h"
void vApplicationMallocFailedHook() {
while(1);
}
void vApplicationStackOverflowHook(TaskHandle_t xTask, char * pcTaskName ) {
while(1);
}
// 定义事件位
#define BIT_0 (1 << 0)
#define BIT_1 (1 << 1)
// 任务句柄
TaskHandle_t xSensorTask1Handle = NULL;
TaskHandle_t xSensorTask2Handle = NULL;
TaskHandle_t xHandlerTaskHandle = NULL;
// 事件组句柄
EventGroupHandle_t xEventGroupHandle = NULL;
// 传感器任务1
void vSensorTask1(void *pvParameters)
{
for (;;)
{
// 模拟传感器事件
printf("Sensor Task 1: Event occurred.\n");
xEventGroupSetBits(xEventGroupHandle, BIT_0);
vTaskDelay(pdMS_TO_TICKS(1000)); // 延时1秒
}
}
// 传感器任务2
void vSensorTask2(void *pvParameters)
{
for (;;)
{
// 模拟传感器事件
printf("Sensor Task 2: Event occurred.\n");
xEventGroupSetBits(xEventGroupHandle, BIT_1);
vTaskDelay(pdMS_TO_TICKS(1500)); // 延时1.5秒
}
}
// 处理任务
void vHandlerTask(void *pvParameters)
{
const EventBits_t xBitsToWaitFor = BIT_0 | BIT_1;
for (;;)
{
// 等待事件位被设置
EventBits_t uxBits = xEventGroupWaitBits(
xEventGroupHandle, // 事件组句柄
xBitsToWaitFor, // 要等待的事件位
pdTRUE, // 退出时清除事件位
pdFALSE, // 不等待所有事件位,任意一个即可
portMAX_DELAY); // 无限期等待
// 检查事件位
if ((uxBits & BIT_0) != 0)
{
printf("Handler Task: Handling event from Sensor Task 1.\n");
}
if ((uxBits & BIT_1) != 0)
{
printf("Handler Task: Handling event from Sensor Task 2.\n");
}
}
}
// 主函数
int main(void)
{
// 创建事件组
xEventGroupHandle = xEventGroupCreate();
// 创建任务
xTaskCreate(vSensorTask1, "SensorTask1", 1000, NULL, 1, &xSensorTask1Handle);
xTaskCreate(vSensorTask2, "SensorTask2", 1000, NULL, 1, &xSensorTask2Handle);
xTaskCreate(vHandlerTask, "HandlerTask", 1000, NULL, 2, &xHandlerTaskHandle);
// 启动调度器
vTaskStartScheduler();
// 如果调度器启动失败,将不会到达这里
for (;;);
}
Sensor Task 1: Event occurred.
Handler Task: Handling event from Sensor Task 1.
Sensor Task 2: Event occurred.
Handler Task: Handling event from Sensor Task 2.
Sensor Task 1: Event occurred.
如果有优先级的话,函数 xEventGroupSetBitsFromISR 对事件标志的置位操作是在 daemon 任务里面执行的, 如果想让置位操作立即生效,即让等此事件标志的任务能够得到及时执行,需要设置 daemon 任务的优先级高于使用此事件标志组的所有其它任务。
static void TIM2_IRQHandler(void)
{
BaseType_t xResult;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* 中断消息处理, 此处省略 */
/* 向任务 vTaskMsgPro 发送事件标志 */
xResult = xEventGroupSetBitsFromISR(xCreatedEventGroup, /* 事件标志组句柄 */ BIT_0 , /* 设置 bit0 */&xHigherPriorityTaskWoken );
/* 消息被成功发出 */
if( xResult != pdFAIL )
{
/* 如果 xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
使用场景
-
中断通知: 当一个中断发生,并且需要通知任务某些事件已经发生时,可以在中断服务例程中使用
xEventGroupSetBitsFromISR
来设置事件位。 -
任务同步: 如果有任务正在等待某些事件的发生,通过在ISR中设置事件位,可以唤醒等待这些事件的任务。
其他函数补充
EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear );
将事件标志组中的指定事件位清零,此函数只能用在任务中,不能用在中断服务函数中!
EventBits_t xEventGroupGetBits( EventGroupHandle_t xEventGroup )
此函数用于获取当前事件标志组的值,也就是各个事件位的值,此函数用在任务中。