目录
一、事件标志组
什么是事件标志组?
事件标志组相关 API 函数
1. 创建事件标志组
2. 设置事件标志位
3. 清除事件标志位
4. 等待事件标志位
实操
实验需求
代码实现
二、任务通知
什么是任务通知?
任务通知值的更新方式
任务通知的优势和劣势
任务通知的优势
任务通知的劣势
任务通知相关 API 函数
1. 发送通知
2. 等待通知
实操
1. 模拟二值信号量
2. 模拟计数型信号量
3. 模拟事件标志组
4. 模拟邮箱
事件标志位:表明某个事件是否发生,联想:全局变量 flag。通常按位表示,每一个位表示一个事件(高8位不算)
事件标志组是一组事件标志位的集合, 可以简单的理解事件标志组,就是一个整数。
事件标志组本质是一个 16 位或 32 位无符号的数据类型 EventBits_t ,由 configUSE_16_BIT_TICKS决定。
虽然使用了 32 位无符号的数据类型变量来存储事件标志, 但其中的高8位用作存储事件标志组的控制信息,低 24 位用作存储事件标志 ,所以说一个事件组最多可以存储 24 个事件标志!
函数 | 描述 |
xEventGroupCreate() | 使用动态方式创建事件标志组 |
xEventGroupCreateStatic() | 使用静态方式创建事件标志组 |
xEventGroupClearBits() | 清零事件标志位 |
xEventGroupClearBitsFromISR() | 在中断中清零事件标志位 |
xEventGroupSetBits() | 设置事件标志位 |
xEventGroupSetBitsFromISR() | 在中断中设置事件标志位 |
xEventGroupWaitBits() | 等待事件标志位 |
EventGroupHandle_t xEventGroupCreate( void );
参数:
无
返回值:
成功,返回对应事件标志组的句柄;
失败,返回 NULL 。
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );
参数:
xEventGroup:对应事件组句柄。 uxBitsToSet:指定要在事件组中设置的一个或多个位的按位值。
返回值:
设置之后事件组中的事件标志位值。
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear );
参数:
xEventGroup:对应事件组句柄。 uxBitsToClear:指定要在事件组中清除的一个或多个位的按位值。
返回值:
清零之前事件组中事件标志位的值。
EventBits_t xEventGroupWaitBits(
const EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait );
参数:
xEventGroup:对应的事件标志组句柄
uxBitsToWaitFor:指定事件组中要等待的一个或多个事件位的按位值
xClearOnExit:pdTRUE——清除对应事件位,pdFALSE——不清除
xWaitForAllBits:
pdTRUE——所有等待事件位全为1(逻辑与),pdFALSE——等待的事件位有一个为1(逻辑或)
xTicksToWait:超时
返回值:
等待的事件标志位值:等待事件标志位成功,返回等待到的事件标志位
其他值:等待事件标志位失败,返回事件组中的事件标志位
创建一个事件标志组和两个任务( task1 和 task2),task1 检测按键,如果检测到 KEY1 和 KEY2,都按过,则执行 task2 。
//任意一个按下都检测
event_bit = xEventGroupWaitBits(eventgroup_handle,
0x01 | 0x02, pdTRUE, pdFALSE, portMAX_DELAY);
//两个全按过才检测
event_bit = xEventGroupWaitBits(eventgroup_handle,
0x01 | 0x02, pdTRUE, pdTRUE, portMAX_DELAY);
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
EventGroupHandle_t eventgroup_handle;
/* USER CODE END Variables */
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
eventgroup_handle = xEventGroupCreate();
/* USER CODE END RTOS_THREADS */
/* USER CODE BEGIN Header_Start_Task1 */
/**
* @brief Function implementing the Task1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Start_Task1 */
void Start_Task1(void const * argument)
{
/* USER CODE BEGIN Start_Task1 */
/* Infinite loop */
for(;;)
{
// 等待 KEY1 按下
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
osDelay(20);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
xEventGroupSetBits(eventgroup_handle, 0x01);
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
}
// 等待 KEY2 按下
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
{
osDelay(20);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
{
xEventGroupSetBits(eventgroup_handle, 0x02);
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
}
osDelay(1);
}
/* USER CODE END Start_Task1 */
}
/* USER CODE BEGIN Header_Start_Task2 */
/**
* @brief Function implementing the Task2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Start_Task2 */
void Start_Task2(void const * argument)
{
/* USER CODE BEGIN Start_Task2 */
EventBits_t event_bit = 0;
/* Infinite loop */
for(;;)
{
//event_bit = xEventGroupWaitBits(eventgroup_handle, 0x01 | 0x02, pdTRUE, pdFALSE, portMAX_DELAY);
event_bit = xEventGroupWaitBits(eventgroup_handle, 0x01 | 0x02, pdTRUE, pdTRUE, portMAX_DELAY);
printf("%#x\r\n", event_bit);
osDelay(1);
}
/* USER CODE END Start_Task2 */
}
FreeRTOS 从版本 V8.2.0 开始提供任务通知这个功能,每个任务都有一个 32 位的通知值。按照FreeRTOS 官方的说法,使用消息通知比通过二进制信号量方式解除阻塞任务快 45%, 并且更加省内存(无需创建队列)。
在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件标志组,可以替代长度为 1 的队列(可以保存一个 32 位整数或指针值),并且任务通知速度更快、使用的RAM更少!
FreeRTOS 提供以下几种方式发送通知给任务 :
发送消息给任务,如果有通知未读, 不覆盖通知值
发送消息给任务,直接覆盖通知值
发送消息给任务,设置通知值的一个或者多个位
发送消息给任务,递增通知值
通过对以上方式的合理使用,可以在一定场合下替代原本的队列、信号量、事件标志组等。
1. 使用任务通知向任务发送事件或数据,比使用队列、事件标志组或信号量快得多。
2. 使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。
1. 只有任务可以等待通知,中断服务函数中不可以,因为中断没有 TCB 。
2. 通知只能一对一,因为通知必须指定任务。
3. 等待通知的任务可以被阻塞, 但是发送消息的任务,任何情况下都不会被阻塞等待。
4. 任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保
持一个数据。
函数 | 描述 |
xTaskNotify() | 发送通知,带有通知值 |
xTaskNotifyAndQuery() | 发送通知,带有通知值并且保留接收任务的原通知值 |
xTaskNotifyGive() | 发送通知,不带通知值 |
xTaskNotifyFromISR() | 在中断中发送任务通知 |
xTaskNotifyAndQueryFromISR() | 在中断中发送任务通知 |
vTaskNotifyGiveFromISR() | 在中断中发送任务通知 |
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction );
参数:
xTaskToNotify:需要接收通知的任务句柄;
ulValue:用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;
eAction:一个枚举,代表如何使用任务通知的值;
枚举值 | 描述 |
eNoAction | 发送通知,但不更新值(参数ulValue未使用) |
eSetBits | 被通知任务的通知值按位或ulValue。(某些场景下可代替事件组,效率更高) |
eIncrement | 被通知任务的通知值增加1(参数ulValue未使用),相当于xTaskNotifyGive |
eSetValueWithOverwrite | 被通知任务的通知值设置为 ulValue。(某些场景下可代替xQueueOverwrite ,效率更高) |
eSetValueWithoutOverwrite | 如果被通知的任务当前没有通知,则被通知的任务的通知值设为ulValue。 如果被通知任务没有取走上一个通知,又接收到了一个通知,则这次通知值丢弃,在这种情况下视为调用失败并返回pdFALSE (某些场景下可代替xQueueSend ,效率更高) |
返回值:
如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回
pdFALSE, 而其他情况均返回pdPASS。
BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t *pulPreviousNotifyValue );
参数:
xTaskToNotify:需要接收通知的任务句柄;
ulValue:用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;
eAction:一个枚举,代表如何使用任务通知的值;
pulPreviousNotifyValue:对象任务的上一个任务通知值,如果为 NULL, 则不需要回传, 这个时候就等价于函数 xTaskNotify()。
返回值:
如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回pdFALSE, 而其他情况均返回pdPASS。
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
参数:
xTaskToNotify:接收通知的任务句柄, 并让其自身的任务通知值加 1。
返回值:
总是返回 pdPASS。
等待通知API函数只能用在任务,不可应用于中断中!
函数 | 描述 |
ulTaskNotifyTake() | 获取任务通知,可以设置在退出此函数的时候将任务通知值清零或者减一。当任务通知用作二值信号量或者计数信号量的时候,使用此函数来获取信号量。 |
xTaskNotifyWait() | 获取任务通知,比 ulTaskNotifyTak()更为复杂,可获取通知值和清除通知值的指定位 |
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
TickType_t xTicksToWait );
参数:(关键)
xClearCountOnExit:指定在成功接收通知后,将通知值清零或减 1,
pdTRUE:把通知值清零(二值信号量);
pdFALSE:把通知值减一(计数型信号量);
xTicksToWait:阻塞等待任务通知值的最大时间;
返回值:
0:接收失败
非0:接收成功,返回任务通知的通知值
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );
ulBitsToClearOnEntry:函数执行前清零任务通知值那些位 。
ulBitsToClearOnExit:表示在函数退出前,清零任务通知值那些位,在清 0 前,接收到的任务通知值会先被保存到形参*pulNotificationValue 中。
pulNotificationValue:用于保存接收到的任务通知值。 如果 不需要使用,则设置为 NULL 即可 。
xTicksToWait:等待消息通知的最大等待时间。
/* USER CODE BEGIN Header_StartSend_Task */
/**
* @brief Function implementing the Send_Task thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartSend_Task */
void StartSend_Task(void const * argument)
{
/* USER CODE BEGIN StartSend_Task */
/* Infinite loop */
for(;;)
{
// 等待 KEY1 按下
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
osDelay(20);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
xTaskNotifyGive(Recieve_TaskHandle);
printf("任务通知:模拟二值信号量发送成功!\r\n");
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
}
osDelay(1);
}
/* USER CODE END StartSend_Task */
}
/* USER CODE BEGIN Header_StartRecieve_Task */
/**
* @brief Function implementing the Recieve_Task thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartRecieve_Task */
void StartRecieve_Task(void const * argument)
{
/* USER CODE BEGIN StartRecieve_Task */
uint32_t rev = 0;
/* Infinite loop */
for(;;)
{
// 等待 KEY2 按下
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
{
osDelay(20);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
{
rev = ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
if (rev != 0)
printf("任务通知:模拟二值信号量接收成功!\r\n");
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
}
osDelay(1);
}
/* USER CODE END StartRecieve_Task */
}
// 等待 KEY2 按下
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
{
osDelay(20);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
{
rev = ulTaskNotifyTake(pdFALSE,portMAX_DELAY);
if (rev != 0)
printf("任务通知:模拟二值信号量接收成功,rev = %d !\r\n",rev);
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
}
/* USER CODE BEGIN Header_Start_Task1 */
/**
* @brief Function implementing the Task1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Start_Task1 */
void Start_Task1(void const * argument)
{
/* USER CODE BEGIN Start_Task1 */
/* Infinite loop */
for(;;)
{
// 等待 KEY1 按下
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
osDelay(20);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
printf("bit0置为1\r\n");
xTaskNotify(Task2Handle,0x01,eSetBits);
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
}
// 等待 KEY2 按下
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
{
osDelay(20);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
{
printf("bit1置为1\r\n");
xTaskNotify(Task2Handle,0x02,eSetBits);
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
}
osDelay(1);
}
/* USER CODE END Start_Task1 */
}
/* USER CODE BEGIN Header_Start_Task2 */
/**
* @brief Function implementing the Task2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Start_Task2 */
void Start_Task2(void const * argument)
{
/* USER CODE BEGIN Start_Task2 */
uint32_t notify_val = 0, event_bit = 0;
/* Infinite loop */
for(;;)
{
xTaskNotifyWait(0,0xFFFFFFFF,¬ify_val,portMAX_DELAY);
if (notify_val & 0x01)
event_bit |= 0x01;
if (notify_val & 0x02)
event_bit |= 0x02;
if (event_bit == (0x01 | 0x02))
{
printf("%#x\r\n",event_bit);
event_bit = 0;
}
osDelay(1);
}
/* USER CODE END Start_Task2 */
}
邮箱就是长度为1的队列
eSetValueWithOverwrite ==》eSetValueWithoutOverwrite 是否覆写
/* USER CODE BEGIN Header_Start_Task1 */
/**
* @brief Function implementing the Task1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Start_Task1 */
void Start_Task1(void const * argument)
{
/* USER CODE BEGIN Start_Task1 */
/* Infinite loop */
for(;;)
{
// 等待 KEY1 按下
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
osDelay(20);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
{
printf("按键1按下\r\n");
xTaskNotify(Task2Handle,1,eSetValueWithOverwrite);
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
}
// 等待 KEY2 按下
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
{
osDelay(20);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
{
printf("按键0按下\r\n");
xTaskNotify(Task2Handle,2,eSetValueWithOverwrite);
}
while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
}
osDelay(1);
}
/* USER CODE END Start_Task1 */
}
/* USER CODE BEGIN Header_Start_Task2 */
/**
* @brief Function implementing the Task2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Start_Task2 */
void Start_Task2(void const * argument)
{
/* USER CODE BEGIN Start_Task2 */
uint32_t notify_val = 0;
/* Infinite loop */
for(;;)
{
xTaskNotifyWait(0,0xFFFFFFFF,¬ify_val,portMAX_DELAY);
printf("%d\r\n",notify_val);
osDelay(1);
}
/* USER CODE END Start_Task2 */
}