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

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

[复制链接]
STMCU小助手 发布时间:2023-6-18 11:07
1. 图形应用和低功耗
5 C4 f7 H6 \/ b5 v% c: T) b, E随着智能设备的普及,越来越多的设备会用到图形界面,而在 STM32 MCU 上使用TouchGFX,使得图形设计变得非常简单。其中智能手表、智能手环等产品对功耗要求比较高,这就需要在图形应用同时,结合芯片的低功耗模式来优化能耗。
+ B4 X* M8 R+ H/ ]! G9 z. @. ]2 y* B在图形应用中使用低功耗,一般分为两种场景,一种是在关闭屏幕时,MCU 进入 STOP 模式,能极大的降低 MCU 功耗;另一种是在屏幕亮着的状态,MCU 进入 SLEEP 模式,也能降低MCU 功耗。而在 STM32L4+芯片上,LPSLEEP 模式相比 SLEEP 模式功耗更低,本文将在STM32L4R9 芯片上,结合 TouchGFX 和 LPSLEEP 来介绍如何在亮屏状态下实现 MCU 低功耗。
+ h" X& v* T: ?5 h
2. 低功耗实现原理介绍
) P3 Z# S! w* I; t3 u2 n在使用 TouchGFX 做图形应用时,会使用到 FreeRTOS 操作系统,使用 SysTick 定时产生 1ms的中断来作为系统 tick。在所有应用任务空闲时,系统会调度 Idle 任务。此时我们能让 MCU 进入低功耗,从而达到降低 MCU 功耗的目的。0 l/ v, L" ?( }

" O6 Y+ K3 }5 D; L; H9 r
2.1. Tickless Idle
8 S' e! a4 T! u# e  fFreeRTOS 提供了 Tickless Idle 配置,在 FreeRTOSConfig.h 文件中,通过配置configUSE_TICKLESS_IDLE 为不同的值来配置 FreeRTOS 是否在 Idle 任务里进入低功耗,默认为 0 表示不进入低功耗。本文中将配置为 2,自定义 Idle 任务中低功耗实现。: O* c/ o# z' `: W1 e6 A
5 s' K: p% Z0 E- k3 Z( i
MXTS%_0U37[GA{N7AIGY[DQ.png ' N: I3 a* J/ a: Z% p$ F
7 \! `1 r8 s9 p6 N& g0 x8 W5 ~
FreeRTOS 中进入低功耗具体是由 vPortSuppressTicksAndSleep 函数实现。配置configUSE_TICKLESS_IDLE 为 2 时,还需要重写 vPortSetupTimerInterrupt 函数。 5 g& X0 y( @: ~
3 N% c& E6 D0 O* J0 D3 i* }
2.2. TouchGFX 渲染与进 LPSLEEP 限制
. q8 L: K4 g' n" C根据参考手册描述,在进入 LPSLEEP 时要先切换到 LPRUN 模式,系统时钟要降低到2MHz,因此在进入、退出 LPSLEEP 时会有时钟的切换过程。1 G& V1 j3 T7 z) Y
在屏幕亮着的状态时,用户可能随时触摸屏幕,或者系统信息可能更新,因此屏幕显示的内容可能随时变化,如果此时 MCU 进入 LPSLEEP 状态,会导致系统响应不及时。另外,在屏幕内容更新时,L4R9 通过 DSI 向屏幕更新显示数据,此过程由硬件完成,在此过程中,软件也有可能会进入 LPSLEEP,这样系统时钟发生变化,会导致 DSI 更新显示数据异常,产生屏幕显示花屏的问题。( X6 J2 A8 g8 K7 l) U; H
为了避免以上问题,在进入 LPSLEEP 前会检查当前状态,如果用户未操作屏幕,1 h" l, X2 n& h: n: P# L
TouchGFX 已经完成渲染,DSI 刷新过程也完成,则允许系统进入 LPSLEEP 状态。这样既保证了系统正常工作,又达到了降低功耗的目的。, n: b- Y5 f) e' k/ _
/ k7 e4 M* d# y7 c
3. 实现过程
# I! [$ P, p) p0 N) t重写 vPortSetupTimerInterrupt 函数,主要是设置与休眠时间补偿相关的变量,初始化 systick 寄存器:  ?2 _' A5 B0 A0 K1 S! L8 H4 Z3 X, q
; @. n* c# l1 Y+ Z# k' P8 i# d
0 C) I7 a8 v, Y" F0 |
  1. void vPortSetupTimerInterrupt( void )
    7 r1 v' [6 M# U7 K( x8 J# K
  2. {# @& P! C. w5 }% T" d' G
  3. /* Calculate the constants required to configure the tick interrupt. */3 d% G/ E$ W# s# d" i- x2 B0 z
  4. #if( configUSE_TICKLESS_IDLE == 2 )# z( S( x; p1 d6 X% N5 ?
  5. {/ B  l! P6 O$ {+ |
  6. ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
    8 Q' t' _, \5 I% Y; N! h* }
  7. xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;* P1 a/ K/ y+ g. o1 k
  8. ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ /
    7 `' @& V* ^% A. V0 @7 X
  9. configSYSTICK_CLOCK_HZ );% A! N% ]1 ?0 r: H$ S. ^
  10. }9 U8 x% A! D" m% l0 z
  11. #endif /* configUSE_TICKLESS_IDLE */( A8 }5 a1 Y% y2 X! U
  12. /* Stop and clear the SysTick. */
    % l0 }+ n: W: U7 e) W
  13. portNVIC_SYSTICK_CTRL_REG = 0UL;
    9 c& a9 v, s5 g
  14. portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
    2 N8 Z5 \$ e% d; [5 |
  15. /* Configure SysTick to interrupt at the requested rate. */! g  r* O) S6 |4 ?, I5 q
  16. portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
    1 F% w& q2 r' S; ~7 _  v
  17. portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT |
    " m0 L3 x* }8 T1 P9 v9 h; Q. G
  18. portNVIC_SYSTICK_ENABLE_BIT );$ V0 v0 e5 i4 Q' t
  19. }
复制代码

3 f: \0 e; j, U+ s1 R4 ?
, @/ k( q0 U& C9 u
重写 vPortSuppressTicksAndSleep 函数。在进入 LPSLEEP 前,会停止 systick,在休眠完成后,需要补偿系统 tick 值,示例中使用 LPTIM 计时来补偿系统 tick 值。在进入 LPSLEEP 前先进行状态检查,只要有以下情况,都不进入低功耗模式:
$ q! `) B, m; z$ X$ qa) 触摸事件产生:nosleep_flg7 U) O- b+ t( J$ ]! h
b) DMA2D busy 状态:isDMAbusy_a()
9 r: R/ ~- c2 J8 n1 q7 Kc) DSI refreshing 状态:displayRefreshing
: ]: V+ ~& V( B9 L& Nd) 渲染完成,请求 DSI 刷新:refreshRequested5 E  T% |! `% i4 v. _, V8 d
  1. void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ): |& n, Z5 m0 {8 J
  2. {
    4 K/ Q) x, n' Y8 M3 R  o
  3. uint32_t xMaximumPossibleSuppressedTicks,ulReloadValue, ulCompleteTickPeriods,ulCount;: C" b4 _1 B& I# f0 c
  4. xMaximumPossibleSuppressedTicks=portMAX_16_BIT_NUMBER * configTICK_RATE_HZ / lptim_CLOCK_HZ;
    ! J! ^: N  W1 c
  5. if(nosleep_flg || isDMAbusy_a() || displayRefreshing || refreshRequested) {
    % B7 I3 q  m* v) Z8 z, ~" T  r
  6. if(!isDMAbusy_a() && !displayRefreshing && !refreshRequested) {! }2 q& q" B4 V3 q9 x8 V' r
  7. if(osKernelSysTick() > nosleep_flg_t0 + nosleep_time)
    . M& U, S  R2 o; U: h3 p
  8. nosleep_flg = 0;  J. T: O+ o" [6 E, Z, G
  9. }
    8 Z% i$ B0 v! K+ e! w0 h; |
  10. return;! f$ ]0 H5 h6 V& l* C: S, {
  11. }5 X2 t2 X; z( @" W
  12. if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks ) {/ D( W; p  p) o  ]( Z5 N) p
  13. xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
    3 G- `7 L+ M( ?, `9 |3 l4 s
  14. }! v2 U$ n9 z# R9 v) A! ]: e" D" x
  15. portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT;0 u' p$ l7 f" c; J; I
  16. HAL_SuspendTick();5 Y  T9 A; z8 U* ]& R- _& A
  17. ulReloadValue = (lptim_CLOCK_HZ * (xExpectedIdleTime - 1UL)/configTICK_RATE_HZ);  B6 e4 w6 T+ E3 h
  18. __DSB();
    2 @. b, B4 X6 b0 j
  19. __ISB();2 ~3 P0 i4 D) [$ z
  20. LPTIM_ReloadMatch_flag=pdFALSE;' t( P& w1 N+ W, b5 Z  q
  21. if( eTaskConfirmSleepModeStatus() == eAbortSleep ) {
    6 S. T4 h+ M" X9 y2 ^' ?" H+ R
  22. portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
      ^' y3 u( R( H* y7 e
  23. portNVIC_SYSTICK_LOAD_REG = configCPU_CLOCK_HZ/configTICK_RATE_HZ - 1UL;
    & l( G" C: D. }) B2 j
  24. HAL_ResumeTick();
    ! j$ O' D0 J# `- [3 i
  25. } else {9 ~% {7 N$ i6 X: `1 i
  26. MX_LPTIM1_Init(LPTIM_PRESCALER_DIV2, ulReloadValue);
    ) a% j2 T  w$ h) t# M3 e/ j
  27. if( xExpectedIdleTime > 0 ) {: J" N" y* ?9 l" t
  28. __DSB();! d6 e" [- `- f; e
  29. enter_lpsleep();  o9 b/ L( H2 l' U+ b* O
  30. __ISB();
    9 S, H# z& D; ?9 H: U2 z/ t! H
  31. }
    ) q' |# I, a* y6 R4 T
  32. ulCount = HAL_LPTIM_ReadCounter(&Lptim1);' i% h. d  `/ A0 L6 U
  33. HAL_LPTIM_TimeOut_Stop_IT(&Lptim1);( `7 v0 K7 o: i, C4 \+ G% ?6 y
  34. HAL_ResumeTick();
    " E5 U, O- Y4 Z1 d+ J3 x! L
  35. if( LPTIM_ReloadMatch_flag != pdFALSE ) {- x5 Y$ S1 }" Z9 f
  36. ulCompleteTickPeriods = xExpectedIdleTime - 1UL;0 ^1 P9 S, Q+ W, Q, k1 T
  37. } else {
    - G- l. U; D; H0 m
  38. ulCompleteTickPeriods = (ulCount * configTICK_RATE_HZ)/ lptim_CLOCK_HZ;
    ) w0 U# a$ H6 w& W7 u8 z
  39. }8 V: l4 `# X- L- O. D% b( O* t
  40. portENTER_CRITICAL();
    : d0 O2 Q) ~( O0 H; t% A$ y0 f3 @
  41. uwTick += ulCompleteTickPeriods;
    - v4 X; e: z( p2 r! T$ W* h. m
  42. vTaskStepTick( ulCompleteTickPeriods );
    , A: X3 A, Q7 i- C( A
  43. portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
    + x) E8 ~& I' {
  44. portNVIC_SYSTICK_LOAD_REG = configCPU_CLOCK_HZ/configTICK_RATE_HZ - 1UL;( u8 |) n( @) r7 o
  45. portEXIT_CRITICAL();4 v9 U3 i8 e8 x  E0 }9 v6 k
  46. }
    . a5 d, N" G5 E1 E7 Q9 B# c
  47. }
复制代码
- W9 R' N7 U) A
TouchGFX 渲染过程包括两部分,CPU 渲染和 DMA2D 渲染。在 CPU 渲染过程中,TouchGFX任务处于执行状态,系统不会主动调度 Idle 任务。而在 DMA2D 渲染过 TouchGFX 会让出CPU,此时可能会切换到 Idle 任务执行,因此需要对 DMA2D 是否正在渲染进行判断。对DMA2D 是否在渲染的判断如下:
' q+ p  d" z% d1 f) b# I6 E8 Z4 H3 V
  1. extern "C" uint8_t isDMAbusy_a()
    * w, f2 K* X$ Q! B: ]
  2. {
    6 P# e! L7 w2 V4 [
  3. return ((TouchGFXHAL*)(touchgfx::HAL::getInstance()))->isDMAbusy();: H% [( P+ s6 t0 K
  4. }7 ^( H) v, k3 S3 P( }! C
  5. uint8_t TouchGFXHAL::isDMAbusy()9 G/ K* Y. i5 C( R( G$ g, `, O
  6. {. J# z$ q$ O9 I, |
  7. bool dma_qe = dma.isDmaQueueEmpty();4 P2 w9 ^& n: C1 j6 w
  8. bool dma_run = dma.isDMARunning();! \. y6 {) B. I( `
  9. return (uint8_t)(!dma_qe || dma_run);( V- V1 {: o2 j+ x3 G2 h/ e
  10. }
复制代码

+ k2 q9 k: n$ b
& v7 L* |' ?" _& Q; w; S 完整版请查看:附件* D, l! o7 E5 V. N
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管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版