根据个人的学习方向,学习FreeRTOS。由于野火小哥把FreeRTOS讲得比较含蓄,打算在本专栏尽量细化一点。作为个人笔记,仅供参考或查阅。
配套资料:FreeRTOS内核实现与应用开发实战指南、野火FreeRTOS配套视频源码、b站野火FreeRTOS视频。搭配来看更佳哟!!!
任务通知
每个任务都有一个 32 位的通知值,在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件组,也可以替代长度为 1 的队列(可以保存一个 32 位整数或指针值)。
想要使用任务通知,必须将 FreeRTOSConfig.h 中的宏定义 configUSE_TASK_NOTIFICATIONS 设置为 1,其实FreeRTOS 默认是为 1 的, 所以任务通知是默认使能的。
FreeRTOS 提供以下几种方式发送通知给任务 :
发送通知给任务,如果有通知未读,不覆盖通知值。
发送通知给任务,直接覆盖通知值。
发送通知给任务,设置通知值的一个或者多个位,可以当做事件组来使用。
发送通知给任务,递增通知值,可以当做计数信号量使用。
通过对以上任务通知方式的合理使用,可以在一定场合下替代 FreeRTOS 的信号量,队列、事件组等。
消息通知虽然处理更快,RAM 开销更小,但也有以下限制 :
只能有一个任务接收通知消息,因为必须指定接收通知的任务。
只有等待通知的任务可以被阻塞,发送通知的任务,在任何情况下都不会因为发送失败而进入阻塞态。
任务通知可以在任务中向指定任务发送通知,也可以在中断中向指定任务发送通知,FreeRTOS 的每个任务都有一个 32 位的通知值,任务控制块中的成员变量 ulNotifiedValu就是这个通知值。
只有在任务中可以等待通知,而不允许在中断中等待通知。如果任务等待的通知暂时无效,任务会根据用户指定的阻塞超时时间进入阻塞状态,我们可以将等待通知的任务看作是消费者;
其它任务和中断可以向等待通知的任务发送通知,发送通知的任务和中断服务函数可以看作是生产者,当其他任务或者中断向这个任务发送任务通知,任务获得通知以后,该任务就会从阻塞态中解除,这与 FreeRTOS 中内核的其他通信机制一致。
ulNotifiedValue为任务通知的值,可以保存一个 32 位整数或指针值。
ucNotifyState为务通知状态,用于标识任务是否在等待通知。
发送任务通知
xTaskGenericNotify()用于发送任务通知。
#if( configUSE_TASK_NOTIFICATIONS == 1 )
/*
xTaskToNotify,被通知的任务句柄,指定通知的任务
ulValue,发送的通知值
eAction,指明更新通知值的方式
pulPreviousNotificationValue,任务原本的通知值返回
*/
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue )
{
TCB_t * pxTCB;
BaseType_t xReturn = pdPASS;
uint8_t ucOriginalNotifyState;
configASSERT( xTaskToNotify );
pxTCB = ( TCB_t * ) xTaskToNotify;
taskENTER_CRITICAL();
{
if( pulPreviousNotificationValue != NULL )
{
*pulPreviousNotificationValue = pxTCB->ulNotifiedValue; /* 回传未被更新的任务通知值 */
}
ucOriginalNotifyState = pxTCB->ucNotifyState; /* 获取任务通知的状态,看看任务是否在等待通知,方便在发送通知后恢复任务 */
pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED; /* 不管状态是怎么样的,反正现在发送通知,任务就收到任务通知 */
switch( eAction ) /* 指定更新任务通知的方式 */
{
case eSetBits : /* 使用这种方法可以某些场景下代替事件组,但执行速度更快。 */
pxTCB->ulNotifiedValue |= ulValue;
break;
case eIncrement : /* 这种发送通知方式,参数 ulValue 未使用 */
( pxTCB->ulNotifiedValue )++;
break;
/* 将被通知任务的通知值设置为 ulValue。无论任务是否还有通知,都覆盖当前任务通知值。使用这种方法,可以在某些场景下代替 xQueueoverwrite()函数,但执行速度更快。 */
case eSetValueWithOverwrite :
pxTCB->ulNotifiedValue = ulValue;
break;
/* 如果被通知任务当前没有通知,则被通知任务的通知值设置为 ulValue;在某些场景下替代长度为 1 的 xQueuesend(),但速度更快。 */
case eSetValueWithoutOverwrite :
if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )
{
pxTCB->ulNotifiedValue = ulValue;
}
else
{
/* 如果被通知任务还没取走上一个通知,本次发送通知,任务又接收到了一个通知,则这次通知值丢弃,在这种情况下,函数调用失败并返回 pdFALSE。 */
xReturn = pdFAIL;
}
break;
case eNoAction:
/* 发送通知但不更新通知值,这意味着参数 ulValue 未使用。 */
break;
}
traceTASK_NOTIFY();
/* 如果被通知任务由于等待任务通知而挂起 */
if( ucOriginalNotifyState == taskWAITING_NOTIFICATION )
{
/* 唤醒任务,将任务从阻塞列表中移除,添加到就绪列表中 */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
// 刚刚唤醒的任务优先级比当前任务高
configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );
#if( configUSE_TICKLESS_IDLE != 0 )
{
prvResetNextTaskUnblockTime();
}
#endif
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return xReturn;
}
#endif /* configUSE_TASK_NOTIFICATIONS */
xTaskNotifyGive()用于向一个任务发送通知,并将对方的任务通知值加 1,不能在中断里面使用。该函数可以作为二值信号量和计数信号量的一种轻量型的实现,速度更快。
在这种情况下对象任务在等待任务通知的时候应该是使用函数 ulTaskNotifyTake() 而不是 xTaskNotifyWait()。
#define xTaskNotifyGive( xTaskToNotify ) xTaskGenericNotify( ( xTaskToNotify ), ( 0 ), eIncrement, NULL )
xTaskNotifyGive()实例
/*定义任务句柄 */
static TaskHandle_t xTask1 = NULL, xTask2 = NULL;
/* 主函数:创建两个任务,然后开始任务调度 */
void main( void )
{
xTaskCreate(prvTask1, "Task1", 200, NULL, tskIDLE_PRIORITY, &xTask1);
xTaskCreate(prvTask2, "Task2", 200, NULL, tskIDLE_PRIORITY, &xTask2);
vTaskStartScheduler();
}
static void prvTask1( void *pvParameters )
{
for ( ;; )
{
/* 向 prvTask2()发送一个任务通知,让其退出阻塞状态 */
xTaskNotifyGive( xTask2 );
/* 阻塞在 prvTask2()的任务通知上,如果没有收到通知,则一直等待*/
ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
}
}
static void prvTask2( void *pvParameters )
{
for ( ;; )
{
/* 阻塞在 prvTask1()的任务通知上,如果没有收到通知,则一直等待 */
ulTaskNotifyTake( pdTRUE, portMAX_DELAY );
/* 向 prvTask1()发送一个任务通知,让其退出阻塞状态 */
xTaskNotifyGive( xTask1 );
}
}
vTaskNotifyGiveFromISR()是 vTaskNotifyGive()的中断保护版本。在某些场景中可以替代信号量操作,因为这两个通知都是不带有通知值的。
*pxHigherPriorityTaskWoken 在使用之前必须先初始化为pdFALSE。当调用该函数发送一个任务通知时,目标任务接收到通知后将从阻塞态变为就绪态,并且如果其优先级比当前运行的任务的优先级高,
那么*pxHigherPriorityTaskWoken 会被设置为 pdTRUE,然后在中断退出前执行一次上下文切换,去执行刚刚被唤醒的中断优先级较高的任务。pxHigherPriorityTaskWoken是一个可选的参数可以设置为 NULL。
#if( configUSE_TASK_NOTIFICATIONS == 1 )
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken )
{
TCB_t * pxTCB;
uint8_t ucOriginalNotifyState;
UBaseType_t uxSavedInterruptStatus;
configASSERT( xTaskToNotify );
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
pxTCB = ( TCB_t * ) xTaskToNotify;
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR(); //进入中断
{
ucOriginalNotifyState = pxTCB->ucNotifyState; //保存任务通知的原始状态,看看任务是否在等待通知,方便在发送通知后恢复任务
pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED; /* 不管状态是怎么样的,反正现在发送通知,任务就收到任务通知 */
( pxTCB->ulNotifiedValue )++; /* 通知值自加,类似于信号量的释放 */
traceTASK_NOTIFY_GIVE_FROM_ISR();
if( ucOriginalNotifyState == taskWAITING_NOTIFICATION ) /* 如果任务在阻塞等待通知 */
{
configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) //如果任务调度器运行中
{
( void ) uxListRemove( &( pxTCB->xStateListItem ) ); /* 唤醒任务,将任务从阻塞列表中移除,添加到就绪列表中 */
prvAddTaskToReadyList( pxTCB );
}
else
{
/* 调度器处于挂起状态,中断依然正常发生,但是不能直接操作就绪列表,将任务加入到就绪挂起列表,任务调度恢复后会移动到就绪列表 */
vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) );
}
/* 如果刚刚唤醒的任务优先级比当前任务高,则设置上下文切换标识,等退出函数后手动切换上下文,或者在系统节拍中断服务程序中自动切换上下文 */
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
if( pxHigherPriorityTaskWoken != NULL ) /* 设置返回参数,表示需要任务切换,在退出中断前进行任务切换 */
{
*pxHigherPriorityTaskWoken = pdTRUE;
}
else
{
xYieldPending = pdTRUE; /* 设置自动切换标志 */
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
}
#endif /* configUSE_TASK_NOTIFICATIONS */
任务通过接收函数返回值是否大于零,判断是否获取到了通知,任务通知值初始化为 0,(如果与信号量做对比)则对应为信号量无效。
当中断调用 vTaskNotifyGiveFromISR()通知函数给任务的时候,任务的通知值增加,使其大于零,使其表示的通知值变为有效,任务获取有效的通知值将会被恢复。
vTaskNotifyGiveFromISR()实例
static TaskHandle_t xTaskToNotify = NULL;
/* 外设驱动的数据传输函数 */
void StartTransmission( uint8_t *pcData, size_t xDataLength )
{
/* 在这个时候,xTaskToNotify 应为 NULL,因为发送并没有进行。如果有必要,对外设的访问可以用互斥量来保护*/
configASSERT( xTaskToNotify == NULL );
/* 获取调用函数 StartTransmission()的任务的句柄 */
xTaskToNotify = xTaskGetCurrentTaskHandle();
/* 开始传输,当数据传输完成时产生一个中断 */
vStartTransmit( pcData, xDatalength );
}
/* 数据传输完成中断 */
void vTransmitEndISR( void )
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* 这个时候不应该为 NULL,因为数据传输已经开始 */
configASSERT( xTaskToNotify != NULL );
/* 通知任务传输已经完成 */
vTaskNotifyGiveFromISR( xTaskToNotify, &xHigherPriorityTaskWoken );
/* 传输已经完成,所以没有任务需要通知 */
xTaskToNotify = NULL;
/* 如果为 pdTRUE,则进行一次上下文切换 */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/* 任务:启动数据传输,然后进入阻塞态,直到数据传输完成 */
void vAFunctionCalledFromATask( uint8_t ucDataToTransmit, size_t xDataLength )
{
uint32_t ulNotificationValue;
const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 200 );
/* 调用上面的函数 StartTransmission()启动传输 */
StartTransmission( ucDataToTransmit, xDataLength );
/* 等待传输完成 */
ulNotificationValue = ulTaskNotifyTake( pdFALSE, xMaxBlockTime );
/* 当传输完成时,会产生一个中断在中断服务函数中调用 vTaskNotifyGiveFromISR()向启动数据传输的任务发送一个任务通知,并
将对象任务的任务通知值加 1,任务通知值在任务创建的时候是初始化为 0 的,当接收到任务后就变成 1 */
if ( ulNotificationValue == 1 )
{
/* 传输按预期完成 */
} else
{
/* 调用函数 ulTaskNotifyTake()超时 */
}
}
xTaskNotify()用于在任务中直接向另外一个任务发送一个事件,接收到该任务通知的任务有可能解锁,不能在中断里面使用。xTaskNotify()函数在发送任务通知的时候会指定一个通知值,并且用户可以指定通知值发送的方式。
如果你想使用任务通知来实现二值信号量和计数信号量,那么应该使用更加简单的函数 xTaskNotifyGive(),而不是使用 xTaskNotify()。
#define xTaskNotify( xTaskToNotify, ulValue, eAction ) xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL )
eAction取值
typedef enum
{
eNoAction = 0,
eSetBits,
eIncrement,
eSetValueWithOverwrite,
eSetValueWithoutOverwrite
}eNotifyAction;
xTaskNotify()函数实例
xTaskNotify( xTask1Handle, ( 1UL << 8UL ), eSetBits ); /* 设置任务 xTask1Handle 的任务通知值的位 8 为 1*/
xTaskNotify( xTask2Handle, 0, eNoAction ); /* 向任务 xTask2Handle 发送一个任务通知,有可能会解除该任务的阻塞状态,但是并不会更新该任务自身的任务通知值 */
xTaskNotify( xTask3Handle, 0x50, eSetValueWithOverwrite ); /* 向任务 xTask3Handle 发送一个任务通知并把该任务自身的任务通知值更新为 0x50 即使该任务的上一次的任务通知都没有读取的情况下即覆盖写 */
/* 向任务 xTask4Handle 发送一个任务通知,并把该任务自身的任务通知值更新为 0xfff,但是并不会覆盖该任务之前接收到的任务通知值 */
if(xTaskNotify(xTask4Handle,0xfff,eSetValueWithoutOverwrite) == pdPASS )
{
/* 任务 xTask4Handle 的任务通知值已经更新 */
} else
{
/* 任务 xTask4Handle 的任务通知值没有更新,即上一次的通知值还没有被取走*/
}
xTaskNotifyFromISR()是 xTaskNotify()的中断保护版本,真正起作用的函数是中断发送任务通知通用函数 xTaskGenericNotifyFromISR()。
xTaskNotifyFromISR()用于在中断中向指定的任务发送一个任务通知,该任务通知是带有通知值并且用户可以指定通知的发送方式,不返回上一个任务在的通知值。
#define xTaskNotifyFromISR( xTaskToNotify, ulValue, eAction, pxHigherPriorityTaskWoken ) xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), NULL, ( pxHigherPriorityTaskWoken ) )
xTaskNotifyFromISR()实例
中断:向一个任务发送任务通知,并根据不同的中断将目标任务的任务通知值的相应位置 1。
void vANInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
uint32_t ulStatusRegister;
/* 读取中断状态寄存器,判断到来的是哪个中断这里假设了 Rx、 Tx 和 buffer overrun 三个中断 */
ulStatusRegister = ulReadPeripheralInterruptStatus();
/* 清除中断标志位 */
vClearPeripheralInterruptStatus( ulStatusRegister );
/* xHigherPriorityTaskWoken 在使用之前必须初始化为 pdFALSE。如果调用函数 xTaskNotifyFromISR()解锁了解锁了接收该通知的任务,
而且该任务的优先级比当前运行的任务的优先级高,那么xHigherPriorityTaskWoken 就会自动的被设置为 pdTRUE*/
xHigherPriorityTaskWoken = pdFALSE;
/* 向任务 xHandlingTask 发送任务通知,并将其任务通知值与 ulStatusRegister 的值相或,这样可以不改变任务通知其它位的值*/
xTaskNotifyFromISR( xHandlingTask,ulStatusRegister,eSetBits,&xHigherPriorityTaskWoken );
/* 如果 xHigherPriorityTaskWoken 的值为 pdRTUE,则执行一次上下文切换*/
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/* 任务:等待任务通知,然后处理相关的事情 */
void vHandlingTask( void *pvParameters )
{
uint32_t ulInterruptStatus;
for ( ;; )
{
/* 等待任务通知,无限期阻塞(没有超时,所以没有必要检查函数返回值) */
xTaskNotifyWait( 0x00, /* 在进入的时候不清除通知值的任何位 */
ULONG_MAX, /* 在退出的时候复位通知值为 0 */
&ulNotifiedValue, /* 任务通知值传递到变量 ulNotifiedValue 中*/
portMAX_DELAY ); /* 无限期等待 */
/* 根据任务通知值里面的各个位的值处理事情 */
if ( ( ulInterruptStatus & 0x01 ) != 0x00 )
{
/* Rx 中断 */
prvProcessRxInterrupt();
}
if ( ( ulInterruptStatus & 0x02 ) != 0x00 )
{
/* Tx 中断 */
prvProcessTxInterrupt();
}
if ( ( ulInterruptStatus & 0x04 ) != 0x00 )
{
/* 缓冲区溢出中断 */
prvClearBufferOverrun();
}
}
}
xTaskGenericNotifyFromISR() 是一个在中断中发送任务通知的通用函数,xTaskNotifyFromISR()、xTaskNotifyAndQueryFromISR()等函数都是以其为基础,采用宏定义的方式实现。
xTaskGenericNotifyFromISR()如下
#if( configUSE_TASK_NOTIFICATIONS == 1 )
BaseType_t xTaskGenericNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue, BaseType_t *pxHigherPriorityTaskWoken )
{
TCB_t * pxTCB;
uint8_t ucOriginalNotifyState;
BaseType_t xReturn = pdPASS;
UBaseType_t uxSavedInterruptStatus;
configASSERT( xTaskToNotify );
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
pxTCB = ( TCB_t * ) xTaskToNotify;
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
if( pulPreviousNotificationValue != NULL )
{
*pulPreviousNotificationValue = pxTCB->ulNotifiedValue; //回传未被更新的任务通知值
}
ucOriginalNotifyState = pxTCB->ucNotifyState; //保存任务通知的原始状态,看看任务是否在等待通知,方便在发送通知后恢复任务
pxTCB->ucNotifyState = taskNOTIFICATION_RECEIVED; /* 不管状态是怎么样的,反正现在发送通知,任务就收到任务通知 */
switch( eAction )
{
case eSetBits : /*通知值按位或上 ulValue。使用这种方法可以某些场景下代替事件组,但执行速度更快。 */
pxTCB->ulNotifiedValue |= ulValue;
break;
case eIncrement : /* 被通知任务的通知值增加 1,这种发送通知方式,参数 ulValue 未使用,在某些场景下可以代替信号量,执行速度更快 */
( pxTCB->ulNotifiedValue )++;
break;
/* 将被通知任务的通知值设置为 ulValue。无论任务是否还有通知,都覆盖当前任务通知值。使用这种方法,可以在某些场景下代替 xQueueoverwrite()函数,但执行速度更快。 */
case eSetValueWithOverwrite :
pxTCB->ulNotifiedValue = ulValue;
break;
case eSetValueWithoutOverwrite : //采用不覆盖发送任务通知的方式
if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )/* 如果被通知任务当前没有通知,则被通知任务的通知值设置为 ulValue;在某些场景下替代长度为 1 的 xQueuesend(),但速度更快。 */
{
pxTCB->ulNotifiedValue = ulValue;
}
else /*如果被通知任务还没取走上一个通知,本次发送通知,任务又接收到了一个通知,则这次通知值丢弃,在这种情况下,函数调用失败并返回 pdFALSE。 */
{
xReturn = pdFAIL;
}
break;
case eNoAction :
break;
}
traceTASK_NOTIFY_FROM_ISR();
if( ucOriginalNotifyState == taskWAITING_NOTIFICATION ) /* 如果任务在阻塞等待通知 */
{
configASSERT( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) == NULL );
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) //如果任务调度器运行中,表示可用操作就绪级列表
{
( void ) uxListRemove( &( pxTCB->xStateListItem ) ); /* 唤醒任务,将任务从阻塞列表中移除,添加到就绪列表中 */
prvAddTaskToReadyList( pxTCB );
}
else
{
/* 调度器处于挂起状态,中断依然正常发生,但是不能直接操作就绪列表,将任务加入到就绪挂起列表,任务调度恢复后会移动到就绪列表 */
vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) );
}
/* 如果刚刚唤醒的任务优先级比当前任务高,则设置上下文切换标识,等退出函数后手动切换上下文,或者自动切换上下文 */
if( pxTCB->uxPriority > pxCurrentTCB->uxPriority )
{
if( pxHigherPriorityTaskWoken != NULL ) /* 设置返回参数,表示需要任务切换,在退出中断前进行任务切换 */
{
*pxHigherPriorityTaskWoken = pdTRUE;
}
else
{
xYieldPending = pdTRUE; /*设置自动切换标志,等高优先级任务释放 CPU 使用权 */
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
return xReturn;
}
#endif /* configUSE_TASK_NOTIFICATIONS */
xTaskNotifyAndQuery()与 xTaskNotify()很像,都是调用通用的任务通知发送函数 xTaskGenericNotify() 来实现通知的发送 ,不同的是多了一个附加的参数 pulPreviousNotifyValue 用于回传接收任务的上一个通知值,xTaskNotifyAndQuery()函数不能用在中断。
#define xTaskNotifyAndQuery( xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue ) xTaskGenericNotify( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotifyValue ) )
xTaskNotifyAndQuery()实例
uint32_t ulPreviousValue;
/* 设置对象任务 xTask1Handle 的任务通知值的位 8 为 1,在更新位 8 的值之前把任务通知值回传存储在变量 ulPreviousValue 中*/
xTaskNotifyAndQuery( xTask1Handle, ( 1UL << 8UL ), eSetBits,&ulPreviousValue );
/* 向对象任务 xTask2Handle 发送一个任务通知,有可能解除对象任务的阻塞状态,但是不更新对象任务的通知值,并将对象任务的通知值存储在变量 ulPreviousValue 中 */
xTaskNotifyAndQuery( xTask2Handle, 0, eNoAction, &ulPreviousValue );
/* 覆盖式设置对象任务的任务通知值为 0x50,且对象任务的任务通知值不用回传,则最后一个形参设置为 NULL */
xTaskNotifyAndQuery( xTask3Handle, 0x50, eSetValueWithOverwrite, NULL );
/* 设置对象任务的任务通知值为 0xfff,但是并不会覆盖对象任务通过 xTaskNotifyWait()和 ulTaskNotifyTake()这两个函数获取到的已经存在的任务通知值。对象任务的前一个任务通知值存储在变量 ulPreviousValue 中*/
if ( xTaskNotifyAndQuery( xTask4Handle,0xfff,eSetValueWithoutOverwrite,&ulPreviousValue ) == pdPASS )
{
/* 任务通知值已经更新 */
} else
{
/* 任务通知值没有更新 */
}
xTaskNotifyAndQueryFromISR()是 xTaskNotifyAndQuery ()的中断版本,用于向指定的任务发送一个任务通知, 并返回对象任务的上一个通知值。
xTaskToNotify,需要接收通知的任务句柄。
ulValue,用于更新接收任务通知的任务通知值。
eAction,任务通知值的状态。
pulPreviousNotificationValue,对象任务的上一个任务通知值。 如果为 NULL, 则不需要回传。
*pxHigherPriorityTaskWoken 在使用之前必须先初始化为pdFALSE。 当调用该函数发送一个任务通知时,目标任务接收到通知后将从阻塞态变为就绪态,并且如果其优
先级比当前运行的任务的优先级高 ,那么 *pxHigherPriorityTaskWoken 会被设置为 pdTRUE,然后在中断退出前执行一次上下文切换,去执行刚刚被唤醒的中断优先级较高的任务。
pxHigherPriorityTaskWoken是一个可选的参数可以设置为 NULL。
参数 eAction 为 eSetValueWithoutOverwrite 时,如果被通知任务还没取走上一个通知,又接收到了一个通知,则这次通知值未能更新并返回 pdFALSE,其他情况均返回pdPASS。
#define xTaskNotifyAndQueryFromISR( xTaskToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken ) xTaskGenericNotifyFromISR( ( xTaskToNotify ), ( ulValue ), ( eAction ), ( pulPreviousNotificationValue ), ( pxHigherPriorityTaskWoken ) )
xTaskNotifyAndQueryFromISR()实例
void vAnISR( void )
{
/* xHigherPriorityTaskWoken 在使用之前必须设置为 pdFALSE */
BaseType_t xHigherPriorityTaskWoken = pdFALSE.
uint32_t ulPreviousValue;
/* 设置目标任务 xTask1Handle 的任务通知值的位 8 为 1,在任务通知值的位 8 被更新之前把上一次的值存储在变量 ulPreviousValue 中*/
xTaskNotifyAndQueryFromISR( xTask1Handle,( 1UL << 8UL ),eSetBits,&ulPreviousValue,&xHigherPriorityTaskWoken );
/* 如果任务 xTask1Handle 阻塞在任务通知上,那么现在已经被解锁进入就绪态。如果其优先级比当前正在运行的任务的优先级高,则 xHigherPriorityTaskWoken会被设置为 pdRTUE,
然后在中断退出前执行一次上下文切换,在中断退出后则去执行这个被唤醒的高优先级的任务 */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
至此,发送任务通知内容就已经结束,多数函数都已经被官方封装好,我们直接用就行。
xTaskNotifyGive( xTaskToNotify ) 发送任务通知
vTaskNotifyGiveFromISR() vTaskNotifyGive()的中断保护版本
xTaskNotify( xTaskToNotify, ulValue, eAction ) 发送任务通知
xTaskNotifyFromISR() xTaskNotify()的中断保护版本
xTaskNotifyAndQuery( xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue )
xTaskNotifyAndQueryFromISR( xTaskToNotify, ulValue, eAction, pulPreviousNotificationValue, pxHigherPriorityTaskWoken )
获取任务通知
ulTaskNotifyTake()作为二值信号量和计数信号量的一种轻量级实现,速度更快。如果FreeRTOS 中使用函数 xSemaphoreTake() 来获取信号量,这个时候则可以试试使用函数ulTaskNotifyTake()来代替。
任务通知值为 0,对应信号量无效,如果任务设置了阻塞等待,任务被阻塞挂起。
当其他任务或中断发送了通知值使其不为 0 后,通知变为有效,等待通知的任务将获取到通知, 并且在退出时候根据用户传递的第一个参数 xClearCountOnExit 选择清零通知值或者执行减一操作。
xTaskNotifyTake()在退出的时候处理任务的通知值的时候有两种方法,一种是在函数退出时将通知值清零,这种方法适用于实现二值信号量;另外一种是在函数退出时将通知值减 1,这种方法适用于实现计数信号量。
当一个任务使用其自身的任务通知值作为二值信号量或者计数信号量时, 其他任务应该使用函数 xTaskNotifyGive()或者 xTaskNotify( ( xTaskToNotify ), ( 0 ), eIncrement )来向其发送信号量。 如果是在中断中,则应该使用他们的中断版本函数。
#if( configUSE_TASK_NOTIFICATIONS == 1 )
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait )
{
uint32_t ulReturn;
taskENTER_CRITICAL();
{
// 如果通知值为 0,阻塞任务。默认初始化通知值为 0,说明没有未读通知
if( pxCurrentTCB->ulNotifiedValue == 0UL )
{
pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION; /* 标记任务状态 : 等待消息通知 */
if( xTicksToWait > ( TickType_t ) 0 ) //用户指定超时时间了,那就进入等待状态
{
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE ); //根据用户指定超时时间将任务添加到延时列表
traceTASK_NOTIFY_TAKE_BLOCK();
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
// 到这里说明其它任务或中断向这个任务发送了通知,或者任务阻塞超时,现在继续处理
taskENTER_CRITICAL();
{
traceTASK_NOTIFY_TAKE(); // 获取任务通知值
ulReturn = pxCurrentTCB->ulNotifiedValue;
if( ulReturn != 0UL ) // 看看任务通知是否有效,有效则返回
{
if( xClearCountOnExit != pdFALSE ) //是否需要清除通知
{
pxCurrentTCB->ulNotifiedValue = 0UL;
}
else
{
pxCurrentTCB->ulNotifiedValue = ulReturn - ( uint32_t ) 1; // 不清除,就减一
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION; //恢复任务通知状态变量
}
taskEXIT_CRITICAL();
return ulReturn;
}
#endif /* configUSE_TASK_NOTIFICATIONS */
与获取二值信号量和获取计数信号量的函数相比, ulTaskNotifyTake()函数少了很多调用子函数开销、少了很多判断、少了事件列表处理、少了队列上锁与解锁处理等等,因此ulTaskNotifyTake()函数相对效率很高。
ulTaskNotifyTake()函数实例
/* 中断服务程序:向一个任务发送任务通知 */
void vANInterruptHandler( void )
{
BaseType_t xHigherPriorityTaskWoken;
prvClearInterruptSource(); /* 清除中断 */
/* xHigherPriorityTaskWoken 在使用之前必须设置为 pdFALSE。如果调用 vTaskNotifyGiveFromISR()会解除 vHandlingTask 任务的阻塞状态,
并且 vHandlingTask 任务的优先级高于当前处于运行状态的任务,则 xHigherPriorityTaskWoken 将会自动被设置为 pdTRUE */
xHigherPriorityTaskWoken = pdFALSE;
/* 发送任务通知,并解锁阻塞在该任务通知下的任务 */
vTaskNotifyGiveFromISR( xHandlingTask, &xHigherPriorityTaskWoken );
/* 如果被解锁的任务优先级比当前运行的任务的优先级高,则在中断退出前执行一次上下文切换,在中断退出后去执行刚刚被唤醒的优先级更高的任务*/
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/* 任务:阻塞在一个任务通知上 */
void vHandlingTask( void *pvParameters )
{
BaseType_t xEvent;
for ( ;; )
{
/* 一直阻塞(没有时间限制,所以没有必要检测函数的返回值)。这里 RTOS 的任务通知值被用作二值信号量,所以在函数退出时,任务通知值要被清 0 。要注意的是真正的应用程序不应该无限期的阻塞*/
ulTaskNotifyTake( pdTRUE, /* 在退出前清 0 任务通知值 */
portMAX_DELAY ); /* 无限阻塞 */
/* RTOS 任务通知被当作二值信号量使用,当处理完所有的事情后继续等待下一个任务通知*/
do
{
xEvent = xQueryPeripheral();
if ( xEvent != NO_MORE_EVENTS )
{
vProcessPeripheralEvent( xEvent );
}
} while ( xEvent != NO_MORE_EVENTS );
}
}
xTaskNotifyWait()函数用于实现全功能版的等待任务通知,根据用户指定的参数的不同,可以灵活的用于实现轻量级的消息队列队列、二值信号量、计数信号量和事件组功能,并带有超时等待。
ulBitsToClearOnEntry 表示在使用通知之前,将任务通知值的哪些位清 0 ,实现过程就是将任务的通知值与参数 ulBitsToClearOnEntry 的按位取反值按位与操作。
ulBitsToClearOnExit 表示在函数 xTaskNotifyWait()退出前, 决定任务接收到的通知值的哪些位会被清 0,实现过程就是将任务的通知值与参数 ulBitsToClearOnExit 的按位取反值按位与操作。
在清 0 前,接收到的任务通知值会先被保存到形参 *pulNotificationValue 中。
pulNotificationValue 用于保存接收到的任务通知值。 如果接收到的任务通知不需要使用,则设置为 NULL 即可。
#if( configUSE_TASK_NOTIFICATIONS == 1 )
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait )
{
BaseType_t xReturn;
taskENTER_CRITICAL();
{
if( pxCurrentTCB->ucNotifyState != taskNOTIFICATION_RECEIVED ) /* 只有任务当前没有收到任务通知,才会将任务阻塞 */
{
pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnEntry; /* 使用任务通知值之前,根据用户指定参数 ulBitsToClearOnEntryClear 将通知值的某些或全部位清零 */
pxCurrentTCB->ucNotifyState = taskWAITING_NOTIFICATION; /* 设置任务状态标识:等待通知 */
if( xTicksToWait > ( TickType_t ) 0 ) /* 挂起任务等待通知或者进入阻塞态 */
{
prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE ); /* 根据用户指定超时时间将任务添加到延时列表 */
traceTASK_NOTIFY_WAIT_BLOCK();
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
//程序能执行到这里说明其它任务或中断向这个任务发送了通知或者任务阻塞超时,现在继续处理
taskENTER_CRITICAL();
{
traceTASK_NOTIFY_WAIT();
if( pulNotificationValue != NULL )
{
*pulNotificationValue = pxCurrentTCB->ulNotifiedValue; /* 返回当前通知值,通过指针参数传递 */
}
if( pxCurrentTCB->ucNotifyState != taskNOTIFICATION_RECEIVED ) /* 判断是否是因为任务阻塞超时,因为如果有任务发送了通知的话,任务通知状态会被改变 */
{
xReturn = pdFALSE; /* 没有收到任务通知,是阻塞超时 */
}
else
{
pxCurrentTCB->ulNotifiedValue &= ~ulBitsToClearOnExit; /* 收到任务值,先将参数 ulBitsToClearOnExit 取反后与通知值位做按位与运算,在退出函数前,将通知值的某些或者全部位清零 */
xReturn = pdTRUE;
}
pxCurrentTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION; //重新设置任务通知状态
}
taskEXIT_CRITICAL();
return xReturn;
}
#endif /* configUSE_TASK_NOTIFICATIONS */
xTaskNotifyWait()实例
/* 这个任务展示使用任务通知值的位来传递不同的事件这在某些情况下可以代替事件标志组。 */
void vAnEventProcessingTask( void *pvParameters )
{
uint32_t ulNotifiedValue;
for ( ;; )
{
/* 等待任务通知,无限期阻塞(没有超时,所以没有必要检查函数返回值)。这个任务的任务通知值的位由标志事件发生的任务或者中断来设置*/
xTaskNotifyWait( 0x00, /* 在进入的时候不清除通知值的任何位 */
ULONG_MAX, /* 在退出的时候复位通知值为 0 */
&ulNotifiedValue, /* 任务通知值传递到变量 ulNotifiedValue 中*/
portMAX_DELAY ); /* 无限期等待 */
/* 根据任务通知值里面的各个位的值处理事情 */
if ( ( ulNotifiedValue & 0x01 ) != 0 ) /* 位 0 被置 1 */
{
prvProcessBit0Event();
}
if ( ( ulNotifiedValue & 0x02 ) != 0 ) /* 位 1 被置 1 */
{
prvProcessBit1Event();
}
if ( ( ulNotifiedValue & 0x04 ) != 0 ) /* 位 2 被置 1 */
{
prvProcessBit2Event();
}
/* ... 等等 */
}
}
至此,获取任务通知内容就已经结束,多数函数都已经被官方封装好,我们直接用就行。
ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait )
xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait )
任务通知实验
任务通知代替消息队列
在 FreeRTOS 中创建了三个任务, 其中两个任务是用于接收任务通知,另一个任务发送任务通知。三个任务独立运行。
发送消息任务是通过检测按键的按下情况来发送消息通知,另两个任务获取消息通知,在任务通知中没有可用的通知之前就一直等待消息,一旦获取到消息通知就把消息打印在串口调试助手里。
#include "FreeRTOS.h"
#include "task.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
#include "limits.h"
static TaskHandle_t AppTaskCreate_Handle = NULL; /*创建任务句柄 */
static TaskHandle_t Receive1_Task_Handle = NULL; /*Receive1_Task 任务句柄 */
static TaskHandle_t Receive2_Task_Handle = NULL; /*Receive2_Task 任务句柄 */
static TaskHandle_t Send_Task_Handle = NULL; /* Send_Task 任务句柄 */
#define USE_CHAR 0 /* 测试字符串的时候配置为 1 ,测试变量配置为 0 */
int main(void)
{
BaseType_t xReturn = pdPASS; /* 定义一个创建信息返回值,默认为 pdPASS */
BSP_Init();
printf("按下 KEY1 或者 KEY2 向任务发送消息通知\n");
xReturn = xTaskCreate( (TaskFunction_t )AppTaskCreate, /*任务入口函数 */
(const char* )"AppTaskCreate", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )1, /* 任务的优先级 */
(TaskHandle_t* )&AppTaskCreate_Handle); /* 任务控制块指针 */
if (pdPASS == xReturn)
vTaskStartScheduler(); /* 启动任务,开启调度 */
else
return -1;
while (1); /* 正常不会执行到这里 */
}
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
taskENTER_CRITICAL(); //进入临界区
xReturn = xTaskCreate( (TaskFunction_t )Receive1_Task, /*任务入口函数 */
(const char* )"Receive1_Task", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t*)&Receive1_Task_Handle); /*任务控制块指针 */
if (pdPASS == xReturn)
printf("创建 Receive1_Task 任务成功!\r\n");
xReturn = xTaskCreate( (TaskFunction_t )Receive2_Task, /* 任务入口函数*/
(const char* )"Receive2_Task", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )3, /* 任务的优先级 */
(TaskHandle_t*)&Receive2_Task_Handle); /*任务控制块指针 */
if (pdPASS == xReturn)
printf("创建 Receive2_Task 任务成功!\r\n");
xReturn = xTaskCreate( (TaskFunction_t )Send_Task, /* 任务入口函数 */
(const char* )"Send_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )4, /* 任务的优先级 */
(TaskHandle_t* )&Send_Task_Handle);/* 任务控制块指针 */
if (pdPASS == xReturn)
printf("创建 Send_Task 任务成功!\n\n");
vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务
taskEXIT_CRITICAL(); //退出临界区
}
static void Receive1_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为 pdPASS */
#if USE_CHAR
char *r_char;
#else
uint32_t r_num;
#endif
while (1)
{
//获取任务通知 ,没获取到则一直等待
xReturn=xTaskNotifyWait( 0x0, //进入函数的时候不清除任务 bit
ULONG_MAX, //退出函数的时候清除所有的 bit
#if USE_CHAR
(uint32_t *)&r_char,//保存任务通知值
#else
&r_num, //保存任务通知值
#endif
portMAX_DELAY); //阻塞时间
if ( pdTRUE == xReturn )
#if USE_CHAR
printf("Receive1_Task 任务通知为 %s \n",r_char);
#else
printf("Receive1_Task 任务通知为 %d \n",r_num);
#endif
LED1_TOGGLE;
}
}
static void Receive2_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE; /* 定义一个创建信息返回值,默认为 pdPASS */
#if USE_CHAR
char *r_char;
#else
uint32_t r_num;
#endif
while (1)
{
//获取任务通知 ,没获取到则一直等待
xReturn=xTaskNotifyWait( 0x0, //进入函数的时候不清除任务 bit
ULONG_MAX, //退出函数的时候清除所有的 bit
#if USE_CHAR
(uint32_t *)&r_char, //保存任务通知值
#else
&r_num, //保存任务通知值
#endif
portMAX_DELAY); //阻塞时间
if ( pdTRUE == xReturn )
#if USE_CHAR
printf("Receive2_Task 任务通知为 %s \n",r_char);
#else
printf("Receive2_Task 任务通知为 %d \n",r_num);
#endif
LED2_TOGGLE;
}
}
static void Send_Task(void* parameter)
{
BaseType_t xReturn = pdPASS; /* 定义一个创建信息返回值,默认为 pdPASS */
#if USE_CHAR
char test_str1[] = "this is a mail test 1"; /* 消息 test1 */
char test_str2[] = "this is a mail test 2"; /* 消息 test2 */
#else
uint32_t send1 = 1;
uint32_t send2 = 2;
#endif
while (1)
{
if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) /* KEY1 被按下 */
{
xReturn = xTaskNotify( Receive1_Task_Handle, /*任务句柄*/
#if USE_CHAR
(uint32_t)&test_str1, /*发送的数据,最大为 4 字节 */
#else
send1, /* 发送的数据,最大为 4 字节 */
#endif
eSetValueWithOverwrite ); /*覆盖当前通知*/
if ( xReturn == pdPASS )
printf("Receive1_Task_Handle 任务通知释放成功!\r\n");
}
if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) /* KEY2 被按下 */
{
xReturn = xTaskNotify( Receive2_Task_Handle, /*任务句柄*/
#if USE_CHAR
(uint32_t)&test_str2,/* 发送的数据,最大为 4 字节 */
#else
send2, /* 发送的数据,最大为 4 字节 */
#endif
eSetValueWithOverwrite );/*覆盖当前通知*/
/* 此函数只会返回 pdPASS */
if ( xReturn == pdPASS )
printf("Receive2_Task_Handle 任务通知释放成功!\r\n");
}
vTaskDelay(20);
}
}
任务通知代替二值信号量
在 FreeRTOS 中创建了三个任务,其中两个任务是用于接收任务通知,另一个任务发送任务通知。三个任务独立运行。
发送通知任务是通过检测按键的按下情况来发送通知,另两个任务获取通知,在任务通知中没有可用的通知之前就一直等待任务通知,获取到通知以后就将通知值清 0,这样子是为了代替二值信号量,任务同步成功则继续执行,然后在串口调试助手里将运行信息打印出来。
#include "FreeRTOS.h"
#include "task.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
static TaskHandle_t AppTaskCreate_Handle = NULL; /*创建任务句柄 */
static TaskHandle_t Receive1_Task_Handle = NULL; /*Receive1_Task 任务句柄 */
static TaskHandle_t Receive2_Task_Handle = NULL; /*Receive2_Task 任务句柄 */
static TaskHandle_t Send_Task_Handle = NULL; /* Send_Task 任务句柄 */
int main(void)
{
BaseType_t xReturn = pdPASS; /* 定义一个创建信息返回值,默认为 pdPASS */
BSP_Init();
printf("按下 KEY1 或者 KEY2 进行任务与任务间的同步\n");
xReturn = xTaskCreate( (TaskFunction_t )AppTaskCreate, /*任务入口函数 */
(const char* )"AppTaskCreate", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )1, /* 任务的优先级 */
(TaskHandle_t* )&AppTaskCreate_Handle); /* 任务控制块指针 */
if (pdPASS == xReturn)
vTaskStartScheduler(); /* 启动任务,开启调度 */
else
return -1;
while (1); /* 正常不会执行到这里 */
}
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
taskENTER_CRITICAL(); //进入临界区
xReturn = xTaskCreate( (TaskFunction_t )Receive1_Task, /*任务入口函数 */
(const char* )"Receive1_Task", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t*)&Receive1_Task_Handle); /*任务控制块指针 */
if (pdPASS == xReturn)
printf("创建 Receive1_Task 任务成功!\r\n");
xReturn = xTaskCreate( (TaskFunction_t )Receive2_Task, /* 任务入口函数*/
(const char* )"Receive2_Task", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )3, /* 任务的优先级 */
(TaskHandle_t*)&Receive2_Task_Handle); /*任务控制块指针 */
if (pdPASS == xReturn)
printf("创建 Receive2_Task 任务成功!\r\n");
xReturn = xTaskCreate( (TaskFunction_t )Send_Task, /* 任务入口函数 */
(const char* )"Send_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )4, /* 任务的优先级 */
(TaskHandle_t* )&Send_Task_Handle);/* 任务控制块指针 */
if (pdPASS == xReturn)
printf("创建 Send_Task 任务成功!\n\n");
vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务
taskEXIT_CRITICAL(); //退出临界区
}
static void Receive1_Task(void* parameter)
{
while (1)
{
/* uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit,TickType_t TicksToWait );
* xClearCountOnExit: pdTRUE 在退出函数的时候任务任务通知值清零,类似二值信号量 pdFALSE 在退出函数 ulTaskNotifyTakeO 的时候任务通知值减一,类似计数型信号量。
*/
//获取任务通知 ,没获取到则一直等待
ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
printf("Receive1_Task 任务通知获取成功!\n\n");
LED1_TOGGLE;
}
}
static void Receive2_Task(void* parameter)
{
while (1)
{
/* uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit,TickType_t TicksToWait );
* xClearCountOnExit: pdTRUE 在退出函数的时候任务任务通知值清零,类似二值信号量 pdFALSE 在退出函数 ulTaskNotifyTakeO 的时候任务通知值减一,类似计数型信号量。
*/
//获取任务通知 ,没获取到则一直等待
ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
printf("Receive2_Task 任务通知获取成功!\n\n");
LED2_TOGGLE;
}
}
static void Send_Task(void* parameter)
{
BaseType_t xReturn = pdPASS; /* 定义一个创建信息返回值,默认为 pdPASS */
while (1)
{
if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) /* KEY1 被按下 */
{
/* 原型:BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); */
xReturn = xTaskNotifyGive(Receive1_Task_Handle);
/* 此函数只会返回 pdPASS */
if ( xReturn == pdPASS )
printf("Receive1_Task_Handle 任务通知释放成功!\r\n");
}
if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) /* KEY2 被按下 */
{
/* 原型:BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); */
xReturn = xTaskNotifyGive(Receive2_Task_Handle);
/* 此函数只会返回 pdPASS */
if ( xReturn == pdPASS )
printf("Receive2_Task_Handle 任务通知释放成功!\r\n");
}
vTaskDelay(20);
}
}
任务通知代替计数信号量
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 */
static TaskHandle_t Take_Task_Handle = NULL; /* Take_Task 任务句柄 */
static TaskHandle_t Give_Task_Handle = NULL; /* Give_Task 任务句柄 */
SemaphoreHandle_t CountSem_Handle =NULL;
int main(void)
{
BaseType_t xReturn = pdPASS; /* 定义一个创建信息返回值,默认为 pdPASS */
BSP_Init();
printf("车位默认值为 0 个,按下 KEY1 申请车位,按下 KEY2 释放车位! \n\n");
xReturn = xTaskCreate( (TaskFunction_t )AppTaskCreate, /*任务入口函数 */
(const char* )"AppTaskCreate", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )1, /* 任务的优先级 */
(TaskHandle_t* )&AppTaskCreate_Handle); /* 任务控制块指针 */
if (pdPASS == xReturn)
vTaskStartScheduler(); /* 启动任务,开启调度 */
else
return -1;
while (1); /* 正常不会执行到这里 */
}
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
taskENTER_CRITICAL(); //进入临界区
xReturn = xTaskCreate( (TaskFunction_t )Take_Task, /*任务入口函数 */
(const char* )"Take_Task", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t*)&Take_Task_Handle); /*任务控制块指针 */
if (pdPASS == xReturn)
printf("创建 Take_Task 任务成功!\r\n");
xReturn = xTaskCreate( (TaskFunction_t )Give_Task, /* 任务入口函数*/
(const char* )"Give_Task", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )3, /* 任务的优先级 */
(TaskHandle_t*)&Give_Task_Handle); /*任务控制块指针 */
if (pdPASS == xReturn)
printf("创建 Give_Task 任务成功!\r\n");
xReturn = xTaskCreate( (TaskFunction_t )Send_Task, /* 任务入口函数 */
(const char* )"Send_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )4, /* 任务的优先级 */
(TaskHandle_t* )&Send_Task_Handle);/* 任务控制块指针 */
if (pdPASS == xReturn)
printf("创建 Send_Task 任务成功!\n\n");
vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务
taskEXIT_CRITICAL(); //退出临界区
}
static void Take_Task(void* parameter)
{
uint32_t take_num = pdTRUE; /* 定义一个创建信息返回值,默认为 pdPASS */
while (1)
{
if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) //如果 KEY1 被按下
{
/*uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit,TickType_t xTicksToWait );
* xClearCountOnExit: pdTRUE 在退出函数的时候任务任务通知值清零,类似二值信号量 pdFALSE 在退出函数 ulTaskNotifyTakeO 的时候任务通知值减一,类似计数型信号量。
*/
//获取任务通知 ,没获取到则不等待
take_num=ulTaskNotifyTake(pdFALSE,0);
if (take_num > 0)
printf( "KEY1 被按下, 成功申请到停车位。当前车位为 %d \n", take_num - 1);
else
printf( "KEY1 被按下, 车位已经没有了。按 KEY2 释放车位\n" );
}
vTaskDelay(20); //每 20ms 扫描一次
}
}
static void Give_Task(void* parameter)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
while (1)
{
if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) //如果 KEY2 被按下
{
/* 释放一个任务通知 */
xTaskNotifyGive(Take_Task_Handle);//发送任务通知
/* 此函数只会返回 pdPASS */
if ( pdPASS == xReturn )
printf( "KEY2 被按下, 释放 1 个停车位。 \n" );
}
vTaskDelay(20); //每 20ms 扫描一次
}
}
任务通知代替事件组
在 FreeRTOS 中创建了两个任务,一个是发送事件通知任务,一个是等待事件通知任务,两个任务独立运行。
发送事件通知任务通过检测按键的按下情况设置不同的通知值位,等待事件通知任务则获取这任务通知值,并且根据通知值判断两个事件是否都发生,如果是则输出相应信息,LED 进行翻转。
等待事件通知任务的等待时间是 portMAX_DELAY,一直在等待事件通知的发生,等待获取到事件之后清除对应的任务通知值的位。
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
#include "limits.h"
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 */
static TaskHandle_t LED_Task_Handle = NULL; /* LED_Task 任务句柄 */
static TaskHandle_t KEY_Task_Handle = NULL; /* KEY_Task 任务句柄 */
static EventGroupHandle_t Event_Handle = NULL;
#define KEY1_EVENT (0x01 << 0) //设置事件掩码的位 0
#define KEY2_EVENT (0x01 << 1) //设置事件掩码的位 1
int main(void)
{
BaseType_t xReturn = pdPASS; /* 定义一个创建信息返回值,默认为 pdPASS */
BSP_Init();
printf("按下 KEY1|KEY2 发送任务事件通知!\n");
xReturn = xTaskCreate( (TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
(const char* )"AppTaskCreate", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )1, /* 任务的优先级 */
(TaskHandle_t*)&AppTaskCreate_Handle); /* 任务控制块指针 */
if (pdPASS == xReturn)
vTaskStartScheduler(); /* 启动任务,开启调度 */
else
return -1;
while (1); /* 正常不会执行到这里 */
}
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建 Event_Handle */
Event_Handle = xEventGroupCreate();
if (NULL != Event_Handle)
printf("Event_Handle 事件创建成功!\r\n");
xReturn = xTaskCreate( (TaskFunction_t )LED_Task, /* 任务入口函数 */
(const char* )"LED_Task", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t* )&LED_Task_Handle); /* 任务控制块指针 */
if (pdPASS == xReturn)
printf("创建 LED_Task 任务成功!\r\n");
xReturn = xTaskCreate( (TaskFunction_t )KEY_Task, /* 任务入口函数 */
(const char* )"KEY_Task", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )3, /* 任务的优先级 */
(TaskHandle_t* )&KEY_Task_Handle); /* 任务控制块指针 */
if (pdPASS == xReturn)
printf("创建 KEY_Task 任务成功!\n");
vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务
taskEXIT_CRITICAL(); //退出临界区
}
static void LED_Task(void* parameter)
{
uint32_t r_event = 0; /* 定义一个事件接收变量 */
uint32_t last_event = 0; /* 定义一个保存事件的变量 */
BaseType_t xReturn = pdTRUE; /* 定义一个创建信息返回值,默认为 pdPASS */
while (1)
{
/* BaseType_t xTaskNotifyWait(uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait );
* ulBitsToClearOnEntry:当没有接收到任务通知的时候将任务通知值与此参数的取反值进行按位与运算,当此参数为 Oxfffff 或者 ULONG_MAX 的时候就会将任务通知值清零。
* ulBits ToClearOnExit:如果接收到了任务通知,在做完相应的处理退出函数之前将任务通知值与此参数的取反值进行按位与运算,当此参数为 0xfffff 或者 ULONG MAX 的时候就会将任务通知值清零。
* pulNotification Value:此参数用来保存任务通知值。
* xTick ToWait:阻塞时间。
*
* 返回值:pdTRUE:获取到了任务通知。pdFALSE:任务通知获取失败。
*/
//获取任务通知 ,没获取到则一直等待
xReturn = xTaskNotifyWait( 0x0, //进入函数的时候不清除任务 bit
ULONG_MAX, //退出函数的时候清除所有的 bitR
&r_event, //保存任务通知值
portMAX_DELAY); //阻塞时间
if ( pdTRUE == xReturn )
{
last_event |= r_event;
/* 如果接收完成并且正确 */
if (last_event == (KEY1_EVENT|KEY2_EVENT))
{
last_event = 0; /* 上一次的事件清零 */
printf ( "Key1 与 Key2 都按下\n");
LED1_TOGGLE; //LED1 反转
} else /* 否则就更新事件 */
last_event = r_event; /* 更新上一次触发的事件 */
}
}
}
static void KEY_Task(void* parameter)
{
while (1)
{
if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
{
printf ( "KEY1 被按下\n" );
/* 原型:BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction );
* eNoAction = 0,通知任务而不更新其通知值。
* eSetBits, 设置任务通知值中的位。
* eIncrement, 增加任务的通知值。
* eSetvaluewithoverwrite,覆盖当前通知
* eSetValueWithoutoverwrite 不覆盖当前通知
*
* pdFAIL:当参数 eAction 设置为 eSetValueWithoutOverwrite 的时候,如果任务通知值没有更新成功就返回 pdFAIL。
* pdPASS: eAction 设置为其他选项的时候统一返回 pdPASS。
*/
/* 触发一个事件 1 */
xTaskNotify((TaskHandle_t)LED_Task_Handle,//接收任务通知的任务句柄
(uint32_t)KEY1_EVENT, //要触发的事件
(eNotifyAction)eSetBits); //设置任务通知值中的位
}
if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
{
printf ( "KEY2 被按下\n" );
/* 触发一个事件 2 */
xTaskNotify((TaskHandle_t )LED_Task_Handle, //接收任务通知的任务句柄
(uint32_t )KEY2_EVENT, //要触发的事件
(eNotifyAction)eSetBits); //设置任务通知值中的位
}
vTaskDelay(20); //每 20ms 扫描一次
}
}