1. 图形应用和低功耗
3 y4 k' {5 A' D/ z8 t7 O/ G随着智能设备的普及,越来越多的设备会用到图形界面,而在 STM32 MCU 上使用TouchGFX,使得图形设计变得非常简单。其中智能手表、智能手环等产品对功耗要求比较高,这就需要在图形应用同时,结合芯片的低功耗模式来优化能耗。6 d6 {" }% W5 t- W. w! Z5 u
在图形应用中使用低功耗,一般分为两种场景,一种是在关闭屏幕时,MCU 进入 STOP 模式,能极大的降低 MCU 功耗;另一种是在屏幕亮着的状态,MCU 进入 SLEEP 模式,也能降低MCU 功耗。而在 STM32L4+芯片上,LPSLEEP 模式相比 SLEEP 模式功耗更低,本文将在STM32L4R9 芯片上,结合 TouchGFX 和 LPSLEEP 来介绍如何在亮屏状态下实现 MCU 低功耗。9 N- h* @( Z9 }5 A& R& u
2. 低功耗实现原理介绍1 V- h" e; O( V# h( J
在使用 TouchGFX 做图形应用时,会使用到 FreeRTOS 操作系统,使用 SysTick 定时产生 1ms的中断来作为系统 tick。在所有应用任务空闲时,系统会调度 Idle 任务。此时我们能让 MCU 进入低功耗,从而达到降低 MCU 功耗的目的。0 n% t: W' h. y! F4 u) E+ D% n
`0 S% c6 {# a2.1. Tickless Idle
- s5 I% ^! n' U* F' C+ RFreeRTOS 提供了 Tickless Idle 配置,在 FreeRTOSConfig.h 文件中,通过配置configUSE_TICKLESS_IDLE 为不同的值来配置 FreeRTOS 是否在 Idle 任务里进入低功耗,默认为 0 表示不进入低功耗。本文中将配置为 2,自定义 Idle 任务中低功耗实现。 A" c4 s+ b* G
' {2 e: r5 Q; H; A# Z7 z
; ^7 {* R' ~% L Z# `
7 ~( H8 Q! V* h* CFreeRTOS 中进入低功耗具体是由 vPortSuppressTicksAndSleep 函数实现。配置configUSE_TICKLESS_IDLE 为 2 时,还需要重写 vPortSetupTimerInterrupt 函数。 / i, K# [. T* g/ f" {4 l) e
5 k% R6 R" d1 A" N4 l2 U: n7 j9 j2.2. TouchGFX 渲染与进 LPSLEEP 限制6 U n3 E0 j; O! Y$ V* d5 m
根据参考手册描述,在进入 LPSLEEP 时要先切换到 LPRUN 模式,系统时钟要降低到2MHz,因此在进入、退出 LPSLEEP 时会有时钟的切换过程。6 H6 [) I4 E6 @2 x$ G
在屏幕亮着的状态时,用户可能随时触摸屏幕,或者系统信息可能更新,因此屏幕显示的内容可能随时变化,如果此时 MCU 进入 LPSLEEP 状态,会导致系统响应不及时。另外,在屏幕内容更新时,L4R9 通过 DSI 向屏幕更新显示数据,此过程由硬件完成,在此过程中,软件也有可能会进入 LPSLEEP,这样系统时钟发生变化,会导致 DSI 更新显示数据异常,产生屏幕显示花屏的问题。! Y7 W3 F0 Q2 `' l
为了避免以上问题,在进入 LPSLEEP 前会检查当前状态,如果用户未操作屏幕,: y1 ^, s6 C% w% w6 D
TouchGFX 已经完成渲染,DSI 刷新过程也完成,则允许系统进入 LPSLEEP 状态。这样既保证了系统正常工作,又达到了降低功耗的目的。
* h8 h+ M& n# ~7 \5 j0 X6 t- f `, o2 ^. B) q
3. 实现过程" }( z, D* ~7 B3 i s/ |$ C
重写 vPortSetupTimerInterrupt 函数,主要是设置与休眠时间补偿相关的变量,初始化 systick 寄存器:5 j$ y" m( u8 Z: A$ T' C
5 A5 H: d& {% ^: a. ]/ I& y2 T
/ m( E* T z& B6 @
- void vPortSetupTimerInterrupt( void )
2 y" R, F* i# Z; a8 j, H - {
* o+ j K/ v" e* r" u8 X - /* Calculate the constants required to configure the tick interrupt. */7 M! J4 D8 k0 I( w* ?# K; D
- #if( configUSE_TICKLESS_IDLE == 2 )
( a8 q) X: {+ S. J" S T - {& V& H" s9 Y1 ^* c
- ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
w" |( ~3 b, V& O5 `4 p2 c$ h - xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;4 Y* e) w* ^- ^; A% ?
- ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / 1 C5 D5 ^% @0 l- B1 j2 d$ |# _
- configSYSTICK_CLOCK_HZ );8 l$ C- P% e. ~
- }7 {% X8 W: N5 h% _, X% j1 [
- #endif /* configUSE_TICKLESS_IDLE */
5 ]' P9 ?5 `" t; q2 S; S, D* m0 {6 V - /* Stop and clear the SysTick. */
, v, L/ F) P% T9 j% T5 I0 | - portNVIC_SYSTICK_CTRL_REG = 0UL;
3 L# c( J5 N" g - portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
/ W' Z$ _+ c: X9 g - /* Configure SysTick to interrupt at the requested rate. */5 n1 ?2 u" W2 m1 p( h
- portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
3 ?! `. l7 w% E( m, P K/ q - portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT |
4 M. G, H% l% f - portNVIC_SYSTICK_ENABLE_BIT );& _$ D+ j+ @9 t! C5 r( F
- }
复制代码
4 u5 s7 [, q5 b. a; Q: a3 q( r u4 Q+ ^- M( _" u
重写 vPortSuppressTicksAndSleep 函数。在进入 LPSLEEP 前,会停止 systick,在休眠完成后,需要补偿系统 tick 值,示例中使用 LPTIM 计时来补偿系统 tick 值。在进入 LPSLEEP 前先进行状态检查,只要有以下情况,都不进入低功耗模式:
0 l ~1 b2 N# T% i, Xa) 触摸事件产生:nosleep_flg7 }* P o6 a" @: `( A
b) DMA2D busy 状态:isDMAbusy_a()
2 m* O6 U/ y" U$ Y% E, L: ?8 Lc) DSI refreshing 状态:displayRefreshing3 T* P) n' I' M2 A6 s2 N& _7 L
d) 渲染完成,请求 DSI 刷新:refreshRequested
' N [2 y( R4 c" Z; X$ S0 ~- void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )' D% d& F# R" y& k
- {( j) r% \6 i; O6 ], R- l1 y( ?+ E
- uint32_t xMaximumPossibleSuppressedTicks,ulReloadValue, ulCompleteTickPeriods,ulCount;
) T) a9 `' K& I7 ?3 g - xMaximumPossibleSuppressedTicks=portMAX_16_BIT_NUMBER * configTICK_RATE_HZ / lptim_CLOCK_HZ;' u2 |0 K3 r1 D5 T% w
- if(nosleep_flg || isDMAbusy_a() || displayRefreshing || refreshRequested) {
% Y6 G, x* l$ d, e( d# a6 M; z - if(!isDMAbusy_a() && !displayRefreshing && !refreshRequested) {: r1 }3 a8 k5 h! O" A5 Y j
- if(osKernelSysTick() > nosleep_flg_t0 + nosleep_time)# W/ U. `$ n& I" y
- nosleep_flg = 0;
/ Y2 b( }3 g6 j - }; @# F: g& X. m; H
- return;) r9 P+ l+ q1 |# S. A% P( A6 U
- }
; h5 ?- h/ ~& s# Q5 y - if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks ) {& S9 ^. Z6 g4 M: H1 o, S6 L5 T
- xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
6 O3 p/ A( Y9 a3 P* p P - }
# [) ], y y* a: W; Z - portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT;4 }) y" f4 H( b0 u! ~8 y* c
- HAL_SuspendTick();, {& h3 F3 V; G) H! v
- ulReloadValue = (lptim_CLOCK_HZ * (xExpectedIdleTime - 1UL)/configTICK_RATE_HZ);
8 t3 x- j8 O1 \- H - __DSB();
5 R& @: H+ v( x/ c8 @4 W) h) u - __ISB();7 `# F# P0 k% m; r
- LPTIM_ReloadMatch_flag=pdFALSE;
0 m4 a, G& x- S8 G0 n6 B - if( eTaskConfirmSleepModeStatus() == eAbortSleep ) {
% j5 L; v% C7 i, M7 Z - portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;; j5 i/ t9 E6 H4 {4 {4 {; k% p
- portNVIC_SYSTICK_LOAD_REG = configCPU_CLOCK_HZ/configTICK_RATE_HZ - 1UL;8 i% W+ S5 @, w
- HAL_ResumeTick();/ Y# k3 p/ o0 h6 Y! x$ Z6 h& U
- } else {" c- B; J( L; U8 H& E7 d! j
- MX_LPTIM1_Init(LPTIM_PRESCALER_DIV2, ulReloadValue);
+ w: K4 t7 b9 a9 y - if( xExpectedIdleTime > 0 ) {& h+ K$ X* m7 G
- __DSB();
! D a# x" w- U2 d; }+ i - enter_lpsleep();; \% ?/ R& v8 o
- __ISB();
) L: Q0 t# Z7 F6 c - }
8 S2 A" a# H/ U$ t" A, J3 } - ulCount = HAL_LPTIM_ReadCounter(&Lptim1);$ I1 n8 c* u E7 W2 M2 y
- HAL_LPTIM_TimeOut_Stop_IT(&Lptim1);
+ x5 \$ ?0 Z) S ] - HAL_ResumeTick();
0 l- O& f; g0 s- J - if( LPTIM_ReloadMatch_flag != pdFALSE ) {0 ]5 Y. l* M5 t) I# j$ o4 @/ i2 w- q
- ulCompleteTickPeriods = xExpectedIdleTime - 1UL;3 M' F; O% M. `# @
- } else {
, E2 |6 W2 O! w5 A1 C# a/ C - ulCompleteTickPeriods = (ulCount * configTICK_RATE_HZ)/ lptim_CLOCK_HZ;
7 B5 ~- e! o5 o7 X3 t1 Q( v - }' E$ I/ x" q0 |# V: _
- portENTER_CRITICAL();
2 f# U, O: E! _8 K8 ? - uwTick += ulCompleteTickPeriods;6 Q+ t- q7 }8 a4 b
- vTaskStepTick( ulCompleteTickPeriods );
, n# {1 H: v8 G. n+ z - portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
6 E! O$ q, w) K9 s! I - portNVIC_SYSTICK_LOAD_REG = configCPU_CLOCK_HZ/configTICK_RATE_HZ - 1UL;) N7 Z# | w" D% N% }( p
- portEXIT_CRITICAL();0 H# i3 g# _' M: {# s4 t- }2 z
- }" o% I8 X* n# U+ h
- }
复制代码
$ V0 k T) \! B9 _0 ~. o( K6 xTouchGFX 渲染过程包括两部分,CPU 渲染和 DMA2D 渲染。在 CPU 渲染过程中,TouchGFX任务处于执行状态,系统不会主动调度 Idle 任务。而在 DMA2D 渲染过 TouchGFX 会让出CPU,此时可能会切换到 Idle 任务执行,因此需要对 DMA2D 是否正在渲染进行判断。对DMA2D 是否在渲染的判断如下:
( k4 ~3 l }1 A5 n$ v5 v- extern "C" uint8_t isDMAbusy_a()% @. F6 L- |- T6 d; K! l6 b0 C
- {; C$ [! j4 X P6 ?9 \
- return ((TouchGFXHAL*)(touchgfx::HAL::getInstance()))->isDMAbusy();' W- S* y! d, Q
- }$ N. P2 T S6 L
- uint8_t TouchGFXHAL::isDMAbusy()
* a2 j! }/ R, {* r2 V - {
$ i- v( U" m7 a' _# K& C# `0 |3 U! P - bool dma_qe = dma.isDmaQueueEmpty();. e8 W: t, Y: x s
- bool dma_run = dma.isDMARunning();. L0 v" s0 z9 V5 i3 V6 v9 Z7 ?
- return (uint8_t)(!dma_qe || dma_run);
0 C/ e6 L; p: @; a - }
复制代码 , |9 L: j$ Z2 d5 B" Y3 A
4 L1 K2 P# T0 I& m- {' a: F 完整版请查看:附件! W; b4 E/ e3 q( c
TouchGFX图形应用在亮屏时的低功耗实现.pdf
(255.25 KB, 下载次数: 4)
|
低功耗确实是非常需要考虑的一个点