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 h2. 低功耗实现原理介绍
) 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 r2.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
' 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 |
- void vPortSetupTimerInterrupt( void )
7 r1 v' [6 M# U7 K( x8 J# K - {# @& P! C. w5 }% T" d' G
- /* Calculate the constants required to configure the tick interrupt. */3 d% G/ E$ W# s# d" i- x2 B0 z
- #if( configUSE_TICKLESS_IDLE == 2 )# z( S( x; p1 d6 X% N5 ?
- {/ B l! P6 O$ {+ |
- ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
8 Q' t' _, \5 I% Y; N! h* } - xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;* P1 a/ K/ y+ g. o1 k
- ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ /
7 `' @& V* ^% A. V0 @7 X - configSYSTICK_CLOCK_HZ );% A! N% ]1 ?0 r: H$ S. ^
- }9 U8 x% A! D" m% l0 z
- #endif /* configUSE_TICKLESS_IDLE */( A8 }5 a1 Y% y2 X! U
- /* Stop and clear the SysTick. */
% l0 }+ n: W: U7 e) W - portNVIC_SYSTICK_CTRL_REG = 0UL;
9 c& a9 v, s5 g - portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
2 N8 Z5 \$ e% d; [5 | - /* Configure SysTick to interrupt at the requested rate. */! g r* O) S6 |4 ?, I5 q
- portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
1 F% w& q2 r' S; ~7 _ v - portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT |
" m0 L3 x* }8 T1 P9 v9 h; Q. G - portNVIC_SYSTICK_ENABLE_BIT );$ V0 v0 e5 i4 Q' t
- }
复制代码
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
- void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ): |& n, Z5 m0 {8 J
- {
4 K/ Q) x, n' Y8 M3 R o - uint32_t xMaximumPossibleSuppressedTicks,ulReloadValue, ulCompleteTickPeriods,ulCount;: C" b4 _1 B& I# f0 c
- xMaximumPossibleSuppressedTicks=portMAX_16_BIT_NUMBER * configTICK_RATE_HZ / lptim_CLOCK_HZ;
! J! ^: N W1 c - if(nosleep_flg || isDMAbusy_a() || displayRefreshing || refreshRequested) {
% B7 I3 q m* v) Z8 z, ~" T r - if(!isDMAbusy_a() && !displayRefreshing && !refreshRequested) {! }2 q& q" B4 V3 q9 x8 V' r
- if(osKernelSysTick() > nosleep_flg_t0 + nosleep_time)
. M& U, S R2 o; U: h3 p - nosleep_flg = 0; J. T: O+ o" [6 E, Z, G
- }
8 Z% i$ B0 v! K+ e! w0 h; | - return;! f$ ]0 H5 h6 V& l* C: S, {
- }5 X2 t2 X; z( @" W
- if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks ) {/ D( W; p p) o ]( Z5 N) p
- xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
3 G- `7 L+ M( ?, `9 |3 l4 s - }! v2 U$ n9 z# R9 v) A! ]: e" D" x
- portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT;0 u' p$ l7 f" c; J; I
- HAL_SuspendTick();5 Y T9 A; z8 U* ]& R- _& A
- ulReloadValue = (lptim_CLOCK_HZ * (xExpectedIdleTime - 1UL)/configTICK_RATE_HZ); B6 e4 w6 T+ E3 h
- __DSB();
2 @. b, B4 X6 b0 j - __ISB();2 ~3 P0 i4 D) [$ z
- LPTIM_ReloadMatch_flag=pdFALSE;' t( P& w1 N+ W, b5 Z q
- if( eTaskConfirmSleepModeStatus() == eAbortSleep ) {
6 S. T4 h+ M" X9 y2 ^' ?" H+ R - portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
^' y3 u( R( H* y7 e - portNVIC_SYSTICK_LOAD_REG = configCPU_CLOCK_HZ/configTICK_RATE_HZ - 1UL;
& l( G" C: D. }) B2 j - HAL_ResumeTick();
! j$ O' D0 J# `- [3 i - } else {9 ~% {7 N$ i6 X: `1 i
- MX_LPTIM1_Init(LPTIM_PRESCALER_DIV2, ulReloadValue);
) a% j2 T w$ h) t# M3 e/ j - if( xExpectedIdleTime > 0 ) {: J" N" y* ?9 l" t
- __DSB();! d6 e" [- `- f; e
- enter_lpsleep(); o9 b/ L( H2 l' U+ b* O
- __ISB();
9 S, H# z& D; ?9 H: U2 z/ t! H - }
) q' |# I, a* y6 R4 T - ulCount = HAL_LPTIM_ReadCounter(&Lptim1);' i% h. d `/ A0 L6 U
- HAL_LPTIM_TimeOut_Stop_IT(&Lptim1);( `7 v0 K7 o: i, C4 \+ G% ?6 y
- HAL_ResumeTick();
" E5 U, O- Y4 Z1 d+ J3 x! L - if( LPTIM_ReloadMatch_flag != pdFALSE ) {- x5 Y$ S1 }" Z9 f
- ulCompleteTickPeriods = xExpectedIdleTime - 1UL;0 ^1 P9 S, Q+ W, Q, k1 T
- } else {
- G- l. U; D; H0 m - ulCompleteTickPeriods = (ulCount * configTICK_RATE_HZ)/ lptim_CLOCK_HZ;
) w0 U# a$ H6 w& W7 u8 z - }8 V: l4 `# X- L- O. D% b( O* t
- portENTER_CRITICAL();
: d0 O2 Q) ~( O0 H; t% A$ y0 f3 @ - uwTick += ulCompleteTickPeriods;
- v4 X; e: z( p2 r! T$ W* h. m - vTaskStepTick( ulCompleteTickPeriods );
, A: X3 A, Q7 i- C( A - portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
+ x) E8 ~& I' { - portNVIC_SYSTICK_LOAD_REG = configCPU_CLOCK_HZ/configTICK_RATE_HZ - 1UL;( u8 |) n( @) r7 o
- portEXIT_CRITICAL();4 v9 U3 i8 e8 x E0 }9 v6 k
- }
. a5 d, N" G5 E1 E7 Q9 B# c - }
复制代码 - 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- extern "C" uint8_t isDMAbusy_a()
* w, f2 K* X$ Q! B: ] - {
6 P# e! L7 w2 V4 [ - return ((TouchGFXHAL*)(touchgfx::HAL::getInstance()))->isDMAbusy();: H% [( P+ s6 t0 K
- }7 ^( H) v, k3 S3 P( }! C
- uint8_t TouchGFXHAL::isDMAbusy()9 G/ K* Y. i5 C( R( G$ g, `, O
- {. J# z$ q$ O9 I, |
- bool dma_qe = dma.isDmaQueueEmpty();4 P2 w9 ^& n: C1 j6 w
- bool dma_run = dma.isDMARunning();! \. y6 {) B. I( `
- return (uint8_t)(!dma_qe || dma_run);( V- V1 {: o2 j+ x3 G2 h/ e
- }
复制代码
+ k2 q9 k: n$ b
& v7 L* |' ?" _& Q; w; S 完整版请查看:附件* D, l! o7 E5 V. N
TouchGFX图形应用在亮屏时的低功耗实现.pdf
(255.25 KB, 下载次数: 4)
|
低功耗确实是非常需要考虑的一个点