任务通知的本质
对于之前使用过的几种互斥操作方式队列,互斥量,信号量,事件组,他们都是黑箱操作,对于写入和读取的任务来说并不知道对方是哪个任务,只是操作环形缓冲区和链表。
而任务通知的方式就是通知方任务知道要通知具体哪个任务,所以在得到数据或者中断后直接通知接收任务,不需要使用环形缓冲区。
每个任务都有一个结构体:TCB(Task Control Block),里面有 2 个成员:
一个是 uint8_t 类型,用来表示通知状态
一个是 uint32_t 类型,用来表示通知值
typedef struct tskTaskControlBlock
{....../* configTASK_NOTIFICATION_ARRAY_ENTRIES = 1 */volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];......
} tskTCB;
通知状态有3种取值:
taskNOT_WAITING_NOTIFICATION:任务没有在等待通知,默认状态
taskWAITING_NOTIFICATION:任务在等待通知
taskNOTIFICATION_RECEIVED:任务接收到了通知,也被称为 pending(有数据了,待处理)
例:有任务AB,当任务B设置为没有等待通知时,即使B在阻塞同时A发来通知也不能唤醒B,但是会将状态置为已经接收到通知,假如后续任务B运行过程中将自己设置为等待通知状态那将不会阻塞直接获取数据并置回默认状态
但当B设置为等待通知的状态后就会进入阻塞状态,此时A发送通知就会唤醒B并更改Value值,在B处理完数据后就会恢复默认状态 。
任务通知操作函数
任务通知有 2 套函数,简化版、专业版,列表如下:
简化版函数的使用比较简单,它实际上也是使用专业版函数实现的
专业版函数支持很多参数,可以实现很多功能
使用简化版时:
任务A向任务B发送通知就会将value值累加1并唤醒正在等待通知而阻塞的任务B(调用ulTaskNotifyTake函数进入等待状态),唤醒后value--。
任务B没有在等待,任务A只会将value++但没办法唤醒B
使用专业版时:
首先我们需要了解专业版的参数:
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyActio
n eAction );BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,uint32_t ulValue,eNotifyAction eAction,
BaseType_t *pxHigherPriorityTaskWoken );BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,uint32_t ulBitsToClearOnExit,uint32_t *pulNotificationValue,TickType_t xTicksToWait );
eNotifyAction参数说明:
在任务A调用xTaskNotify函数后一定会将任务B的状态修改为已经接收到的状态
ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait
在任务B调用xTaskNotifyWait函数时参数ulBitsToClearOnEntry表示任务B不在pending状态下将会清除value中某些位,ulBitsToClearOnExit表示如果是因为收到数据而退出则可以清除value的某些位,在清除前先将通知值赋给value,pulNotificationValue参数表示在收到通知时保存的通知值。
软件定时器
软件定时器的本质
在freertos系统中有tick线,在线上会产生tick中断,在中断服务函数中就会处理软件定时器。
使用定时器跟使用手机闹钟是类似的,定时器会有一个结构体:
指定时间:启动定时器和运行回调函数,两者的间隔被称为定时器的周期 (period)。
指定类型,定时器有两种类型: ◼ 一次性(One-shot timers): 这类定时器启动后,它的回调函数只会被调用一次; 可以手工再次启动它,但是不会自动启动它。 ◼ 自动加载定时器(Auto-reload timers ): 这类定时器启动后,时间到之后它会自动启动它; 这使得回调函数被周期性地调用。
指定要做什么事,就是指定回调函数
定时器运行方式:
1.每一个定时器都有一个结构体,而这些结构体会放入一个调用的链表中,这些定时器按照到达排列在链表中,在每一次tick中断触发时就会对链表中的定时器进行判断是否有已到达,如果有则调用其函数
2.同样也是tick中断判断是否到达定时器,但是在判断成功后将会通过写队列的方式通知一个timer任务,告知有需要处理的定时器函数,由这个任务进行调用。
对于FreeRTOS来说使用的就是第二种方法。
不仅是定时器回调函数,用户操作定时器的函数也是通过操作队列使得timer运作的,因此定时器timer任务需要有极高的优先级,其优先级可以在cubemx中调节
定时器函数
创建
/* 使用动态分配内存的方法创建定时器
* pcTimerName:定时器名字, 用处不大, 尽在调试时用到
* xTimerPeriodInTicks: 周期, 以 Tick 为单位
* uxAutoReload: 类型, pdTRUE 表示自动加载, pdFALSE 表示一次性
* pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器
* pxCallbackFunction: 回调函数
* 返回值: 成功则返回 TimerHandle_t, 否则返回 NULL
*/
TimerHandle_t xTimerCreate( const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );/* 使用静态分配内存的方法创建定时器
* pcTimerName:定时器名字, 用处不大, 尽在调试时用到
* xTimerPeriodInTicks: 周期, 以 Tick 为单位
* uxAutoReload: 类型, pdTRUE 表示自动加载, pdFALSE 表示一次性
* pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器
* pxCallbackFunction: 回调函数
* pxTimerBuffer: 传入一个 StaticTimer_t 结构体, 将在上面构造定时器
* 返回值: 成功则返回 TimerHandle_t, 否则返回 NULL
*/
TimerHandle_t xTimerCreateStatic(const char * const pcTimerName,TickType_t xTimerPeriodInTicks,
UBaseType_t uxAutoReload,
void * pvTimerID,TimerCallbackFunction_t pxCallbackFunction,
StaticTimer_t *pxTimerBuffer );
修改周期
/* 修改定时器的周期
* xTimer: 哪个定时器
* xNewPeriod: 新周期
* xTicksToWait: 超时时间, 命令写入队列的超时时间
* 返回值: pdFAIL 表示"修改周期命令"在 xTicksToWait 个 Tick 内无法写入队列
* pdPASS 表示成功
*/
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,TickType_t xNewPeriod,
TickType_t xTicksToWait );/* 修改定时器的周期
* xTimer: 哪个定时器
* xNewPeriod: 新周期
* pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
* 如果守护任务的优先级比当前任务的高,
* 则"*pxHigherPriorityTaskWoken = pdTRUE",
* 表示需要进行任务调度
* 返回值: pdFAIL 表示"修改周期命令"在 xTicksToWait 个 Tick 内无法写入队列
* pdPASS 表示成功
*/
BaseType_t xTimerChangePeriodFromISR( TimerHandle_t xTimer,TickType_t xNewPeriod,
BaseType_t *pxHigherPriorityTaskWoken )