FreeRTOS学习笔记:任务通知和软件定时器
任务通知
A写数据到队列的环形Buf,B读取Buf中的数据
A不知道谁读取的,B也不知道谁写入的
A想要写队列,当Buf写满时,A就会阻塞,在某个链表中挂入A,当B读取Buf时,会空出一个位置,读队列函数会去队列中,找链表唤醒第一个任务,对于任务B来说,B也不知道唤醒的谁。(B只知道唤醒链表的第一个任务)
他们之间相互不知道
A去give 释放一个信号量 B去take获得信号量或互斥量,两个任务之间也是通过信号量/互斥量进行通信的。双方不知道彼此
假设B想要take信号量或互斥量,里面的Value为0,B决定阻塞,于是在某个链表中挂起B。如果A来give信号量或互斥量,会顺便去结构体中,找到这个链表,把里面的第一个任务,拿出来唤醒。(A不知道谁是第一个任务)
任务通知
当中断或者任务A里面,明确该通知谁,去通知B任务。(中间不引入其他结构体)
场景一 :
A追求B
B的状态一开始处于任务没有在等待通知,当去处理别的任务,导致B阻塞,此时A发来通知特无法唤醒B,而B的状态变为接收到通知(但是B不受A通知的影响),当B再次运行时,B的状态为接收到通知,但是B不在乎。
但是如果B回心转意后,就会再次运行。因为A已经通知过了,所以B会立刻运行,不进行阻塞,此时B的状态为任务没有在等待通知
可以看为A舔狗追求B女神
场景二:
一开始B的状态为,任务没有通知,而任务B调用某个函数,他想等待通知,因为等待通知,进入阻塞,B的状态为,任务在等待通知,此时A发来通知唤醒B,(如何唤醒?将B的TCB结构体的值变为一个新值,状态为接收到任务通知),B开始运行,此时B的状态为任务没有通知。
使用任务通知有两类函数
简化版
xTaskNotifyGive/ulTaskNotifyTake
就是B的Val的值当成信号量(类比的说法),就是一个统计值cnt。
如果A give B
让B的Val值++
B的状态根据有没有调用 ulTaskNotifyTake 进行判断。如果B调用了函数ulTaskNotifyTake而处于等待任务通知状态(taskWAITING_NOTIFICATION)
B就会被A唤醒
B的Val值--
如果B没有调用 ulTaskNotifyTake 函数,B对A不管兴趣,变修改B的状态为接收到任务通知(taskNOTIFICATION_RECEIVED)
专业版
简化版就是调用 eIncrement 的形式
A想要通知B 调用 xTaskNotify 修改B的值,状态为接收到通知
B调用 xTaskNotifyWait
uint32_t ulBitsToClearOnEntry,
如果在入口处,B不是接收到通知,就会清除B的Val中的值。如果接收到通知就不会清除B中Val的值。
uint32_t *pulNotificationValue,
收到通知后,用来保存通知值
uint32_t ulBitsToClearOnExit
接收通知后,记录通知值,清除B的Val值的若干位
任务通知,车辆运行
第一辆车到达终点后,发出任务通知,让第二辆车,第三辆车开始运行
- 发送任务通知
同过调用任务句柄,来调用任务。
调用任务二
当任务三的值为100(覆盖型)时,调用任务三
- 等待任务通知
任务2 等待
任务3 等待 Val值为100
- 第一个参数 ~0(即所有位为 1):表示 “关注所有通知位”(不屏蔽任何位)。
- 第二个参数 ~0:表示 “接收后清除所有通知位”(处理完通知后复位通知状态)。
- 第三个参数 &val:用于存储接收到的通知值。
- 第四个参数 portMAX_DELAY:表示永久阻塞等待,直到收到通知才唤醒。
软件定时器
保证定时器任务的优先级足够高
软件定时器就是在Tick中断中调用
定时器:
就是某个结构体{
.Flg 一次性,周期性
.Period 周期
.fun 函数
.params 参数
.链表项
}
定时器A B C
A:2个周期后调用
B:5个周期后调用
C:10个周期后调用
怎么处理这些定时器呢?通过一个链表,把定时器串在一起
谁的时间先到达,谁就在链表的前面
怎么处理链表里面的定时器?
两种办法
- 在硬件中断处理函数中处理
每有一次硬件中断cnt值就++
当在cnt=x时,启动定时器,定时器会自动计算超时时间
A=cnt+2=x+2
B= cnt+5=x+5
C= cnt+10=x+10
在Tick ISR
- 判断Time List 有无已超时的Timer
- 若有调用其函数
在中断函数调用:中断要快速执行,不要执行的时间太长,如果要执行很复杂的程序,就不要放在定时器函数中做,否则会影响整个系统的效率。
定时器的任务运行于中断函数的上下文
二、 也是在Tick中断里进行判断定时器的时间是否到了。但是他不是调用其函数,而是通知一个“Timer 任务”,唤醒。
怎么唤醒“Timer任务”,通过写一个队列
平时,这个定时器任务就会读队列,当收到写入的数据后,就会知道,某个定时器的时间到了,调用其函数
定时器的函数运行于任务的上下文
当操作定时器时,就是写队列,以便唤醒定时器的任务。如果有别的任务的优先级高于定时器任务的优先级,那么定时器就会全部报废,一个用不了
所以当使用FreeRTOS里面的定时器时,要注意定时器的任务的优先级足够高。
进行设置软件定时器优先级
使用的函数
要使用定时器,需要先创建它,得到它的句柄。
启动定时器:
让定时器为Running状态
任务:
软件定时器 增加游戏音效
启动PWM发出声音,启动定时器
定时器的时间到后,定制PWM以静音
由于想要发出不同频率的声音
先初始化
先初始化蜂鸣器,再创建定时器
就封装一个函数,设置蜂鸣器的频率,时间
设置蜂鸣器的发声频率和 PWM 占空比
启动定时器,发声多长时间
- g_TimerSound:
定时器句柄(之前通过 xTimerCreate 创建的 GameSound 定时器对象),标识要操作的具体定时器。 - time_ms:
新的定时器周期(单位:系统节拍 Tick,但结合上下文通常表示 “毫秒”,需确保与系统配置一致)。- 这个值决定了蜂鸣器的持续发声时间:从调用该函数开始,到定时器超时触发回调函数(关闭蜂鸣器)的间隔,就是 time_ms 对应的实际时间。
- 0:
阻塞超时时间(单位:Tick),表示 “立即执行修改操作”:- 若为 0,函数会尝试立即修改定时器周期,若暂时无法完成(如定时器正在被其他任务操作),则直接返回失败(而非等待)。
- 实际使用中,因定时器通常仅由当前任务操作,传 0 可高效执行。