1. 图形应用和低功耗
( K7 r3 V. k( \' C$ A随着智能设备的普及,越来越多的设备会用到图形界面,而在 STM32 MCU 上使用TouchGFX,使得图形设计变得非常简单。其中智能手表、智能手环等产品对功耗要求比较高,这就需要在图形应用同时,结合芯片的低功耗模式来优化能耗。& C9 w3 d7 _/ A$ w' _- |
在图形应用中使用低功耗,一般分为两种场景,一种是在关闭屏幕时,MCU 进入 STOP 模式,能极大的降低 MCU 功耗;另一种是在屏幕亮着的状态,MCU 进入 SLEEP 模式,也能降低MCU 功耗。而在 STM32L4+芯片上,LPSLEEP 模式相比 SLEEP 模式功耗更低,本文将在STM32L4R9 芯片上,结合 TouchGFX 和 LPSLEEP 来介绍如何在亮屏状态下实现 MCU 低功耗。3 S1 m, c0 b0 ~
2. 低功耗实现原理介绍
4 E& z2 q; ?* F& S7 q+ v* E在使用 TouchGFX 做图形应用时,会使用到 FreeRTOS 操作系统,使用 SysTick 定时产生 1ms的中断来作为系统 tick。在所有应用任务空闲时,系统会调度 Idle 任务。此时我们能让 MCU 进入低功耗,从而达到降低 MCU 功耗的目的。! Q$ i- `% c8 E. v# X
, W: v7 b4 h3 V# j& b; ^; Q! B
2.1. Tickless Idle
4 O% w. o9 H9 Y: SFreeRTOS 提供了 Tickless Idle 配置,在 FreeRTOSConfig.h 文件中,通过配置configUSE_TICKLESS_IDLE 为不同的值来配置 FreeRTOS 是否在 Idle 任务里进入低功耗,默认为 0 表示不进入低功耗。本文中将配置为 2,自定义 Idle 任务中低功耗实现。! d& r e9 h0 D4 b/ [6 }' l! i1 |
; h' j. O6 u/ ]6 K/ p4 z( Z
0 W1 e" {/ p& S+ ~6 |# K
: Q- |* ?9 X# m. U+ k" N M
FreeRTOS 中进入低功耗具体是由 vPortSuppressTicksAndSleep 函数实现。配置configUSE_TICKLESS_IDLE 为 2 时,还需要重写 vPortSetupTimerInterrupt 函数。
1 h1 c3 u3 S2 w2 `, E
" u5 J2 r' o! T3 F2 Z4 r5 T$ X C" I2.2. TouchGFX 渲染与进 LPSLEEP 限制
: ?# X' i! n/ Z( m5 x根据参考手册描述,在进入 LPSLEEP 时要先切换到 LPRUN 模式,系统时钟要降低到2MHz,因此在进入、退出 LPSLEEP 时会有时钟的切换过程。4 G/ |" P6 Y# B5 O5 X# S ~- \
在屏幕亮着的状态时,用户可能随时触摸屏幕,或者系统信息可能更新,因此屏幕显示的内容可能随时变化,如果此时 MCU 进入 LPSLEEP 状态,会导致系统响应不及时。另外,在屏幕内容更新时,L4R9 通过 DSI 向屏幕更新显示数据,此过程由硬件完成,在此过程中,软件也有可能会进入 LPSLEEP,这样系统时钟发生变化,会导致 DSI 更新显示数据异常,产生屏幕显示花屏的问题。
j1 k+ v- Q0 G为了避免以上问题,在进入 LPSLEEP 前会检查当前状态,如果用户未操作屏幕,( ^# ?* i! F2 K& T* g
TouchGFX 已经完成渲染,DSI 刷新过程也完成,则允许系统进入 LPSLEEP 状态。这样既保证了系统正常工作,又达到了降低功耗的目的。
/ W3 ^( b2 F5 j& m4 b |4 s2 B. K6 B7 ]0 |& B
3. 实现过程
7 g8 g7 F; J6 [2 L2 T重写 vPortSetupTimerInterrupt 函数,主要是设置与休眠时间补偿相关的变量,初始化 systick 寄存器:
4 y6 U0 B# @6 { B1 C7 Y
0 s; \4 j$ X+ u5 {
2 \1 z3 [: Q: V1 y# C- void vPortSetupTimerInterrupt( void )! X7 x$ i7 f' }! Q/ y
- {5 `/ ?. x( L& F' a
- /* Calculate the constants required to configure the tick interrupt. */
) G9 F% \9 j0 Q7 \ a5 w - #if( configUSE_TICKLESS_IDLE == 2 )
/ k/ j$ \9 Y5 |, x0 |: A/ k: X - {
' k9 w n+ n' v+ G4 ~& ]' M - ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
+ |, R9 `) c; S1 d- b - xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;6 N4 w# z6 s0 x$ \' [, T0 W4 K) \* R5 x3 n
- ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ /
. B! f4 K) ]& R6 ^; L! ^ - configSYSTICK_CLOCK_HZ );& P) Q, F5 F- Y5 v( v9 h' \
- }. j: u. R- m: j5 A
- #endif /* configUSE_TICKLESS_IDLE */
: P& z( }; c0 `8 f* d - /* Stop and clear the SysTick. */7 _; x1 D/ w- H n1 j$ J; Y4 p
- portNVIC_SYSTICK_CTRL_REG = 0UL;
1 d8 X( K: \$ ~: I/ b4 b. x - portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;4 i5 L5 ~1 r7 N
- /* Configure SysTick to interrupt at the requested rate. */
5 g7 ~" _1 Z' x) x# H' k( p - portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;$ E; ]+ ?* ^# L
- portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | 6 m: E6 F' S3 V8 T0 j! b3 @
- portNVIC_SYSTICK_ENABLE_BIT );
4 z/ _4 Y) v/ u% p; { - }
复制代码
7 j' E4 m. M' {+ G9 D9 k# g% q: n- G+ X
重写 vPortSuppressTicksAndSleep 函数。在进入 LPSLEEP 前,会停止 systick,在休眠完成后,需要补偿系统 tick 值,示例中使用 LPTIM 计时来补偿系统 tick 值。在进入 LPSLEEP 前先进行状态检查,只要有以下情况,都不进入低功耗模式:
% m; {, l: `% T/ u$ K Ha) 触摸事件产生:nosleep_flg
, C8 |- x7 W6 a# B+ t' }2 rb) DMA2D busy 状态:isDMAbusy_a()9 N1 p/ m' Y1 d6 K: b9 L
c) DSI refreshing 状态:displayRefreshing5 A7 Z; ]$ o# k9 [" z
d) 渲染完成,请求 DSI 刷新:refreshRequested
! G% j& J# S: t4 Q: {( ]- W$ M- void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ). P2 K# p, U5 `+ i: p) d
- {
" F9 X4 k, C# p: k! | t. H3 ? - uint32_t xMaximumPossibleSuppressedTicks,ulReloadValue, ulCompleteTickPeriods,ulCount;
9 y9 p5 H6 y9 A9 A - xMaximumPossibleSuppressedTicks=portMAX_16_BIT_NUMBER * configTICK_RATE_HZ / lptim_CLOCK_HZ;
$ U/ o* r9 h! ]" D - if(nosleep_flg || isDMAbusy_a() || displayRefreshing || refreshRequested) {6 V T+ A, j. B" @+ D8 y
- if(!isDMAbusy_a() && !displayRefreshing && !refreshRequested) {
6 \7 t, {. a0 n& W$ d - if(osKernelSysTick() > nosleep_flg_t0 + nosleep_time)
/ n: N) j8 P: P: F - nosleep_flg = 0;3 b/ a3 M. l/ @5 R3 G7 H
- }
. ~9 X2 G, q& B% K6 M - return;
! b& t$ {, y, |2 z4 R% O2 W7 I - }
P. y/ Q8 Z4 Z; O( t ~9 X - if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks ) {
5 S9 z* k8 U( G R9 {, z - xExpectedIdleTime = xMaximumPossibleSuppressedTicks;- k3 N8 k: P9 y9 u0 g' X9 w! ?. g. w
- }1 i0 D5 p, p! N- J! o
- portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT;
8 U p6 S8 ^7 [7 k$ A z4 c% y - HAL_SuspendTick();
9 B% [: S, T& K/ W - ulReloadValue = (lptim_CLOCK_HZ * (xExpectedIdleTime - 1UL)/configTICK_RATE_HZ);8 O" ]( n. ]/ h: A: E- n
- __DSB();
2 b7 S* A! V. r8 m. J; U. q - __ISB();) I; T1 [5 L3 S, {& c4 e
- LPTIM_ReloadMatch_flag=pdFALSE;
1 ?& [! H/ S3 u P, W3 s/ G3 e% N5 Z - if( eTaskConfirmSleepModeStatus() == eAbortSleep ) {: k4 _. B9 a; }. f9 y% v
- portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;! S. e' \9 v. P
- portNVIC_SYSTICK_LOAD_REG = configCPU_CLOCK_HZ/configTICK_RATE_HZ - 1UL;$ @: y) ]2 C0 V' T, c
- HAL_ResumeTick();
6 R- R$ M$ m# ?/ d& E G5 x. C - } else {7 H' A' ^* o# j
- MX_LPTIM1_Init(LPTIM_PRESCALER_DIV2, ulReloadValue);
0 N$ [. n @/ d% ?( N' a1 f* d - if( xExpectedIdleTime > 0 ) {
! J3 }8 i$ Q2 p( x i - __DSB();
$ g1 \3 F8 d/ J0 F* q+ S4 Q - enter_lpsleep();
0 Y4 b- D0 `7 a; v5 i. y; U - __ISB();/ @( `, ?7 x8 f8 `
- }1 a' d" \% B( @ r
- ulCount = HAL_LPTIM_ReadCounter(&Lptim1);2 B, E. h! n8 T- Y, C
- HAL_LPTIM_TimeOut_Stop_IT(&Lptim1);1 N+ B, X5 X- L0 D l9 g& M; c) E) X
- HAL_ResumeTick();! R$ G9 W& E0 v1 ^: d8 t( ?# @" @
- if( LPTIM_ReloadMatch_flag != pdFALSE ) {/ E% `) Y7 e8 F8 A. V9 H! v6 U
- ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
@0 F( C' R& f: R - } else {5 w& W9 I0 ~1 H4 ^4 z% y
- ulCompleteTickPeriods = (ulCount * configTICK_RATE_HZ)/ lptim_CLOCK_HZ;. }9 g6 y+ `) Z
- }" F0 O+ [ ^1 u( q3 c' \& @0 k
- portENTER_CRITICAL();
4 \: Z+ Y3 p6 B# a4 n - uwTick += ulCompleteTickPeriods;" k/ P: a5 g. v, _4 S5 F b ^
- vTaskStepTick( ulCompleteTickPeriods );$ L/ D/ H$ i ?5 {
- portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
% @2 v7 e3 G$ A P: Q# h' D! P - portNVIC_SYSTICK_LOAD_REG = configCPU_CLOCK_HZ/configTICK_RATE_HZ - 1UL;# Q' _2 y6 K* f% `
- portEXIT_CRITICAL();
6 p* l& _4 v8 q2 l) ] - }
* I, F% r) _, Y! S/ F - }
复制代码
) T8 W1 [! E3 aTouchGFX 渲染过程包括两部分,CPU 渲染和 DMA2D 渲染。在 CPU 渲染过程中,TouchGFX任务处于执行状态,系统不会主动调度 Idle 任务。而在 DMA2D 渲染过 TouchGFX 会让出CPU,此时可能会切换到 Idle 任务执行,因此需要对 DMA2D 是否正在渲染进行判断。对DMA2D 是否在渲染的判断如下:
! N$ P( b! j( V5 B5 R2 y$ W- extern "C" uint8_t isDMAbusy_a()
* F6 e* A. A V$ a$ B - {
1 i' i) c# ~6 R1 n- x. N - return ((TouchGFXHAL*)(touchgfx::HAL::getInstance()))->isDMAbusy();( f H) C: `; h; V4 i, p, u
- }
) h9 A3 Z& M0 T; w4 Z; w+ S" X - uint8_t TouchGFXHAL::isDMAbusy()* G8 K, J+ i+ A2 b8 b( I( R
- {
+ Y" x1 G+ k% v/ _, w3 O, B) z; P* F - bool dma_qe = dma.isDmaQueueEmpty();
) h4 H$ E F# l - bool dma_run = dma.isDMARunning();
8 N# P- ~( V j+ b Z+ ~7 Y - return (uint8_t)(!dma_qe || dma_run);
3 n5 {, P& l! n$ W - }
复制代码
; d+ Q$ F7 H5 v! n% ~* l' F/ s' q& U2 L
完整版请查看:附件
; E' Z: e5 {0 {4 k3 ?
TouchGFX图形应用在亮屏时的低功耗实现.pdf
(255.25 KB, 下载次数: 4)
|
低功耗确实是非常需要考虑的一个点