FreeRTOS基础篇(七)——事件组和任务通知
前言:
本篇文章介绍FreeRTOS的移植方法,以stm32f103举例。
1 事件组(Event Group)
1.1 介绍
事件组是一种允许一个或多个任务等待多个“事件”中某些或全部发生的同步机制。它使用一个24位(STM32上通常为23位可用)的位图来表示不同的事件标志(bit flags),每个bit可代表一个特定事件。
典型用途:
- 等待多个条件中的任意一个发生(如:按键按下 OR 定时器超时)
- 等待多个条件全部满足(如:网络连接 + 时间同步 + 配置加载完成)
1.2 API
核心函数。
| 函数 |
功能 |
xEventGroupCreate() |
创建事件组 |
xEventGroupSetBits() / xEventGroupSetBitsFromISR() |
设置某个事件标志位(可在任务或中断中调用) |
xEventGroupWaitBits() |
等待指定的事件位被设置 |
xEventGroupClearBits() |
清除某些事件位 |
vEventGroupDelete() |
删除事件组 |
1.3 示例
任务等待多个事件任一发生。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include "FreeRTOS.h" #include "event_groups.h"
#define BIT_0 (1 << 0) #define BIT_1 (1 << 1)
EventGroupHandle_t xEventGroup;
void vTask_A(void *pvParameters) { while(1) { EventBits_t uxBits = xEventGroupWaitBits( xEventGroup, BIT_0 | BIT_1, pdTRUE, pdFALSE, portMAX_DELAY );
if(uxBits & BIT_0) { printf("事件 BIT_0 发生\n"); }
if(uxBits & BIT_1) { printf("事件 BIT_1 发生\n"); } } }
void vSetEventFromISR(void) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; xEventGroupSetBitsFromISR(xEventGroup, BIT_0, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); }
|
&esmp;不适合传输大量数据,仅用于 状态/事件通知 。
2 任务通知(Task Notifications)
2.1 介绍
任务通知是FreeRTOS提供的最快速的任务间通信机制。 每个任务都有一个内置的通知值( ulNotifiedValue )和通知状态 ,无需额外创建对象(如队列、信号量),直接通过API向目标任务发送通知。
性能优势:
- 比队列快 ~45%
- 占用内存更少(不需要动态分配结构体)
- 最多可替代二值信号量、计数信号量、事件组、简单消息传递
2.2 三种模式
| 模式 |
描述 |
eSetBits |
将通知值按位或操作(类似事件组) |
eIncrement |
递增通知值(类似释放信号量) |
eSetValueWithOverwrite |
直接设置通知值(覆盖旧值 |
2.3 API
| 函数 |
功能 |
xTaskNotifyGive(xTaskToNotify) |
递增目标任务的通知值(等价于释放一个信号量) |
ulTaskNotifyTake(pdTRUE, timeout) |
获取通知值并清零或减一(类似 xSemaphoreTake()) |
ulTaskNotifyValueClear(xTask, ulBitsToClear) |
清除通知值中的某些位 |
xTaskNotify(xTaskToNotify, ulValue, eAction) |
发送通知,支持多种操作模式 |
xTaskNotifyWait(ulBitsToClearOnEntry, ulBitsToClearOnExit, pulNotificationValue, xTicksToWait) |
等待通知到达 |
所有函数都 无需额外通信对象 ,直接通过任务句柄操作。
2.4 示例
用任务通知替代二值信号量。假设有一个中断需要唤醒某个任务处理数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| TaskHandle_t xHandler = NULL;
void vWaitingTask(void *pvParameters) { while(1) { uint32_t ulNotifiedValue; if (xTaskNotifyWait(0, 0xFFFFFFFF, &ulNotifiedValue, portMAX_DELAY) == pdPASS) { printf("任务被唤醒,通知值: %lu\n", ulNotifiedValue); } } }
void USART1_IRQHandler(void) { BaseType_t xHigherPriorityTaskWoken = pdFALSE;
char c = USART1->DR;
xTaskNotifyFromISR(xHandler, 0, eNoAction, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken); }
int main() { xTaskCreate(vWaitingTask, "WaitTask", 1000, NULL, 2, &xHandler); vTaskStartScheduler(); }
|
&esmp;模拟计数信号量(Counting Semaphore)。
使用 eIncrement 模式可以实现类似 xSemaphoreGiveFromISR() 的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void vProducerISR(void) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; xTaskNotifyFromISR(xWorkerTask, 0, eIncrement, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); }
void vConsumerTask(void *pvParameters) { while(1) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); printf("处理一个工作项\n"); } }
|
再来一个例子,一个任务(SenderTask)通过 xTaskNotify() 发送一个数值。另一个任务(ReceiverTask)通过 ulTaskNotifyTake() 接收并处理这个值。没有使用任何硬件中断,纯软件任务间通信。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| TaskHandle_t xReceiverTask = NULL;
void vSenderTask(void *pvParameters) { uint32_t ulNotificationValue = 0; const TickType_t xDelay = pdMS_TO_TICKS(1000);
while(1) { ulNotificationValue++;
printf("Sender: Sending notification value %lu to Receiver.\n", ulNotificationValue);
xTaskNotify(xReceiverTask, ulNotificationValue, eSetValueWithOverwrite);
vTaskDelay(xDelay); } }
void vReceiverTask(void *pvParameters) { uint32_t ulReceivedValue = 0;
while(1) { ulReceivedValue = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
printf("Receiver: Received notification value %lu.\n", ulReceivedValue); } }
int main(void) { xTaskCreate(vReceiverTask, "Receiver", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, &xReceiverTask); xTaskCreate(vSenderTask, "Sender", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); }
|
这段代码实现了两个FreeRTOS任务通过任务通知进行通信的简单示例:vSenderTask 每秒递增一个数值并使用 xTaskNotify 将其发送给 vReceiverTask,接收任务通过 ulTaskNotifyTake 阻塞等待并获取该值,从而实现无需队列或信号量的高效任务间数据传递,整个过程不涉及中断。