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

基于STM32的TouchGFX 图形应用在亮屏时的低功耗实现

[复制链接]
STMCU小助手 发布时间:2023-6-18 11:07
1. 图形应用和低功耗
8 @* O% e1 r( u9 |: X随着智能设备的普及,越来越多的设备会用到图形界面,而在 STM32 MCU 上使用TouchGFX,使得图形设计变得非常简单。其中智能手表、智能手环等产品对功耗要求比较高,这就需要在图形应用同时,结合芯片的低功耗模式来优化能耗。0 V* r/ l. X$ x5 g0 P5 A
在图形应用中使用低功耗,一般分为两种场景,一种是在关闭屏幕时,MCU 进入 STOP 模式,能极大的降低 MCU 功耗;另一种是在屏幕亮着的状态,MCU 进入 SLEEP 模式,也能降低MCU 功耗。而在 STM32L4+芯片上,LPSLEEP 模式相比 SLEEP 模式功耗更低,本文将在STM32L4R9 芯片上,结合 TouchGFX 和 LPSLEEP 来介绍如何在亮屏状态下实现 MCU 低功耗。$ `6 v+ S4 T: c  y  w' O9 X( E
2. 低功耗实现原理介绍+ m! h0 y( v- G5 l: Y7 x5 U
在使用 TouchGFX 做图形应用时,会使用到 FreeRTOS 操作系统,使用 SysTick 定时产生 1ms的中断来作为系统 tick。在所有应用任务空闲时,系统会调度 Idle 任务。此时我们能让 MCU 进入低功耗,从而达到降低 MCU 功耗的目的。% z0 B- s7 n8 o

- {: L. J2 X; A% n& S. u
2.1. Tickless Idle: p$ E3 R) x* {  C/ E, r+ ]
FreeRTOS 提供了 Tickless Idle 配置,在 FreeRTOSConfig.h 文件中,通过配置configUSE_TICKLESS_IDLE 为不同的值来配置 FreeRTOS 是否在 Idle 任务里进入低功耗,默认为 0 表示不进入低功耗。本文中将配置为 2,自定义 Idle 任务中低功耗实现。
; D  Q$ t2 E! n" x& i( n5 \
  O- z+ m- ?" S, b5 W- a( R0 p( b" r
MXTS%_0U37[GA{N7AIGY[DQ.png
3 K4 u# v$ r4 V, r- u# I% O# N  ?! L& l
FreeRTOS 中进入低功耗具体是由 vPortSuppressTicksAndSleep 函数实现。配置configUSE_TICKLESS_IDLE 为 2 时,还需要重写 vPortSetupTimerInterrupt 函数。
* u7 R/ J( W: @# g- `+ N2 a. b* k, |& o% l0 r7 b: `
2.2. TouchGFX 渲染与进 LPSLEEP 限制, p4 d0 @) F, b' w
根据参考手册描述,在进入 LPSLEEP 时要先切换到 LPRUN 模式,系统时钟要降低到2MHz,因此在进入、退出 LPSLEEP 时会有时钟的切换过程。
% b6 e2 }! A5 L; R在屏幕亮着的状态时,用户可能随时触摸屏幕,或者系统信息可能更新,因此屏幕显示的内容可能随时变化,如果此时 MCU 进入 LPSLEEP 状态,会导致系统响应不及时。另外,在屏幕内容更新时,L4R9 通过 DSI 向屏幕更新显示数据,此过程由硬件完成,在此过程中,软件也有可能会进入 LPSLEEP,这样系统时钟发生变化,会导致 DSI 更新显示数据异常,产生屏幕显示花屏的问题。0 D8 A. F* o0 c8 u# d
为了避免以上问题,在进入 LPSLEEP 前会检查当前状态,如果用户未操作屏幕,& l* L) B, q: Z: ~5 @! w( ?5 i3 u
TouchGFX 已经完成渲染,DSI 刷新过程也完成,则允许系统进入 LPSLEEP 状态。这样既保证了系统正常工作,又达到了降低功耗的目的。6 B7 d. \4 S! z0 W3 y

5 T8 Y8 K! U# U
3. 实现过程
- T( ^( b, p( l8 |重写 vPortSetupTimerInterrupt 函数,主要是设置与休眠时间补偿相关的变量,初始化 systick 寄存器:% h$ i% X7 @6 m; Y3 q" i
8 D9 ]5 l* i0 G8 q+ S1 X. v

$ J2 D: L( I) x+ ?6 w
  1. void vPortSetupTimerInterrupt( void )
    # u: E3 u3 h4 u3 `
  2. {
    # L6 `$ \- ?* R9 @6 O
  3. /* Calculate the constants required to configure the tick interrupt. */  `; P8 q+ v! E5 \! Z
  4. #if( configUSE_TICKLESS_IDLE == 2 )- G& _$ g' n) x  S! o# y1 _+ }
  5. {0 L. R& a% c; y
  6. ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
    9 _- Z! I4 t2 k/ S- w
  7. xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;1 g! c$ m9 {3 I1 C2 c% x
  8. ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ /
    0 K1 \1 z6 C5 I2 b% T9 C" C3 o
  9. configSYSTICK_CLOCK_HZ );# O6 E6 N* j' w/ Q, W9 T
  10. }
      D2 Y1 A, U$ E! I
  11. #endif /* configUSE_TICKLESS_IDLE */  i4 X8 W8 e' H, a4 e9 }, _) V
  12. /* Stop and clear the SysTick. */
    / ^- f5 U2 M; }$ j6 X; Z
  13. portNVIC_SYSTICK_CTRL_REG = 0UL;
    4 K/ n% u! _( ]$ Q1 d/ \0 `' H/ T
  14. portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
    / K- Y1 O# @: W3 S9 F
  15. /* Configure SysTick to interrupt at the requested rate. */) w$ \/ _' B) ]* k/ H
  16. portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
    $ {+ N/ o; N$ ]2 Q8 z) ?8 s! t
  17. portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT |
    7 f/ s  ?, s3 P3 X  d4 b, W
  18. portNVIC_SYSTICK_ENABLE_BIT );* t* A9 Y. {" X
  19. }
复制代码
; @9 G7 J" ~3 U  j/ Y% O( |  @/ W

# o2 l. h' P3 f
重写 vPortSuppressTicksAndSleep 函数。在进入 LPSLEEP 前,会停止 systick,在休眠完成后,需要补偿系统 tick 值,示例中使用 LPTIM 计时来补偿系统 tick 值。在进入 LPSLEEP 前先进行状态检查,只要有以下情况,都不进入低功耗模式:. |, C1 O8 d6 f, v- e7 L# Z
a) 触摸事件产生:nosleep_flg; a: |% c. \$ n3 r3 O$ w+ N
b) DMA2D busy 状态:isDMAbusy_a()
6 ~- k5 _) T# i4 a/ jc) DSI refreshing 状态:displayRefreshing0 z4 {3 z  |" _, u9 N
d) 渲染完成,请求 DSI 刷新:refreshRequested6 d- b  f& P' z* e1 r# G
  1. void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
    8 D( s6 J; V: E" k' t
  2. {$ e. f/ V' [; ^
  3. uint32_t xMaximumPossibleSuppressedTicks,ulReloadValue, ulCompleteTickPeriods,ulCount;  B9 r, U. n) A
  4. xMaximumPossibleSuppressedTicks=portMAX_16_BIT_NUMBER * configTICK_RATE_HZ / lptim_CLOCK_HZ;9 T  `' X/ {, E' ~
  5. if(nosleep_flg || isDMAbusy_a() || displayRefreshing || refreshRequested) {- s7 T# x7 y! d3 ~4 r( ^7 U
  6. if(!isDMAbusy_a() && !displayRefreshing && !refreshRequested) {/ b: `9 O- H/ n: O! |0 K
  7. if(osKernelSysTick() > nosleep_flg_t0 + nosleep_time)
    & b. l+ e- _% @
  8. nosleep_flg = 0;+ H; ]/ o# b4 f
  9. }
    ) p" S1 A) u: f- K6 M* ]
  10. return;, C) R+ `% s7 B" e
  11. }/ a+ l$ ~- n0 j- `# L* D; c% |
  12. if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks ) {
    6 B# K0 m2 o7 n  H7 j
  13. xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
    1 S, X0 I" S% v0 c
  14. }% v" Y+ P* d9 i3 u7 s8 P, q
  15. portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT;
    , T) [" R8 e- F
  16. HAL_SuspendTick();
    . |2 h% ~; n/ `3 w
  17. ulReloadValue = (lptim_CLOCK_HZ * (xExpectedIdleTime - 1UL)/configTICK_RATE_HZ);
    , l' _  s/ B8 J* o; T# n, M
  18. __DSB();  b* r9 U6 p( m( n
  19. __ISB();
    / P" K: W# ~: q# V9 h0 ?% m* b7 p
  20. LPTIM_ReloadMatch_flag=pdFALSE;
    9 }) l+ T' x* {! a) `
  21. if( eTaskConfirmSleepModeStatus() == eAbortSleep ) {: C* F  N8 X4 O& }
  22. portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;' ?4 x# x. ^. f
  23. portNVIC_SYSTICK_LOAD_REG = configCPU_CLOCK_HZ/configTICK_RATE_HZ - 1UL;
    + K) x! l& x- w3 p6 N0 F; L
  24. HAL_ResumeTick();8 i2 G" i: n; [" L3 z% N
  25. } else {" T5 N' m8 I, ^! O# P8 G' x: ^1 H
  26. MX_LPTIM1_Init(LPTIM_PRESCALER_DIV2, ulReloadValue);( J9 w9 l. K) t
  27. if( xExpectedIdleTime > 0 ) {% {1 V2 L8 G) Z
  28. __DSB();2 z  ^. N, q3 N  A2 [9 n* c9 s/ e
  29. enter_lpsleep();- A2 S0 a7 L& V  W- M7 |9 W8 J
  30. __ISB();3 X! l, d9 l% q9 f" ^* I/ {% s) n
  31. }
    - w- c1 \5 {2 {9 w: @1 R
  32. ulCount = HAL_LPTIM_ReadCounter(&Lptim1);
      m0 D4 G$ f. o- u+ j+ e
  33. HAL_LPTIM_TimeOut_Stop_IT(&Lptim1);
    " d& o, H& M9 z9 W4 P3 ?
  34. HAL_ResumeTick();
    ; `6 H" |! z" M
  35. if( LPTIM_ReloadMatch_flag != pdFALSE ) {
    1 e" D% w. l/ ]5 t, i+ t
  36. ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
      w& s& u/ g6 ^
  37. } else {7 F' q) L4 F/ q$ F1 S& m0 f& Z6 a
  38. ulCompleteTickPeriods = (ulCount * configTICK_RATE_HZ)/ lptim_CLOCK_HZ;
    . T  Z" U6 L+ W8 J$ B; c& g! G$ X* M
  39. }
    - z3 a7 Q: f. B! |' D. L9 r! e4 P; Q
  40. portENTER_CRITICAL();8 a* Q1 ~+ W+ \& i4 Q) V
  41. uwTick += ulCompleteTickPeriods;
    + D, a6 D" A& y9 M2 i
  42. vTaskStepTick( ulCompleteTickPeriods );) }* {& p, o# s
  43. portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
    " q) F6 b. m( k" m! I
  44. portNVIC_SYSTICK_LOAD_REG = configCPU_CLOCK_HZ/configTICK_RATE_HZ - 1UL;
    1 A3 o: q) Y, z" u6 K
  45. portEXIT_CRITICAL();8 s2 ^5 c6 b( n1 d0 X( l
  46. }6 F7 s2 J! s9 f+ |
  47. }
复制代码
- e+ p2 W  {. [' a2 C( h$ P+ e
TouchGFX 渲染过程包括两部分,CPU 渲染和 DMA2D 渲染。在 CPU 渲染过程中,TouchGFX任务处于执行状态,系统不会主动调度 Idle 任务。而在 DMA2D 渲染过 TouchGFX 会让出CPU,此时可能会切换到 Idle 任务执行,因此需要对 DMA2D 是否正在渲染进行判断。对DMA2D 是否在渲染的判断如下:6 h& {7 q; Z$ W' C1 S- v* _; d
  1. extern "C" uint8_t isDMAbusy_a()% S% r( H% i* |# B/ {
  2. {
    9 ?/ c, {. a8 a
  3. return ((TouchGFXHAL*)(touchgfx::HAL::getInstance()))->isDMAbusy();! A+ i1 H- a0 Q( }) j( m
  4. }
    4 d, d, u. c1 Q8 s+ P& q8 ~, c
  5. uint8_t TouchGFXHAL::isDMAbusy()9 q* p" E; l7 ?- B9 f4 a3 ~( M
  6. {
    / O  W" _) s+ [& s$ a
  7. bool dma_qe = dma.isDmaQueueEmpty();, a( S/ s; w- s) X* J/ I+ p' ]( g
  8. bool dma_run = dma.isDMARunning();
    : l$ X& P# C! i* L7 O
  9. return (uint8_t)(!dma_qe || dma_run);
    0 Y% ?5 @: d1 S* p. U4 a( c
  10. }
复制代码
7 y% q$ Z3 q1 }4 W
% ]& w4 B9 f1 u4 x! p" a& X3 f
完整版请查看:附件
$ W& o' P: A( W1 Y& c5 x TouchGFX图形应用在亮屏时的低功耗实现.pdf (255.25 KB, 下载次数: 4)
收藏 评论2 发布时间:2023-6-18 11:07

举报

2个回答
lospring 回答时间:2023-8-10 10:15:58

低功耗确实是非常需要考虑的一个点

shenxiaolin_mai 回答时间:2023-8-10 10:16:03
感谢分享!

所属标签

相似技术帖

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