一. 前言
~& g7 ?2 n4 t+ J8 l目前,越来越多的嵌入式产品在开发中使用 RTOS 作为软件平台,同时,开发中对低功耗的要求也越来越高,这篇文档会讨论一下如何在 RTOS 中处理微控制器的低功耗特性。应用中使用的 RTOS 一般采用基于时间片轮转的抢占式任务调度机制,( X' E7 u9 j2 L) k: H! |! _
一般的低功耗设计思路如下:3 q, H: s3 v- }" d; c3 X0 q- f
1. 当 Idle 任务运行时,进入低功耗模式;
( L1 q/ ?% D$ a0 [. ?, V8 I! b+ E2. 在适当的条件下,通过中断或者外部事件唤醒 MCU。
- h# E8 ^' m v5 G9 E 但是,从第二点可以看出,每次当 OS 系统定时器产生中断时,也会将 MCU 从低功耗模式中唤醒,而频繁的进入低功耗模式/从低功耗模式中唤醒会使得 MCU 无法进入深度睡眠,对低功耗设计而言也是不合理的。; ~5 S5 t1 ^2 ?* n
在 FreeRTOS 中给出了一种低功耗设计模式 ——Tickless Idle Mode,这个方法可以让 MCU 更长时间的处于低功耗模式。: t" T- d! u- Y1 d" l# h/ X
二.Tickless Idle Mode 的原理及实现
; x# y2 Q7 }" D* L' W1. 情景分析
8 v. O% j( I D
3 l5 |) y ^! n7 t" @& ^4 ?4 R8 c
上图是任务调度示意图,横轴是时间轴,T1,T2,T3,T4 是 RTOS 的时间片基准,有四个任务分别是 TaskA,B,C,D,9 w7 @0 Q' B) x W3 O' F2 C( v3 q
Task A: 周期性任务- I& O# e" R) h2 j" f
Task B: 周期性任务
. `7 N! P+ X" l% Z" f: v Task C: 突发性任务
, O r f+ m/ }* o1 v Task D: 周期性任务
. R6 m; B% I4 K! e3 P& s从图中可以看出在四个任务进行调度之间,会有四次空闲期间(此时 RTOS 会调度 Idle 任务运行,软件设计的目标应该是尽可能使 MCU 在 Idle 任务运行时处于低功耗模式)。
8 D; Z! r( i! QIdle1: Idle 任务运行期间,会产生一次系统时钟滴答,此时会唤醒 MCU,唤醒后 MCU 又会进入低功耗模式,这次唤醒是无意义的。期望使 MCU 在 Idle1 期间一直处于低功耗模式,因此适当调整系统定时器中断使得 T1 时不触发系统时钟中断,中断触发点设置为 Task B 到来时;$ a7 \& R. C) L
Idle2:Task C 在系统滴答到达前唤醒 MCU(外部事件),MCU 可以在 Idle2 中可以一直处于低功耗模式;
' {; T; Q4 E Y1 @9 N7 }$ EIdle3: 与 Idle2 情况相同,但 Idle3 时间很短,如果这个时间很短,那么进入低功耗模式的意义并不大,因此在进入低功耗模式时软件应该添加策略;) }! `/ T- B; u- s: ~+ G
Idle4: 与 Idle1 情况相同。
- [+ ]" I/ W, N) O. F2. Tickless Idle Mode 的软件设计原理
, d6 N" A, e# R; y: K' WTickless Idle Mode 的设计思想在于尽可能得在 MCU 空闲时使其进入低功耗模式。从上述情景中可以看出软件设计需要解决的问题有:
7 E1 }0 S* n& R. _3 ^: W1 ~ a. 合理的进入低功耗模式(避免频繁使 MCU 在低功耗模式和运行模式下进行不必要的切换);RTOS 的系统时钟源于硬件的某个周期性定时器(Cortex-M 系列内核多数采用 SysTick),RTOS 的任务调度器可以预期到下一个周期性任务(或者定时器任务)的触发时间,如上文所述,调整系统时钟定时器中断触发时间,可以避免 RTOS 进入不必要的时间中断,从而更长的时间停留在低功耗模式中,此时 RTOS 的时钟不再是周期的而是动态的(在原有的时钟基准时将不再产生中断,即 Tickless);
: ^, B+ Q0 V3 S' k% I; m b. 当 MCU 被唤醒时,通过某种方式提供为系统时钟提供补偿。MCU 可能被两种情况所唤醒,动态调整过的系统时钟中断或者突发性的外部事件,无论是哪一种情况,都可以通过运行在低功耗模式下的某种定时器来计算出 MCU 处于低功耗模式下的时间,在 MCU 唤醒后对系统时间进行软件补偿;
+ P9 Q1 `! w8 `9 e1 \8 `0 W c. 软件实现时,要根据具体的应用情景和 MCU 低功耗特性来处理问题。尤其是 MCU 的低功耗特性,不同 MCU 处于不同的低功耗模式下所能使用的外设(主要是定时器)是不同的,RTOS 的系统时钟可以进行适当的调整。; g6 @9 ~2 J0 X) {9 o
3. Tickless Idle Mode 的实现/ L& j! A, w2 c0 E6 t4 p# h; ~
这里以 STM32F407 系列的 MCU 为例,首先需要明确的是 MCU 的低功耗模式,F407 有 3 种低功耗模式,Sleep, Stop, Standby,在 RTOS 平台时,SRAM 和寄存器的数据不应丢失,此外需要一个定时器为 RTOS 提供系统时钟,这里选择 Sleep 模式下进行实现。
c& g1 M- ~8 s! u/ I4 s: S3 r. W$ V {$ l& i4 O
0 p9 |8 ^" `2 r; k5 {. `( [$ u: N, L. e5 S8 a; k/ w; G0 T
使能6 h0 b2 A" F. k, s2 g
- #define configUSE_TICKLESS_IDLE 1
复制代码 ' _% K0 Q0 M/ R5 A7 ]6 S
空闲任务(RTOS 空闲时自动调用)+ g& {* n7 V9 \( f
- /* Idle 任务 */
$ L( `- u% s8 z' v- l - void prvIdleTask( void *pvParameters )
6 P# k$ M' ^5 q" _; Y/ U2 C - {
5 q8 T8 s4 X% L - for( ; ; )9 Y3 D1 K/ G/ @9 b% n, L+ W
- {5 f8 u; A3 v' } L6 r( W
- ...
0 p" }) {; R* M9 H - #if ( configUSE_TICKLESS_IDLE != 0 )
' `- c, o, W) a5 ]6 W& W: @ - {
7 q8 R8 E; j1 M7 y - TickType_t xExpectedIdleTime;; J, G, x! f' \& M+ M
- /* 用户策略以决定是否需要进入 Tickless Mode */, q( N' l; L/ T' u: \2 P8 w
- xExpectedIdleTime = prvGetExpectedIdleTime();6 o7 C/ u/ g- D4 O9 M
- if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
4 |4 d1 x1 G% I- L: G; A% J - {" V7 P4 j& `4 f7 ~
- vTaskSuspendAll(); // 挂起调度器
) [; I/ v# Y G, y- z% h) J - {. b8 i7 q/ V/ `1 Z3 p+ B* J
- configASSERT( xNextTaskUnblockTime >= xTickCount );
& N! V0 m- |! _' ^- w) V0 X - xExpectedIdleTime = prvGetExpectedIdleTime();
9 p7 O3 m8 x& i2 Z' w - if( xExpectedIdleTime >=; i- k Y5 X9 H( `( P3 R4 i
- configEXPECTED_IDLE_TIME_BEFORE_SLEEP )& |! g7 |, g- T# t; t
- {
9 O6 }" e. p6 u8 g) T% T - /* 用户函数接口 */
, x3 A2 W C" ^ a - /* 1. 进入低功耗模式和如何退出低功耗模式 */
2 @" Y2 d0 e0 d7 N7 l, W - /* 2. 系统时间补偿 */
( Y0 [ M! c# x - portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );4 O3 g# O) d: t, p
- }' _0 x5 D( o2 g" B8 H0 x0 p
- }7 ~- B; P& f x0 T: k
- (void) xTaskResumeAll(); // 恢复调度器
1 J8 r5 q: t! L0 L* ^# j% w/ i1 n - }9 }. ~+ f9 f- p1 T* J$ n: n" Q
- }
+ T& G. O5 b2 B \! I& M* K - #endif /* configUSE_TICKLESS_IDLE */
" A$ G$ b$ i. p& Z: R# l$ \4 g - ...& k% f' ?, p4 K; V I, p
- }
0 ?( p/ L; Z; v& ~; l - }
复制代码 % }5 n6 B5 _& S6 Q
低功耗模式处理(根据 MCU 的低功耗模式编写代码,代码有点长……)# ?4 z/ w2 [0 j
- void vPortSuppressTicksAndSleep( portTickType xExpectedIdleTime ). X, A7 T2 d* U( h! q7 ]6 H3 {
- {. e, h# y3 T' ^/ A' f
- unsigned long ulReloadValue, ulCompleteTickPeriods,* y- n3 P4 ]- s: [2 G; Y$ F( U1 u
- ulCompletedSysTickDecrements;* X4 g. \7 `/ p; E% [/ s
- portTickType xModifiableIdleTime;
* X$ s/ O. K3 a% n+ W. g - /* 最长睡眠时间不可以超过定时器的最大定时值 */5 ~' r, M1 v+ `
- /* 通过调整定时器的时间基准可以获得更理想的最大定时值 */
' m" @6 v9 y5 V( Y0 z. j - if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )
8 v8 h& c# d( J, D1 h - {) ?* K& ^! y& P6 g9 x
- xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
- @6 Y* ?: [$ D - }( i2 ?+ r; Z$ |1 E5 q/ i5 \
- /* 停止 SysTick */
8 [0 ?1 |0 V( C0 I- q5 n - portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |
2 B: D& p6 {7 R0 x2 h$ D - portNVIC_SYSTICK_INT_BIT;
0 c% b. z3 q6 b ?5 D3 r - /* 计算唤醒时的系统时间,用于唤醒后的系统时间补偿 */2 b6 X( _2 e, b' a
- ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG +
! f6 J, c" l3 m* n* s8 T! s - ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) );$ @( G- g* f$ w+ {( l! G n
- if( ulReloadValue > ulStoppedTimerCompensation )0 f1 L6 n( V1 t8 x( ]3 `$ _/ k
- {
6 S* R4 I* X" Z' Y. C. ? l" L ? - ulReloadValue -= ulStoppedTimerCompensation;
: P* a- K1 N! K- r! V9 K - }) N" W3 y" ]+ r
- __disable_interrupt();% l1 e' M8 z( B) Q& k' t
- /* 确认下是否可以进入低功耗模式 */
5 \% s5 A a$ s6 [, E7 R* c5 G - if( eTaskConfirmSleepModeStatus() == eAbortSleep )- a6 l0 H1 t4 s( X
- {$ Q5 ? q: k5 [' d
- /* 不可以,重新启动系统定时器 */
8 B# T0 H( {! l$ m, i9 ?- d2 R, t - portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG;3 A& U5 y5 g; \: l3 D. Y
- portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |
" _( A. g' i0 \' |! f7 X: h7 W* | - portNVIC_SYSTICK_INT_BIT |# J: n8 X, w6 g
- portNVIC_SYSTICK_ENABLE_BIT;
- \& E: J9 k! d3 N - portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
& N" x( w3 |7 _. x8 [3 J5 J7 J w - __enable_interrupt();8 `4 R! f2 x% E- K6 _$ V8 b6 w
- }; c3 k( ?% [' C: `6 l; U
- else
6 W7 K; y/ \ t5 y- y# | - {* g- M' m/ v/ ]
- /* 可以进入低功耗模式 */5 m% x$ I: ~0 j
- /* 保存时间补偿,重启系统定时器 */% Z) `) _! a4 O
- portNVIC_SYSTICK_LOAD_REG = ulReloadValue;3 }) j2 |# z* {8 I
- portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
& `, Q5 a% P. ^* Q# { - portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |
1 i4 F, H1 d- M - portNVIC_SYSTICK_INT_BIT |7 q: S# Y1 f; e7 p: L6 [
- portNVIC_SYSTICK_ENABLE_BIT;
9 ?% F" U) E$ n - /* 进入低功耗模式,可以通过 configPRE_SLEEP_PROCESSING 函数进行低功耗模式下
N& ~' ^( z) c+ S1 j - 时钟及外设的配置*/
% F: {1 d! b( R7 a% R - xModifiableIdleTime = xExpectedIdleTime;6 a6 q% @5 s3 `1 C& j i' {* j! e9 x; Q
- configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
( x. l' d6 {) ? - if( xModifiableIdleTime > 0 )
+ U) N4 W7 H9 ]5 I& p& h+ I7 R - {- S# ?7 \$ V) I9 G1 B Q3 d2 y' i
- __DSB();
0 l5 t1 O: I0 c) s" p* K - __WFI();1 [# f( d" u! @
- __ISB();
8 T# ~# ^7 a2 h1 o$ Y - }
% b! w" v% _. |, B - /* 退出低功耗模式 */1 B. P' C. Z+ E& N' N& U: v
- configPOST_SLEEP_PROCESSING( xExpectedIdleTime );
; g5 h. B e+ u/ x - portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |
& X1 y; j7 |# U& c6 w9 y - portNVIC_SYSTICK_INT_BIT;, e4 E1 U, ?9 L u, b9 G; u" P
- __disable_interrupt() u, r0 A% y4 t) H2 x1 s& f* L4 k& r- O
- __enable_interrupt();
( ]7 `5 A5 z8 R) C - /*唤醒有两种情况:系统定时器或者外部事件(中断)*/
7 ]/ I* [) u+ z. h# F' f4 | - if((portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT) != 0)
! {/ |& l& s$ n# o - {
0 K6 i, s3 }) @6 C - /* 系统定时器唤醒,时间补偿 */
6 g( u2 T$ a, a2 z4 A( e - unsigned long ulCalculatedLoadValue;
# _; o% e: n% J - ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) –
5 h! ?7 e% w5 K$ Z- s8 Z) ]6 f - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );
" _9 h0 z2 H' w3 w, B. @( s, } - if( ( ulCalculatedLoadValue ulTimerCountsForOneTick ) )/ Z, m& |1 N+ s0 n( B3 t" t
- {
k5 i! |2 ]; H* Z5 K+ n, T; U - ulCalculatedLoadValue = (ulTimerCountsForOneTick - 1UL);
6 k( A' z& F/ H8 K - }
# U+ Y; I1 s7 f' X- K, ~ - portNVIC_SYSTICK_LOAD_REG = ulCalculatedLoadValue;# Y; {6 U' h0 }2 i0 V
- ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
* e1 E1 R. N) S5 U9 x& G) z - }4 Q9 Z8 R9 y$ j7 N/ r- o3 U' s
- else
% ]9 c$ H' v0 g - {
& X" V* p& q- q# m0 W
5 c, s' w h/ l- O1 Q& k" p2 L- /* 外部事件(中断)唤醒 */
2 s2 b5 l) X" _" _" B, _+ z% F - ulCompletedSysTickDecrements = ( xExpectedIdleTime *; ]: Z0 P) d, z2 F
- ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG;$ O/ U0 t/ y; D6 B4 `+ q0 F* t; g
- ulCompleteTickPeriods = ulCompletedSysTickDecrements /
2 \5 A" _7 r! O F- j# P3 ~ - ulTimerCountsForOneTick;0 G- T( `1 H2 W9 Q
- portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1 ) *
* n! m* R* i4 x" u - ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;
2 d, D- k" H. [1 {" Q' M8 b5 l# ? - }8 k8 ]; F! Q. v$ M( |+ S. [& j3 ~. e
- /* 重启 Systick,调整系统定时器中断为正常值 */# t% _7 p5 g8 u4 F) ^% f
- portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;. w$ r: y% d1 v: y: g$ }
- portENTER_CRITICAL();
: E7 s) P' V( e - { R; j% H/ y J9 v+ `. \0 O) ~- j( `
- portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |
" s, Q Z6 p8 s' V$ K7 p - portNVIC_SYSTICK_INT_BIT |
$ u6 _8 f, Q3 N+ W. K+ U. K- l% B - portNVIC_SYSTICK_ENABLE_BIT;+ i6 p, L6 R' ?% A
- vTaskStepTick( ulCompleteTickPeriods );: x$ W; O; A/ c. I5 w2 T
- portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
7 o# Y1 Q- W9 {3 a c/ I v2 E: G - }
. A8 j# Z7 l- V) X- R - portEXIT_CRITICAL();: U( ?; U. \3 h, {+ V
- }! r7 O$ O: g% |9 N3 E9 i9 ?
- }
复制代码
& n3 v" H6 |9 q( O/ t, T4. 写在最后的话
# x. P/ a+ J. [5 ]" w, FSTM32 家族中拥有不同的系列,特别是专为低功耗应用设计的 L 系列,为其设计 RTOS 低功耗特性实现时可以有更多的实现方式(例,某种模式下内核停止运行,此时可以使用外部定时器或者 RTC 来代替 Systick 作为系统定时器)。, f6 X" r/ P9 b, ~1 o* @
2 ~" h; P: i) h* k) v
|