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

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

[复制链接]
STMCU小助手 发布时间:2022-3-1 13:02
一. 前言  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# @ 02CK6PADGGOBTP$(N7I3}WG.png 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 ~{O@L}ZD{V30{YC3D}K~@BX.png * f6 r( Q0 _7 |  w& ~

* D7 {: X2 w. Y使能2 R3 ], Y8 g% P. g
  1.    #define configUSE_TICKLESS_IDLE 1
复制代码

: f( T7 g9 N" _  f( G1 Z/ O  ]- k8 i空闲任务(RTOS 空闲时自动调用)0 f/ @. K- d. X+ X
  1.    /* Idle 任务 */
    8 @9 S/ r! p% U2 k& c* a: F2 H
  2.    void prvIdleTask( void *pvParameters )* d: H! V* ]& a. n7 [$ c
  3.    {: n7 C; C/ J. ^, h& {
  4.    for( ; ; )( l% N# _8 a7 p
  5.    {
    9 k2 e+ P, R1 |, m2 E
  6.    ...: Z6 l! o  I* [. b: e* t
  7.    #if ( configUSE_TICKLESS_IDLE != 0 )
    % k0 H$ r- S5 {- D! ^3 k- N. `2 B
  8.    {0 J! B& `6 X. k4 F3 ?
  9.    TickType_t xExpectedIdleTime;+ E" D* w' A) c# I" ]2 `& R' s
  10.    /* 用户策略以决定是否需要进入 Tickless Mode */0 S! c  k$ ^6 N7 l& g2 |" }
  11.    xExpectedIdleTime = prvGetExpectedIdleTime();/ y# Y; e' H8 M6 I, X
  12.    if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )* {+ `8 ~) e1 M, [1 r' W- e5 Z( [
  13.    {
    1 `7 ~' A6 w7 C7 m8 M/ y- V
  14.    vTaskSuspendAll(); // 挂起调度器' z! t* @! h% F; D. I0 m1 H, E/ T
  15.    {
    & l( f: Q; K+ O
  16.    configASSERT( xNextTaskUnblockTime >= xTickCount );" A) h8 g7 l, ?( [! |  i3 Y! q# y
  17.    xExpectedIdleTime = prvGetExpectedIdleTime();
    1 g3 l: I$ N0 D. x
  18.    if( xExpectedIdleTime >=0 z( c8 M9 f" Y  \: B5 @
  19.    configEXPECTED_IDLE_TIME_BEFORE_SLEEP )% W! z- \6 u/ f" Z3 a
  20.    {
    ' w! t% C8 l. \% n+ u; E/ |2 p
  21.    /* 用户函数接口 */
    0 V: s8 `  e7 O# v/ \: `
  22.    /* 1. 进入低功耗模式和如何退出低功耗模式 */: V  S6 `9 _6 a/ B
  23.    /* 2. 系统时间补偿 */' m2 z7 e& l* E. `( ?
  24.    portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
    " h8 v9 r' B) @* h, Y
  25.    }* {0 R3 U- ?5 p  ?: ]5 b
  26.    }
    5 ^) }, |3 }) z4 X
  27.    (void) xTaskResumeAll(); // 恢复调度器
    ! y- U$ V9 M6 W4 Z2 a+ k; C
  28.    }
    ; P+ |: f# u8 @
  29.    }
    " O$ R5 }2 l' S! e
  30.    #endif /* configUSE_TICKLESS_IDLE */1 \, \8 j5 J, t1 l
  31.    ...3 I5 k* _/ Z) y2 C4 J- O4 _2 k
  32.    }/ M/ B! c7 N7 u2 L5 v( [' H! u
  33.    }
复制代码

% r" o9 z/ M$ `. g) D0 y' t. d$ F低功耗模式处理(根据 MCU 的低功耗模式编写代码,代码有点长……)( ~) F. N; K9 e  T* E% w( q
  1.    void vPortSuppressTicksAndSleep( portTickType xExpectedIdleTime )2 h6 z" r0 a" U. D. d
  2.    {
    8 ^% ^+ z  v. ^/ G
  3.    unsigned long ulReloadValue, ulCompleteTickPeriods,
    4 p* M+ U: H) Z* d8 t; s( K
  4.    ulCompletedSysTickDecrements;; u5 }- u# n/ K" W
  5.    portTickType xModifiableIdleTime;' j; D, `! i+ K  I5 A' x
  6.    /* 最长睡眠时间不可以超过定时器的最大定时值 */
      C) l, Q7 u1 ^+ z& n$ H
  7.    /* 通过调整定时器的时间基准可以获得更理想的最大定时值 */8 Z& }, @, x' b( X
  8.    if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )  M. M. a+ o! I; {) H* T2 L% Z
  9.    {4 Q5 Z( R' T  P# n
  10.    xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
    ( Q: N1 q5 ?* e, e8 U3 r8 r
  11.    }
    ! G7 L7 j# p( f8 e7 g+ @1 _* @# Y
  12.    /* 停止 SysTick */
    * U) z% S( _9 z
  13.    portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |
    & G: @1 d9 X* X
  14.    portNVIC_SYSTICK_INT_BIT;  P* T  `9 [% N  v" ?# @
  15.    /* 计算唤醒时的系统时间,用于唤醒后的系统时间补偿 */- t8 ?; R) N0 \3 D
  16.    ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG +3 S: E+ s3 j, V" B4 f/ W  i
  17.    ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) );
    : j9 d" F; M  N0 |
  18.    if( ulReloadValue > ulStoppedTimerCompensation )
    ! F$ b& H; q$ z' T" D2 L$ x; C
  19.    {) D. Y* C2 V5 I8 |) {# ^" }+ z$ n1 {
  20.    ulReloadValue -= ulStoppedTimerCompensation;# E0 z& @% K! B& V% z
  21.    }) W) M$ E$ G1 X. T" H1 \
  22.    __disable_interrupt();0 T/ l+ n, X: N, S6 a9 Z
  23.    /* 确认下是否可以进入低功耗模式 */
    ' @4 i, r3 `: Y% f0 }
  24.    if( eTaskConfirmSleepModeStatus() == eAbortSleep )  V; u. i8 X0 E4 d
  25.    {' t8 \$ r1 i9 M: X8 e
  26.    /* 不可以,重新启动系统定时器 */# X. c; l8 C# q0 G4 O( K8 f. b
  27.    portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG;3 O( z1 J) B1 x' m& ?
  28.    portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |" N* k. S- }/ Q! p& C
  29.    portNVIC_SYSTICK_INT_BIT |5 X5 ]1 F8 x  |* {2 O* \$ f4 W
  30.    portNVIC_SYSTICK_ENABLE_BIT;
    6 L- O9 O6 ~8 M: u% k
  31.    portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
    $ q" c% F5 x$ e
  32.    __enable_interrupt();* W( J  A& q5 f* }' K
  33.    }" c9 @$ ~1 m! g# X1 k
  34.    else2 k0 d1 l$ Z5 \9 o! a  n2 v
  35.    {
    3 U) F2 a' g1 C, f# ]
  36.    /* 可以进入低功耗模式 */, |( H4 R9 f# `0 k4 Z/ R! E! L
  37.    /* 保存时间补偿,重启系统定时器 */& Y! q' [: D( u& J' H% y4 ^
  38.    portNVIC_SYSTICK_LOAD_REG = ulReloadValue;
    4 P3 f% J0 j1 h5 b) i% m
  39.    portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;1 h, [, b2 k2 J' {) f
  40.    portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |" L, R: U8 u( B- c: X) W0 A
  41.    portNVIC_SYSTICK_INT_BIT |8 R. j% s# x7 b8 A
  42.    portNVIC_SYSTICK_ENABLE_BIT;# i' W. T5 [8 Z1 i" P6 x& W( k( r
  43.    /* 进入低功耗模式,可以通过 configPRE_SLEEP_PROCESSING 函数进行低功耗模式下5 W* W& N( n( Y3 J
  44.    时钟及外设的配置*/( p( Y- w! r( ^: D$ u0 l2 ]
  45.    xModifiableIdleTime = xExpectedIdleTime;
    ' [2 e: [& M& y' A" b3 ^% _
  46.    configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
    3 j, [7 S: L6 }+ `, B( w8 D
  47.    if( xModifiableIdleTime > 0 )
    1 t& ~9 C: Y# G5 W
  48.    {
    ' R! ^+ P$ q4 J5 X& H4 l
  49.    __DSB();) v* h: j2 O6 w( o" Q
  50.    __WFI();
    ) r( T1 Q% h/ T* f; f3 ?
  51.    __ISB();
    : e5 F& K  s: i5 r1 P: }" N
  52.    }
    1 M" a" e0 q- E5 b: g3 q, E
  53.    /* 退出低功耗模式 */
    # b" I* ~7 z5 A
  54.    configPOST_SLEEP_PROCESSING( xExpectedIdleTime );
    " o0 U& y& [* ~; |* i  i5 B8 X
  55.    portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |* p& ^: e! ^5 J8 F2 N" |
  56.    portNVIC_SYSTICK_INT_BIT;3 h: ^0 A& w: O6 Q( a" C
  57.    __disable_interrupt()$ {' V% ~# g0 W+ J
  58.    __enable_interrupt();! P, m: U+ J! \* G: R1 f
  59.    /*唤醒有两种情况:系统定时器或者外部事件(中断)*/
    / A) W- b! b: c& C# y" W+ }' |0 r
  60.    if((portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT) != 0)3 p3 E+ l% m9 x* o5 U8 J0 v, U) P
  61.    {
    - ]4 [% P% h, E! U* Y  A8 J
  62.    /* 系统定时器唤醒,时间补偿 */
    ( Y7 ~, J8 G3 s; I+ y
  63.    unsigned long ulCalculatedLoadValue;" S+ N! g4 M; E% h
  64.    ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) –
    1 p+ w6 W+ {. `: v# K) A, f' A
  65.    ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );
    7 C/ I# Q/ H( T2 {) V1 {- v
  66.    if( ( ulCalculatedLoadValue  ulTimerCountsForOneTick ) )  x; m* G" I4 Y- m) q' i
  67.    {8 _$ i& Z( v& f' B& @) i/ H: O
  68.    ulCalculatedLoadValue = (ulTimerCountsForOneTick - 1UL);! ^: d6 C' H- d. W& J
  69.    }1 @! L8 u) S9 h) o9 b! E5 Y/ G
  70.    portNVIC_SYSTICK_LOAD_REG = ulCalculatedLoadValue;
    7 _+ w- H. ^. c5 Y0 o  B$ E% \3 u
  71.    ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
    " a3 r2 l9 z- h7 u6 ?5 k3 [
  72.    }& N6 D3 F; X& e; J, R
  73.    else
    4 }; p$ Y- o. G1 K, E
  74.    {7 l, [- N1 \* f. X" F* ?8 h8 b
  75. * O; K4 ^# ?! _
  76. /* 外部事件(中断)唤醒 */- i4 y0 s5 q! H/ R! [; }( ~, U! s
  77. ulCompletedSysTickDecrements = ( xExpectedIdleTime ** g# U9 K7 e& D( J
  78. ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG;
    ) s+ N; R9 Y9 Y3 q/ T3 t9 C: {5 y
  79. ulCompleteTickPeriods = ulCompletedSysTickDecrements /7 a: V% M7 C  r1 f" T1 x* v
  80. ulTimerCountsForOneTick;# x* r/ j% R2 |$ w6 f, X; ~
  81. portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1 ) *
    . W4 L* B2 P/ K# I% R, D
  82. ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;) |: h" n) M9 M4 C2 {  J
  83. }' U) }! f7 v& c, `7 d* @% f
  84. /* 重启 Systick,调整系统定时器中断为正常值 */" \  u6 V5 r/ C9 r6 L* y3 s
  85. portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
    # w- ^3 v5 t4 G
  86. portENTER_CRITICAL();, c& D( l) O3 Y- q
  87. {  N% F2 j- d; S( o, E0 H# _
  88. portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |& j- H5 O! q$ y- {3 O3 D
  89. portNVIC_SYSTICK_INT_BIT |% `: W+ u4 R  h6 W$ Z8 [$ T" {
  90. portNVIC_SYSTICK_ENABLE_BIT;  I0 M( R' @* W1 d5 i. f
  91. vTaskStepTick( ulCompleteTickPeriods );
    1 J8 D* p0 x- W' N1 g7 B. n( w
  92. portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
    1 A* r4 l5 f9 l! \
  93. }
    7 o6 Z3 I# B+ F! t2 P  h
  94. portEXIT_CRITICAL();0 \9 s( `$ L2 _$ V* p
  95. }9 o# r, Y' D5 ^7 `
  96. }
复制代码

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

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版