你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】RTOS 低功耗设计原理及实现

[复制链接]
STMCU小助手 发布时间:2022-3-1 13:02
一. 前言
  ~& 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 02CK6PADGGOBTP$(N7I3}WG.png
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
~{O@L}ZD{V30{YC3D}K~@BX.png
0 p9 |8 ^" `2 r; k5 {. `( [$ u: N, L. e5 S8 a; k/ w; G0 T
使能6 h0 b2 A" F. k, s2 g
  1.    #define configUSE_TICKLESS_IDLE 1
复制代码
' _% K0 Q0 M/ R5 A7 ]6 S
空闲任务(RTOS 空闲时自动调用)+ g& {* n7 V9 \( f
  1.    /* Idle 任务 */
    $ L( `- u% s8 z' v- l
  2.    void prvIdleTask( void *pvParameters )
    6 P# k$ M' ^5 q" _; Y/ U2 C
  3.    {
    5 q8 T8 s4 X% L
  4.    for( ; ; )9 Y3 D1 K/ G/ @9 b% n, L+ W
  5.    {5 f8 u; A3 v' }  L6 r( W
  6.    ...
    0 p" }) {; R* M9 H
  7.    #if ( configUSE_TICKLESS_IDLE != 0 )
    ' `- c, o, W) a5 ]6 W& W: @
  8.    {
    7 q8 R8 E; j1 M7 y
  9.    TickType_t xExpectedIdleTime;; J, G, x! f' \& M+ M
  10.    /* 用户策略以决定是否需要进入 Tickless Mode */, q( N' l; L/ T' u: \2 P8 w
  11.    xExpectedIdleTime = prvGetExpectedIdleTime();6 o7 C/ u/ g- D4 O9 M
  12.    if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
    4 |4 d1 x1 G% I- L: G; A% J
  13.    {" V7 P4 j& `4 f7 ~
  14.    vTaskSuspendAll(); // 挂起调度器
    ) [; I/ v# Y  G, y- z% h) J
  15.    {. b8 i7 q/ V/ `1 Z3 p+ B* J
  16.    configASSERT( xNextTaskUnblockTime >= xTickCount );
    & N! V0 m- |! _' ^- w) V0 X
  17.    xExpectedIdleTime = prvGetExpectedIdleTime();
    9 p7 O3 m8 x& i2 Z' w
  18.    if( xExpectedIdleTime >=; i- k  Y5 X9 H( `( P3 R4 i
  19.    configEXPECTED_IDLE_TIME_BEFORE_SLEEP )& |! g7 |, g- T# t; t
  20.    {
    9 O6 }" e. p6 u8 g) T% T
  21.    /* 用户函数接口 */
    , x3 A2 W  C" ^  a
  22.    /* 1. 进入低功耗模式和如何退出低功耗模式 */
    2 @" Y2 d0 e0 d7 N7 l, W
  23.    /* 2. 系统时间补偿 */
    ( Y0 [  M! c# x
  24.    portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );4 O3 g# O) d: t, p
  25.    }' _0 x5 D( o2 g" B8 H0 x0 p
  26.    }7 ~- B; P& f  x0 T: k
  27.    (void) xTaskResumeAll(); // 恢复调度器
    1 J8 r5 q: t! L0 L* ^# j% w/ i1 n
  28.    }9 }. ~+ f9 f- p1 T* J$ n: n" Q
  29.    }
    + T& G. O5 b2 B  \! I& M* K
  30.    #endif /* configUSE_TICKLESS_IDLE */
    " A$ G$ b$ i. p& Z: R# l$ \4 g
  31.    ...& k% f' ?, p4 K; V  I, p
  32.    }
    0 ?( p/ L; Z; v& ~; l
  33.    }
复制代码
% }5 n6 B5 _& S6 Q
低功耗模式处理(根据 MCU 的低功耗模式编写代码,代码有点长……)# ?4 z/ w2 [0 j
  1.    void vPortSuppressTicksAndSleep( portTickType xExpectedIdleTime ). X, A7 T2 d* U( h! q7 ]6 H3 {
  2.    {. e, h# y3 T' ^/ A' f
  3.    unsigned long ulReloadValue, ulCompleteTickPeriods,* y- n3 P4 ]- s: [2 G; Y$ F( U1 u
  4.    ulCompletedSysTickDecrements;* X4 g. \7 `/ p; E% [/ s
  5.    portTickType xModifiableIdleTime;
    * X$ s/ O. K3 a% n+ W. g
  6.    /* 最长睡眠时间不可以超过定时器的最大定时值 */5 ~' r, M1 v+ `
  7.    /* 通过调整定时器的时间基准可以获得更理想的最大定时值 */
    ' m" @6 v9 y5 V( Y0 z. j
  8.    if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )
    8 v8 h& c# d( J, D1 h
  9.    {) ?* K& ^! y& P6 g9 x
  10.    xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
    - @6 Y* ?: [$ D
  11.    }( i2 ?+ r; Z$ |1 E5 q/ i5 \
  12.    /* 停止 SysTick */
    8 [0 ?1 |0 V( C0 I- q5 n
  13.    portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |
    2 B: D& p6 {7 R0 x2 h$ D
  14.    portNVIC_SYSTICK_INT_BIT;
    0 c% b. z3 q6 b  ?5 D3 r
  15.    /* 计算唤醒时的系统时间,用于唤醒后的系统时间补偿 */2 b6 X( _2 e, b' a
  16.    ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG +
    ! f6 J, c" l3 m* n* s8 T! s
  17.    ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) );$ @( G- g* f$ w+ {( l! G  n
  18.    if( ulReloadValue > ulStoppedTimerCompensation )0 f1 L6 n( V1 t8 x( ]3 `$ _/ k
  19.    {
    6 S* R4 I* X" Z' Y. C. ?  l" L  ?
  20.    ulReloadValue -= ulStoppedTimerCompensation;
    : P* a- K1 N! K- r! V9 K
  21.    }) N" W3 y" ]+ r
  22.    __disable_interrupt();% l1 e' M8 z( B) Q& k' t
  23.    /* 确认下是否可以进入低功耗模式 */
    5 \% s5 A  a$ s6 [, E7 R* c5 G
  24.    if( eTaskConfirmSleepModeStatus() == eAbortSleep )- a6 l0 H1 t4 s( X
  25.    {$ Q5 ?  q: k5 [' d
  26.    /* 不可以,重新启动系统定时器 */
    8 B# T0 H( {! l$ m, i9 ?- d2 R, t
  27.    portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG;3 A& U5 y5 g; \: l3 D. Y
  28.    portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |
    " _( A. g' i0 \' |! f7 X: h7 W* |
  29.    portNVIC_SYSTICK_INT_BIT |# J: n8 X, w6 g
  30.    portNVIC_SYSTICK_ENABLE_BIT;
    - \& E: J9 k! d3 N
  31.    portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
    & N" x( w3 |7 _. x8 [3 J5 J7 J  w
  32.    __enable_interrupt();8 `4 R! f2 x% E- K6 _$ V8 b6 w
  33.    }; c3 k( ?% [' C: `6 l; U
  34.    else
    6 W7 K; y/ \  t5 y- y# |
  35.    {* g- M' m/ v/ ]
  36.    /* 可以进入低功耗模式 */5 m% x$ I: ~0 j
  37.    /* 保存时间补偿,重启系统定时器 */% Z) `) _! a4 O
  38.    portNVIC_SYSTICK_LOAD_REG = ulReloadValue;3 }) j2 |# z* {8 I
  39.    portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
    & `, Q5 a% P. ^* Q# {
  40.    portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |
    1 i4 F, H1 d- M
  41.    portNVIC_SYSTICK_INT_BIT |7 q: S# Y1 f; e7 p: L6 [
  42.    portNVIC_SYSTICK_ENABLE_BIT;
    9 ?% F" U) E$ n
  43.    /* 进入低功耗模式,可以通过 configPRE_SLEEP_PROCESSING 函数进行低功耗模式下
      N& ~' ^( z) c+ S1 j
  44.    时钟及外设的配置*/
    % F: {1 d! b( R7 a% R
  45.    xModifiableIdleTime = xExpectedIdleTime;6 a6 q% @5 s3 `1 C& j  i' {* j! e9 x; Q
  46.    configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
    ( x. l' d6 {) ?
  47.    if( xModifiableIdleTime > 0 )
    + U) N4 W7 H9 ]5 I& p& h+ I7 R
  48.    {- S# ?7 \$ V) I9 G1 B  Q3 d2 y' i
  49.    __DSB();
    0 l5 t1 O: I0 c) s" p* K
  50.    __WFI();1 [# f( d" u! @
  51.    __ISB();
    8 T# ~# ^7 a2 h1 o$ Y
  52.    }
    % b! w" v% _. |, B
  53.    /* 退出低功耗模式 */1 B. P' C. Z+ E& N' N& U: v
  54.    configPOST_SLEEP_PROCESSING( xExpectedIdleTime );
    ; g5 h. B  e+ u/ x
  55.    portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |
    & X1 y; j7 |# U& c6 w9 y
  56.    portNVIC_SYSTICK_INT_BIT;, e4 E1 U, ?9 L  u, b9 G; u" P
  57.    __disable_interrupt()  u, r0 A% y4 t) H2 x1 s& f* L4 k& r- O
  58.    __enable_interrupt();
    ( ]7 `5 A5 z8 R) C
  59.    /*唤醒有两种情况:系统定时器或者外部事件(中断)*/
    7 ]/ I* [) u+ z. h# F' f4 |
  60.    if((portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT) != 0)
    ! {/ |& l& s$ n# o
  61.    {
    0 K6 i, s3 }) @6 C
  62.    /* 系统定时器唤醒,时间补偿 */
    6 g( u2 T$ a, a2 z4 A( e
  63.    unsigned long ulCalculatedLoadValue;
    # _; o% e: n% J
  64.    ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) –
    5 h! ?7 e% w5 K$ Z- s8 Z) ]6 f
  65.    ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );
    " _9 h0 z2 H' w3 w, B. @( s, }
  66.    if( ( ulCalculatedLoadValue  ulTimerCountsForOneTick ) )/ Z, m& |1 N+ s0 n( B3 t" t
  67.    {
      k5 i! |2 ]; H* Z5 K+ n, T; U
  68.    ulCalculatedLoadValue = (ulTimerCountsForOneTick - 1UL);
    6 k( A' z& F/ H8 K
  69.    }
    # U+ Y; I1 s7 f' X- K, ~
  70.    portNVIC_SYSTICK_LOAD_REG = ulCalculatedLoadValue;# Y; {6 U' h0 }2 i0 V
  71.    ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
    * e1 E1 R. N) S5 U9 x& G) z
  72.    }4 Q9 Z8 R9 y$ j7 N/ r- o3 U' s
  73.    else
    % ]9 c$ H' v0 g
  74.    {
    & X" V* p& q- q# m0 W

  75. 5 c, s' w  h/ l- O1 Q& k" p2 L
  76. /* 外部事件(中断)唤醒 */
    2 s2 b5 l) X" _" _" B, _+ z% F
  77. ulCompletedSysTickDecrements = ( xExpectedIdleTime *; ]: Z0 P) d, z2 F
  78. ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG;$ O/ U0 t/ y; D6 B4 `+ q0 F* t; g
  79. ulCompleteTickPeriods = ulCompletedSysTickDecrements /
    2 \5 A" _7 r! O  F- j# P3 ~
  80. ulTimerCountsForOneTick;0 G- T( `1 H2 W9 Q
  81. portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1 ) *
    * n! m* R* i4 x" u
  82. ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;
    2 d, D- k" H. [1 {" Q' M8 b5 l# ?
  83. }8 k8 ]; F! Q. v$ M( |+ S. [& j3 ~. e
  84. /* 重启 Systick,调整系统定时器中断为正常值 */# t% _7 p5 g8 u4 F) ^% f
  85. portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;. w$ r: y% d1 v: y: g$ }
  86. portENTER_CRITICAL();
    : E7 s) P' V( e
  87. {  R; j% H/ y  J9 v+ `. \0 O) ~- j( `
  88. portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |
    " s, Q  Z6 p8 s' V$ K7 p
  89. portNVIC_SYSTICK_INT_BIT |
    $ u6 _8 f, Q3 N+ W. K+ U. K- l% B
  90. portNVIC_SYSTICK_ENABLE_BIT;+ i6 p, L6 R' ?% A
  91. vTaskStepTick( ulCompleteTickPeriods );: x$ W; O; A/ c. I5 w2 T
  92. portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
    7 o# Y1 Q- W9 {3 a  c/ I  v2 E: G
  93. }
    . A8 j# Z7 l- V) X- R
  94. portEXIT_CRITICAL();: U( ?; U. \3 h, {+ V
  95. }! r7 O$ O: g% |9 N3 E9 i9 ?
  96. }
复制代码

& 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
赞 收藏 评论0 发布时间:2022-3-1 13:02

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版