目录
一、任务通知的基本概念
通俗易懂的解释就是一个任务给另一个任务发送通知,该任务收到通知后便执行某些操作
二、任务通知的优势
1.内存开销更小
使用队列、信号量、事件标志组时都需另外创建一个结构体,通过中间的结构体进行间接通信!
而使用任务通知时无需额外创建结构体
![](https://i-blog.csdnimg.cn/blog_migrate/c2605f23685dc9da377b2259a437e811.png)
使用任务通知时,任务结构体
TCB
中就包含了内部对象,可以直接接收别人发过来的
"
通知
"
![](https://i-blog.csdnimg.cn/blog_migrate/4aff4178a98148df8fbf768f7bffbfd5.png)
2.效率更高
使用任务通知向任务发送事件或数据比使用队列、事件标志组或信号量快得多
三、任务通知值和通知状态
在任务的TCB控制块中,有两个任务通知操作对象:任务通知值和任务通知状态
typedef struct tskTaskControlBlock
{
.........
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ]; //任务通知值
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];//任务通知状态
#endif
...........
} tskTCB;
一个任务的通知状态又有三种,定义在task.c中,
#define taskNOT_WAITING_NOTIFICATION ( ( uint8_t ) 0 ) 任务没有在等待通知
#define taskWAITING_NOTIFICATION ( ( uint8_t ) 1 ) 任务在等待通知
#define taskNOTIFICATION_RECEIVED ( ( uint8_t ) 2 ) 任务接收到了通知,也被称为 pending(有数据了,待处理)
而任务通知值的更新方式又有多种类型:
计数值(数值累加,类似信号量)
相应位置一(类似事件标志组)
任意数值(支持覆写和不覆写,类似队列)
四、相关API源码解析
xTaskNotify() 发送通知,带有通知值
源码宏定义如下,
#define xTaskNotify( xTaskToNotify, ulValue, eAction ) \
xTaskGenericNotify( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( ulValue ), ( eAction ), NULL )
参数解析
xTaskToNotify --要通知任务的任务句柄
ulValue --通知值
eAction --任务通知值的通知动作类型结构体,结构体成员,及参数作用如下
/* Actions that can be performed when vTaskNotify() is called. */
typedef enum
{
eNoAction = 0, /* Notify the task without updating its notify value. */
eSetBits, /* Set bits in the task's notification value. */
eIncrement, /* Increment the task's notification value. */
eSetValueWithOverwrite, /* Set the task's notification value to a specific value even if the previous value has not yet been read by the task. */
eSetValueWithoutOverwrite /* Set the task's notification value if the previous value has been read by the task. */
} eNotifyAction;
--------------------
eNoAction (值为 0):
操作:通知任务,但不更新任务的通知值。
解释:仅仅通知任务,不涉及对任务通知值的修改。
-----------------------------
eSetBits:
操作:在任务的通知值中设置特定的位。
解释:将指定的位设置为1,可以用于标记事件或条件。
-------------------
eIncrement:
操作:递增任务的通知值。
解释:将任务的通知值增加1,可以用于计数或其他需要增加的情况。
---------------------
eSetValueWithOverwrite:
操作:将任务的通知值设置为特定的值,即使任务之前的值尚未被读取。
解释:强制设置任务的通知值为指定的值,不考虑之前是否有未读取的值。
------------------------------
eSetValueWithoutOverwrite:
操作:只有在任务之前的通知值已经被任务读取的情况下,设置任务的通知值。
解释:仅当任务之前的通知值已经被任务读取时,设置任务的通知值。如果之前的值未被读取,此操作不会生效。
源码解析
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, // 被通知的任务句柄
UBaseType_t uxIndexToNotify, // 通知数组的索引
uint32_t ulValue, // 通知值
eNotifyAction eAction, // 通知动作类型
uint32_t * pulPreviousNotificationValue ) // 保存之前通知值的指针
{
TCB_t * pxTCB; // 任务控制块指针
BaseType_t xReturn = pdPASS; // 返回值,默认成功
uint8_t ucOriginalNotifyState; // 保存原始通知状态
// 检查通知数组索引是否在有效范围内
configASSERT( uxIndexToNotify < configTASK_NOTIFICATION_ARRAY_ENTRIES );
// 检查被通知的任务句柄是否存在
configASSERT( xTaskToNotify );
// 获取被通知任务的任务控制块指针
pxTCB = xTaskToNotify;
// 进入临界区,禁止任务切换
taskENTER_CRITICAL();
{
// 如果传入的参数 pulPreviousNotificationValue 不为NULL,则保存之前的通知值
if( pulPreviousNotificationValue != NULL )
{
*pulPreviousNotificationValue = pxTCB->ulNotifiedValue[ uxIndexToNotify ];
}
// 保存原始的通知状态
ucOriginalNotifyState = pxTCB->ucNotifyState[ uxIndexToNotify ];
// 更新通知状态为任务已收到通知
pxTCB->ucNotifyState[ uxIndexToNotify ] = taskNOTIFICATION_RECEIVED;
// 根据通知动作类型进行相应的操作
switch( eAction )
{
case eSetBits:
// 设置通知值的指定位
pxTCB->ulNotifiedValue[ uxIndexToNotify ] |= ulValue;
break;
case eIncrement:
// 递增通知值
( pxTCB->ulNotifiedValue[ uxIndexToNotify ] )++;
break;
case eSetValueWithOverwrite:
// 设置通知值,覆盖之前的值
pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue;
break;
case eSetValueWithoutOverwrite:
// 如果之前未收到通知,则设置通知值,否则返回失败
if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )
{
pxTCB->ulNotifiedValue[ uxIndexToNotify ] = ulValue;
}
else
{
/* The value could not be written to the task. */
xReturn = pdFAIL;
}
break;
case eNoAction:
// 仅通知任务,不更新通知值
break;
default:
// 不应该到达这里,触发断言
configASSERT( xTickCount == ( TickType_t ) 0 );
break;
}
// 记录任务通知的轨迹
traceTASK_NOTIFY( uxIndexToNotify );
// 如果任务处于等待通知状态,则将其移出等待队列,添加到就绪队列
if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )
{
// 从任务等待队列中移除
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
// 将任务添加到就绪队列
prvAddTaskToReadyList( pxTCB );
// 确保任务不在事件列表中
configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );
#if ( configUSE_TICKLESS_IDLE != 0 )
{
/* 如果任务因等待通知而被阻塞,xNextTaskUnblockTime可能设置为被阻塞任务的超时时间。
* 如果任务因非超时原因而解除阻塞,通常不会更改xNextTaskUnblockTime,因为它将在
* tick count 等于 xNextTaskUnblockTime 时自动重置为新值。但是,如果使用了节电
* 时钟滞留,则最好在此处重置 xNextTaskUnblockTime,以确保在最早可能的时间进入
* 休眠模式。*/
prvResetNextTaskUnblockTime();
}
#endif
// 如果被通知的任务优先级高于当前执行的任务,则进行任务切换
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
/* 被通知的任务的优先级高于当前执行的任务,需要进行任务切换。 */
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
// 退出临界区,允许任务切换
taskEXIT_CRITICAL();
// 返回通知结果
return xReturn;
}
ulTaskNotifyTake() 获取通知值
定义如下:
#define ulTaskNotifyTake( xClearCountOnExit, xTicksToWait ) \
ulTaskGenericNotifyTake( ( tskDEFAULT_INDEX_TO_NOTIFY ), ( xClearCountOnExit ), ( xTicksToWait ) )
参数解析:
xClearCountOnExit --退出时是否清楚通知值
xTicksToWait --等待时间
源码解析
uint32_t ulTaskGenericNotifyTake( UBaseType_t uxIndexToWait, // 等待通知的数组索引
BaseType_t xClearCountOnExit, // 在退出时是否清零通知值
TickType_t xTicksToWait ) // 最大等待时间(以时钟节拍为单位)
{
uint32_t ulReturn; // 返回值,即通知值
// 检查等待通知的数组索引是否在有效范围内
configASSERT( uxIndexToWait < configTASK_NOTIFICATION_ARRAY_ENTRIES );
// 进入临界区,禁止任务切换
taskENTER_CRITICAL();
{
/* 只有在通知计数不为非零时才阻塞。 */
if( pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] == 0UL )
{
/* 将当前任务标记为等待通知状态。 */
pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskWAITING_NOTIFICATION;
if( xTicksToWait > ( TickType_t ) 0 )
{
// 如果设置了等待时间,将当前任务添加到延迟列表,并标记为可切换状态
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
traceTASK_NOTIFY_TAKE_BLOCK( uxIndexToWait );
/* 所有的端口都允许在临界区中产生 yield(有些会立即产生 yield,而其他的则等待临界区退出),
* 但这不是应用程序代码应该做的事情。 */
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
// 退出临界区,允许任务切换
taskEXIT_CRITICAL();
// 再次进入临界区,用于读取通知值和更新状态
taskENTER_CRITICAL();
{
// 记录任务通知取走的轨迹
traceTASK_NOTIFY_TAKE( uxIndexToWait );
// 读取通知值并保存到返回值
ulReturn = pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ];
// 如果通知值非零,根据xClearCountOnExit参数决定是否清零通知值
if( ulReturn != 0UL )
{
if( xClearCountOnExit != pdFALSE )
{
pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] = 0UL;
}
else
{
pxCurrentTCB->ulNotifiedValue[ uxIndexToWait ] = ulReturn - ( uint32_t ) 1;
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
// 将任务的通知状态更新为未等待通知
pxCurrentTCB->ucNotifyState[ uxIndexToWait ] = taskNOT_WAITING_NOTIFICATION;
}
// 退出临界区,允许任务切换
taskEXIT_CRITICAL();
// 返回通知值
return ulReturn;
}
五、例程
在该程中,vsenderTask 将字母放入环形缓冲区并通知vReceiverTask任务,vReceiverTask接收到通知后将缓冲区内字符打印出来
#include <stdio.h>
/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
/* Library includes. */
#include "stm32f10x_it.h"
extern void UART_Init(unsigned long ulWantedBaud);
/* Demo app includes. */
static void prvSetupHardware( void );
/*-----------------------------------------------------------*/
static void vSenderTask( void *pvParameters );
static void vReceiverTask( void *pvParameters );
static TaskHandle_t xRecvTask;
/*-----------------------------------------------------------*/
int main( void )
{
prvSetupHardware();
/* 创建1个任务用于发送任务通知
* 优先级为2
*/
xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
/* 创建1个任务用于接收任务通知
* 优先级为1
*/
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, &xRecvTask );
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
/*-----------------------------------------------------------*/
/* 环形缓冲区 */
#define BUF_LEN 32
#define NEXT_PLACE(i) ((i+1)&0x1F)
uint8_t txbuf[BUF_LEN];
uint32_t tx_r = 0;
uint32_t tx_w = 0;
static int is_txbuf_empty(void)
{
return tx_r == tx_w;
}
static int is_txbuf_full(void)
{
return NEXT_PLACE(tx_w) == tx_r;
}
static int txbuf_put(unsigned char val)
{
if (is_txbuf_full())
return -1;
txbuf[tx_w] = val;
tx_w = NEXT_PLACE(tx_w);
return 0;
}
static int txbuf_get(unsigned char *pval)
{
if (is_txbuf_empty())
return -1;
*pval = txbuf[tx_r];
tx_r = NEXT_PLACE(tx_r);
return 0;
}
/*-----------------------------------------------------------*/
static void vSenderTask( void *pvParameters )
{
int i;
int cnt_tx = 0;
int cnt_ok = 0;
int cnt_err = 0;
char c;
const TickType_t xTicksToWait = pdMS_TO_TICKS( 20UL );
/* 无限循环 */
for( ;; )
{
for (i = 0; i < 3; i++)
{
/* 放入数据 */
c = 'a'+cnt_tx;
txbuf_put(c);
cnt_tx++;
/* 发出任务通知 */
if (xTaskNotifyGive(xRecvTask) == pdPASS)
printf("xTaskNotifyGive %d time: OK, val :%c\r\n", cnt_ok++, c);
else
printf("xTaskNotifyGive %d time: ERR\r\n", cnt_err++);
}
vTaskDelay(xTicksToWait);
}
}
/*-----------------------------------------------------------*/
static void vReceiverTask( void *pvParameters )
{
int cnt_ok = 0;
uint8_t c;
int notify_val;
/* 无限循环 */
for( ;; )
{
/* 得到了任务通知, 让通知值清零 */
notify_val = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
/* 打印这几个字符 */
printf("ulTaskNotifyTake OK: %d, data: ", cnt_ok++);
/* 一次性把所有数据取出来 */
while (notify_val--)
{
txbuf_get(&c);
printf("%c", c);
}
printf("\r\n");
}
}
/*-----------------------------------------------------------*/
static void prvSetupHardware( void )
{
/* Start with the clocks in their expected state. */
RCC_DeInit();
/* Enable HSE (high speed external clock). */
RCC_HSEConfig( RCC_HSE_ON );
/* Wait till HSE is ready. */
while( RCC_GetFlagStatus( RCC_FLAG_HSERDY ) == RESET )
{
}
/* 2 wait states required on the flash. */
*( ( unsigned long * ) 0x40022000 ) = 0x02;
/* HCLK = SYSCLK */
RCC_HCLKConfig( RCC_SYSCLK_Div1 );
/* PCLK2 = HCLK */
RCC_PCLK2Config( RCC_HCLK_Div1 );
/* PCLK1 = HCLK/2 */
RCC_PCLK1Config( RCC_HCLK_Div2 );
/* PLLCLK = 8MHz * 9 = 72 MHz. */
RCC_PLLConfig( RCC_PLLSource_HSE_Div1, RCC_PLLMul_9 );
/* Enable PLL. */
RCC_PLLCmd( ENABLE );
/* Wait till PLL is ready. */
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{
}
/* Select PLL as system clock source. */
RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK );
/* Wait till PLL is used as system clock source. */
while( RCC_GetSYSCLKSource() != 0x08 )
{
}
/* Enable GPIOA, GPIOB, GPIOC, GPIOD, GPIOE and AFIO clocks */
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOC
| RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE | RCC_APB2Periph_AFIO, ENABLE );
/* SPI2 Periph clock enable */
RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );
/* Set the Vector Table base address at 0x08000000 */
NVIC_SetVectorTable( NVIC_VectTab_FLASH, 0x0 );
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
/* Configure HCLK clock as SysTick clock source. */
SysTick_CLKSourceConfig( SysTick_CLKSource_HCLK );
//vParTestInitialise();
UART_Init(115200);
}
/*-----------------------------------------------------------*/
#ifdef DEBUG
/* Keep the linker happy. */
void assert_failed( unsigned char* pcFile, unsigned long ulLine )
{
for( ;; )
{
}
}
#endif