## 为什么需要任务间通信 在嵌入式RTOS系统中,多个任务并发执行,任务间往往需要交换数据或同步状态。例如,传感器采集任务将数据发送给处理任务,或者按键任务通知LED控制任务。FreeRTOS提供了丰富的通信机制,其中**队列(Queue)**和**事件组(Event Group)**是使用最广泛的两种。 - 队列:用于传输数据块,适合点对点或一对多的数据流。 - 事件组:用于事件标志的置位与等待,适合多事件同步或条件触发。 熟读掌握这两个机制,是写出高效RTOS应用的关键。 ## 队列原理与使用 队列是FreeRTOS中的FIFO缓冲区,通过`xQueueSend()`发送数据,`xQueueReceive()`接收数据。每个队列有固定长度和元素大小,数据通过拷贝方式传递,因此需要确保数据大小不超出队列元素尺寸。 核心API: - `xQueueCreate( uxQueueLength, uxItemSize )`:创建队列。 - `xQueueSend( xQueue, pvItemToSend, xTicksToWait )`:发送数据。 - `xQueueReceive( xQueue, pvBuffer, xTicksToWait )`:接收数据(并移除)。 ```c QueueHandle_t xDataQueue; #define QUEUE_LENGTH 10 #define ITEM_SIZE sizeof(uint32_t) void vSenderTask(void *pvParameters) { uint32_t value = 0; while(1) { // 发送数据 if(xQueueSend(xDataQueue, &value, pdMS_TO_TICKS(100)) == pdPASS) { value++; } vTaskDelay(pdMS_TO_TICKS(1000)); } } void vReceiverTask(void *pvParameters) { uint32_t received; while(1) { // 等待数据,超时100ms if(xQueueReceive(xDataQueue, &received, pdMS_TO_TICKS(100)) == pdPASS) { // 处理数据 } } } ``` ## 事件组原理与使用 事件组用一个`EventBits_t`类型的变量表示多个事件标志,每个位代表一个事件。任务可等待一个或多个位,执行逻辑与或逻辑或操作,适合“等待多个条件同时满足”的场景。 核心API: - `xEventGroupCreate()`:创建事件组。 - `xEventGroupSetBits( xEventGroup, bitsToSet )`:置位事件。 - `xEventGroupWaitBits( xEventGroup, bitsToWaitFor, xClearOnExit, xWaitForAllBits, xTicksToWait )`:等待事件位。 ```c EventGroupHandle_t xEventGroup; #define EVENT_BIT_1 (1 << 0) #define EVENT_BIT_2 (1 << 1) void vEventTask(void *pvParameters) { EventBits_t bits; while(1) { // 等待两个事件都置位,并自动清除 bits = xEventGroupWaitBits(xEventGroup, EVENT_BIT_1 | EVENT_BIT_2, pdTRUE, pdTRUE, pdMS_TO_TICKS(1000)); if((bits & (EVENT_BIT_1 | EVENT_BIT_2)) == (EVENT_BIT_1 | EVENT_BIT_2)) { // 两个事件都发生,执行动作 } } } ``` ## 实战需求:传感器数据采集与报警 设计一个系统,包含两个任务: - 采集任务:周期性读取传感器值,放入队列。 - 处理任务:从队列接收数据,当数据超过阈值时,置位“报警”事件。 - 控制任务:等待“报警”事件,触发LED闪烁。 这样我们同时使用了队列(传递数据)和事件组(同步报警)。 ## 配置步骤 1. 在FreeRTOSConfig.h中启用队列和事件组支持(通常默认开启)。 2. 创建队列和事件组句柄。 3. 创建3个任务,设置合适的优先级(采集任务1,处理任务2,控制任务1)。 4. 启动调度器`vTaskStartScheduler()`。 ## 完整代码示例 下面基于STM32 HAL库,使用FreeRTOS动态内存管理。 ```c #include "FreeRTOS.h" #include "task.h" #include "queue.h" #include "event_groups.h" // 句柄与宏定义 QueueHandle_t xSensorQueue; EventGroupHandle_t xAlarmEventGroup; #define SENSOR_QUEUE_LEN 5 #define SENSOR_ITEM_SIZE sizeof(uint16_t) #define ALARM_EVENT (1 << 0) #define THRESHOLD 100 // 采集任务 void vSensorTask(void *pvParameters) { uint16_t sensor_value = 0; for(;;) { // 模拟读取传感器 sensor_value = (sensor_value + 17) % 150; // 发送到队列 xQueueSend(xSensorQueue, &sensor_value, pdMS_TO_TICKS(10)); vTaskDelay(pdMS_TO_TICKS(500)); } } // 处理任务 void vProcessTask(void *pvParameters) { uint16_t value; for(;;) { // 接收数据,超时等待100ms if(xQueueReceive(xSensorQueue, &value, pdMS_TO_TICKS(100)) == pdPASS) { // 判断是否超阈值 if(value > THRESHOLD) { // 置位报警事件 xEventGroupSetBits(xAlarmEventGroup, ALARM_EVENT); } } } } // 控制任务 void vControlTask(void *pvParameters) { for(;;) { // 等待报警事件 EventBits_t bits = xEventGroupWaitBits(xAlarmEventGroup, ALARM_EVENT, pdTRUE, pdTRUE, pdMS_TO_TICKS(1000)); if((bits & ALARM_EVENT) != 0) { // 触发LED闪烁(示例:翻转GPIO) // HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } } } int main(void) { // 外设初始化等(略) // 创建队列和事件组 xSensorQueue = xQueueCreate(SENSOR_QUEUE_LEN, SENSOR_ITEM_SIZE); xAlarmEventGroup = xEventGroupCreate(); if(xSensorQueue == NULL || xAlarmEventGroup == NULL) { // 创建失败处理 for(;;); } // 创建任务 xTaskCreate(vSensorTask, "Sensor", configMINIMAL_STACK_SIZE, NULL, 1, NULL); xTaskCreate(vProcessTask, "Process", configMINIMAL_STACK_SIZE, NULL, 2, NULL); xTaskCreate(vControlTask, "Control", configMINIMAL_STACK_SIZE, NULL, 1, NULL); // 启动调度器 vTaskStartScheduler(); // 正常不会到这里 for(;;); } ``` ## 注意事项 - **队列数据拷贝**:队列传输是拷贝,不要传大块数据(如数组),可传递指针或结构体指针。若必须传大块,使用动态内存或内存池。 - **超时设置**:`xQueueSend`和`xQueueReceive`中的超时参数单位是tick,使用`pdMS_TO_TICKS(ms)`转换。在中断中不能使用带阻塞的版本,应使用`xQueueSendFromISR`和`xEventGroupSetBitsFromISR`。 - **事件组清除位**:`xEventGroupWaitBits`的`xClearOnExit`参数决定退出时是否自动清除已匹配的位,务必谨慎设置,避免事件丢失或重复触发。 - **优先级反转**:高优先级任务等待低优先级任务发数据时,可能造成优先级反转。可考虑使用二值信号量或互斥量,必要时使用优先级继承。 - **内存需求**:队列和事件组会消耗堆内存,注意调整`configTOTAL_HEAP_SIZE`。 - **调试技巧**:利用`uxQueueMessagesWaiting()`检查队列剩余消息数,使用`xEventGroupGetBits()`查看事件位状态,便于调试。 通过本文的实战,可以看到队列和事件组各司其职:队列承载数据流,事件组同步事件触发器。在实际RTOS项目中,合理组合它们能够高效构建出健壮的多任务系统。