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

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

[复制链接]
STMCU小助手 发布时间:2022-3-1 13:02
一. 前言+ 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 02CK6PADGGOBTP$(N7I3}WG.png
+ 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: ^
~{O@L}ZD{V30{YC3D}K~@BX.png
. x; e" T8 ^+ p/ Y: s
/ i  {: G5 ~- \" T- M. e" N使能. N) O/ f% d) U# P7 I
  1.    #define configUSE_TICKLESS_IDLE 1
复制代码
  P% W) i9 Z7 g* f
空闲任务(RTOS 空闲时自动调用)
. S( N  _# S7 n3 T" J8 ?% K
  1.    /* Idle 任务 */
    . G8 |: I" K# m
  2.    void prvIdleTask( void *pvParameters )5 h+ h: J1 Q0 X2 X2 S7 W# a# x
  3.    {
    3 ]9 V" P, @# z# f) Z5 D
  4.    for( ; ; )
    " H2 @8 ^! T) D# f
  5.    {
    3 i+ p5 C( j9 C" Z! _* E) [2 W$ z
  6.    ...
    / o# m9 r* H, B
  7.    #if ( configUSE_TICKLESS_IDLE != 0 )
    ! a# b* r! s8 k' j0 |3 _
  8.    {& B) r" N0 o' \& V3 @( T4 ~; m
  9.    TickType_t xExpectedIdleTime;5 f* M  G) @! d& T/ i9 f
  10.    /* 用户策略以决定是否需要进入 Tickless Mode */
    . T1 U' x: u0 Z: U! y
  11.    xExpectedIdleTime = prvGetExpectedIdleTime();. z0 T# {- L: ~' d5 k' _. e
  12.    if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
    7 u, g/ g3 k2 F6 O
  13.    {6 ^% P; \8 Z4 G1 d1 z% T
  14.    vTaskSuspendAll(); // 挂起调度器" j2 {8 e" s# _+ z
  15.    {3 R$ T7 L0 k) y( K+ R
  16.    configASSERT( xNextTaskUnblockTime >= xTickCount );$ D7 p4 ]( |& |8 P
  17.    xExpectedIdleTime = prvGetExpectedIdleTime();
    & {8 L- [  u/ \% t3 h* |
  18.    if( xExpectedIdleTime >=
    & U8 e# l8 x& Y" C, q7 ]$ a! }. z
  19.    configEXPECTED_IDLE_TIME_BEFORE_SLEEP )  C) L6 e; A% t* U. r* a
  20.    {
    % O2 C; Q$ d/ @6 i$ F" Z
  21.    /* 用户函数接口 */7 Z4 O; |& l3 g; v% a; P0 P
  22.    /* 1. 进入低功耗模式和如何退出低功耗模式 */+ V" X# j6 r- E
  23.    /* 2. 系统时间补偿 */
    4 P, J+ l4 w% ?- ]
  24.    portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
    * Q) G3 F' {! \" R7 R
  25.    }
    ( B" V2 F' [3 \4 ]$ K  T/ @$ a
  26.    }
    : o. B! f  c5 p9 j4 f5 g& z: }
  27.    (void) xTaskResumeAll(); // 恢复调度器! A+ B$ F1 Q. L) a  C7 x
  28.    }
    ) o  e4 T2 l* ?0 R
  29.    }
    # W4 @3 f$ _) R8 u. d; Z3 G
  30.    #endif /* configUSE_TICKLESS_IDLE */9 s' L. o0 ]* i. N8 Y
  31.    ...2 L9 T  |3 Q5 l
  32.    }
    9 D6 S4 @8 U+ V4 y
  33.    }
复制代码

+ D2 u% V4 F4 s0 X; p低功耗模式处理(根据 MCU 的低功耗模式编写代码,代码有点长……)/ ?7 R) o* X$ Z% \5 W6 x9 o$ P! B5 J
  1.    void vPortSuppressTicksAndSleep( portTickType xExpectedIdleTime ), R2 V* a( L1 Q# f: E& G! P
  2.    {+ \! U2 y' Y9 k0 z  @- S0 r
  3.    unsigned long ulReloadValue, ulCompleteTickPeriods,
    . h8 a, _3 b5 u- T; g* R7 Z
  4.    ulCompletedSysTickDecrements;
    : ~+ t" p- Y* b8 b' f4 D- s2 d
  5.    portTickType xModifiableIdleTime;
    3 v9 U  B1 |9 Q2 x. g7 n( ~
  6.    /* 最长睡眠时间不可以超过定时器的最大定时值 */
    / y% V4 x& v: t; Y( T
  7.    /* 通过调整定时器的时间基准可以获得更理想的最大定时值 */6 W2 d  V% R! i& M
  8.    if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )# v+ l4 h' J3 n/ ~& A5 I/ t
  9.    {
    8 _$ Q" D% {/ |9 F9 a0 d$ F7 y
  10.    xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
    3 ]& N) T; u2 r" M; {% j3 r
  11.    }0 M% L& I# N7 w
  12.    /* 停止 SysTick */
    " i0 w. N9 C3 }- E( c! p2 z
  13.    portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |. I& D3 l* x6 _2 _4 h
  14.    portNVIC_SYSTICK_INT_BIT;: k. K6 k% |& {5 g" z* W9 g
  15.    /* 计算唤醒时的系统时间,用于唤醒后的系统时间补偿 */
    8 Y  U6 h+ @0 a$ y  b- t' y0 A
  16.    ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG +
    / Y$ H# m0 Y& }) C6 D$ j
  17.    ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) );  w9 Q* j4 Y3 }$ t$ Z
  18.    if( ulReloadValue > ulStoppedTimerCompensation )
    9 J0 T: ?' V1 T# E9 Q
  19.    {
    & P& h5 ?$ b( |$ [* j( ^9 N
  20.    ulReloadValue -= ulStoppedTimerCompensation;
    ! J6 g: u3 F+ }  ?1 O* B# P0 E
  21.    }
    $ k; O/ p5 G& P. F2 Z4 h! `9 R
  22.    __disable_interrupt();4 g* p4 |( x* m1 B+ J, M
  23.    /* 确认下是否可以进入低功耗模式 */
    + J$ G* t0 K2 ]/ n
  24.    if( eTaskConfirmSleepModeStatus() == eAbortSleep )8 l( o8 \& P1 E1 ~( o- g, q+ N
  25.    {( G  X3 {) O+ s* @# y
  26.    /* 不可以,重新启动系统定时器 */
    4 e' }- e$ y9 G* u. U% @' `
  27.    portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG;
    & N3 v  _5 Y' \& l
  28.    portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |3 {( R0 {+ v2 P" X' F
  29.    portNVIC_SYSTICK_INT_BIT |
    ) F. K) P* j9 s' y
  30.    portNVIC_SYSTICK_ENABLE_BIT;, O& J% U  I9 g3 d8 ?  Z  F
  31.    portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;$ {9 J6 l& m. Z+ G4 ]+ r
  32.    __enable_interrupt();
    * k& y( K3 c. g
  33.    }( _* l2 d5 ?- Z! n
  34.    else
    3 H% [0 U- w) }( h
  35.    {
    ( t1 a5 j( C+ ]
  36.    /* 可以进入低功耗模式 */
    . U" u0 Q+ ~; o8 o3 Y+ R
  37.    /* 保存时间补偿,重启系统定时器 */* ~; c, j2 j; _9 [" A+ {
  38.    portNVIC_SYSTICK_LOAD_REG = ulReloadValue;4 a0 P5 o- E2 P
  39.    portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;4 z5 K2 M7 j1 |3 I8 _, Q
  40.    portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |2 _+ E% C- d8 K) g, o
  41.    portNVIC_SYSTICK_INT_BIT |
    4 Q& Y% d* W2 M4 Q: v8 @7 E
  42.    portNVIC_SYSTICK_ENABLE_BIT;- `5 k2 k6 c+ W+ D3 |* u' J! s
  43.    /* 进入低功耗模式,可以通过 configPRE_SLEEP_PROCESSING 函数进行低功耗模式下
    : i/ C0 D$ z: G" d% [' }0 m
  44.    时钟及外设的配置*/. J9 M8 |$ R" J' L
  45.    xModifiableIdleTime = xExpectedIdleTime;8 e' I( Q1 F5 E- F, T$ N6 Y
  46.    configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
    ! A! U. B0 n" w# G6 ?# {: A
  47.    if( xModifiableIdleTime > 0 )7 P5 p  a3 m8 x; {$ y8 F) q
  48.    {
    / H: v0 E) ?/ q& ^/ D: Z
  49.    __DSB();
    ; f  x6 n+ W0 O! M1 G9 p
  50.    __WFI();: R& \! W5 G) u8 J2 v  ~% j
  51.    __ISB();
    3 \1 S8 m3 ?) t% i
  52.    }5 y% e  x7 N" _4 `( G; x/ e
  53.    /* 退出低功耗模式 */
    $ N/ l* J* `- V: x, Z0 K9 d4 @) K
  54.    configPOST_SLEEP_PROCESSING( xExpectedIdleTime );* ^  f7 Q, w: f% E$ R6 J) ?4 F
  55.    portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |! M$ Q3 {' _/ o
  56.    portNVIC_SYSTICK_INT_BIT;
    # t% v  J. n6 p- ?; |2 _
  57.    __disable_interrupt()
    7 `( l7 X" S% a
  58.    __enable_interrupt();
    + z' D3 Y' G6 d2 r( d! |9 x& h+ x
  59.    /*唤醒有两种情况:系统定时器或者外部事件(中断)*/" J  D/ r5 d" R; J2 G  ?
  60.    if((portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT) != 0)$ u! M5 f% ?8 c7 ^: ^
  61.    {% B/ J' f# z+ ?6 {
  62.    /* 系统定时器唤醒,时间补偿 */- h4 H* ~5 m0 r  ~+ l
  63.    unsigned long ulCalculatedLoadValue;
    3 o3 D# s2 U/ S
  64.    ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) –0 B/ y" J, X4 H5 y; v( f- o
  65.    ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );1 o5 B$ t( M3 U$ E; j
  66.    if( ( ulCalculatedLoadValue  ulTimerCountsForOneTick ) ), W' K/ X5 {8 v* t
  67.    {% m2 D- @) h$ ]- i) @
  68.    ulCalculatedLoadValue = (ulTimerCountsForOneTick - 1UL);' D! Y% s9 y, q( j5 @; Z: a
  69.    }% D9 G5 i) k! k/ u2 w/ a
  70.    portNVIC_SYSTICK_LOAD_REG = ulCalculatedLoadValue;
    . B! W" {$ l, f$ b
  71.    ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
    8 X8 Q+ @/ k& ]- r
  72.    }0 A9 _- W, M3 S4 A5 f( s
  73.    else
    : q/ Z8 e' ^/ |8 E! W+ ]$ f
  74.    {$ V5 p7 W$ p1 I1 q; t2 d) g! c! N

  75. ' \  C+ W4 E; f
  76. /* 外部事件(中断)唤醒 */
    + H1 |; t6 C: k) z6 H
  77. ulCompletedSysTickDecrements = ( xExpectedIdleTime *
    # c6 ^. R* V: I0 m7 Z
  78. ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG;3 h; P" s! }1 ?0 z, P
  79. ulCompleteTickPeriods = ulCompletedSysTickDecrements /
    1 s. I2 F+ H+ v
  80. ulTimerCountsForOneTick;' w" S% S; ]' D. W0 ]' \( l' g8 F
  81. portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1 ) *5 q* Y- u; v! @1 A7 R$ l/ r  @
  82. ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;; M& B/ q5 m( X2 {
  83. }8 @' U/ o' {' U* m
  84. /* 重启 Systick,调整系统定时器中断为正常值 */
    ( j4 Q+ K; [1 T4 v2 w$ T& O9 h
  85. portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
    . s" p9 r( {& O' s% u1 ]
  86. portENTER_CRITICAL();& @$ j2 m4 ]+ i, y& J) q+ N6 a' r7 g
  87. {2 \% E% ?& Q9 k# K
  88. portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |, ^( V" r$ J8 ?3 E
  89. portNVIC_SYSTICK_INT_BIT |
    6 _. t# ?# k7 r4 U# J' l7 ]& ?$ x
  90. portNVIC_SYSTICK_ENABLE_BIT;# o: R9 `; H% x0 P3 k! r
  91. vTaskStepTick( ulCompleteTickPeriods );6 G' ^7 T8 `8 C7 U- h* B
  92. portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;* L, [+ b/ D2 h) z  ?% {
  93. }
    3 E0 y; c  G5 ]& z  V
  94. portEXIT_CRITICAL();. I4 ~1 z  D6 H' ]( X
  95. }
    3 p6 T( N" w' M4 `: m/ H
  96. }
复制代码

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

举报

0个回答

所属标签

相似分享

官网相关资源

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