上一节学习了钩子函数与运行时间统计,知道了FreeRTOS可以通过Idle Hook判断系统什么时候处于空闲状态。
当系统没有任务需要运行时:
CPU还在干什么?
正常情况下,即使所有任务都在等待,FreeRTOS的系统Tick仍然会周期性产生中断。
例如:
configTICK_RATE_HZ = 1000
那么每隔:
1ms
CPU就会被Tick中断唤醒一次。
对于普通系统影响不大,但是对于电池供电设备,这种频繁唤醒会增加功耗。
因此FreeRTOS提供了:
Tickless Idle
也就是低功耗无节拍空闲模式。
本节先学习它的基本工作原理和配置方法。
一、普通Idle模式有什么问题
假设有一个任务:
void Task1(void *pvParameters)
{
while(1)
{
GPIO_ToggleBits(
GPIOE,
GPIO_Pin_3
);
vTaskDelay(
pdMS_TO_TICKS(1000)
);
}
}
Task1执行一次以后:
阻塞1000ms
如果系统中没有其他任务需要运行:
Task1阻塞
↓
Idle Task运行
但是系统Tick仍然存在。
假设:
Tick = 1ms
那么Task1等待的这1秒内,理论上仍然会产生约:
1000次Tick中断
CPU不断:
休息
↓
Tick唤醒
↓
处理Tick
↓
继续休息
对于低功耗系统来说,这些频繁唤醒很多时候没有必要。
二、什么是Tickless Idle
Tickless可以拆成:
Tick + less
可以简单理解成:
减少不必要的Tick
假设FreeRTOS发现:
接下来1000ms
没有任务需要运行
那么系统可以:
暂时停止周期Tick
↓
进入低功耗状态
↓
等待接近任务唤醒时间
↓
重新恢复Tick
因此原来的:
Tick Tick Tick Tick Tick......
变成:
进入休眠
↓
长时间不产生周期Tick
↓
唤醒
这样CPU就可以拥有更长的连续休眠时间。
三、开启Tickless Idle
在:
FreeRTOSConfig.h
中配置:
#define configUSE_TICKLESS_IDLE 1
表示:
开启Tickless Idle功能。
但是需要注意:
打开这个宏并不代表任何STM32工程都能直接获得理想的低功耗效果。
FreeRTOS负责判断:
什么时候可以休眠
而STM32具体:
进入哪种低功耗模式
使用什么时钟唤醒
唤醒后怎样恢复时钟
还与移植层和具体工程配置有关。
四、Tickless Idle什么时候进入
FreeRTOS并不是:
一进入Idle Task
马上停止Tick。
系统会先计算:
距离下一个任务唤醒还有多少Tick
例如:
Task1
还需要等待500Tick
同时:
Task2
还需要等待800Tick
那么最近需要运行的是:
Task1
因此系统知道:
未来大约500Tick内
没有任务需要正常运行
这时才有机会进入较长时间的低功耗状态。
五、configEXPECTED_IDLE_TIME_BEFORE_SLEEP
FreeRTOS中还有一个相关配置:
configEXPECTED_IDLE_TIME_BEFORE_SLEEP
它可以理解成:
至少预计空闲多少个Tick
才值得进入Tickless Idle。
例如:
#define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 2
如果系统预计只空闲:
1Tick
可能就没有必要:
进入低功耗
↓
马上又退出
因为进入和退出低功耗本身也需要一定开销。
六、一个简单实验
创建两个LED任务:
void Task1(void *pvParameters)
{
while(1)
{
GPIO_ToggleBits(
GPIOE,
GPIO_Pin_3
);
vTaskDelay(
pdMS_TO_TICKS(1000)
);
}
}
第二个任务:
void Task2(void *pvParameters)
{
while(1)
{
GPIO_ToggleBits(
GPIOE,
GPIO_Pin_4
);
vTaskDelay(
pdMS_TO_TICKS(2000)
);
}
}
两个任务执行完成以后都会进入阻塞态。
当:
Task1阻塞
Task2阻塞
并且没有其他任务需要运行时:
FreeRTOS
↓
进入Idle
↓
计算预计空闲时间
↓
满足条件
↓
进入Tickless Idle
到了任务需要运行的时间,再恢复正常调度。
从LED现象上看:
LED1仍然约1秒翻转一次
LED2仍然约2秒翻转一次
也就是说:
降低功耗并不意味着原来的任务延时逻辑失效。
七、Tickless Idle和Idle Task的区别
这一点很容易混淆。
普通Idle Task:
没有其他任务运行
↓
CPU执行空闲任务
但是:
系统Tick仍然周期产生。
Tickless Idle:
没有其他任务运行
↓
预计能够空闲较长时间
↓
抑制一段时间的周期Tick
↓
CPU进入低功耗状态
因此:
Idle Task
不一定等于
CPU真正长时间休眠
Tickless Idle才是FreeRTOS专门为降低空闲期间周期唤醒次数提供的机制。
八、中断还能不能唤醒CPU
可以。
假设STM32进入低功耗以后:
UART收到数据
按键产生外部中断
其他允许的外设产生中断
都可能让CPU提前醒来,具体取决于STM32所使用的低功耗模式和外设配置。
例如:
预计休眠500ms
但是:
100ms时
UART中断到来
CPU就可能提前退出低功耗状态。
之后FreeRTOS需要根据实际经过的时间:
修正系统Tick
然后继续进行任务调度。
九、实际使用时注意什么
Tickless Idle并不是简单打开:
configUSE_TICKLESS_IDLE
就结束了。
STM32实际工程中还需要注意:
系统时钟
低功耗模式
唤醒源
调试器
UART
定时器
等外设的状态。
特别是在调试阶段,如果发现:
开启Tickless以后程序异常
串口异常
系统时间不准确
不要首先怀疑任务代码。
应该重点检查:
低功耗进入和退出过程
以及时钟恢复是否正确。
另外,如果系统中的任务一直:
while(1)
{
/* 一直运行 */
}
没有:
vTaskDelay();
也没有等待:
Queue
Semaphore
Notification
那么系统几乎没有空闲时间:
Tickless Idle
自然也没有机会发挥作用。
十、总结
这一节学习了FreeRTOS中的Tickless Idle低功耗机制。
普通FreeRTOS运行时:
任务全部阻塞
↓
Idle Task运行
↓
Tick仍然周期产生
开启Tickless Idle以后:
任务全部阻塞
↓
计算预计空闲时间
↓
停止一段时间的周期Tick
↓
进入低功耗
↓
定时或中断唤醒
↓
恢复系统运行
核心配置:
#define configUSE_TICKLESS_IDLE 1
因此Tickless Idle真正解决的问题并不是:
让任务运行得更快
而是:
CPU没有事情做的时候
尽量让它真正休息更久
对于:
电池供电设备
无线传感节点
便携式设备
这类对功耗比较敏感的系统非常有意义。
不过STM32真正进入低功耗模式还涉及具体的时钟和唤醒配置,本节先把FreeRTOS层面的工作原理搞清楚即可。