一. 前言+ g5 f' @; x, v
目前,越来越多的嵌入式产品在开发中使用 RTOS 作为软件平台,同时,开发中对低功耗的要求也越来越高,这篇文档会讨论一下如何在 RTOS 中处理微控制器的低功耗特性。应用中使用的 RTOS 一般采用基于时间片轮转的抢占式任务调度机制,
7 c9 B# e+ P( s1 v一般的低功耗设计思路如下:
) _) |2 P# z4 z. g1. 当 Idle 任务运行时,进入低功耗模式;4 r2 O8 v. d% C9 O: P0 ?+ U
2. 在适当的条件下,通过中断或者外部事件唤醒 MCU。% q- |0 X5 a; k) z7 P1 n* y: ]
但是,从第二点可以看出,每次当 OS 系统定时器产生中断时,也会将 MCU 从低功耗模式中唤醒,而频繁的进入低功耗模式/从低功耗模式中唤醒会使得 MCU 无法进入深度睡眠,对低功耗设计而言也是不合理的。
9 h) s: u: _1 s! h* Y: E 在 FreeRTOS 中给出了一种低功耗设计模式 ——Tickless Idle Mode,这个方法可以让 MCU 更长时间的处于低功耗模式。
% ]0 }' C+ M C3 S. f 二.Tickless Idle Mode 的原理及实现
/ x) _2 `& D9 {7 s. i; Q- N* U( p1. 情景分析
3 N/ [2 x* X. h! S& F2 L' Q
+ N7 L1 \: [1 x+ |: ]$ m T5 K8 j6 R. [+ x1 [' }) z/ B
上图是任务调度示意图,横轴是时间轴,T1,T2,T3,T4 是 RTOS 的时间片基准,有四个任务分别是 TaskA,B,C,D,* w. W3 e3 M# {/ L
Task A: 周期性任务
: Y' U6 `. a$ Z! t* W* S6 Z Task B: 周期性任务
5 F& h) x: P2 U% F Task C: 突发性任务5 y1 h' s& G9 ~$ X+ o/ f
Task D: 周期性任务' ~- D" u4 V x. l
从图中可以看出在四个任务进行调度之间,会有四次空闲期间(此时 RTOS 会调度 Idle 任务运行,软件设计的目标应该是尽可能使 MCU 在 Idle 任务运行时处于低功耗模式)。: `+ @2 R9 [& O
Idle1: Idle 任务运行期间,会产生一次系统时钟滴答,此时会唤醒 MCU,唤醒后 MCU 又会进入低功耗模式,这次唤醒是无意义的。期望使 MCU 在 Idle1 期间一直处于低功耗模式,因此适当调整系统定时器中断使得 T1 时不触发系统时钟中断,中断触发点设置为 Task B 到来时;5 m1 x# X8 `6 l: B5 u+ r7 m
Idle2:Task C 在系统滴答到达前唤醒 MCU(外部事件),MCU 可以在 Idle2 中可以一直处于低功耗模式;8 @! Y+ ^% S8 s3 j) k
Idle3: 与 Idle2 情况相同,但 Idle3 时间很短,如果这个时间很短,那么进入低功耗模式的意义并不大,因此在进入低功耗模式时软件应该添加策略;9 r; r6 D# b2 U
Idle4: 与 Idle1 情况相同。3 [1 q3 k5 a; W! Q
2. Tickless Idle Mode 的软件设计原理
F" l6 ~; L% [, U$ W3 S- pTickless Idle Mode 的设计思想在于尽可能得在 MCU 空闲时使其进入低功耗模式。从上述情景中可以看出软件设计需要解决的问题有:
0 @ Z5 _6 d, ]+ Q" C" m% f a. 合理的进入低功耗模式(避免频繁使 MCU 在低功耗模式和运行模式下进行不必要的切换);RTOS 的系统时钟源于硬件的某个周期性定时器(Cortex-M 系列内核多数采用 SysTick),RTOS 的任务调度器可以预期到下一个周期性任务(或者定时器任务)的触发时间,如上文所述,调整系统时钟定时器中断触发时间,可以避免 RTOS 进入不必要的时间中断,从而更长的时间停留在低功耗模式中,此时 RTOS 的时钟不再是周期的而是动态的(在原有的时钟基准时将不再产生中断,即 Tickless); e+ Q! ]' n+ h! R7 l
b. 当 MCU 被唤醒时,通过某种方式提供为系统时钟提供补偿。MCU 可能被两种情况所唤醒,动态调整过的系统时钟中断或者突发性的外部事件,无论是哪一种情况,都可以通过运行在低功耗模式下的某种定时器来计算出 MCU 处于低功耗模式下的时间,在 MCU 唤醒后对系统时间进行软件补偿;
1 f1 Y) ]# R, r! |! g c. 软件实现时,要根据具体的应用情景和 MCU 低功耗特性来处理问题。尤其是 MCU 的低功耗特性,不同 MCU 处于不同的低功耗模式下所能使用的外设(主要是定时器)是不同的,RTOS 的系统时钟可以进行适当的调整。
0 Y$ X4 ^$ ]( d' T1 w" v! i5 x3. Tickless Idle Mode 的实现6 b. U7 P4 i) m9 R8 g4 z |6 X
这里以 STM32F407 系列的 MCU 为例,首先需要明确的是 MCU 的低功耗模式,F407 有 3 种低功耗模式,Sleep, Stop, Standby,在 RTOS 平台时,SRAM 和寄存器的数据不应丢失,此外需要一个定时器为 RTOS 提供系统时钟,这里选择 Sleep 模式下进行实现。
& N+ S! t. q2 n3 t/ H# { X) H4 v$ J7 Y: ^
. x; e" T8 ^+ p/ Y: s
/ i {: G5 ~- \" T- M. e" N使能. N) O/ f% d) U# P7 I
- #define configUSE_TICKLESS_IDLE 1
复制代码 P% W) i9 Z7 g* f
空闲任务(RTOS 空闲时自动调用)
. S( N _# S7 n3 T" J8 ?% K- /* Idle 任务 */
. G8 |: I" K# m - void prvIdleTask( void *pvParameters )5 h+ h: J1 Q0 X2 X2 S7 W# a# x
- {
3 ]9 V" P, @# z# f) Z5 D - for( ; ; )
" H2 @8 ^! T) D# f - {
3 i+ p5 C( j9 C" Z! _* E) [2 W$ z - ...
/ o# m9 r* H, B - #if ( configUSE_TICKLESS_IDLE != 0 )
! a# b* r! s8 k' j0 |3 _ - {& B) r" N0 o' \& V3 @( T4 ~; m
- TickType_t xExpectedIdleTime;5 f* M G) @! d& T/ i9 f
- /* 用户策略以决定是否需要进入 Tickless Mode */
. T1 U' x: u0 Z: U! y - xExpectedIdleTime = prvGetExpectedIdleTime();. z0 T# {- L: ~' d5 k' _. e
- if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
7 u, g/ g3 k2 F6 O - {6 ^% P; \8 Z4 G1 d1 z% T
- vTaskSuspendAll(); // 挂起调度器" j2 {8 e" s# _+ z
- {3 R$ T7 L0 k) y( K+ R
- configASSERT( xNextTaskUnblockTime >= xTickCount );$ D7 p4 ]( |& |8 P
- xExpectedIdleTime = prvGetExpectedIdleTime();
& {8 L- [ u/ \% t3 h* | - if( xExpectedIdleTime >=
& U8 e# l8 x& Y" C, q7 ]$ a! }. z - configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) C) L6 e; A% t* U. r* a
- {
% O2 C; Q$ d/ @6 i$ F" Z - /* 用户函数接口 */7 Z4 O; |& l3 g; v% a; P0 P
- /* 1. 进入低功耗模式和如何退出低功耗模式 */+ V" X# j6 r- E
- /* 2. 系统时间补偿 */
4 P, J+ l4 w% ?- ] - portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
* Q) G3 F' {! \" R7 R - }
( B" V2 F' [3 \4 ]$ K T/ @$ a - }
: o. B! f c5 p9 j4 f5 g& z: } - (void) xTaskResumeAll(); // 恢复调度器! A+ B$ F1 Q. L) a C7 x
- }
) o e4 T2 l* ?0 R - }
# W4 @3 f$ _) R8 u. d; Z3 G - #endif /* configUSE_TICKLESS_IDLE */9 s' L. o0 ]* i. N8 Y
- ...2 L9 T |3 Q5 l
- }
9 D6 S4 @8 U+ V4 y - }
复制代码
+ D2 u% V4 F4 s0 X; p低功耗模式处理(根据 MCU 的低功耗模式编写代码,代码有点长……)/ ?7 R) o* X$ Z% \5 W6 x9 o$ P! B5 J
- void vPortSuppressTicksAndSleep( portTickType xExpectedIdleTime ), R2 V* a( L1 Q# f: E& G! P
- {+ \! U2 y' Y9 k0 z @- S0 r
- unsigned long ulReloadValue, ulCompleteTickPeriods,
. h8 a, _3 b5 u- T; g* R7 Z - ulCompletedSysTickDecrements;
: ~+ t" p- Y* b8 b' f4 D- s2 d - portTickType xModifiableIdleTime;
3 v9 U B1 |9 Q2 x. g7 n( ~ - /* 最长睡眠时间不可以超过定时器的最大定时值 */
/ y% V4 x& v: t; Y( T - /* 通过调整定时器的时间基准可以获得更理想的最大定时值 */6 W2 d V% R! i& M
- if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )# v+ l4 h' J3 n/ ~& A5 I/ t
- {
8 _$ Q" D% {/ |9 F9 a0 d$ F7 y - xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
3 ]& N) T; u2 r" M; {% j3 r - }0 M% L& I# N7 w
- /* 停止 SysTick */
" i0 w. N9 C3 }- E( c! p2 z - portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |. I& D3 l* x6 _2 _4 h
- portNVIC_SYSTICK_INT_BIT;: k. K6 k% |& {5 g" z* W9 g
- /* 计算唤醒时的系统时间,用于唤醒后的系统时间补偿 */
8 Y U6 h+ @0 a$ y b- t' y0 A - ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG +
/ Y$ H# m0 Y& }) C6 D$ j - ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) ); w9 Q* j4 Y3 }$ t$ Z
- if( ulReloadValue > ulStoppedTimerCompensation )
9 J0 T: ?' V1 T# E9 Q - {
& P& h5 ?$ b( |$ [* j( ^9 N - ulReloadValue -= ulStoppedTimerCompensation;
! J6 g: u3 F+ } ?1 O* B# P0 E - }
$ k; O/ p5 G& P. F2 Z4 h! `9 R - __disable_interrupt();4 g* p4 |( x* m1 B+ J, M
- /* 确认下是否可以进入低功耗模式 */
+ J$ G* t0 K2 ]/ n - if( eTaskConfirmSleepModeStatus() == eAbortSleep )8 l( o8 \& P1 E1 ~( o- g, q+ N
- {( G X3 {) O+ s* @# y
- /* 不可以,重新启动系统定时器 */
4 e' }- e$ y9 G* u. U% @' ` - portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG;
& N3 v _5 Y' \& l - portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |3 {( R0 {+ v2 P" X' F
- portNVIC_SYSTICK_INT_BIT |
) F. K) P* j9 s' y - portNVIC_SYSTICK_ENABLE_BIT;, O& J% U I9 g3 d8 ? Z F
- portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;$ {9 J6 l& m. Z+ G4 ]+ r
- __enable_interrupt();
* k& y( K3 c. g - }( _* l2 d5 ?- Z! n
- else
3 H% [0 U- w) }( h - {
( t1 a5 j( C+ ] - /* 可以进入低功耗模式 */
. U" u0 Q+ ~; o8 o3 Y+ R - /* 保存时间补偿,重启系统定时器 */* ~; c, j2 j; _9 [" A+ {
- portNVIC_SYSTICK_LOAD_REG = ulReloadValue;4 a0 P5 o- E2 P
- portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;4 z5 K2 M7 j1 |3 I8 _, Q
- portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |2 _+ E% C- d8 K) g, o
- portNVIC_SYSTICK_INT_BIT |
4 Q& Y% d* W2 M4 Q: v8 @7 E - portNVIC_SYSTICK_ENABLE_BIT;- `5 k2 k6 c+ W+ D3 |* u' J! s
- /* 进入低功耗模式,可以通过 configPRE_SLEEP_PROCESSING 函数进行低功耗模式下
: i/ C0 D$ z: G" d% [' }0 m - 时钟及外设的配置*/. J9 M8 |$ R" J' L
- xModifiableIdleTime = xExpectedIdleTime;8 e' I( Q1 F5 E- F, T$ N6 Y
- configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
! A! U. B0 n" w# G6 ?# {: A - if( xModifiableIdleTime > 0 )7 P5 p a3 m8 x; {$ y8 F) q
- {
/ H: v0 E) ?/ q& ^/ D: Z - __DSB();
; f x6 n+ W0 O! M1 G9 p - __WFI();: R& \! W5 G) u8 J2 v ~% j
- __ISB();
3 \1 S8 m3 ?) t% i - }5 y% e x7 N" _4 `( G; x/ e
- /* 退出低功耗模式 */
$ N/ l* J* `- V: x, Z0 K9 d4 @) K - configPOST_SLEEP_PROCESSING( xExpectedIdleTime );* ^ f7 Q, w: f% E$ R6 J) ?4 F
- portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |! M$ Q3 {' _/ o
- portNVIC_SYSTICK_INT_BIT;
# t% v J. n6 p- ?; |2 _ - __disable_interrupt()
7 `( l7 X" S% a - __enable_interrupt();
+ z' D3 Y' G6 d2 r( d! |9 x& h+ x - /*唤醒有两种情况:系统定时器或者外部事件(中断)*/" J D/ r5 d" R; J2 G ?
- if((portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT) != 0)$ u! M5 f% ?8 c7 ^: ^
- {% B/ J' f# z+ ?6 {
- /* 系统定时器唤醒,时间补偿 */- h4 H* ~5 m0 r ~+ l
- unsigned long ulCalculatedLoadValue;
3 o3 D# s2 U/ S - ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) –0 B/ y" J, X4 H5 y; v( f- o
- ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );1 o5 B$ t( M3 U$ E; j
- if( ( ulCalculatedLoadValue ulTimerCountsForOneTick ) ), W' K/ X5 {8 v* t
- {% m2 D- @) h$ ]- i) @
- ulCalculatedLoadValue = (ulTimerCountsForOneTick - 1UL);' D! Y% s9 y, q( j5 @; Z: a
- }% D9 G5 i) k! k/ u2 w/ a
- portNVIC_SYSTICK_LOAD_REG = ulCalculatedLoadValue;
. B! W" {$ l, f$ b - ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
8 X8 Q+ @/ k& ]- r - }0 A9 _- W, M3 S4 A5 f( s
- else
: q/ Z8 e' ^/ |8 E! W+ ]$ f - {$ V5 p7 W$ p1 I1 q; t2 d) g! c! N
' \ C+ W4 E; f- /* 外部事件(中断)唤醒 */
+ H1 |; t6 C: k) z6 H - ulCompletedSysTickDecrements = ( xExpectedIdleTime *
# c6 ^. R* V: I0 m7 Z - ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG;3 h; P" s! }1 ?0 z, P
- ulCompleteTickPeriods = ulCompletedSysTickDecrements /
1 s. I2 F+ H+ v - ulTimerCountsForOneTick;' w" S% S; ]' D. W0 ]' \( l' g8 F
- portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1 ) *5 q* Y- u; v! @1 A7 R$ l/ r @
- ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;; M& B/ q5 m( X2 {
- }8 @' U/ o' {' U* m
- /* 重启 Systick,调整系统定时器中断为正常值 */
( j4 Q+ K; [1 T4 v2 w$ T& O9 h - portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
. s" p9 r( {& O' s% u1 ] - portENTER_CRITICAL();& @$ j2 m4 ]+ i, y& J) q+ N6 a' r7 g
- {2 \% E% ?& Q9 k# K
- portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |, ^( V" r$ J8 ?3 E
- portNVIC_SYSTICK_INT_BIT |
6 _. t# ?# k7 r4 U# J' l7 ]& ?$ x - portNVIC_SYSTICK_ENABLE_BIT;# o: R9 `; H% x0 P3 k! r
- vTaskStepTick( ulCompleteTickPeriods );6 G' ^7 T8 `8 C7 U- h* B
- portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;* L, [+ b/ D2 h) z ?% {
- }
3 E0 y; c G5 ]& z V - portEXIT_CRITICAL();. I4 ~1 z D6 H' ]( X
- }
3 p6 T( N" w' M4 `: m/ H - }
复制代码
6 s# l+ m$ C8 x/ s4. 写在最后的话
( v4 D7 v) w# W- sSTM32 家族中拥有不同的系列,特别是专为低功耗应用设计的 L 系列,为其设计 RTOS 低功耗特性实现时可以有更多的实现方式(例,某种模式下内核停止运行,此时可以使用外部定时器或者 RTC 来代替 Systick 作为系统定时器)。4 s+ q5 K# F% s0 ?9 G
1 W& D* k6 v+ f1 g; x! M |