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. u2.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
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# U3. 实现过程
- 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- void vPortSetupTimerInterrupt( void )
# u: E3 u3 h4 u3 ` - {
# L6 `$ \- ?* R9 @6 O - /* Calculate the constants required to configure the tick interrupt. */ `; P8 q+ v! E5 \! Z
- #if( configUSE_TICKLESS_IDLE == 2 )- G& _$ g' n) x S! o# y1 _+ }
- {0 L. R& a% c; y
- ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
9 _- Z! I4 t2 k/ S- w - xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;1 g! c$ m9 {3 I1 C2 c% x
- ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ /
0 K1 \1 z6 C5 I2 b% T9 C" C3 o - configSYSTICK_CLOCK_HZ );# O6 E6 N* j' w/ Q, W9 T
- }
D2 Y1 A, U$ E! I - #endif /* configUSE_TICKLESS_IDLE */ i4 X8 W8 e' H, a4 e9 }, _) V
- /* Stop and clear the SysTick. */
/ ^- f5 U2 M; }$ j6 X; Z - portNVIC_SYSTICK_CTRL_REG = 0UL;
4 K/ n% u! _( ]$ Q1 d/ \0 `' H/ T - portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
/ K- Y1 O# @: W3 S9 F - /* Configure SysTick to interrupt at the requested rate. */) w$ \/ _' B) ]* k/ H
- portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
$ {+ N/ o; N$ ]2 Q8 z) ?8 s! t - portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT |
7 f/ s ?, s3 P3 X d4 b, W - portNVIC_SYSTICK_ENABLE_BIT );* t* A9 Y. {" X
- }
复制代码 ; @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
- void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
8 D( s6 J; V: E" k' t - {$ e. f/ V' [; ^
- uint32_t xMaximumPossibleSuppressedTicks,ulReloadValue, ulCompleteTickPeriods,ulCount; B9 r, U. n) A
- xMaximumPossibleSuppressedTicks=portMAX_16_BIT_NUMBER * configTICK_RATE_HZ / lptim_CLOCK_HZ;9 T `' X/ {, E' ~
- if(nosleep_flg || isDMAbusy_a() || displayRefreshing || refreshRequested) {- s7 T# x7 y! d3 ~4 r( ^7 U
- if(!isDMAbusy_a() && !displayRefreshing && !refreshRequested) {/ b: `9 O- H/ n: O! |0 K
- if(osKernelSysTick() > nosleep_flg_t0 + nosleep_time)
& b. l+ e- _% @ - nosleep_flg = 0;+ H; ]/ o# b4 f
- }
) p" S1 A) u: f- K6 M* ] - return;, C) R+ `% s7 B" e
- }/ a+ l$ ~- n0 j- `# L* D; c% |
- if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks ) {
6 B# K0 m2 o7 n H7 j - xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
1 S, X0 I" S% v0 c - }% v" Y+ P* d9 i3 u7 s8 P, q
- portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT;
, T) [" R8 e- F - HAL_SuspendTick();
. |2 h% ~; n/ `3 w - ulReloadValue = (lptim_CLOCK_HZ * (xExpectedIdleTime - 1UL)/configTICK_RATE_HZ);
, l' _ s/ B8 J* o; T# n, M - __DSB(); b* r9 U6 p( m( n
- __ISB();
/ P" K: W# ~: q# V9 h0 ?% m* b7 p - LPTIM_ReloadMatch_flag=pdFALSE;
9 }) l+ T' x* {! a) ` - if( eTaskConfirmSleepModeStatus() == eAbortSleep ) {: C* F N8 X4 O& }
- portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;' ?4 x# x. ^. f
- portNVIC_SYSTICK_LOAD_REG = configCPU_CLOCK_HZ/configTICK_RATE_HZ - 1UL;
+ K) x! l& x- w3 p6 N0 F; L - HAL_ResumeTick();8 i2 G" i: n; [" L3 z% N
- } else {" T5 N' m8 I, ^! O# P8 G' x: ^1 H
- MX_LPTIM1_Init(LPTIM_PRESCALER_DIV2, ulReloadValue);( J9 w9 l. K) t
- if( xExpectedIdleTime > 0 ) {% {1 V2 L8 G) Z
- __DSB();2 z ^. N, q3 N A2 [9 n* c9 s/ e
- enter_lpsleep();- A2 S0 a7 L& V W- M7 |9 W8 J
- __ISB();3 X! l, d9 l% q9 f" ^* I/ {% s) n
- }
- w- c1 \5 {2 {9 w: @1 R - ulCount = HAL_LPTIM_ReadCounter(&Lptim1);
m0 D4 G$ f. o- u+ j+ e - HAL_LPTIM_TimeOut_Stop_IT(&Lptim1);
" d& o, H& M9 z9 W4 P3 ? - HAL_ResumeTick();
; `6 H" |! z" M - if( LPTIM_ReloadMatch_flag != pdFALSE ) {
1 e" D% w. l/ ]5 t, i+ t - ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
w& s& u/ g6 ^ - } else {7 F' q) L4 F/ q$ F1 S& m0 f& Z6 a
- ulCompleteTickPeriods = (ulCount * configTICK_RATE_HZ)/ lptim_CLOCK_HZ;
. T Z" U6 L+ W8 J$ B; c& g! G$ X* M - }
- z3 a7 Q: f. B! |' D. L9 r! e4 P; Q - portENTER_CRITICAL();8 a* Q1 ~+ W+ \& i4 Q) V
- uwTick += ulCompleteTickPeriods;
+ D, a6 D" A& y9 M2 i - vTaskStepTick( ulCompleteTickPeriods );) }* {& p, o# s
- portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
" q) F6 b. m( k" m! I - portNVIC_SYSTICK_LOAD_REG = configCPU_CLOCK_HZ/configTICK_RATE_HZ - 1UL;
1 A3 o: q) Y, z" u6 K - portEXIT_CRITICAL();8 s2 ^5 c6 b( n1 d0 X( l
- }6 F7 s2 J! s9 f+ |
- }
复制代码 - 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
- extern "C" uint8_t isDMAbusy_a()% S% r( H% i* |# B/ {
- {
9 ?/ c, {. a8 a - return ((TouchGFXHAL*)(touchgfx::HAL::getInstance()))->isDMAbusy();! A+ i1 H- a0 Q( }) j( m
- }
4 d, d, u. c1 Q8 s+ P& q8 ~, c - uint8_t TouchGFXHAL::isDMAbusy()9 q* p" E; l7 ?- B9 f4 a3 ~( M
- {
/ O W" _) s+ [& s$ a - bool dma_qe = dma.isDmaQueueEmpty();, a( S/ s; w- s) X* J/ I+ p' ]( g
- bool dma_run = dma.isDMARunning();
: l$ X& P# C! i* L7 O - return (uint8_t)(!dma_qe || dma_run);
0 Y% ?5 @: d1 S* p. U4 a( c - }
复制代码 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)
|
低功耗确实是非常需要考虑的一个点