一. 前言 Y2 w6 M4 n' U1 a
目前,越来越多的嵌入式产品在开发中使用 RTOS 作为软件平台,同时,开发中对低功耗的要求也越来越高,这篇文档会讨论一下如何在 RTOS 中处理微控制器的低功耗特性。应用中使用的 RTOS 一般采用基于时间片轮转的抢占式任务调度机制,+ a% @( Z3 b7 x
一般的低功耗设计思路如下:& E' Z% X$ {" [; F5 I# t& v
1. 当 Idle 任务运行时,进入低功耗模式;
$ q3 |0 J! |+ A( E7 [2. 在适当的条件下,通过中断或者外部事件唤醒 MCU。
( p1 L- t1 C2 } 但是,从第二点可以看出,每次当 OS 系统定时器产生中断时,也会将 MCU 从低功耗模式中唤醒,而频繁的进入低功耗模式/从低功耗模式中唤醒会使得 MCU 无法进入深度睡眠,对低功耗设计而言也是不合理的。5 B9 s" g- {2 I
在 FreeRTOS 中给出了一种低功耗设计模式 ——Tickless Idle Mode,这个方法可以让 MCU 更长时间的处于低功耗模式。
3 w; ]+ a6 t r 二.Tickless Idle Mode 的原理及实现
1 [" g- i1 d% b: F* x( ]1. 情景分析
. @" K9 Z: o( Q# @
6 s7 Y4 @/ P9 D* g4 \5 `% R3 U
7 h/ O$ P9 m1 h& _! B1 a2 p
上图是任务调度示意图,横轴是时间轴,T1,T2,T3,T4 是 RTOS 的时间片基准,有四个任务分别是 TaskA,B,C,D,9 h# T _; Y0 F" l Z
Task A: 周期性任务) r/ D0 T6 z2 u! F6 F; F$ G' X
Task B: 周期性任务) U0 R6 l/ z) B4 l5 a
Task C: 突发性任务* ~. o1 a: n6 r4 b6 {, u
Task D: 周期性任务
: v" L6 _* [# l* K' _8 [从图中可以看出在四个任务进行调度之间,会有四次空闲期间(此时 RTOS 会调度 Idle 任务运行,软件设计的目标应该是尽可能使 MCU 在 Idle 任务运行时处于低功耗模式)。; z: e$ D$ D, m* P. I: F7 t$ ~6 E
Idle1: Idle 任务运行期间,会产生一次系统时钟滴答,此时会唤醒 MCU,唤醒后 MCU 又会进入低功耗模式,这次唤醒是无意义的。期望使 MCU 在 Idle1 期间一直处于低功耗模式,因此适当调整系统定时器中断使得 T1 时不触发系统时钟中断,中断触发点设置为 Task B 到来时;1 `" _0 |. { P) V7 x6 \' z
Idle2:Task C 在系统滴答到达前唤醒 MCU(外部事件),MCU 可以在 Idle2 中可以一直处于低功耗模式;
- y2 n' R8 u) j- T$ k* ^Idle3: 与 Idle2 情况相同,但 Idle3 时间很短,如果这个时间很短,那么进入低功耗模式的意义并不大,因此在进入低功耗模式时软件应该添加策略; s* f( r8 d3 D0 `4 _: s0 {
Idle4: 与 Idle1 情况相同。
% b/ N4 M- M8 o( T m( n2. Tickless Idle Mode 的软件设计原理
* ^% Z5 F1 U N( f3 e' e DTickless Idle Mode 的设计思想在于尽可能得在 MCU 空闲时使其进入低功耗模式。从上述情景中可以看出软件设计需要解决的问题有:
- X5 d) [8 N4 M# E* Q( P* `. i6 w' o" K a. 合理的进入低功耗模式(避免频繁使 MCU 在低功耗模式和运行模式下进行不必要的切换);RTOS 的系统时钟源于硬件的某个周期性定时器(Cortex-M 系列内核多数采用 SysTick),RTOS 的任务调度器可以预期到下一个周期性任务(或者定时器任务)的触发时间,如上文所述,调整系统时钟定时器中断触发时间,可以避免 RTOS 进入不必要的时间中断,从而更长的时间停留在低功耗模式中,此时 RTOS 的时钟不再是周期的而是动态的(在原有的时钟基准时将不再产生中断,即 Tickless);1 J- ^# m( k; F$ o
b. 当 MCU 被唤醒时,通过某种方式提供为系统时钟提供补偿。MCU 可能被两种情况所唤醒,动态调整过的系统时钟中断或者突发性的外部事件,无论是哪一种情况,都可以通过运行在低功耗模式下的某种定时器来计算出 MCU 处于低功耗模式下的时间,在 MCU 唤醒后对系统时间进行软件补偿;+ f) e4 q7 U. S- }! K
c. 软件实现时,要根据具体的应用情景和 MCU 低功耗特性来处理问题。尤其是 MCU 的低功耗特性,不同 MCU 处于不同的低功耗模式下所能使用的外设(主要是定时器)是不同的,RTOS 的系统时钟可以进行适当的调整。
) H |( e9 ~# Q% F1 p5 v5 Z3. Tickless Idle Mode 的实现2 i( s- Q% Q; b8 O% ?
这里以 STM32F407 系列的 MCU 为例,首先需要明确的是 MCU 的低功耗模式,F407 有 3 种低功耗模式,Sleep, Stop, Standby,在 RTOS 平台时,SRAM 和寄存器的数据不应丢失,此外需要一个定时器为 RTOS 提供系统时钟,这里选择 Sleep 模式下进行实现。
& ?! c# a2 R0 d f5 E
" w" L6 E& h% K
* f6 r( Q0 _7 | w& ~
* D7 {: X2 w. Y使能2 R3 ], Y8 g% P. g
- #define configUSE_TICKLESS_IDLE 1
复制代码
: f( T7 g9 N" _ f( G1 Z/ O ]- k8 i空闲任务(RTOS 空闲时自动调用)0 f/ @. K- d. X+ X
- /* Idle 任务 */
8 @9 S/ r! p% U2 k& c* a: F2 H - void prvIdleTask( void *pvParameters )* d: H! V* ]& a. n7 [$ c
- {: n7 C; C/ J. ^, h& {
- for( ; ; )( l% N# _8 a7 p
- {
9 k2 e+ P, R1 |, m2 E - ...: Z6 l! o I* [. b: e* t
- #if ( configUSE_TICKLESS_IDLE != 0 )
% k0 H$ r- S5 {- D! ^3 k- N. `2 B - {0 J! B& `6 X. k4 F3 ?
- TickType_t xExpectedIdleTime;+ E" D* w' A) c# I" ]2 `& R' s
- /* 用户策略以决定是否需要进入 Tickless Mode */0 S! c k$ ^6 N7 l& g2 |" }
- xExpectedIdleTime = prvGetExpectedIdleTime();/ y# Y; e' H8 M6 I, X
- if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )* {+ `8 ~) e1 M, [1 r' W- e5 Z( [
- {
1 `7 ~' A6 w7 C7 m8 M/ y- V - vTaskSuspendAll(); // 挂起调度器' z! t* @! h% F; D. I0 m1 H, E/ T
- {
& l( f: Q; K+ O - configASSERT( xNextTaskUnblockTime >= xTickCount );" A) h8 g7 l, ?( [! | i3 Y! q# y
- xExpectedIdleTime = prvGetExpectedIdleTime();
1 g3 l: I$ N0 D. x - if( xExpectedIdleTime >=0 z( c8 M9 f" Y \: B5 @
- configEXPECTED_IDLE_TIME_BEFORE_SLEEP )% W! z- \6 u/ f" Z3 a
- {
' w! t% C8 l. \% n+ u; E/ |2 p - /* 用户函数接口 */
0 V: s8 ` e7 O# v/ \: ` - /* 1. 进入低功耗模式和如何退出低功耗模式 */: V S6 `9 _6 a/ B
- /* 2. 系统时间补偿 */' m2 z7 e& l* E. `( ?
- portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
" h8 v9 r' B) @* h, Y - }* {0 R3 U- ?5 p ?: ]5 b
- }
5 ^) }, |3 }) z4 X - (void) xTaskResumeAll(); // 恢复调度器
! y- U$ V9 M6 W4 Z2 a+ k; C - }
; P+ |: f# u8 @ - }
" O$ R5 }2 l' S! e - #endif /* configUSE_TICKLESS_IDLE */1 \, \8 j5 J, t1 l
- ...3 I5 k* _/ Z) y2 C4 J- O4 _2 k
- }/ M/ B! c7 N7 u2 L5 v( [' H! u
- }
复制代码
% r" o9 z/ M$ `. g) D0 y' t. d$ F低功耗模式处理(根据 MCU 的低功耗模式编写代码,代码有点长……)( ~) F. N; K9 e T* E% w( q
- void vPortSuppressTicksAndSleep( portTickType xExpectedIdleTime )2 h6 z" r0 a" U. D. d
- {
8 ^% ^+ z v. ^/ G - unsigned long ulReloadValue, ulCompleteTickPeriods,
4 p* M+ U: H) Z* d8 t; s( K - ulCompletedSysTickDecrements;; u5 }- u# n/ K" W
- portTickType xModifiableIdleTime;' j; D, `! i+ K I5 A' x
- /* 最长睡眠时间不可以超过定时器的最大定时值 */
C) l, Q7 u1 ^+ z& n$ H - /* 通过调整定时器的时间基准可以获得更理想的最大定时值 */8 Z& }, @, x' b( X
- if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks ) M. M. a+ o! I; {) H* T2 L% Z
- {4 Q5 Z( R' T P# n
- xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
( Q: N1 q5 ?* e, e8 U3 r8 r - }
! G7 L7 j# p( f8 e7 g+ @1 _* @# Y - /* 停止 SysTick */
* U) z% S( _9 z - portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |
& G: @1 d9 X* X - portNVIC_SYSTICK_INT_BIT; P* T `9 [% N v" ?# @
- /* 计算唤醒时的系统时间,用于唤醒后的系统时间补偿 */- t8 ?; R) N0 \3 D
- ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG +3 S: E+ s3 j, V" B4 f/ W i
- ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) );
: j9 d" F; M N0 | - if( ulReloadValue > ulStoppedTimerCompensation )
! F$ b& H; q$ z' T" D2 L$ x; C - {) D. Y* C2 V5 I8 |) {# ^" }+ z$ n1 {
- ulReloadValue -= ulStoppedTimerCompensation;# E0 z& @% K! B& V% z
- }) W) M$ E$ G1 X. T" H1 \
- __disable_interrupt();0 T/ l+ n, X: N, S6 a9 Z
- /* 确认下是否可以进入低功耗模式 */
' @4 i, r3 `: Y% f0 } - if( eTaskConfirmSleepModeStatus() == eAbortSleep ) V; u. i8 X0 E4 d
- {' t8 \$ r1 i9 M: X8 e
- /* 不可以,重新启动系统定时器 */# X. c; l8 C# q0 G4 O( K8 f. b
- portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG;3 O( z1 J) B1 x' m& ?
- portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |" N* k. S- }/ Q! p& C
- portNVIC_SYSTICK_INT_BIT |5 X5 ]1 F8 x |* {2 O* \$ f4 W
- portNVIC_SYSTICK_ENABLE_BIT;
6 L- O9 O6 ~8 M: u% k - portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
$ q" c% F5 x$ e - __enable_interrupt();* W( J A& q5 f* }' K
- }" c9 @$ ~1 m! g# X1 k
- else2 k0 d1 l$ Z5 \9 o! a n2 v
- {
3 U) F2 a' g1 C, f# ] - /* 可以进入低功耗模式 */, |( H4 R9 f# `0 k4 Z/ R! E! L
- /* 保存时间补偿,重启系统定时器 */& Y! q' [: D( u& J' H% y4 ^
- portNVIC_SYSTICK_LOAD_REG = ulReloadValue;
4 P3 f% J0 j1 h5 b) i% m - portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;1 h, [, b2 k2 J' {) f
- portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |" L, R: U8 u( B- c: X) W0 A
- portNVIC_SYSTICK_INT_BIT |8 R. j% s# x7 b8 A
- portNVIC_SYSTICK_ENABLE_BIT;# i' W. T5 [8 Z1 i" P6 x& W( k( r
- /* 进入低功耗模式,可以通过 configPRE_SLEEP_PROCESSING 函数进行低功耗模式下5 W* W& N( n( Y3 J
- 时钟及外设的配置*/( p( Y- w! r( ^: D$ u0 l2 ]
- xModifiableIdleTime = xExpectedIdleTime;
' [2 e: [& M& y' A" b3 ^% _ - configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
3 j, [7 S: L6 }+ `, B( w8 D - if( xModifiableIdleTime > 0 )
1 t& ~9 C: Y# G5 W - {
' R! ^+ P$ q4 J5 X& H4 l - __DSB();) v* h: j2 O6 w( o" Q
- __WFI();
) r( T1 Q% h/ T* f; f3 ? - __ISB();
: e5 F& K s: i5 r1 P: }" N - }
1 M" a" e0 q- E5 b: g3 q, E - /* 退出低功耗模式 */
# b" I* ~7 z5 A - configPOST_SLEEP_PROCESSING( xExpectedIdleTime );
" o0 U& y& [* ~; |* i i5 B8 X - portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |* p& ^: e! ^5 J8 F2 N" |
- portNVIC_SYSTICK_INT_BIT;3 h: ^0 A& w: O6 Q( a" C
- __disable_interrupt()$ {' V% ~# g0 W+ J
- __enable_interrupt();! P, m: U+ J! \* G: R1 f
- /*唤醒有两种情况:系统定时器或者外部事件(中断)*/
/ A) W- b! b: c& C# y" W+ }' |0 r - if((portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT) != 0)3 p3 E+ l% m9 x* o5 U8 J0 v, U) P
- {
- ]4 [% P% h, E! U* Y A8 J - /* 系统定时器唤醒,时间补偿 */
( Y7 ~, J8 G3 s; I+ y - unsigned long ulCalculatedLoadValue;" S+ N! g4 M; E% h
- ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) –
1 p+ w6 W+ {. `: v# K) A, f' A - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );
7 C/ I# Q/ H( T2 {) V1 {- v - if( ( ulCalculatedLoadValue ulTimerCountsForOneTick ) ) x; m* G" I4 Y- m) q' i
- {8 _$ i& Z( v& f' B& @) i/ H: O
- ulCalculatedLoadValue = (ulTimerCountsForOneTick - 1UL);! ^: d6 C' H- d. W& J
- }1 @! L8 u) S9 h) o9 b! E5 Y/ G
- portNVIC_SYSTICK_LOAD_REG = ulCalculatedLoadValue;
7 _+ w- H. ^. c5 Y0 o B$ E% \3 u - ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
" a3 r2 l9 z- h7 u6 ?5 k3 [ - }& N6 D3 F; X& e; J, R
- else
4 }; p$ Y- o. G1 K, E - {7 l, [- N1 \* f. X" F* ?8 h8 b
- * O; K4 ^# ?! _
- /* 外部事件(中断)唤醒 */- i4 y0 s5 q! H/ R! [; }( ~, U! s
- ulCompletedSysTickDecrements = ( xExpectedIdleTime ** g# U9 K7 e& D( J
- ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG;
) s+ N; R9 Y9 Y3 q/ T3 t9 C: {5 y - ulCompleteTickPeriods = ulCompletedSysTickDecrements /7 a: V% M7 C r1 f" T1 x* v
- ulTimerCountsForOneTick;# x* r/ j% R2 |$ w6 f, X; ~
- portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1 ) *
. W4 L* B2 P/ K# I% R, D - ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;) |: h" n) M9 M4 C2 { J
- }' U) }! f7 v& c, `7 d* @% f
- /* 重启 Systick,调整系统定时器中断为正常值 */" \ u6 V5 r/ C9 r6 L* y3 s
- portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
# w- ^3 v5 t4 G - portENTER_CRITICAL();, c& D( l) O3 Y- q
- { N% F2 j- d; S( o, E0 H# _
- portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |& j- H5 O! q$ y- {3 O3 D
- portNVIC_SYSTICK_INT_BIT |% `: W+ u4 R h6 W$ Z8 [$ T" {
- portNVIC_SYSTICK_ENABLE_BIT; I0 M( R' @* W1 d5 i. f
- vTaskStepTick( ulCompleteTickPeriods );
1 J8 D* p0 x- W' N1 g7 B. n( w - portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
1 A* r4 l5 f9 l! \ - }
7 o6 Z3 I# B+ F! t2 P h - portEXIT_CRITICAL();0 \9 s( `$ L2 _$ V* p
- }9 o# r, Y' D5 ^7 `
- }
复制代码
; `5 x4 P3 j2 N. ^5 N4 ^4. 写在最后的话
3 v$ y5 g5 I% h( q gSTM32 家族中拥有不同的系列,特别是专为低功耗应用设计的 L 系列,为其设计 RTOS 低功耗特性实现时可以有更多的实现方式(例,某种模式下内核停止运行,此时可以使用外部定时器或者 RTC 来代替 Systick 作为系统定时器)。
, W+ U, d1 ~" M' y
( r/ X# O! N6 ` |