1. 图形应用和低功耗
, \" \" I, M6 P2 T随着智能设备的普及,越来越多的设备会用到图形界面,而在 STM32 MCU 上使用TouchGFX,使得图形设计变得非常简单。其中智能手表、智能手环等产品对功耗要求比较高,这就需要在图形应用同时,结合芯片的低功耗模式来优化能耗。
6 b$ k3 V, p, h* E( `, K6 ^在图形应用中使用低功耗,一般分为两种场景,一种是在关闭屏幕时,MCU 进入 STOP 模式,能极大的降低 MCU 功耗;另一种是在屏幕亮着的状态,MCU 进入 SLEEP 模式,也能降低MCU 功耗。而在 STM32L4+芯片上,LPSLEEP 模式相比 SLEEP 模式功耗更低,本文将在STM32L4R9 芯片上,结合 TouchGFX 和 LPSLEEP 来介绍如何在亮屏状态下实现 MCU 低功耗。
2 w( `5 U1 W% ], Z) n- n2. 低功耗实现原理介绍" f9 C& v0 `8 j. g
在使用 TouchGFX 做图形应用时,会使用到 FreeRTOS 操作系统,使用 SysTick 定时产生 1ms的中断来作为系统 tick。在所有应用任务空闲时,系统会调度 Idle 任务。此时我们能让 MCU 进入低功耗,从而达到降低 MCU 功耗的目的。
+ F% `; U6 h5 _* {5 l2 E$ y9 z- N0 i6 V# F
2.1. Tickless Idle$ P9 q9 L; g3 }/ Q d8 w6 k
FreeRTOS 提供了 Tickless Idle 配置,在 FreeRTOSConfig.h 文件中,通过配置configUSE_TICKLESS_IDLE 为不同的值来配置 FreeRTOS 是否在 Idle 任务里进入低功耗,默认为 0 表示不进入低功耗。本文中将配置为 2,自定义 Idle 任务中低功耗实现。
. ^, m% L" @* |9 D, S8 p& d A8 ^. Y0 h9 R2 g# L! t+ B" g
! N+ b3 |7 n' P- u5 t
9 ?& Q4 O6 S, K$ W {5 QFreeRTOS 中进入低功耗具体是由 vPortSuppressTicksAndSleep 函数实现。配置configUSE_TICKLESS_IDLE 为 2 时,还需要重写 vPortSetupTimerInterrupt 函数。 6 U5 Q1 C' d, O
; Q# j- o1 z( K# K# \
2.2. TouchGFX 渲染与进 LPSLEEP 限制" H, J) _7 n' g& |/ y1 T
根据参考手册描述,在进入 LPSLEEP 时要先切换到 LPRUN 模式,系统时钟要降低到2MHz,因此在进入、退出 LPSLEEP 时会有时钟的切换过程。
, @7 x, M3 ~" {: o5 y3 Z7 S: J7 D在屏幕亮着的状态时,用户可能随时触摸屏幕,或者系统信息可能更新,因此屏幕显示的内容可能随时变化,如果此时 MCU 进入 LPSLEEP 状态,会导致系统响应不及时。另外,在屏幕内容更新时,L4R9 通过 DSI 向屏幕更新显示数据,此过程由硬件完成,在此过程中,软件也有可能会进入 LPSLEEP,这样系统时钟发生变化,会导致 DSI 更新显示数据异常,产生屏幕显示花屏的问题。
% {7 Z. @' y& |2 W s) b4 A; u为了避免以上问题,在进入 LPSLEEP 前会检查当前状态,如果用户未操作屏幕," L4 J/ @, H4 M( [
TouchGFX 已经完成渲染,DSI 刷新过程也完成,则允许系统进入 LPSLEEP 状态。这样既保证了系统正常工作,又达到了降低功耗的目的。" q. d3 L( N- i- {! b) L
, j U: h8 N5 v" p. \3. 实现过程% N1 \3 ~2 @0 q
重写 vPortSetupTimerInterrupt 函数,主要是设置与休眠时间补偿相关的变量,初始化 systick 寄存器:
7 v H' T- [7 I9 V! ?
+ I) C/ @% }6 o4 h+ f3 [- ]* V+ T8 z! a. x) T. T
- void vPortSetupTimerInterrupt( void )
- W% A4 o n- F) H3 D/ @' J( a - {' |3 \$ G) o0 d7 j
- /* Calculate the constants required to configure the tick interrupt. */% d5 |& w: i ]3 {' Q b6 f4 Y
- #if( configUSE_TICKLESS_IDLE == 2 )- L! v6 R& B$ E# L
- {
; t9 e( I4 n) ^; ~4 |9 E: B( y - ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );5 H5 a. H; h5 \7 ~5 P
- xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;
' q' k5 J: p: O" k* D7 C) G - ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ /
( r0 a) @* X( e4 G, t1 J) S" O2 } - configSYSTICK_CLOCK_HZ );- Y! W2 f, M3 d. J+ u( \9 @
- }. y; _! ^% {0 ~. r y" E+ f
- #endif /* configUSE_TICKLESS_IDLE */2 M8 d0 j1 |% o* t
- /* Stop and clear the SysTick. */
! e; u! r- J- J* j% [ |! s+ I) B( m - portNVIC_SYSTICK_CTRL_REG = 0UL;; \; H3 Q" o0 L* w$ p$ h( v5 Q
- portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;4 F" {# `5 B5 r
- /* Configure SysTick to interrupt at the requested rate. */
+ t. Y& t1 q1 e+ U - portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;7 H: C) N# _3 S. T
- portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT |
3 ?7 l, O8 C% d: C# v - portNVIC_SYSTICK_ENABLE_BIT );
+ q+ f9 w% r4 d5 f - }
复制代码 7 m: S+ z* O: `- Y' b
' X9 K6 n; F: w: v
重写 vPortSuppressTicksAndSleep 函数。在进入 LPSLEEP 前,会停止 systick,在休眠完成后,需要补偿系统 tick 值,示例中使用 LPTIM 计时来补偿系统 tick 值。在进入 LPSLEEP 前先进行状态检查,只要有以下情况,都不进入低功耗模式:
6 J; O" j& D1 ~' e! i# [% h" y0 la) 触摸事件产生:nosleep_flg [: x B( }7 g! b; j
b) DMA2D busy 状态:isDMAbusy_a()
; d- ~& V; f! q( oc) DSI refreshing 状态:displayRefreshing$ ^8 @/ V/ r5 w
d) 渲染完成,请求 DSI 刷新:refreshRequested9 L" m5 A. l7 Z& s) E- V
- void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )4 }' A2 B5 M5 E9 m% u6 f
- {
% _$ p' Y4 R$ E- _( d7 B - uint32_t xMaximumPossibleSuppressedTicks,ulReloadValue, ulCompleteTickPeriods,ulCount;
" L9 S( [8 g) y - xMaximumPossibleSuppressedTicks=portMAX_16_BIT_NUMBER * configTICK_RATE_HZ / lptim_CLOCK_HZ;) V9 Z6 i0 |7 X0 V: u3 [1 `5 y* I4 Y
- if(nosleep_flg || isDMAbusy_a() || displayRefreshing || refreshRequested) {9 n" N8 \- }3 F' d# C
- if(!isDMAbusy_a() && !displayRefreshing && !refreshRequested) {" j" E" n6 Q3 P; n' G/ w
- if(osKernelSysTick() > nosleep_flg_t0 + nosleep_time)3 a2 L7 T6 ?, d! u
- nosleep_flg = 0;
; H0 r/ ^: Z9 B - }
A" r" \" A( _ e7 g0 P/ w6 h/ O - return;
4 l7 _! R9 f# b0 s8 W - }8 }. j2 p9 g* \+ e& r% F
- if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks ) {
% p) z4 T5 F* Z, K6 Z: a" }8 b - xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
7 `$ ^9 c6 U7 | - }. m( _; {) C# o1 K6 d
- portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT;
3 K" Y/ n- ~5 |1 `0 l" S0 n - HAL_SuspendTick();% k5 n/ B) r& R
- ulReloadValue = (lptim_CLOCK_HZ * (xExpectedIdleTime - 1UL)/configTICK_RATE_HZ);) f f# P( E; K1 n7 m8 @
- __DSB();
+ B. s' B, \1 R( V6 V9 A: G4 L - __ISB();
* N) x1 b- X& P - LPTIM_ReloadMatch_flag=pdFALSE;+ L" c% l' r3 S! s/ G! r. y
- if( eTaskConfirmSleepModeStatus() == eAbortSleep ) {
3 q) G* M2 y5 y1 D( H- m- G% Y - portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
) l* ~( Z4 W! g - portNVIC_SYSTICK_LOAD_REG = configCPU_CLOCK_HZ/configTICK_RATE_HZ - 1UL;. U. F* R* V% U. {0 {3 z) Y5 X* y/ t
- HAL_ResumeTick();
& I: n4 k1 W( G$ y( q - } else {
0 B8 |# R, C/ ?* @: g% v$ k - MX_LPTIM1_Init(LPTIM_PRESCALER_DIV2, ulReloadValue);4 }4 x V9 Y. J, ^% S+ j( F5 M
- if( xExpectedIdleTime > 0 ) {
4 T# b; G2 F+ x. g6 S- z1 } - __DSB();9 u' H9 g2 b: s' C, s* V; U% g- c
- enter_lpsleep();
0 O' U I4 J' b6 D' e+ u. w5 ` - __ISB();
0 }+ v# o% n" @' p6 { - }3 n& {6 R; t+ Q W ~% c+ k
- ulCount = HAL_LPTIM_ReadCounter(&Lptim1);1 O' D A5 m, {8 v f' P0 G. ?3 ?
- HAL_LPTIM_TimeOut_Stop_IT(&Lptim1);$ b1 @5 U1 M, s5 o; ?, R! s
- HAL_ResumeTick();' M" h" f* N g
- if( LPTIM_ReloadMatch_flag != pdFALSE ) {& C: d+ X2 n0 `' B( [( x5 m3 M
- ulCompleteTickPeriods = xExpectedIdleTime - 1UL;- `$ ^* L" k+ x" z1 j. U8 c
- } else {
- }9 A1 ]: D/ q( @ - ulCompleteTickPeriods = (ulCount * configTICK_RATE_HZ)/ lptim_CLOCK_HZ;
6 [* y4 P( }) P% v - }1 P5 g% y# C# c6 F
- portENTER_CRITICAL();: b* o8 K% Y" G& i2 p
- uwTick += ulCompleteTickPeriods;
* o+ P* @8 T2 C7 i: A, n } - vTaskStepTick( ulCompleteTickPeriods );, N4 Z' @* d5 H; v. M" ?% q
- portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;) B3 W( Q+ k' Q
- portNVIC_SYSTICK_LOAD_REG = configCPU_CLOCK_HZ/configTICK_RATE_HZ - 1UL;
2 K8 J, `1 Z' M' \/ [+ B( n - portEXIT_CRITICAL();1 S# M: g4 d6 V" |$ W8 f, H; @
- }6 g+ w0 b) g4 k! f4 R p
- }
复制代码
3 z8 q, c6 X; \+ A& R; l {. @TouchGFX 渲染过程包括两部分,CPU 渲染和 DMA2D 渲染。在 CPU 渲染过程中,TouchGFX任务处于执行状态,系统不会主动调度 Idle 任务。而在 DMA2D 渲染过 TouchGFX 会让出CPU,此时可能会切换到 Idle 任务执行,因此需要对 DMA2D 是否正在渲染进行判断。对DMA2D 是否在渲染的判断如下:
4 P+ H- E% y5 s' v9 A. s. J1 N' ^, q* b3 m- extern "C" uint8_t isDMAbusy_a()# L) W1 z) _4 T7 I. T
- {/ |$ _/ X/ f% `5 X6 D! N' ^: P& d. r
- return ((TouchGFXHAL*)(touchgfx::HAL::getInstance()))->isDMAbusy();4 }) h6 {1 `8 Y) a
- }6 l& q2 ?7 `: l. j4 I
- uint8_t TouchGFXHAL::isDMAbusy(); b0 h% c" ]. o# r
- {
0 P/ ^& N) C j. n/ v. w - bool dma_qe = dma.isDmaQueueEmpty();
, }( h ^8 L( ?+ I/ t& O G - bool dma_run = dma.isDMARunning();4 k* T8 T- J) ?
- return (uint8_t)(!dma_qe || dma_run);( s! j' P& ^ F
- }
复制代码
9 a; }' ?2 c0 W) e) {: J# a7 d! N" b0 Z+ f
完整版请查看:附件
. j5 o6 a. P: I6 O8 X& y
TouchGFX图形应用在亮屏时的低功耗实现.pdf
(255.25 KB, 下载次数: 4)
|
低功耗确实是非常需要考虑的一个点