网站建设方案新闻,网站seo搜索引擎优化教程,wordpress 收费 主题,wordpress 打印参考教程#xff1a;【正点原子】手把手教你学FreeRTOS实时系统_哔哩哔哩_bilibili
一、任务通知简介
1、概述
#xff08;1#xff09;任务通知顾名思义是用来通知任务的#xff0c;任务控制块中的结构体成员变量ulNotifiedValue就是这个通知值。
#xff08;2#…参考教程【正点原子】手把手教你学FreeRTOS实时系统_哔哩哔哩_bilibili
一、任务通知简介
1、概述
1任务通知顾名思义是用来通知任务的任务控制块中的结构体成员变量ulNotifiedValue就是这个通知值。
2队列、信号量、事件标志组与任务通知的区别
①使用队列、信号量、事件标志组时都需另外创建一个结构体通过中间的结构体进行任务间的通信。 ②使用任务通知时任务结构体TCB中就包含了内部对象可以直接接收其它任务发过来的“通知”。 3只要合理、灵活地利用任务通知的特点可以在一些场合中替代队列、信号量、事件标志组。
4任务通知的优势及劣势
①优势
[1]使用任务通知向任务发送事件或数据比使用队列、事件标志组或信号量快得多。
[2]使用其它方法进行任务间通讯时都要先创建对应的结构体而使用任务通知则无需额外创建结构体。
②劣势
[1]ISR没有任务结构体所以无法给ISR发送数据但是ISR可以使用任务通知的功能发数据给任务。
[2]任务通知只能是被指定的一个任务接收并处理无法广播给多个任务。
[3]任务通知是通过更新任务通知值来发送数据的任务结构体中只有一个任务通知值只能保持一个数据无法缓存多个数据。
[4]发送方无法进入阻塞状态等待。
2、任务通知值和通知状态
1任务都有一个结构体——任务控制块TCB它里边有两个结构体成员变量与任务通知相关一个是uint32_t类型用来表示通知值另一个是uint8_t类型用来表示通知状态。
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;
#define configTASK_NOTIFICATION_ARRAY_ENTRIES 1 //定义任务通知数组的大小默认为12任务通知值的更新方式有多种类型
①计数值数值累加类似信号量。
②相应位置1类似事件标志组。
③写任意数值支持覆写和不覆写类似队列。
3任务通知状态共有3种取值
①任务未等待通知任务通知默认的初始化状态。
②任务在等待通知接收方已经准备好了调用了接收任务通知函数等待发送方给通知。
③任务在等待接收发送方已经将通知发送出去调用了发送任务通知函数等待接收方接收。
#define taskNOT_WAITING_NOTIFICATION ( ( uint8_t ) 0 ) //任务未等待通知
#define taskWAITING_NOTIFICATION ( ( uint8_t ) 1 ) //任务在等待通知
#define taskNOTIFICATION_RECEIVED ( ( uint8_t ) 2 ) //任务在等待接收
二、任务通知相关API函数介绍
1、任务通知相关API函数概览
1任务通知API函数主要有两类——发送通知、接收通知发送通知API函数可以用于任务和中断服务函数中而接收通知API函数只能用在任务中。
2发送通知相关API函数 函数 描述 xTaskNotify() 发送通知带有通知值 xTaskNotifyAndQuery() 发送通知带有通知值并且保留接收任务的原通知值 xTaskNotifyGive() 发送通知不带通知值 xTaskNotifyFromISR() 在中断中发送任务通知 xTaskNotifyAndQueryFromISR() vTaskNotifyGiveFromISR()
3接收通知相关API函数 函数 描述 ulTaskNotifyTake() 获取任务通知可以设置在退出此函数的时候将任务通知值清零或者减一 当任务通知用作二值信号量或者计数信号量的时候使用此函数来获取信号量 xTaskNotifyWait() 获取任务通知比 ulTaskNotifyTak()更为复杂可获取通知值和清除通知值的指定位 当任务通知用作于事件标志组或队列时使用此函数来获取
2、发送通知相关API函数
1xTaskNotifyAndQuery函数、xTaskNotify函数和xTaskNotifyGive函数它们的底层函数实际上都是同一个函数。
#define xTaskNotifyAndQuery(xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue) \ xTaskGenericNotify( ( xTaskToNotify ), ( tskDEFAULT_INDEX_TO_NOTIFY ), ( ulValue ), ( eAction ),( pulPreviousNotifyValue ) )#define xTaskNotify(xTaskToNotify, ulValue, eAction) \ xTaskGenericNotify( ( xTaskToNotify ),( tskDEFAULT_INDEX_TO_NOTIFY ), ( ulValue ), ( eAction ), NULL )#define xTaskNotifyGive(xTaskToNotify) \ xTaskGenericNotify( ( xTaskToNotify ),( tskDEFAULT_INDEX_TO_NOTIFY ),( 0 ),eIncrement,NULL )2xTaskGenericNotify函数
①函数入口定义
BaseType_t xTaskGenericNotify
(TaskHandle_t xTaskToNotify, //接收任务通知的任务句柄UBaseType_t uxIndexToNotify, //任务的指定通知任务通知相关数组成员uint32_t ulValue, //任务通知值eNotifyAction eAction, //通知方式通知值更新方式uint32_t * pulPreviousNotificationValue //用于保存更新前的任务通知值为NULL则不保存
)②任务通知方式共有以下几种
typedef enum
{ eNoAction 0, //无操作eSetBits //更新指定biteIncrement //通知值加1eSetValueWithOverwrite //覆写的方式更新通知值eSetValueWithoutOverwrite //不覆写通知值
}eNotifyAction;③函数源码剖析
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;traceENTER_xTaskGenericNotify( xTaskToNotify, uxIndexToNotify, ulValue, eAction, pulPreviousNotificationValue );configASSERT( uxIndexToNotify configTASK_NOTIFICATION_ARRAY_ENTRIES );configASSERT( xTaskToNotify );pxTCB xTaskToNotify;taskENTER_CRITICAL();{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{xReturn pdFAIL; //如果有通知值还未被接收不能覆写}break;case eNoAction:break;default:configASSERT( xTickCount ( TickType_t ) 0 );break;}traceTASK_NOTIFY( uxIndexToNotify );if( ucOriginalNotifyState taskWAITING_NOTIFICATION ) //如果接收方已准备好接收通知值{listREMOVE_ITEM( ( pxTCB-xStateListItem ) ); //将接收方任务从阻塞列表中移出prvAddTaskToReadyList( pxTCB ); //将接收方任务添加进就绪列表中configASSERT( listLIST_ITEM_CONTAINER( ( pxTCB-xEventListItem ) ) NULL );#if ( configUSE_TICKLESS_IDLE ! 0 ){prvResetNextTaskUnblockTime();}#endiftaskYIELD_ANY_CORE_IF_USING_PREEMPTION( pxTCB ); //如因优先级问题需要任务切换执行即可}else{mtCOVERAGE_TEST_MARKER();}}taskEXIT_CRITICAL();traceRETURN_xTaskGenericNotify( xReturn );return xReturn;
}
3、接收通知相关API函数
1ulTaskNotifyTake函数
①函数定义
#define ulTaskNotifyTake(xClearCountOnExit, xTicksToWait) \ulTaskGenericNotifyTake( ( tskDEFAULT_INDEX_TO_NOTIFY ), \( xClearCountOnExit ), \( xTicksToWait ) ) ②函数参数 形参 描述 xClearCountOnExit 指定在成功接收通知后将通知值清零或减 1 pdTRUE把通知值清零pdFALSE把通知值减1 xTicksToWait 阻塞等待任务通知值的最大时间
③返回值 返回值 描述 0 接收失败 非 0 接收成功返回任务通知的通知值
④ulTaskGenericNotifyTake函数源码剖析
uint32_t ulTaskGenericNotifyTake( UBaseType_t uxIndexToWaitOn,BaseType_t xClearCountOnExit,TickType_t xTicksToWait )
{uint32_t ulReturn;BaseType_t xAlreadyYielded, xShouldBlock pdFALSE;traceENTER_ulTaskGenericNotifyTake(uxIndexToWaitOn,xClearCountOnExit,xTicksToWait);configASSERT( uxIndexToWaitOn configTASK_NOTIFICATION_ARRAY_ENTRIES );vTaskSuspendAll();{taskENTER_CRITICAL();{if( pxCurrentTCB-ulNotifiedValue[ uxIndexToWaitOn ] 0U ) //判断任务通知值是否为0是则说明发送方还未发送非0通知值{pxCurrentTCB-ucNotifyState[ uxIndexToWaitOn ] taskWAITING_NOTIFICATION; //将状态更改为在等待通知if( xTicksToWait ( TickType_t ) 0 ) //如果阻塞时间大于0xShouldBlock pdTRUE; //任务需要阻塞elsemtCOVERAGE_TEST_MARKER();}else{mtCOVERAGE_TEST_MARKER();}}taskEXIT_CRITICAL();if( xShouldBlock pdTRUE ) //如果任务需要阻塞{traceTASK_NOTIFY_TAKE_BLOCK( uxIndexToWaitOn );prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE ); //将任务添加进阻塞列表中}else{mtCOVERAGE_TEST_MARKER();}}xAlreadyYielded xTaskResumeAll();if( ( xShouldBlock pdTRUE ) ( xAlreadyYielded pdFALSE ) ){taskYIELD_WITHIN_API(); //如果任务进入阻塞态则需要执行任务切换if判断的作用是防止重复切换}else{mtCOVERAGE_TEST_MARKER();}taskENTER_CRITICAL();{traceTASK_NOTIFY_TAKE( uxIndexToWaitOn );ulReturn pxCurrentTCB-ulNotifiedValue[ uxIndexToWaitOn ];if( ulReturn ! 0U ) //如果任务通知值不为0说明发送方已将非0通知值发送{if( xClearCountOnExit ! pdFALSE ) //根据函数参数判断通知值如何处理{pxCurrentTCB-ulNotifiedValue[ uxIndexToWaitOn ] ( uint32_t ) 0U; //通知值清零用于模拟二值信号量}else{pxCurrentTCB-ulNotifiedValue[uxIndexToWaitOn] ulReturn - (uint32_t)1; //通知值-1用于模拟计数型信号量}}else{mtCOVERAGE_TEST_MARKER();}pxCurrentTCB-ucNotifyState[ uxIndexToWaitOn ] taskNOT_WAITING_NOTIFICATION; //状态更改为未等待通知}taskEXIT_CRITICAL();traceRETURN_ulTaskGenericNotifyTake( ulReturn );return ulReturn;
}
2xTaskNotifyWait函数
①函数定义
#define xTaskNotifyWait(ulBitsToClearOnEntry, \ulBitsToClearOnExit, \pulNotificationValue, \xTicksToWait) \xTaskGenericNotifyWait(tskDEFAULT_INDEX_TO_NOTIFY, \( ulBitsToClearOnEntry ), \( ulBitsToClearOnExit ), \( pulNotificationValue ), \( xTicksToWait ))
②函数参数 形参 描述 uxIndexToWaitOn 任务的指定通知任务通知相关数组成员 ulBitesToClearOnEntry 等待前清零指定任务通知值的比特位旧值对应bit清0 ulBitesToClearOnExit 成功等待后清零指定的任务通知值比特位新值对应bit清0 pulNotificationValue 用来取出通知值如果不需要取出可设为NULL xTicksToWait 阻塞等待任务通知值的最大时间
③返回值 返回值 描述 pdTRUE 等待任务通知成功 pdFALSE 等待任务通知失败
④xTaskGenericNotifyWait函数源码剖析
BaseType_t xTaskGenericNotifyWait( UBaseType_t uxIndexToWaitOn,uint32_t ulBitsToClearOnEntry,uint32_t ulBitsToClearOnExit,uint32_t * pulNotificationValue,TickType_t xTicksToWait )
{BaseType_t xReturn, xAlreadyYielded, xShouldBlock pdFALSE;traceENTER_xTaskGenericNotifyWait( uxIndexToWaitOn, ulBitsToClearOnEntry, ulBitsToClearOnExit, pulNotificationValue, xTicksToWait );configASSERT( uxIndexToWaitOn configTASK_NOTIFICATION_ARRAY_ENTRIES );vTaskSuspendAll();{taskENTER_CRITICAL();{if( pxCurrentTCB-ucNotifyState[ uxIndexToWaitOn ] ! taskNOTIFICATION_RECEIVED ) //判断任务是否不在等待接收{pxCurrentTCB-ulNotifiedValue[ uxIndexToWaitOn ] ~ulBitsToClearOnEntry; //将原先通知值的指定位清零pxCurrentTCB-ucNotifyState[ uxIndexToWaitOn ] taskWAITING_NOTIFICATION; //将任务状态更改为在等待通知if( xTicksToWait ( TickType_t ) 0 ) //如果阻塞时间大于0xShouldBlock pdTRUE; //任务需要阻塞elsemtCOVERAGE_TEST_MARKER();}else{mtCOVERAGE_TEST_MARKER();}}taskEXIT_CRITICAL();if( xShouldBlock pdTRUE ) //如果任务需要阻塞{traceTASK_NOTIFY_WAIT_BLOCK( uxIndexToWaitOn );prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE ); //将任务添加进阻塞列表中}else{mtCOVERAGE_TEST_MARKER();}}xAlreadyYielded xTaskResumeAll();if( ( xShouldBlock pdTRUE ) ( xAlreadyYielded pdFALSE ) ){taskYIELD_WITHIN_API(); //如果任务进入阻塞态则需要执行任务切换if判断的作用是防止重复切换}else{mtCOVERAGE_TEST_MARKER();}taskENTER_CRITICAL();{traceTASK_NOTIFY_WAIT( uxIndexToWaitOn );if( pulNotificationValue ! NULL ) //判断是否需要保存原先的任务通知值{*pulNotificationValue pxCurrentTCB-ulNotifiedValue[ uxIndexToWaitOn ]; //保存原先的任务通知值}if( pxCurrentTCB-ucNotifyState[ uxIndexToWaitOn ] ! taskNOTIFICATION_RECEIVED ) //判断任务是否不在等待接收{xReturn pdFALSE; //接收失败}else{pxCurrentTCB-ulNotifiedValue[ uxIndexToWaitOn ] ~ulBitsToClearOnExit; //将通知值接收并把指定位清零xReturn pdTRUE; //接收成功}pxCurrentTCB-ucNotifyState[ uxIndexToWaitOn ] taskNOT_WAITING_NOTIFICATION; //状态更改为未等待通知}taskEXIT_CRITICAL();traceRETURN_xTaskGenericNotifyWait( xReturn );return xReturn;
}
三、任务通知模拟二值信号量实验
1、原理图与实验目标
1原理图 2实验目标
①设计4个任务——start_task、task1、task2、task3
[1]start_task用于创建task1、task2和task3任务。
[2]task1当获取到LED1的硬件资源后控制LED1约每500ms完成亮暗翻转的状态切换每完成一次即将资源释放。
[3]task2当获取到LED2的硬件资源后控制LED2约每1000ms完成亮暗翻转的状态切换每完成一次即将资源释放。
[4]task3按下按键1获取或者说霸占LED1和LED2的硬件资源按下按键2释放LED1和LED2的硬件资源。
②预期实验现象
[1]程序下载到板子上后两个LED灯闪烁。
[2]按下按键1LED1和LED2停止闪烁允许有1秒左右的延迟。
[3]按下按键2LED1和LED2恢复闪烁。
2、实验步骤
1将“二值信号量实验”的工程文件夹复制一份在拷贝版中进行实验。
2将FreeRTOS_experiment.c文件中关于信号量的代码全部移除并更改task1、task2和task3函数的实现。
①task1函数的思路需要使用LED的硬件资源时调用ulTaskNotifyTake函数等待任务通知并且必须等待到通知同时将通知值清零才可进行下一步操作——翻转LED1的状态。
②task2函数的思路需要使用LED的硬件资源时调用ulTaskNotifyTake函数等待任务通知并且必须等待到通知同时将通知值清零才可进行下一步操作——翻转LED2的状态。
③task3函数的思路
[1]task1和task2需要task3的通知才能使用LED硬件资源与二值信号量实验不同二值信号量实验有单独的两个“队列”分别管理两个LED硬件资源而本实验则是把task3当作了二值信号量队列管理员两个二值信号量分别由task1和task2的TCB的任务通知相关成员代替。
[2]task1和task2不断申请和归还硬件资源那么task3也要不断处理task1和task2的申请要不断地管理资源的分配而不是等待按键事件到来的一刻才做一次资源分配操作于是原本的控制算法需要做变更。
[3]在未按下任何按键时task3可以一直做释放LED硬件资源给task1和task2的操作即使手上没有LED资源也不会陷入阻塞而是直接执行下一条语句当然在实际项目中通常不建议这么做此处仅仅是为了功能演示task1和task2哪个先结束阻塞哪个就先调用ulTaskNotifyTake函数等待任务通知或者已经调用ulTaskNotifyTake函数但之前未等待到通知而进入无限阻塞但task3一旦分配资源以后就能立刻被唤醒。 [4]在按下按键1之后task3不做资源分配操作task1和task2只能无限等待任务通知无法执行后续的任何操作。 [5]在按下按键2之后以下执行流程图的情形为按下按键1后再按下按键2task3可以继续一直做释放LED硬件资源给task1和task2的操作。 void task1(void)
{while(1){ulTaskNotifyTake(pdTRUE, portMAX_DELAY); //接收LED硬件资源的使用通知LED1_Turn(); //LED1状态翻转vTaskDelay(500); //延时自我阻塞500ms}
}void task2(void)
{while(1){ulTaskNotifyTake(pdTRUE, portMAX_DELAY); //接收LED硬件资源的使用通知LED2_Turn(); //LED2状态翻转vTaskDelay(1000); //延时自我阻塞1000ms}
}void task3(void)
{uint8_t Key_memory, key 0;while(1){key Key_GetNum(); //读取按键键值if(key ! 0)Key_memory key;if(Key_memory 1);if(Key_memory 2 || Key_memory 0){xTaskNotifyGive(task1_handler); //释放LED硬件资源给task1xTaskNotifyGive(task2_handler); //释放LED硬件资源给task2}vTaskDelay(10); //延时自我阻塞10ms}
}
3程序完善好后点击“编译”然后将程序下载到开发板上根据程序注释进行调试。