FreeRTOS 任务间通信机制:队列、信号量、事件标志组详解与实验

1. FreeRTOS 消息队列

1.1 简介

​ 队列是 任务间通信的主要形式,可用于在任务之间以及中断与任务之间传递消息。队列在 FreeRTOS 中具有以下关键特点:

  • 队列默认采用 先进先出 FIFO 方式,也可以使用 xQueueSendToFront()实现 LIFO。
  • FreeRTOS 确保队列操作是原子的,不会因为任务切换而导致数据损坏。
  • 如果队列 满了(写入时)或 空了(读取时),任务 可以选择阻塞(等待消息) 或 非阻塞(立即返回)。
  • 高优先级任务更快地执行队列操作,如果多个任务在相同优先级,按照 时间片轮转 处理队列。
  • 消息大小固定,但可以存储指向可变数据的指针。

1.2 队列相关 API 函数介绍

FreeRTOS 队列管理 API 速览

1. 队列的创建 API
API 函数描述
xQueueCreate()动态创建队列
xQueueCreateStatic()静态创建队列
2. 队列消息的写入 API
API 函数描述
xQueueSend()往队列尾部写入消息
xQueueSendToBack()xQueueSend(),往队列尾部写入消息
xQueueSendToFront()往队列头部写入消息
xQueueOverwrite()覆写队列消息(仅限队列长度为 1)
xQueueSendFromISR()在中断中往队列尾部写入消息
xQueueSendToBackFromISR()xQueueSendFromISR(),往队列尾部写入
xQueueSendToFrontFromISR()在中断中往队列头部写入消息
xQueueOverwriteFromISR()在中断中覆写队列消息(仅限队列长度为 1)
3. 队列消息的读取 API
API 函数描述
xQueueReceive()从队列头部读取消息,并删除消息
xQueuePeek()从队列头部读取消息(但不删除)
xQueueReceiveFromISR()在中断中从队列头部读取消息,并删除消息
xQueuePeekFromISR()在中断中从队列头部读取消息(但不删除)

1.3 实验

  • start_task:用来创建其他的 3 个任务。
  • task1:当按键 key1 或 key2 按下,将键值拷贝到队列 queue1(入队);当按键 key3 按下,将传输大数据,这里拷贝大数据的地址到队列 big_queue 中。
  • task2:读取队列 queue1 中的消息(出队),打印出接收到的键值。
  • task3:从队列 big_queue 读取大数据地址,通过地址访问大数据。

1 ) 创建队列程序:

QueueHandle_t queue1;    /* 小数据句柄 */
QueueHandle_t big_queue; /* 大数据句柄 */
char buff[100] = {"dioajdiaj fdahjk324hjkhfjksdahjk#$@!@#jfaskdfhjka"};void task_start(void *pvParameters)
{/* 进入临界区:保护临界区里的代码不会被打断 */taskENTER_CRITICAL();/* 创建 queue1 队列 */queue1 = xQueueCreate(2, sizeof(uint8_t));if (queue1 != NULL){printf("queue1 队列创建成功\r\n");}else{printf("queue1 队列创建失败\r\n");}/* 创建 big_queue 队列 */big_queue = xQueueCreate(1, sizeof(char *));if (big_queue != NULL){printf("big_queue 队列创建成功\r\n");}else{printf("big_queue 队列创建失败\r\n");}/* 创建三个任务 */xTaskCreate((TaskFunction_t)task1,(char *)"task1",(configSTACK_DEPTH_TYPE)TASK1_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK1_PRIORITY,(TaskHandle_t *)&Task1_Handler);xTaskCreate((TaskFunction_t)task2,(char *)"task2",(configSTACK_DEPTH_TYPE)TASK2_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK2_PRIORITY,(TaskHandle_t *)&Task2_Handler);xTaskCreate((TaskFunction_t)task3,(char *)"task3",(configSTACK_DEPTH_TYPE)TASK3_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK3_PRIORITY,(TaskHandle_t *)&Task3_Handler);/* 启动任务只需要执行一次即可,用完就删除自己 */vTaskDelete(NULL);/* 退出临界区 */taskEXIT_CRITICAL();
}

2 ) task1:

void task1(void *pvParameters)
{uint8_t key_value = 0;char *buf;BaseType_t err = 0;buf = &buff[0];char task_info[200];while (1){if (key[0].flag == 1 || key[1].flag == 1){key_value = key[0].flag;err = xQueueSendToBack(queue1, &key_value, portMAX_DELAY);if (err != pdTRUE){printf("queue1 队列发送失败\r\n");}key[0].flag = 0;key[1].flag = 0;}else if (key[2].flag == 1){err = xQueueSendToBack(big_queue, &buf, portMAX_DELAY);if (err != pdTRUE){printf("big_queue 队列发送失败\r\n");}key[2].flag = 0;}}
}

3 ) task2:

void task2(void *pvParameters)
{uint8_t key = 0;BaseType_t err = 0;while (1){err = xQueueReceive(queue1, &key, portMAX_DELAY);if (err != pdTRUE){printf("queue1 队列读取失败\r\n");}else{printf("queue1 读取队列成功,数据:%d\r\n", key);}}
}

4 ) task3:

void task3(void *pvParameters)
{char *buf;BaseType_t err = 0;while (1){for (int i = 0; i < 16; i++){if (key[i].flag == 1){err = xQueueReceive(big_queue, &buf, portMAX_DELAY);if (err != pdTRUE){printf("big_queue 队列读取失败\r\n");}else{printf("data:%s\r\n", buf);}}}}
}

2. FreeRTOS 队列集

​ 队列集(Queue Set)是 FreeRTOS 中的一种数据结构,用于管理多个队列。它提供了一种有效的方式,通过单个 API 调用来操作和访问一组相关的队列。在多任务系统中,任务之间可能需要共享数据,而这些数据可能存储在不同的队列中。队列集的作用就是为了更方便地管理这些相关队列,使得任务能够轻松地访问和处理多个队列的数据。

2.1 队列集相关 API 函数介绍

函数描述
xQueueCreateSet()创建队列集,用于管理多个队列或信号量
xQueueAddToSet()向队列集添加队列或信号量
xQueueRemoveFromSet()从队列集中移除队列或信号量
xQueueSelectFromSet()获取队列集中有消息的队列(阻塞)
xQueueSelectFromSetFromISR()在中断中获取队列集中有消息的队列(非阻塞)

2.2 实验

  • start_task:用来创建其他 2 个任务,并创建队列集、俩个队列,将这俩个队列添加到队列集中。
  • task1:用于扫描按键,当 KEY1 按下,往队列1中写入数据,当 KEY2 按下,往队列2中写入数据。
  • task2:读取队列集中的消息,并打印。

1 ) 创建队列集

QueueHandle_t queue1, queue2;
QueueSetHandle_t queue_set;void task_start(void *pvParameters)
{/* 进入临界区:保护临界区里的代码不会被打断 */taskENTER_CRITICAL();/* 创建 queue1 队列 */queue1 = xQueueCreate(1, sizeof(uint8_t));if (queue1 != NULL){printf("queue1 队列创建成功\r\n");}else{printf("queue1 队列创建失败\r\n");}/* 创建 queue2 队列 */queue2 = xQueueCreate(1, sizeof(char *));if (queue2 != NULL){printf("queue2 队列创建成功\r\n");}else{printf("queue2 队列创建失败\r\n");}/* 创建 queue2 队列 */queue_set = xQueueCreateSet(2);if (queue_set != NULL){printf("queue_set 队列创建成功\r\n");}else{printf("queue_set 队列创建失败\r\n");}xQueueAddToSet(queue1, queue_set);xQueueAddToSet(queue2, queue_set);/* 创建任务 */xTaskCreate((TaskFunction_t)task1,(char *)"task1",(configSTACK_DEPTH_TYPE)TASK1_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK1_PRIORITY,(TaskHandle_t *)&Task1_Handler);xTaskCreate((TaskFunction_t)task2,(char *)"task2",(configSTACK_DEPTH_TYPE)TASK2_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK2_PRIORITY,(TaskHandle_t *)&Task2_Handler);/* 启动任务只需要执行一次即可,用完就删除自己 */vTaskDelete(NULL);/* 退出临界区 */taskEXIT_CRITICAL();
}

2 ) task1:

void task1(void *pvParameters)
{char *buf;char text[20];BaseType_t err = 0;while (1){if (key[0].flag == 1){sprintf((char *)text, "queue1 数据");buf = &text[0];err = xQueueSendToBack(queue1, &buf, portMAX_DELAY);if (err != pdTRUE){printf("queue1 队列写入数据错误\r\n");}key[0].flag = 0;}else if (key[1].flag == 1){sprintf((char *)text, "queue2 数据");buf = &text[0];err = xQueueSendToBack(queue2, &buf, portMAX_DELAY);if (err != pdTRUE){printf("queue2 队列写入数据错误\r\n");}key[1].flag = 0;}vTaskDelay(100);}
}

3 ) task2:

void task2(void *pvParameters)
{char *buf;QueueSetMemberHandle_t queue = NULL;while (1){queue = xQueueSelectFromSet(queue_set, portMAX_DELAY);if (queue == queue1){xQueueReceive(queue, &buf, portMAX_DELAY);printf("queue1 读取到数据为: %s\r\n", buf);}if (queue == queue2){xQueueReceive(queue, &buf, portMAX_DELAY);printf("queue2 读取到数据为: %s\r\n", buf);}vTaskDelay(100);}
}

3. FreeRTOS 信号量

3.1 简介

信号量(Semaphore)是一种用于 任务同步任务间资源互斥 的机制,主要用于:

  • 任务同步:协调任务之间的执行顺序,如等待某个事件发生后再执行任务。
  • 互斥访问:保护共享资源,防止多个任务同时访问而引起竞争问题。

FreeRTOS 提供了 二值信号量计数信号量互斥信号量 三种类型:

信号量类型作用
二值信号量(Binary Semaphore)只能取 0 或 1,常用于任务同步或简单的互斥访问
计数信号量(Counting Semaphore)可取多个值,适用于多个资源的同步管理
互斥信号量(Mutex)具备 优先级继承 机制,主要用于任务间的互斥访问

3.2 常用 API 函数

API 函数作用
xSemaphoreCreateBinary()创建二值信号量(初始值为 0,需要 xSemaphoreGive() 释放一次后才能使用)
xSemaphoreCreateBinaryStatic()使用静态方式创建二值信号量
xSemaphoreCreateCounting()创建计数信号量
xSemaphoreCreateMutex()创建互斥信号量
xSemaphoreGive()释放信号量(+1)
xSemaphoreTake()获取信号量(-1),如果没有可用信号量则阻塞
uxSemaphoreGetCount()获取信号量的计数值
xSemaphoreGiveFromISR()在中断中释放信号量
xSemaphoreTakeFromISR()在中断中获取信号量

​ 信号量 API 函数允许指定阻塞时间。 阻塞时间表示当一个任务试图“获取”信号量时,如果信号不是立即可用,那么该任务进入阻塞状态的最大 “tick” 数。 如果多个任务在同一个信号量上阻塞,那么具有最高优先级的任务将在下次信号量可用时最先解除阻塞

在 FreeRTOS 中,信号量 实际上是一个特殊的队列,本质上 创建信号量就是创建一个只存储一个元素的队列,区别在于:

  • 二值信号量(Binary Semaphore):队列长度固定为 1,每次只能存储 一个项目,用于 任务同步
  • 计数信号量(Counting Semaphore):队列长度可以大于 1,用于 多个资源的管理
  • 互斥信号量(Mutex):基于二值信号量,但增加了 优先级继承 机制,用于资源互斥

对于二值信号量 FreeRTOS 通过 xQueueGenericCreate() 这个通用队列创建函数来创建信号量,但是这个函数在 semphr.h 被宏定义为:

/*
(UBaseType_t) 1 → 队列长度 = 1(二值信号量只能存储一个信号量)
semSEMAPHORE_QUEUE_ITEM_LENGTH → 项目大小 = 0(不存储实际数据,仅表示可用信号量)
queueQUEUE_TYPE_BINARY_SEMAPHORE → 队列类型 = 信号量队列
*/
#define xSemaphoreCreateBinary()    xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )

信号量的作用本质是管理这个队列是否为空。

而计数信号量创建函数等效为:

/*
uxMaxCount  → 队列长度 (计数信号量可以为任何整数)
queueSEMAPHORE_QUEUE_ITEM_LENGTH → 项目大小 = 0(不存储实际数据,仅表示可用信号量)
queueQUEUE_TYPE_BINARY_SEMAPHORE → 队列类型 = 信号量队列
*/
xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );

互斥信号量创建函数等效为:

/*
uxMutexLength 1  → 队列长度 = 1(互斥信号量只能存储一个信号量)
uxMutexSize → 项目大小 = 0(不存储实际数据,仅表示可用信号量)
queueQUEUE_TYPE_MUTEX → 队列类型 = 信号量队列
*/
xQueueGenericCreate( uxMutexLength, uxMutexSize, queueQUEUE_TYPE_MUTEX )

3.3 实验

3.3.1 二值信号量实验

  • start_task:用来创建其他的 2 个任务。
  • task1:用于按键扫描,当检测到按键 KEY1 被按下时,释放二值信号量。
  • task2:获取二值信号量,当成功获取后打印提示信息。

1 ) 创建二值信号量:

QueueHandle_t semaphore_handle;void task_start(void *pvParameters)
{/* 进入临界区:保护临界区里的代码不会被打断 */taskENTER_CRITICAL();/* 创建信号量 */semaphore_handle = xSemaphoreCreateBinary();if (semaphore_handle != NULL){printf("二值信号量创建成功\r\n");}else{printf("二值信号量创建失败\r\n");}/* 创建任务 */xTaskCreate((TaskFunction_t)task1,(char *)"task1",(configSTACK_DEPTH_TYPE)TASK1_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK1_PRIORITY,(TaskHandle_t *)&Task1_Handler);xTaskCreate((TaskFunction_t)task2,(char *)"task2",(configSTACK_DEPTH_TYPE)TASK2_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK2_PRIORITY,(TaskHandle_t *)&Task2_Handler);/* 启动任务只需要执行一次即可,用完就删除自己 */vTaskDelete(NULL);/* 退出临界区 */taskEXIT_CRITICAL();
}

2 ) task1:

void task1(void *pvParameters)
{BaseType_t err;while (1){if (key[0].flag == 1){err = xSemaphoreGive(semaphore_handle);if (err != pdTRUE){printf("释放信号量失败\r\n");}led[0].state = !led[0].state;HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, (GPIO_PinState)led[0].state);key[0].flag = 0;}}
}

3 ) task2:

void task2(void *pvParameters)
{BaseType_t err = 0;while (1){err = xSemaphoreTake(semaphore_handle,portMAX_DELAY);if (err != pdTRUE){printf("获取信号量失败\r\n");}else{printf("成功获取信号量\r\n");}}
}

3.3 .2 计数信号量实验

  • start_task:用来创建其他的 2 个任务。
  • task1:用于按键扫描,当检测到按键 KEY1 被按下时,释放计数型信号量。
  • task2:每过一秒获取一次计数型信号量,当成功获取后打印信号量计数值。

1 ) 创建计数信号量:

QueueHandle_t semaphore_handle;void task_start(void *pvParameters)
{/* 进入临界区:保护临界区里的代码不会被打断 */taskENTER_CRITICAL();/* 创建计数信号量 */UBaseType_t uxMaxCount = 4;UBaseType_t uxInitialCount = 0;semaphore_handle = xSemaphoreCreateCounting(uxMaxCount,uxInitialCount);if (semaphore_handle != NULL){printf("计数信号量创建成功\r\n");}else{printf("计数信号量创建失败\r\n");}/* 创建任务 */xTaskCreate((TaskFunction_t)task1,(char *)"task1",(configSTACK_DEPTH_TYPE)TASK1_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK1_PRIORITY,(TaskHandle_t *)&Task1_Handler);xTaskCreate((TaskFunction_t)task2,(char *)"task2",(configSTACK_DEPTH_TYPE)TASK2_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK2_PRIORITY,(TaskHandle_t *)&Task2_Handler);/* 启动任务只需要执行一次即可,用完就删除自己 */vTaskDelete(NULL);/* 退出临界区 */taskEXIT_CRITICAL();
}

2 ) task1:

void task1(void *pvParameters)
{BaseType_t err;while (1){if (key[0].flag == 1){err = xSemaphoreGive(semaphore_handle);if (err != pdTRUE){printf("释放信号量失败\r\n");}else{printf("释放信号量\r\n");}led[0].state = !led[0].state;HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, (GPIO_PinState)led[0].state);key[0].flag = 0;}}
}

3 ) task2:

void task2(void *pvParameters)
{BaseType_t err = 0;while (1){if(uxSemaphoreGetCount(semaphore_handle) != 0){err = xSemaphoreTake(semaphore_handle,portMAX_DELAY);if (err != pdTRUE){printf("获取信号量失败\r\n");}else{printf("成功获取信号量\r\n");}}else{printf("无空闲资源分配\r\n");}vTaskDelay(1000);}
}

3.4 任务优先级翻转 bug 问题

​ 优先级翻转是一个在实时系统中可能出现的问题,特别是在多任务环境中。该问题指的是一个较低优先级的任务阻塞了一个较高优先级任务的执行,从而导致高优先级任务无法及时完成。

典型的优先级翻转场景如下:

  • 任务 A(高优先级):拥有高优先级,需要访问共享资源,比如一个关键数据结构。
  • 任务 B(低优先级):拥有低优先级,目前正在访问该共享资源。
  • 任务 C(中优先级):位于任务 A 和任务 B 之间,具有介于两者之间的优先级。

具体流程如下:

(1)任务 A 开始执行,但由于任务 B 正在访问共享资源,任务 A 被阻塞等待。

(2)任务 C 获得执行权,由于优先级高于任务 B,它可以抢占任务 B。

(3)任务 C 执行完成后,任务 B 被解除阻塞,开始执行,完成后释放了共享资源。

(4)任务 A 重新获取执行权,继续执行。

​ 这个过程中,任务 A 因为资源被占用而被阻塞,而任务 B 却被中优先级的任务 C 抢占,导致任务 B 无法及时完成。这种情况称为优先级翻转,因为任务 C 的介入翻转了高优先级任务 A 的执行顺序。
请添加图片描述

实验模拟

模拟优先级翻转,观察对抢占式内核的影响:

  • start_task:用来创建其他的 3 个任务。
  • task1:低优先级任务,同高优先级一样的操作,不同的是低优先级任务占用信号
  • task2:中等优先级任务,简单的应用任务。
  • task3:高优先级任务,会获取二值信号量,获取成功以后打印提示信息,处理完后释放信号量。

1 ) 创建队列程序:

QueueHandle_t semaphore_handle;/*** @brief : 启动任务函数,创建三个任务,并删除自己** @param pvParameters*/
void task_start(void *pvParameters)
{/* 进入临界区:保护临界区里的代码不会被打断 */taskENTER_CRITICAL();/* 创建计数信号量 */UBaseType_t uxMaxCount = 1;UBaseType_t uxInitialCount = 1;semaphore_handle = xSemaphoreCreateCounting(uxMaxCount, uxInitialCount);if (semaphore_handle != NULL){printf("计数信号量创建成功\r\n");}else{printf("计数信号量创建失败\r\n");}/* 创建三个任务 */xTaskCreate((TaskFunction_t)task1,(char *)"task1",(configSTACK_DEPTH_TYPE)TASK1_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK1_PRIORITY,(TaskHandle_t *)&Task1_Handler);xTaskCreate((TaskFunction_t)task2,(char *)"task2",(configSTACK_DEPTH_TYPE)TASK2_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK2_PRIORITY,(TaskHandle_t *)&Task2_Handler);xTaskCreate((TaskFunction_t)task3,(char *)"task3",(configSTACK_DEPTH_TYPE)TASK3_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK3_PRIORITY,(TaskHandle_t *)&Task3_Handler);/* 启动任务只需要执行一次即可,用完就删除自己 */vTaskDelete(NULL);/* 退出临界区 */taskEXIT_CRITICAL();
}

2 ) task1:

void task1(void *pvParameters)
{while (1){printf("低优先级 Task1 获取信号量\r\n");xSemaphoreTake(semaphore_handle, portMAX_DELAY);printf("低优先级 Task1 正在运行\r\n");HAL_Delay(3000);printf("低优先级 Task1 释放信号量\r\n");xSemaphoreGive(semaphore_handle);vTaskDelay(1000);}
}

3 ) task2:

void task2(void *pvParameters)
{while (1){printf("中优先级的 Task2 正在执行\r\n");HAL_Delay(1500);printf("Task2 执行完成一次.....\r\n");vTaskDelay(1000);}
}

4 ) task3:

void task3(void *pvParameters)
{while (1){printf("高优先级 Task3 获取信号量\r\n");xSemaphoreTake(semaphore_handle, portMAX_DELAY);printf("高优先级 Task3 正在运行\r\n");HAL_Delay(1000);printf("高优先级 Task3 释放信号量\r\n");xSemaphoreGive(semaphore_handle);vTaskDelay(1000);}
}

5 ) 实验结果

请添加图片描述

可以观察到 Task1 获取信号量后,Task2 通过抢占 Task1 实现了翻转优先级,实现了优于 Task3 的执行优先。

3.5 互斥信号量

​ 互斥信号量是包含优先级继承机制的二进制信号量。二进制信号量能更好实现同步(任务间或任务与中断之间), 而互斥信号量有助于更好实现简单互斥(即相互排斥)。优先级继承是一种解决实时系统中任务调度引起的优先级翻转问题的机制。在具体的任务调度中,当一个高优先级任务等待一个低优先级任务所持有的资源时,系统会提升低优先级任务的优先级,以避免高优先级任务长时间等待的情况。
请添加图片描述

互斥信号量的获取和释放函数与二值信号量的相应函数相似,但有一个重要的区别:互斥信号量不支持在中断服务程序中直接调用。注意,当创建互斥信号量时,系统会自动进行一次信号量的释放操作。

通过互斥信号量来解决优先级翻转实验

1 ) 将二值信号量改为互斥信号量:

    semaphore_handle = xSemaphoreCreateMutex();if (semaphore_handle != NULL){printf("互斥信号量创建成功\r\n");}else{printf("互斥信号量创建失败\r\n");}

2 ) 实验结果:

可以观察到 Task2 只能在 Task3 信号量释放的后才可以抢占运行,不会发生任务优先级的翻转(在 Task1 释放信号量后 Task2 立马抢占执行,而是 Task3 立刻占用信号量,开始执行 Task3 再执行 Task2)
请添加图片描述

4. FreeRTOS 事件标志组

4.1 简介

​ 事件标志组(Event Groups)是 FreeRTOS 提供的一种轻量级任务间同步机制,允许任务或中断通过 设置或清除 “事件标志(Event Bits)” 来实现 事件通知、同步和状态监控。事件标志组类似于 二进制标志位集合,每个标志位可以单独操作,多个任务可以等待多个标志位满足特定条件后再执行。

事件标志组特点
  • 每个事件标志组是一个 8/24 位独立的二进制标志位(Event Bits)。
  • 任务可以等待多个事件标志位,并指定所有满足或任意一个满足时触发任务执行。
  • 事件标志位可由任务或中断设置/清除,支持 ISR 操作。
  • 比队列(Queue)和信号量(Semaphore)更高效,适用于简单事件同步。
事件标志组的适用场景
应用场景适合事件标志组适合队列或信号量
多任务同步✔️ 多任务需要等待某些事件发生
中断通知任务✔️ 事件触发后可直接通知多个任务✔️ 但队列或信号量只能通知一个任务
状态监测✔️ 可使用不同事件位表示不同状态
任务间数据传输✔️ 队列适合传输数据

​ 事件标志组和信号量都是 FreeRTOS 中用于任务同步和通信的机制,但它们适用于不同的场景,主要区别如下:

对比项事件标志组(Event Flags Group)信号量(Semaphore)
用途用于任务间事件通知和同步,标志位代表某个事件状态用于任务间资源控制和同步,确保安全访问共享资源
状态表示每个标志位只有 已设置/未设置 两种状态信号量是一个计数器,可用于 计数、互斥、同步
任务等待任务可等待 多个事件同时满足任意一个事件发生任务等待信号量计数变为 非零,然后继续执行
适用场景适用于任务间 事件同步,如 数据准备完成、状态变更通知适用于 资源访问控制、同步、互斥,防止多个任务同时访问共享资源
信号传递可存储多个事件状态,即使任务不在等待,事件状态仍然保留不可存储状态,如果任务未在等待,信号量会直接丢失
示例任务 A 设置事件标志 → 任务 B 等待该事件标志并继续执行任务 A 释放信号量 → 任务 B 获取信号量并访问共享资源

4.2 事件标志组和事件位数据类型

​ 可以将事件组视为一个二进制标志集合,其中的每一位表示一个事件。任务可以设置(Set)、清(Clear)或等待(Wait)某些特定的事件位,从而实现任务间的 同步与信号触发。事件组的大小(即支持的 事件位数)受 FreeRTOS 配置项的影响,由 configUSE_16_BIT_TICKS 控制:

#define configUSE_16_BIT_TICKS 	1 // 事件组内可用标志数位为8
#define configUSE_16_BIT_TICKS 	0 // 事件组内可用标志数位为24

​ 在 FreeRTOS 中,事件组的最大位数EventBits_t 变量的大小决定,而 EventBits_t 的大小受 TickType_t(用于计时的变量类型)影响。所以最高为32位,但是 FreeRTOS 保留了 8 位 供系统内部使用

4.3 事件标志组相关 API 函数介绍

API 函数功能
xEventGroupCreate()创建事件标志组
xEventGroupCreateStatic()使用静态地创建事件标志组
xEventGroupSetBits()设置一个或多个事件标志位
xEventGroupClearBits()清除一个或多个事件标志位
xEventGroupWaitBits()等待一个或多个事件标志位被置位
xEventGroupGetBits()获取当前事件标志状态
xEventGroupSetBitsFromISR()在中断中设置事件标志位
vEventGroupDelete()删除事件标志组
xEventGroupSync()多个任务同步等待所有事件标志

4.4 实验

  • start_task:用来创建其他 3 个任务,并创建事件标志组。
  • task1:读取按键按下键值,根据不同键值将事件标志组相应事件位置一,模拟事件发生。
  • task2:同时等待事件标志组中的多个事件位,当这些事件位都置 1 的话就执行相应的处理。
  • task3:等待事件标志组中的多个任一事件位,当这些事件位任意置 1 的话就执行相应的处理。

1 ) 创建事件标志组程序:

EventGroupHandle_t event_group;/*** @brief : 启动任务函数,创建三个任务,并删除自己** @param pvParameters*/
void task_start(void *pvParameters)
{/* 进入临界区:保护临界区里的代码不会被打断 */taskENTER_CRITICAL();/* 创建事件标志组 */event_group = xEventGroupCreate();if (event_group == NULL){printf("事件标志组创建失败\r\n");}else{printf("事件标志组创建成功\r\n");}/* 创建任务 */xTaskCreate((TaskFunction_t)task1,(char *)"task1",(configSTACK_DEPTH_TYPE)TASK1_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK1_PRIORITY,(TaskHandle_t *)&Task1_Handler);xTaskCreate((TaskFunction_t)task2,(char *)"task2",(configSTACK_DEPTH_TYPE)TASK2_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK2_PRIORITY,(TaskHandle_t *)&Task2_Handler);xTaskCreate((TaskFunction_t)task3,(char *)"task3",(configSTACK_DEPTH_TYPE)TASK3_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK3_PRIORITY,(TaskHandle_t *)&Task3_Handler);/* 启动任务只需要执行一次即可,用完就删除自己 */vTaskDelete(NULL);/* 退出临界区 */taskEXIT_CRITICAL();
}

2 ) task1:

void task1(void *pvParameters)
{while (1){if (key[0].flag == 1){xEventGroupSetBits(event_group, 0x01 << 0);printf("事件0置位\r\n");key[0].flag = 0;}else if (key[1].flag == 1){xEventGroupSetBits(event_group, 0x01 << 1);printf("事件1置位\r\n");key[1].flag = 0;}else if (key[2].flag == 1){xEventGroupSetBits(event_group, 0x01 << 2);printf("事件2置位\r\n");key[2].flag = 0;}else if (key[3].flag == 1){xEventGroupSetBits(event_group, 0x01 << 3);printf("事件3置位\r\n");key[3].flag = 0;}vTaskDelay(100);}
}

3 ) task2:

void task2(void *pvParameters)
{while (1){xEventGroupWaitBits(event_group, 1 << 0 | 1 << 1, pdTRUE, pdTRUE, portMAX_DELAY);printf("事件0和1同时有效,执行task2...\r\n");vTaskDelay(100);}
}

4 ) task3:

void task3(void *pvParameters)
{while (1){xEventGroupWaitBits(event_group, 1 << 2 | 1 << 3, pdTRUE, pdFALSE, portMAX_DELAY);printf("事件2和3任一有效,执行task3...\r\n");vTaskDelay(100);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/71576.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【虚拟化】Docker Desktop 架构简介

在阅读前您需要了解 docker 架构&#xff1a;Docker architecture WSL 技术&#xff1a;什么是 WSL 2 1.Hyper-V backend 我们知道&#xff0c;Docker Desktop 最开始的架构的后端是采用的 Hyper-V。 Docker daemon (dockerd) 运行在一个 Linux distro (LinuxKit build) 中&…

Unity光照之Halo组件

简介 Halo 组件 是一种用于在游戏中创建光晕效果的工具&#xff0c;主要用于模拟光源周围的发光区域&#xff08;如太阳、灯泡等&#xff09;或物体表面的光线反射扩散效果。 核心功能 1.光晕生成 Halo 组件会在光源或物体的周围生成一个圆形光晕&#xff0c;模拟光线在空气…

Flink深入浅出之01:应用场景、基本架构、部署模式

Flink 1️⃣ 一 、知识要点 &#x1f4d6; 1. Flink简介 Apache Flink — Stateful Computations over Data StreamsApache Flink 是一个分布式大数据处理引擎&#xff0c;可对有界数据流和无界数据流进行有状态的计算。Flink 能在所有常见集群环境中运行&#xff0c;并能以…

2025年【高压电工】报名考试及高压电工考试总结

随着电力行业的快速发展&#xff0c;高压电工成为确保电力系统安全稳定运行的重要一环。为了提高高压电工的专业技能和安全意识&#xff0c;“安全生产模拟考试一点通”平台特别整理了2025年高压电工报名考试的相关信息及考试总结&#xff0c;并提供了一套完整的题库&#xff0…

网络HTTP

HTTP Network Request Library A Retrofit-based HTTP network request encapsulation library that provides simple and easy-to-use API interfaces with complete network request functionality. 基于Retrofit的HTTP网络请求封装库&#xff0c;提供简单易用的API接口和完…

os-copilot安装和使用体验测评

简介&#xff1a; OS Copilot是阿里云基于大模型构建的Linux系统智能助手&#xff0c;支持自然语言问答、命令执行和系统运维调优。本文介绍其产品优势、功能及使用方法&#xff0c;并分享个人开发者在云服务器资源管理中的实际应用体验。通过-t/-f/管道功能&#xff0c;OS Cop…

Python Flask框架学习汇编

1、入门级&#xff1a; 《Python Flask Web 框架入门》 这篇博文条理清晰&#xff0c;由简入繁&#xff0c;案例丰富&#xff0c;分十五节详细讲解了Flask框架&#xff0c;强烈推荐&#xff01; 《python的简单web框架flask【附例子】》 讲解的特别清楚&#xff0c;每一步都…

【HarmonyOS Next之旅】DevEco Studio使用指南(一)

目录 1 -> 工具简介 1.1 -> 概述 1.2 -> HarmonyOS应用/服务开发流程 1.2.1 -> 开发准备 1.2.2 -> 开发应用/服务 1.2.3 -> 运行、调试和测试应用/服务 1.2.4 -> 发布应用/服务 2 -> 工程介绍 2.1 -> APP包结构 2.2 -> 切换工程视图 …

Manus开源平替-开源通用智能体

原文链接:https://i68.ltd/notes/posts/250306-opensource-agi-agent/ OWL-比Manus还强的全能开源Agent OWL: Optimized Workforce Learning for General Multi-Agent Assistance in Real-World Task Automation&#xff0c;现实世界中执行自动化任务的通用多代理辅助优化学习…

【3.2-3.8学习周报】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 摘要Abstract一、方法介绍1.任务适应性持续预训练&#xff08;TACP&#xff09;2.领域自适应连续预训练&#xff08;DACP&#xff09;3.ETS-DACP和ETA-DACP 二、实验…

【Linux】用户和组

思考 使用useradd在Linux下面创建一个用户&#xff0c;默认情况下&#xff0c;会自动创建一个同名组&#xff0c;并且加入其中&#xff0c;那么是先创建用户呢&#xff1f;还是先创建组呢&#xff1f; 很简单&#xff0c;我们实践一下不就知道了&#xff0c;如下所示&#xff…

新编大学应用英语综合教程2 U校园全套参考答案

全套答案获取&#xff1a; 链接&#xff1a;https://pan.quark.cn/s/389618f53143

SAP 顾问的五年职业规划

SAP 顾问的职业发展受到技术进步、企业需求变化和全球经济环境的影响&#xff0c;因此制定长远规划充满挑战。面对 SAP 产品路线图的不确定性&#xff0c;如向 S/4HANA 和 Business Technology Platform (BTP) 的转变&#xff0c;顾问必须具备灵活性&#xff0c;以保持竞争力和…

图像生成-ICCV2019-SinGAN: Learning a Generative Model from a Single Natural Image

图像生成-ICCV2019-SinGAN: Learning a Generative Model from a Single Natural Image 文章目录 图像生成-ICCV2019-SinGAN: Learning a Generative Model from a Single Natural Image主要创新点模型架构图生成器生成器源码 判别器判别器源码 损失函数需要源码讲解的私信我 S…

Networking Based ISAC Hardware Testbed and Performance Evaluation

文章目录 Applications and Challenges of Networked SensingCooperation Mechanism in Networked SensingChallenges and Key Enabling Technologies 5G NR Frame Structure Based ISAC ApproachSignals Available for Radio SensingMulti-Dimensiona Resource Optimization S…

2025年主流原型工具测评:墨刀、Axure、Figma、Sketch

2025年主流原型工具测评&#xff1a;墨刀、Axure、Figma、Sketch 要说2025年国内产品经理使用的主流原型设计工具&#xff0c;当然是墨刀、Axure、Figma和Sketch了&#xff0c;但是很多刚入行的产品经理不了解自己适合哪些工具&#xff0c;本文将从核心优势、局限短板、协作能…

我代表中国受邀在亚马逊云科技全球云计算大会re:Invent中技术演讲

大家好我是小李哥&#xff0c;本名叫李少奕&#xff0c;目前在一家金融行业公司担任首席云计算工程师。去年5月很荣幸在全球千万名开发者中被选为了全球亚马逊云科技认证技术专家&#xff08;AWS Hero&#xff09;&#xff0c;是近10年来大陆地区仅有的第9名大陆专家。同时作为…

LeetCode 解题思路 12(Hot 100)

解题思路&#xff1a; 定义三个指针&#xff1a; prev&#xff08;前驱节点&#xff09;、current&#xff08;当前节点&#xff09;、nextNode&#xff08;临时保存下一个节点&#xff09;遍历链表&#xff1a; 每次将 current.next 指向 prev&#xff0c;移动指针直到 curre…

Ubuntu搭建最简单WEB服务器

安装apache2 sudo apt install apache2 检查状态 $ sudo systemctl status apache2 ● apache2.service - The Apache HTTP ServerLoaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor prese>Active: active (running) since Thu 2025-03-06 09:51:10…

Linux 软硬链接

目录 软硬链接 软链接 硬链接 软硬链接的区别 硬链接场景 软连接场景 软硬链接 软链接 我们可以通过以下命令创建一个文件的软连接 ln -s mytest softlink-mytest 通过 ls -i -l 命令我们可以看到&#xff0c;软链接文件的inode号与源文件的inode号是不同的&#xff0c…