FreeRtos 任务通知及源码解析

本文详细介绍了嵌入式系统中任务通知的概念、优势(如内存开销小、效率高),包括任务通知值和通知状态的管理,以及xTaskNotify和ulTaskNotifyTake这两个API的源码解析。通过例子展示了如何在Keil环境下实现任务间的通信。
摘要由CSDN通过智能技术生成

目录

一、任务通知的基本概念

二、任务通知的优势

1.内存开销更小

2.效率更高

三、任务通知值和通知状态

四、相关API源码解析

xTaskNotify() 发送通知,带有通知值

ulTaskNotifyTake() 获取通知值

五、例程

六、keil仿真运行结果


一、任务通知的基本概念

通俗易懂的解释就是一个任务给另一个任务发送通知,该任务收到通知后便执行某些操作

二、任务通知的优势

1.内存开销更小

使用队列、信号量、事件标志组时都需另外创建一个结构体,通过中间的结构体进行间接通信!
而使用任务通知时无需额外创建结构体
使用任务通知时,任务结构体 TCB 中就包含了内部对象,可以直接接收别人发过来的 " 通知 "

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

六、keil仿真运行结果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值