探究STM32、FreeRTOS低功耗设计思路和原理

如今电池供电的产品很多,电池供电通常设计到一个问题,那就是低功耗。 本文为大家讲讲基于stm32、freertos实现低功耗思想和原理。

低功耗设计常规思路应用中使用的 rtos 一般采用基于时间片轮转的抢占式任务调度机制,一般的低功耗设计思路如下:1. 当 idle 任务运行时,进入低功耗模式;2. 在适当的条件下,通过中断或者外部事件唤醒 mcu。
但是, 从第二点可以看出,每次当 os 系统定时器产生中断时,也会将 mcu 从低功耗模式中唤醒,而频繁的进入低功耗模式/从低功耗模式中唤醒会使得 mcu 无法进入深度睡眠,对低功耗设计而言也是不合理的。 在 freertos 中给出了一种低功耗设计模式 ——tickless idle mode, 这个方法可以让 mcu 更长时间的处于低功耗模式。
二tickless idle mode原理及实现
1. 情景分析
freertos各任务情况:
上图是任务调度示意图,横轴是时间轴, t1, t2, t3, t4 是 rtos 的时间片基准,有四个任务分别是 taska,b,c,d。
task a:周期性任务
task b:周期性任务
task c:突发性任务
task d:周期性任务
从图中可以看出在四个任务进行调度之间,会有四次空闲期间(此时 rtos 会调度 idle 任务运行, 软件设计的目标应该是尽可能使 mcu 在 idle 任务运行时处于低功耗模式) 。
idle1: idle 任务运行期间,会产生一次系统时钟滴答,此时会唤醒 mcu,唤醒后 mcu 又会进入低功耗模式, 这次唤醒是无意义的。期望使 mcu 在 idle1 期间一直处于低功耗模式, 因此适当调整系统定时器中断使得 t1 时不触发系统时钟中断, 中断触发点设置为 task b 到来时;
idle2:task c 在系统滴答到达前唤醒 mcu(外部事件) , mcu 可以在 idle2 中可以一直处于低功耗模式;
idle3: 与 idle2 情况相同,但 idle3 时间很短,如果这个时间很短,那么进入低功耗模式的意义并不大,因此在进入低功耗模式时软件应该添加策略;
idle4: 与 idle1 情况相同。
2. tickless idle mode 的软件设计原理
tickless idle mode 的设计思想在于尽可能得在 mcu 空闲时使其进入低功耗模式。从上述情景中可以看出软件设计需要解决的问题有:
a. 合理的进入低功耗模式(避免频繁使 mcu 在低功耗模式和运行模式下进行不必要的切换) ;
rtos 的系统时钟源于硬件的某个周期性定时器(cortex-m 系列内核多数采用 systick) ,rtos 的任务调度器可以预期到下一个周期性任务(或者定时器任务) 的触发时间,如上文所述,调整系统时钟定时器中断触发时间,可以避免 rtos 进入不必要的时间中断,从而更长的时间停留在低功耗模式中,此时 rtos 的时钟不再是周期的而是动态的(在原有的时钟基准时将不再产生中断,即 tickless) ;
b. 当 mcu 被唤醒时,通过某种方式提供为系统时钟提供补偿。
mcu 可能被两种情况所唤醒, 动态调整过的系统时钟中断或者突发性的外部事件,无论是哪一种情况,都可以通过运行在低功耗模式下的某种定时器来计算出 mcu 处于低功耗模式下的时间,在 mcu 唤醒后对系统时间进行软件补偿;
c. 软件实现时,要根据具体的应用情景和 mcu 低功耗特性来处理问题。
尤其是 mcu 的低功耗特性, 不同 mcu 处于不同的低功耗模式下所能使用的外设(主要是定时器) 是不同的, rtos 的系统时钟可以进行适当的调整。
3. tickless idle mode 的实现
这里以 stm32f407 系列的 mcu 为例, 首先需要明确的是 mcu 的低功耗模式, f407 有 3 种低功耗模式:sleep、stop、 standby。
在 rtos 平台时, sram 和寄存器的数据不应丢失, 此外需要一个定时器为 rtos 提供系统时钟, 这里选择 sleep 模式下进行实现。 使能tickless idle:
#define configuse_tickless_idle 1
rtos空闲任务(空闲时自动调用)实现:
/* idle 任务 */void prvidletask( void *pvparameters ){ for( ; ; ) { //。。.#if(configuse_tickless_idle != 0) { ticktype_t xexpectedidletime; /* 用户策略以决定是否需要进入 tickless mode */ xexpectedidletime = prvgetexpectedidletime(); if( xexpectedidletime 》= configexpected_idle_time_before_sleep ) { vtasksuspendall();
// 挂起调度器 { configassert( xnexttaskunblocktime 》= xtickcount ); xexpectedidletime = prvgetexpectedidletime(); if( xexpectedidletime 》= configexpected_idle_time_before_sleep ) { /* 用户函数接口 */ /* 1. 进入低功耗模式和如何退出低功耗模式 */ /* 2. 系统时间补偿 */ portsuppress_ticks_and_sleep( xexpectedidletime ); } } (void) xtaskresumeall(); // 恢复调度器 } }#endif /* configuse_tickless_idle */ //。。。 }}
然后,低功耗模式处理(根据 mcu 的低功耗模式编写代码, 代码有点长……)
void vportsuppressticksandsleep( portticktype xexpectedidletime ){ unsigned long ulreloadvalue, ulcompletetickperiods, ulcompletedsystickdecrements; portticktype xmodifiableidletime; /*
最长睡眠时间不可以超过定时器的最大定时值 */ /* 通过调整定时器的时间基准可以获得更理想的最大定时值 */ if( xexpectedidletime 》 xmaximumpossiblesuppressedticks ) { xexpectedidletime = xmaximumpossiblesuppressedticks; } /* 停止 systick */ portnvic_systick_ctrl_reg = portnvic_systick_clk_bit | portnvic_systick_int_bit; /*
计算唤醒时的系统时间,用于唤醒后的系统时间补偿 */ ulreloadvalue = portnvic_systick_current_value_reg + ( ultimercountsforonetick * ( xexpectedidletime - 1ul ) ); if( ulreloadvalue 》 ulstoppedtimercompensation ) { ulreloadvalue -= ulstoppedtimercompensation; } __disable_interrupt(); /*
确认下是否可以进入低功耗模式 */ if( etaskconfirmsleepmodestatus() == eabortsleep ) { /* 不可以,重新启动系统定时器 */ portnvic_systick_load_reg = portnvic_systick_current_value_reg; portnvic_systick_ctrl_reg = portnvic_systick_clk_bit | portnvic_systick_int_bit | portnvic_systick_enable_bit; portnvic_systick_load_reg = ultimercountsforonetick - 1ul; __enable_interrupt(); } else { /
* 可以进入低功耗模式 */ /* 保存时间补偿,重启系统定时器 */ portnvic_systick_load_reg = ulreloadvalue; portnvic_systick_current_value_reg = 0ul;portnvic_systick_ctrl_reg = portnvic_systick_clk_bit | portnvic_systick_int_bit | portnvic_systick_enable_bit; /* 进入低功耗模式,可以通过 configpre_sleep_processing 函数进行低功耗模式下 时钟及外设的配置*/ xmodifiableidletime = xexpectedidletime; configpre_sleep_processing( xmodifiableidletime ); if( xmodifiableidletime 》 0 ) { __dsb(); __wfi(); __isb(); } /
* 退出低功耗模式 */ configpost_sleep_processing( xexpectedidletime ); portnvic_systick_ctrl_reg = portnvic_systick_clk_bit | portnvic_systick_int_bit; __disable_interrupt() __enable_interrupt(); /
*唤醒有两种情况:系统定时器或者外部事件(中断) */ if((portnvic_systick_ctrl_reg & portnvic_systick_count_flag_bit) != 0) { /* 系统定时器唤醒,时间补偿 */ unsigned long ulcalculatedloadvalue; ulcalculatedloadvalue = ( ultimercountsforonetick - 1ul ) – ( ulreloadvalue - portnvic_systick_current_value_reg ); if( ( ulcalculatedloadvalue 《 ulstoppedtimercompensation ) || ( ulcalculatedloadvalue 》 ultimercountsforonetick ) ) { ulcalculatedloadvalue = (ultimercountsforonetick - 1ul); } portnvic_systick_load_reg = ulcalculatedloadvalue; ulcompletetickperiods = xexpectedidletime - 1ul; } else { /
* 外部事件(中断)唤醒 */ ulcompletedsystickdecrements = ( xexpectedidletime * ultimercountsforonetick ) - portnvic_systick_current_value_reg; ulcompletetickperiods = ulcompletedsystickdecrements / ultimercountsforonetick;portnvic_systick_load_reg = ( ( ulcompletetickperiods + 1 ) * ultimercountsforonetick ) - ulcompletedsystickdecrements; }
/* 重启 systick,调整系统定时器中断为正常值 */ portnvic_systick_current_value_reg = 0ul; portenter_critical(); { portnvic_systick_ctrl_reg = portnvic_systick_clk_bit | portnvic_systick_int_bit | portnvic_systick_enable_bit; vtasksteptick( ulcompletetickperiods ); portnvic_systick_load_reg = ultimercountsforonetick - 1ul; } portexit_critical(); }}
三、最后
低功耗的设计存在很多影响功耗的因素,比如电路设计、io引脚配置等。
mcu实现低功耗的方法和种类有很多,设计时需要注意一些低功耗细节问题。
最后,以上方法仅供学习参考,具体请按照实际项目选择合理的低功耗设计方案。


Vivado时钟分组约束的三类应用
mosfet和mos管的区别 MOSFET的工作原理
15课:单片机位操作指令
2020年英特尔PC销量创下历史新纪录
揭秘未来城市物联网里的“神器”
探究STM32、FreeRTOS低功耗设计思路和原理
Dis模块的使用
口碑最好的国产蓝牙耳机 年度口碑降噪蓝牙耳机排行
ICPark为集成电路设计打辅助,促进企业科技创新
三相电机的功率计算 三相电机的接线方式
基于RFID技术的畜牧监控管理系统解决方案
便携式漏电动作时间检测仪
波峰焊连焊现象原因及解决方法
国产系统deepin安装教程_国产系统deepin怎么样
如何充分发挥传输SDN的全部潜力
如何将土壤湿度传感器和ESP8266连接到IoT云
如何使用带定时器的中断
高度可靠、精密测量控制的热式气体质量流量控制器
最新5G标准必要专利报告,华为排名第一中兴第三
东芝推出可在高温下应用的车载功率MOSFET