1、TouchGFX框架概述:架构、渲染管线与消息循环
各位同学好,我是老周。今天咱们正式开始《TouchGFX控件自定义与扩展》的第一章。说实话,我接触TouchGFX也有五六年了,从最早的4.x版本一直跟到现在。这框架看着简单,但真要深入做控件定制,底层的东西绕不开。
这一章,我带你捋清楚TouchGFX的三大核心:整体架构、渲染管线、消息循环机制。说白了,就是搞清楚这框架怎么搭起来的、画面怎么画出来的、用户操作怎么传下去的。搞懂这些,后面你改控件、加特效,心里才有底。
1.1 整体架构:三层分工
TouchGFX的架构,我习惯把它分成三层。你想想看,一个嵌入式GUI要跑在资源有限的MCU上,分层设计是必然的。
| 层级 | 名称 | 核心职责 | 我常关注的点 |
|---|---|---|---|
| 应用层 | Application & Screen | 管理界面切换、业务逻辑 | Screen之间的数据传递,别搞成全局变量满天飞 |
| 控件层 | Widget & Container | 绘制、事件处理、布局 | 自定义控件时,draw()和handleClickEvent()是重灾区 |
| 引擎层 | HAL & Framebuffer | 驱动抽象、帧缓冲管理、DMA传输 | Framebuffer格式和刷新策略,直接影响性能 |
应用层你平时接触最多,写各种Screen和Presenter。控件层是咱们这门课的主战场。引擎层嘛,平时不太动,但出了问题往往最头疼。我记得有一次客户说界面闪烁,查了两天,最后发现是HAL层帧缓冲地址没对齐——这种坑,不摸到底层根本想不到。
核心观点:TouchGFX的架构设计,本质上是用分层来换取可移植性。你换MCU,只需要重写HAL层;你换UI,只需要改应用层。控件层是中间的粘合剂。
1.2 渲染管线:从脏矩形到屏幕
渲染管线,说白了就是TouchGFX怎么把控件画到屏幕上的过程。我刚开始用的时候,总觉得它每次都会重绘整个屏幕,后来才发现——它只画变化的部分。
这个过程大致分四步:
- 标记脏矩形:控件状态变了(比如文字更新、位置移动),它把自己所在的区域标记为“脏”。
- 合并脏矩形:多个脏区域合并成最少的大矩形,减少绘制次数。
- 裁剪与绘制:只对脏矩形内的控件调用
draw()方法。 - 刷新到屏幕:通过HAL层把帧缓冲里的数据刷到LCD。
嗯,这里要注意:合并脏矩形这一步,如果控件重叠太多,合并后的区域可能接近全屏,那就退化成全屏重绘了。我在做仪表盘项目时,十几个指针控件叠在一起,性能直接崩了。后来改成只更新数值变化的区域,才救回来。
避坑指南:自定义控件时,一定要正确实现invalidate()和draw()。我见过有人直接在draw()里画全屏,结果每次更新都卡成PPT。记住:只画脏矩形内的内容。
下面这张图,是我自己画的渲染管线流程,你一看就明白:
1.3 消息循环机制:事件从哪来到哪去
消息循环,就是TouchGFX怎么处理用户触摸、定时器、系统事件。你点一下屏幕,背后发生了一串事情。
我把它拆成三个阶段:
- 采集阶段:HAL层轮询触摸IC,拿到坐标数据。
- 分发阶段:引擎层根据坐标,找到最上层的可点击控件,调用它的
handleClickEvent()。 - 响应阶段:控件处理事件,可能触发
invalidate(),也可能调用回调函数。
你可能会问:为什么不是中断驱动? 嗯,TouchGFX的消息循环是轮询式的。它在主循环里不断调用touchgfx::HAL::getInstance()->vSync()和touchgfx::Application::getInstance()->handleTickEvent()。这样做的好处是简单、可预测,坏处是——如果主循环被阻塞,触摸就失灵了。
注意:千万不要在handleTickEvent()里做耗时操作!我曾经在一个项目中,在Tick事件里做了SD卡写入,结果触摸响应延迟了200ms,用户反馈“点一下等半天”。后来把写卡操作放到后台任务里,问题才解决。
消息循环的核心函数调用链,我整理了一下:
// 伪代码:TouchGFX 主循环核心
while (1) {
// 1. 等待垂直同步(VSync)
HAL::getInstance()->vSync();
// 2. 采集触摸数据
HAL::getInstance()->backPorchExited();
// 3. 分发事件到当前Screen
Application::getInstance()->handleTickEvent();
// 4. 执行脏矩形重绘
Application::getInstance()->executeDraw();
// 5. 交换帧缓冲(双缓冲模式)
HAL::getInstance()->swapFrameBuffer();
}
你看,这个循环很紧凑。每一步都有严格的时间约束。比如executeDraw()必须在下一帧VSync到来之前完成,否则就会出现撕裂。
1.4 三者关系:架构、管线、循环怎么配合
这三者不是孤立的。我画个关系图你就懂了:
说白了,架构是骨架,决定了代码怎么组织;渲染管线是肌肉,决定了画面怎么呈现;消息循环是神经,决定了交互怎么响应。你自定义控件,本质上就是在“控件层”这个位置,同时跟这三者打交道。
我的建议:刚开始学TouchGFX,别急着写自定义控件。先把这三块的关系理清楚。我见过太多人一上来就改draw(),结果不知道脏矩形怎么工作,画出来的东西要么不刷新,要么闪屏。先花半天把框架搞懂,后面省三天调试时间。