目录
十、消息队列
10.1 简介
队列是任务间通信的主要形式。 它们可以用于在任务之间以及中断和任务之间发送消息。
队列是线程安全的数据结构,任务可以通过队列在彼此之间传递数据。有以下关键特点:
- FIFO顺序:队列采用先进先出 (FIFO) 的顺序,即先发送的消息会被先接收。
- 线程安全:队列操作是原子的,确保在多任务环境中的数据完整性。
- 阻塞和非阻塞操作:任务可以通过阻塞或非阻塞的方式发送和接收消息。如果队列满了或者为空,任务可以选择等待直到有空间或者数据可用,或者立即返回。
- 优先级继承:FreeRTOS 支持基于优先级的消息传递,确保高优先级任务在队列操作期间不会被低优先级任务阻塞。
- 可变长度项:队列中的项可以是不同长度的数据块,而不是固定大小。
使用队列,任务可以通过发送消息来共享信息,从而更好地协调和同步系统中的不同部分。
10.2 队列相关API
10.2.1 创建队列
函数 |
描述 |
xQueueCreate() |
动态方式创建队列 |
xQueueCreateStatic() |
静态方式创建队列 |
动态创建队列时,FreeRTOS会在运行时从其内置的堆中为队列分配所需的内存空间。这种方式更加灵活,允许系统根据需要动态调整内存。
相反,静态创建队列要求用户在编译时手动为队列分配内存,而不依赖于FreeRTOS的堆管理。这使得内存的分配在编写代码时就能确定,因此在资源受限或对内存使用有严格要求的嵌入式系统中可能更为合适。
总体而言,动态创建提供了更大的灵活性,但可能会增加堆管理的复杂性。静态创建则更为直观,适用于在编译时就能确定内存分配的情况。选择使用哪种方式通常取决于系统的需求和设计考虑。
10.2.2 往队列写入消息
函数 |
描述 |
xQueueSend() |
往队列的尾部写入消息 |
xQueueSendToBack() |
同 xQueueSend() |
xQueueSendToFront() |
往队列的头部写入消息 |
xQueueOverwrite() |
覆写队列消息(只用于队列长度为 1 的情况) |
xQueueSendFromISR() |
在中断中往队列的尾部写入消息 |
xQueueSendToBackFromISR() |
同 xQueueSendFromISR() |
xQueueSendToFrontFromISR() |
在中断中往队列的头部写入消息 |
xQueueOverwriteFromISR() |
在中断中覆写队列消息(只用于队列长度为 1 的情况) |
10.2.3 从队列读取消息
函数 |
描述 |
xQueueReceive() |
从队列头部读取消息,并删除消息 |
xQueuePeek() |
从队列头部读取消息 |
xQueueReceiveFromISR() |
在中断中从队列头部读取消息,并删除消息 |
xQueuePeekFromISR() |
在中断中从队列头部读取消息 |
10.3 队列操作实验
目标:
使用 FreeRTOS的队列相关函数,包括创建队列、入队和出队操作:
- start_task:用来创建其他的3个任务。
- task1:当按键key1或key2按下,将键值拷贝到队列queue1(入队);当按键key3按下,将传输大数据,这里拷贝大数据的地址到队列big_queue中。
- task2:读取队列queue1中的消息(出队),打印出接收到的键值。
task3:从队列big_queue读取大数据地址,通过地址访问大数据
示例代码:
1)引入队列头文件
#include "queue.h"
2)任务配置
/* 启动任务函数 */
#define START_TASK_PRIORITY 1
#define START_TASK_STACK_DEPTH 128
TaskHandle_t start_task_handler;
void Start_Task(void *pvParameters);
/* Task1 任务 配置 */
#define TASK1_PRIORITY 2
#define TASK1_STACK_DEPTH 128
TaskHandle_t task1_handler;
void Task1(void *pvParameters);
/* Task2 任务 配置 */
#define TASK2_PRIORITY 3
#define TASK2_STACK_DEPTH 128
TaskHandle_t task2_handler;
void Task2(void *pvParameters);
/* Task3 任务 配置 */
#define TASK3_PRIORITY 4
#define TASK3_STACK_DEPTH 128
TaskHandle_t task3_handler;
void Task3(void *pvParameters);
3)入口函数
QueueHandle_t queue1; /* 小数据句柄 */
QueueHandle_t big_queue; /* 大数据句柄 */
char buff[100] = {"大大大fdahjk324hjkhfjksdahjk#$@!@#jfaskdfhjka"};
/**
* @description: FreeRTOS入口函数:创建任务函数并开始调度
* @return {*}
*/
void FreeRTOS_Start(void)
{
/* 创建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)Start_Task,
(char *)"Start_Task",
(configSTACK_DEPTH_TYPE)START_TASK_STACK_DEPTH,
(void *)NULL,
(UBaseType_t)START_TASK_PRIORITY,
(TaskHandle_t *)&start_task_handler);
vTaskStartScheduler();
}
4)初始任务函数
void Start_Task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区 */
xTaskCreate((TaskFunction_t)Task1,
(char *)"Task1",
(configSTACK_DEPTH_TYPE)TASK1_STACK_DEPTH,
(void *)NULL,
(UBaseType_t)TASK1_PRIORITY,
(TaskHandle_t *)&task1_handler);
xTaskCreate((TaskFunction_t)Task2,
(char *)"Task2",
(configSTACK_DEPTH_TYPE)TASK2_STACK_DEPTH,
(void *)NULL,
(UBaseType_t)TASK2_PRIORITY,
(TaskHandle_t *)&task2_handler);
xTaskCreate((TaskFunction_t)Task3,
(char *)"Task2",
(configSTACK_DEPTH_TYPE)TASK3_STACK_DEPTH,
(void *)NULL,
(UBaseType_t)TASK3_PRIORITY,
(TaskHandle_t *)&task3_handler);
vTaskDelete(NULL);
taskEXIT_CRITICAL(); /* 退出临界区 */
}
5)task1任务函数
/**
* @description: 入队
* @param {void *} pvParameters
* @return {*}
*/
void Task1(void *pvParameters)
{
uint8_t key = 0;
char *buf;
BaseType_t err = 0;
buf = &buff[0];
while (1)
{
key = Key_Detect();
if (key == KEY1_PRESS || key == KEY2_PRESS)
{
err = xQueueSend(queue1, &key, portMAX_DELAY);
if (err != pdTRUE)
{
printf("queue1队列发送失败\r\n");
}
}
else if (key == KEY3_PRESS)
{
err = xQueueSend(big_queue, &buf, portMAX_DELAY);
if (err != pdTRUE)
{
printf("big_queue队列发送失败\r\n");
}
}
vTaskDelay(10);
}
}
6)task2任务函数
/**
* @description: 小数据出队
* @param {void *} pvParameters
* @return {*}
*/
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);
}
}
}
7)task3任务函数
/**
* @description: 大数据出队
* @param {void *} pvParameters
* @return {*}
*/
void Task3(void *pvParameters)
{
char *buf;
BaseType_t err = 0;
while (1)
{
err = xQueueReceive(big_queue, &buf, portMAX_DELAY);
if (err != pdTRUE)
{
printf("big_queue队列读取失败\r\n");
}
else
{
printf("数据:%s\r\n", buf);
}
}
}
十一、信号量
11.1 简介
FreeRTOS中的信号量是一种用于任务间同步和资源管理的机制。信号量可以是二进制的(只能取0或1)也可以是计数型的(可以是任意正整数)。信号量的基本操作包括“获取”和“释放”。
比如动车上的卫生间,一个卫生间同时只能容纳一个人,由指示灯来表示是否有人在使用。当我们想使用卫生间的时候,有如下过程:
- 判断卫生间是否有人使用(判断信号量是否有资源)
- 卫生间空闲(信号量有资源),那么就可以直接进入卫生间(获取信号量成功)
- 卫生间使用中(信号量没有资源),那么这个人可以选择不上卫生间(获取信号量失败),也可以在门口等待(任务阻塞)
信号量与队列的区别如下:
信号量 |
队列 |
主要用于管理对共享资源的访问,确保在同一时刻只有一个任务可以访问共享资源 |
用于任务之间的数据通信,通过在任务之间传递消息,实现信息的传递和同步。 |
可以是二进制信号量(Binary Semaphore)或计数信号量(Counting Semaphore) |
存储和传递消息的数据结构,任务可以发送消息到队列,也可以从队列接收消息。 |
适用于对资源的互斥访问,控制任务的执行顺序,或者限制同时访问某一资源的任务数量。 |
适用于在任务之间传递数据,实现解耦和通信。 |
11.2 二值信号量(熟悉)
二值信号量(Binary Semaphore)是一种特殊类型的信号量,它只有两个可能的值:0和1。这种信号量主要用于实现对共享资源的互斥访问或者任务之间的同步。
- 两个状态: 二值信号量只能处于两个状态之一,通常用0和1表示。当信号量的值为0时,表示资源不可用;当值为1时,表示资源可用。
- 互斥访问: 常用于控制对共享资源的互斥访问,确保在同一时刻只有一个任务可以访问共享资源。任务在访问资源之前会尝试获取信号量,成功则继续执行,失败则等待。
- 任务同步: 也可以用于任务之间的同步,例如一个任务等待另一个任务完成某个操作。
信号量 API 函数允许指定阻塞时间。 阻塞时间表示当一个任务试图“获取”信号量时, 如果信号不是立即可用,那么该任务进入阻塞状态的最大 “tick” 数。 如果 多个任务在同一个信号量上阻塞,那么具有最高优先级的任务将在下次信号量可用时最先解除阻塞 。
可将二进制信号量视为仅能容纳一个项目的队列。 因此,队列只能为空或满(因此称为二进制)。 使用队列的任务和中断 不在乎队列容纳的是什么——它们只想知道队列是空的还是满的。 可以 利用该机制来同步任务和中断。
二值信号量相关函数:
函数 |
描述 |
xSemaphoreCreateBinary() |
使用动态方式创建二值信号量 |
xSemaphoreCreateBinaryStatic() |
使用静态方式创建二值信号量 |
xSemaphoreGive() |
释放信号量 |
xSemaphoreGiveFromISR() |
在中断中释放信号量 |
xSemaphoreTake() |
获取信号量 |
xSemaphoreTakeFromISR() |
在中断中获取信号量 |
11.3 示例代码
11.4 计数型信号量(熟悉)
正如二进制信号量可以被认为是长度为 1 的队列那样,计数信号量也可以被认为是长度大于 1 的队列。 信号量的用户对存储在队列中的数据不感兴趣,他们只关心队列是否为空。
计数信号量通常用于两种情况:
- 事件计数:在此使用方案中,每次事件发生时,事件处理程序将“给出”一个信号量(信号量计数值递增) ,并且 处理程序任务每次处理事件(信号量计数值递减)时“获取”一个信号量。因此,计数值是 已发生的事件数与已处理的事件数之间的差值。在这种情况下, 创建信号量时计数值可以为零。
- 资源管理:在此使用情景中,计数值表示可用资源的数量。要获得对资源的控制权,任务必须首先获取 一个信号量——同时递减信号量计数值。当计数值达到零时,表示没有空闲资源可用。当任务使用完资源时, “返还”一个信号量——同时递增信号量计数值。在这种情况下, 创建信号量时计数值可以等于最大计数值。
计数型信号量相关函数:
函数 |
描述 |
xSemaphoreCreateCounting() |
使用动态方法创建计数型信号量。 |
xSemaphoreCreateCountingStatic() |
使用静态方法创建计数型信号量 |
uxSemaphoreGetCount() |
获取信号量的计数值 |
11.5 优先级翻转简介
优先级翻转是一个在实时系统中可能出现的问题,特别是在多任务环境中。该问题指的是一个较低优先级的任务阻塞了一个较高优先级任务的执行,从而导致高优先级任务无法及时完成。
典型的优先级翻转场景如下:
- 任务A(高优先级):拥有高优先级,需要访问共享资源,比如一个关键数据结构。
- 任务B(低优先级):拥有低优先级,目前正在访问该共享资源。
- 任务C(中优先级):位于任务A和任务B之间,具有介于两者之间的优先级。
具体流程如下:
- 任务A开始执行,但由于任务B正在访问共享资源,任务A被阻塞等待。
- 任务C获得执行权,由于优先级高于任务B,它可以抢占任务B。
- 任务C执行完成后,任务B被解除阻塞,开始执行,完成后释放了共享资源。
- 任务A重新获取执行权,继续执行。
这个过程中,任务A因为资源被占用而被阻塞,而任务B却被中优先级的任务C抢占,导致任务B无法及时完成。这种情况称为优先级翻转,因为任务C的介入翻转了高优先级任务A的执行顺序。
11.6 互斥信号量
互斥信号量是包含优先级继承机制的二进制信号量。二进制信号量能更好实现实现同步(任务间或任务与中断之间), 而互斥信号量有助于更好实现简单互斥(即相互排斥)。
优先级继承是一种解决实时系统中任务调度引起的优先级翻转问题的机制。在具体的任务调度中,当一个高优先级任务等待一个低优先级任务所持有的资源时,系统会提升低优先级任务的优先级,以避免高优先级任务长时间等待的情况。
优先级继承无法完全解决优先级翻转,只是在某些情况下将影响降至最低。
不能在中断中使用互斥信号量,原因如下:
- 互斥信号量使用的优先级继承机制要求从任务中(而不是从中断中)获取和释放互斥信号量。
- 中断无法保持阻塞来等待一个被互斥信号量保护的资源。
互斥信号量相关函数:
函数 |
描述 |
xSemaphoreCreateMutex() |
使用动态方法创建互斥信号量。 |
xSemaphoreCreateMutexStatic() |
使用静态方法创建互斥信号量。 |
互斥信号量的获取和释放函数与二值信号量的相应函数相似,但有一个重要的区别:互斥信号量不支持在中断服务程序中直接调用。注意,当创建互斥信号量时,系统会自动进行一次信号量的释放操作。
十二、任务集
12.1 简介
队列集(Queue Set)是 FreeRTOS 中的一种数据结构,用于管理多个队列。它提供了一种有效的方式,通过单个 API 调用来操作和访问一组相关的队列。
在多任务系统中,任务之间可能需要共享数据,而这些数据可能存储在不同的队列中。队列集的作用就是为了更方便地管理这些相关队列,使得任务能够轻松地访问和处理多个队列的数据。
队列集的特点和用法:
- 集中管理多个队列:队列集允许你将多个相关联的队列组织在一起,方便集中管理。
- 单一 API 调用:通过单一的 API 调用,任务可以同时操作多个队列,而无需分别处理每个队列。
- 简化任务代码:对于需要处理多个相关队列的任务,使用队列集可以简化代码,提高可读性和维护性。
- 提高系统效率:在需要协同工作的任务之间共享和传递数据时,队列集可以提高系统的效率。
- 协同工作:任务可以更方便地协同工作,共享数据,实现更复杂的任务间通信和同步。
使用队列集时,你需要了解如何创建、添加和访问队列集,以及如何使用队列集 API 进行数据的发送和接收。队列集是 FreeRTOS 提供的一个强大工具,用于更灵活地组织和处理任务之间的数据流。
想象一下你有一个智能家居系统,有一个任务负责处理温度信息,另一个任务负责光照信息。你可能有两个队列,一个用于温度,一个用于光照。现在,通过队列集,你可以方便地管理这两个队列,让控制任务能够在需要时从这两个队列中获取信息,从而更智能地控制环境。
12.2 相关API函数
队列集相关函数:
函数 |
描述 |
xQueueCreateSet() |
创建队列集 |
xQueueAddToSet() |
队列添加到队列集中 |
xQueueRemoveFromSet() |
从队列集中移除队列 |
xQueueSelectFromSet() |
获取队列集中有有效消息的队列 |
xQueueSelectFromSetFromISR() |
在中断中获取队列集中有有效消息的队列 |
12.3 示例代码
使用 FreeRTOS 的队列集相关函数:
- start_task:用来创建其他2个任务,并创建队列集、队列/信号量,将队列/信号量添加到队列集中。
- task1:用于扫描按键,当KEY1按下,往队列写入数据,当KEY2按下,释放二值信号量。
- task2:读取队列集中的消息,并打印。
开宏
#define configUSE_QUEUE_SETS 1
//1)引入头文件
#include "queue.h"
#include "semphr.h"
//2)任务配置
/* 启动任务函数 */
#define START_TASK_PRIORITY 1
#define START_TASK_STACK_DEPTH 128
TaskHandle_t start_task_handler;
void Start_Task(void *pvParameters);
/* Task1 任务 配置 */
#define TASK1_PRIORITY 2
#define TASK1_STACK_DEPTH 128
TaskHandle_t task1_handler;
void Task1(void *pvParameters);
/* Task2 任务 配置 */
#define TASK2_PRIORITY 3
#define TASK2_STACK_DEPTH 128
TaskHandle_t task2_handler;
void Task2(void *pvParameters);
3)入口函数
QueueSetHandle_t queueset_handle;
QueueHandle_t queue_handle;
QueueHandle_t semphr_handle;
/**
* @description: FreeRTOS入口函数:创建任务函数并开始调度
* @return {*}
*/
void FreeRTOS_Start(void)
{
xTaskCreate((TaskFunction_t)Start_Task,
(char *)"Start_Task",
(configSTACK_DEPTH_TYPE)START_TASK_STACK_DEPTH,
(void *)NULL,
(UBaseType_t)START_TASK_PRIORITY,
(TaskHandle_t *)&start_task_handler);
vTaskStartScheduler();
}
//4)初始任务函数
void Start_Task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区 */
/* 创建队列集,可以存放2个队列 */
queueset_handle = xQueueCreateSet(2);
if (queueset_handle != NULL)
{
printf("队列集创建成功\r\n");
}
/* 创建队列 */
queue_handle = xQueueCreate(1, sizeof(uint8_t));
/* 创建二值信号量 */
semphr_handle = xSemaphoreCreateBinary();
/* 添加到队列集 */
xQueueAddToSet(queue_handle, queueset_handle);
xQueueAddToSet(semphr_handle, queueset_handle);
xTaskCreate((TaskFunction_t)Task1,
(char *)"Task1",
(configSTACK_DEPTH_TYPE)TASK1_STACK_DEPTH,
(void *)NULL,
(UBaseType_t)TASK1_PRIORITY,
(TaskHandle_t *)&task1_handler);
xTaskCreate((TaskFunction_t)Task2,
(char *)"Task2",
(configSTACK_DEPTH_TYPE)TASK2_STACK_DEPTH,
(void *)NULL,
(UBaseType_t)TASK2_PRIORITY,
(TaskHandle_t *)&task2_handler);
vTaskDelete(NULL);
taskEXIT_CRITICAL(); /* 退出临界区 */
}
//5)task1任务函数
/**
* @description: 实现队列发送以及信号量释放
* @param {void *} pvParameters
* @return {*}
*/
void Task1(void *pvParameters)
{
uint8_t key = 0;
BaseType_t err = 0;
while (1)
{
key = Key_Detect();
if (key == KEY1_PRESS)
{
err = xQueueSend(queue_handle, &key, portMAX_DELAY);
if (err == pdPASS)
{
printf("往队列queue_handle写入数据成功\r\n");
}
}
else if (key == KEY2_PRESS)
{
err = xSemaphoreGive(semphr_handle);
if (err == pdPASS)
{
printf("释放信号量成功\r\n");
}
}
vTaskDelay(10);
}
}
//6)task2任务函数
/**
* @description: 获取队列集的消息
* @param {void *} pvParameters
* @return {*}
*/
void Task2(void *pvParameters)
{
QueueSetMemberHandle_t member_handle;
uint8_t key;
while (1)
{
member_handle = xQueueSelectFromSet(queueset_handle, portMAX_DELAY);
if (member_handle == queue_handle)
{
xQueueReceive(member_handle, &key, portMAX_DELAY);
printf("获取到的队列数据=%d\r\n", key);
}
else if (member_handle == semphr_handle)
{
xSemaphoreTake(member_handle, portMAX_DELAY);
printf("获取信号量成功\r\n");
}
}
}
十三、事件标志组
13.1 简介
13.1.1 基本概念
当在嵌入式系统中运行多个任务时,这些任务可能需要相互通信,协调其操作。FreeRTOS中的事件标志组(Event Flags Group)提供了一种轻量级的机制,用于在任务之间传递信息和同步操作。
事件标志组就像是一个共享的标志牌集合,每个标志位都代表一种特定的状态或事件。任务可以等待或设置这些标志位,从而实现任务之间的协同工作。
1)事件位(事件标志)
事件位用于指示事件是否发生。 事件位通常称为事件标志。例如,应用程序可以:
- 定义一个位(或标志), 设置为 1 时表示“已收到消息并准备好处理”, 设置为 0 时表示“没有消息等待处理”。
- 定义一个位(或标志), 设置为 1 时表示“应用程序已将准备发送到网络的消息排队”, 设置为 0 时表示 “没有消息需要排队准备发送到网络”。
- 定义一个位(或标志), 设置为 1 时表示“需要向网络发送心跳消息”, 设置为 0 时表示“不需要向网络发送心跳消息”。
2)事件组
事件组就是一组事件位。 事件组中的事件位通过位编号来引用。 同样,以上面列出的三个例子为例:
- 事件标志组位编号为 0 表示“已收到消息并准备好处理”。
- 事件标志组位编号为 1 表示“应用程序已将准备发送到网络的消息排队”。
- 事件标志组位编号为 2 表示“需要向网络发送心跳消息”。
13.1.2 事件组和事件位数据类型
事件组由 EventGroupHandle_t 类型的变量引用。
在事件组中实现的位数(或标志数)取决于是使用 configUSE_16_BIT_TICKS 还是 configTICK_TYPE_WIDTH_IN_BITS 来控制 TickType_t 的类型:
- 如果 configUSE_16_BIT_TICKS 设置为 1,则事件组内实现的位数(或标志数)为 8; 如果 configUSE_16_BIT_TICKS 设置为 0,则为 24。
- 如果 configTICK_TYPE_WIDTH_IN_BITS 设为 TICK_TYPE_WIDTH_16_BITS,则事件组内实现的位数(或标志数)为 8。
- 如果 configTICK_TYPE_WIDTH_IN_BITS 设为 TICK_TYPE_WIDTH_32_BITS,则为 24 。
- 如果 configTICK_TYPE_WIDTH_IN_BITS 设为 TICK_TYPE_WIDTH_64_BITS,则为 56。
对configUSE_16_BIT_TICKS或configTICK_TYPE_WIDTH_IN_BITS 的依赖源于 RTOS 任务内部实现中用于线程本地存储的数据类型。我们当前的版本不支持configTICK_TYPE_WIDTH_IN_BITS配置,只有configUSE_16_BIT_TICKS配置。
事件组中的所有事件位都 存储在 EventBits_t 类型的单个无符号整数变量中。 事件位 0 存储在位 0 中,事件位 1 存储在位1 中,依此类推。
下图表示一个 24 位事件组,使用 3 个位来保存前面描述的 3 个示例事件。 在图片中,仅设置了事件位2。
13.1.3 事件标志组和信号量的区别
事件标志组(Event Flags Group)和信号量(Semaphore)都是FreeRTOS中用于任务同步和通信的机制,但它们在用途和行为上有一些关键的区别。
事件标志组 |
信号量 |
主要用于任务之间的事件通知和同步。每个标志位通常代表一个特定的状态或事件,任务可以等待某些标志的发生或者设置标志来通知其他任务。 |
用于任务之间的资源控制和同步。信号量通常用来保护共享资源,控制对共享资源的访问,以及在任务之间提供同步。 |
每个标志位通常代表一个不同的事件,每个标志位只有两个状态,即已设置或未设置。 |
信号量是一个计数器,可以具有大于1的值,表示可用的资源数量。信号量的计数可以动态增减,而且可以用于实现互斥、同步等场景。 |
适用于需要向其他任务通知事件发生或等待特定事件的场景,例如数据准备就绪、某个条件满足等。 |
适用于需要对共享资源进行控制,限制同时访问某个资源的任务数量,以及确保任务按顺序访问共享资源的场景。 |
任务可以等待多个特定的标志位同时发生,或者等待任意一个标志位发生。 |
任务等待信号量的发放,当信号量的计数大于零时,任务可以继续执行。 |
总体来说,事件标志组更侧重于任务间的事件通知和同步,而信号量更侧重于资源的控制和同步。在设计中,根据具体需求选择合适的机制会更有利于系统的设计和性能。
13.2 相关API函数
函数 |
描述 |
xEventGroupCreate() |
使用动态方式创建事件标志组 |
xEventGroupCreateStatic() |
使用静态方式创建事件标志组 |
xEventGroupClearBits() |
清零事件标志位 |
xEventGroupClearBitsFromISR() |
在中断中清零事件标志位 |
xEventGroupSetBits() |
设置事件标志位 |
xEventGroupSetBitsFromISR() |
在中断中设置事件标志位 |
xEventGroupWaitBits() |
等待事件标志位 |
xEventGroupSync() |
设置事件标志位,并等待事件标志位 |
13.3 示例代码
使用 FreeRTOS 的事件标志组相关函数:
- start_task:用来创建其他2个任务,并创建事件标志组。
- task1:读取按键按下键值,根据不同键值将事件标志组相应事件位置一,模拟事件发生。
- task2:同时等待事件标志组中的多个事件位,当这些事件位都置 1 的话就执行相应的处理。
//1)引入头文件
#include "event_groups.h"
//2)任务配置
/* 启动任务函数 */
#define START_TASK_PRIORITY 1
#define START_TASK_STACK_DEPTH 128
TaskHandle_t start_task_handler;
void Start_Task(void *pvParameters);
/* Task1 任务 配置 */
#define TASK1_PRIORITY 2
#define TASK1_STACK_DEPTH 128
TaskHandle_t task1_handler;
void Task1(void *pvParameters);
/* Task2 任务 配置 */
#define TASK2_PRIORITY 3
#define TASK2_STACK_DEPTH 128
TaskHandle_t task2_handler;
void Task2(void *pvParameters);
//3)入口函数
EventGroupHandle_t eventgroup_handle;
#define EVENTBIT_0 (1 << 0)
#define EVENTBIT_1 (1 << 1)
/**
* @description: FreeRTOS入口函数:创建任务函数并开始调度
* @return {*}
*/
void FreeRTOS_Start(void)
{
xTaskCreate((TaskFunction_t)Start_Task,
(char *)"Start_Task",
(configSTACK_DEPTH_TYPE)START_TASK_STACK_DEPTH,
(void *)NULL,
(UBaseType_t)START_TASK_PRIORITY,
(TaskHandle_t *)&start_task_handler);
vTaskStartScheduler();
}
//4)初始任务函数
void Start_Task( void * pvParameters )
{
taskENTER_CRITICAL(); /* 进入临界区 */
/* 创建事件标志组 */
eventgroup_handle = xEventGroupCreate();
if(eventgroup_handle != NULL)
{
printf("事件标志组创建成功\r\n");
}
xTaskCreate((TaskFunction_t ) Task1,
(char * ) "Task1",
(configSTACK_DEPTH_TYPE ) TASK1_STACK_DEPTH,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIORITY,
(TaskHandle_t * ) &task1_handler );
xTaskCreate((TaskFunction_t ) Task2,
(char * ) "Task2",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_DEPTH,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIORITY,
(TaskHandle_t * ) &task2_handler );
vTaskDelete(NULL);
taskEXIT_CRITICAL(); /* 退出临界区 */
}
//5)task1任务函数
/**
* @description: 根据按键,事件标志组相应为置一
* @param {void *} pvParameters
* @return {*}
*/
void Task1(void * pvParameters)
{
uint8_t key = 0;
while(1)
{
key = Key_Detect();
if(key == KEY1_PRESS)
{
/* 将事件标志组的bit0位置1 */
xEventGroupSetBits( eventgroup_handle, EVENTBIT_0);
}
else if(key == KEY2_PRESS)
{
/* 将事件标志组的bit1位置1 */
xEventGroupSetBits( eventgroup_handle, EVENTBIT_1);
}
vTaskDelay(10);
}
}
//6)task2任务函数
/**
* @description: 同时等待事件标志组中的多个事件位
* @param {void *} pvParameters
* @return {*}
*/
void Task2(void * pvParameters)
{
EventBits_t event_bit = 0;
while(1)
{
event_bit = xEventGroupWaitBits( eventgroup_handle, /* 事件标志组句柄 */
EVENTBIT_0 | EVENTBIT_1, /* 等待事件标志组的bit0和bit1位 */
pdTRUE, /* 成功等待到事件标志位后,清除事件标志组中的bit0和bit1位 */
pdTRUE, /* 等待事件标志组的bit0和bit1位都置1,就成立 */
portMAX_DELAY ); /* 一直等 */
printf("等待到的事件标志位值=%#x\r\n",event_bit);
}
}