2. TouchGFX低内存平台移植:硬件抽象层(HAL)基础与初始化流程
好,我们正式开始移植实战的第一关——HAL层。
很多初学者拿到TouchGFX,第一反应是去改框架代码。其实不用。TouchGFX把硬件相关的部分都抽象出来了,你只需要填充一个结构体,实现几个接口函数。说白了,HAL层就是TouchGFX和你的MCU硬件之间的翻译官。
我个人习惯把HAL初始化拆成三步走:时钟与显示接口准备 → HAL对象构造与配置 → 帧缓冲与刷新机制绑定。这三步走通了,你的屏幕就能亮起来,TouchGFX就能跑起来。
核心认知:HAL层不负责UI逻辑,它只负责两件事——把像素数据送到屏幕,把触摸/按键事件送给框架。其他事情,TouchGFX内部自己搞定。
2.1 HAL层在TouchGFX中的位置
先看一张整体架构图,帮你建立全局视角。
看到红色那块了吗?那就是我们今天要啃的骨头。上面两层你不用动,下面那层BSP你本来就有。HAL层就是连接它们的桥梁。
我的经验:刚开始移植时,别急着把HAL写得完美。先让屏幕能显示一个纯色,再逐步加功能。我见过太多人一上来就想把DMA、双缓冲、局部刷新全搞定,结果卡在一个像素格式上三天出不来。
2.2 HAL初始化核心流程
TouchGFX的HAL初始化,本质上就是填充一个HAL::DMA、一个LCD、一个TouchController,然后调用HAL::init()。就这么简单。
但细节藏在流程里。我们拆开看:
- 硬件外设初始化——时钟、GPIO、SPI/FSMC/LTDC、DMA等。这部分属于BSP,但HAL依赖它。
- 帧缓冲地址配置——告诉TouchGFX你的显存放在哪里。低内存平台通常用内部SRAM或外部PSRAM。
- LCD控制器实例化——根据你的显示接口(RGB并行、SPI串行、MIPI DSI等)选择对应的LCD类。
- 触摸控制器实例化——实现
sampleTouch()接口,返回触摸坐标。 - HAL对象构造——把上面三个对象传给HAL构造函数。
- 调用HAL::init()——框架内部完成定时器、刷新策略、帧缓冲管理等初始化。
嗯,这里要注意:第1步和第2步的顺序不能乱。你得先确保内存和显示接口能工作,再去构造HAL对象。
2.3 关键数据结构与接口
我们来看一个典型的HAL初始化代码。这是我在STM32G070上做的一个低内存移植示例,帧缓冲只有32KB。
// 1. 硬件初始化(BSP层)
void BSP_Init()
{
HAL_Init();
SystemClock_Config(); // 配置系统时钟
MX_GPIO_Init(); // 初始化GPIO
MX_SPI1_Init(); // 初始化SPI(接LCD)
MX_DMA_Init(); // 初始化DMA(用于刷屏)
}
// 2. 帧缓冲地址定义
// 低内存平台:使用内部SRAM,地址固定
#define FRAME_BUFFER_ADDR 0x20000000 // STM32G070 SRAM起始
#define FRAME_BUFFER_SIZE (128 * 128 * 2) // 128x128 RGB565 = 32KB
// 3. LCD控制器实例化
// 使用SPI接口的ST7735,128x128分辨率
static ST7735_LCD lcd;
static ST7735_Touch touch;
// 4. HAL对象构造与初始化
void TouchGFX_HAL_Init()
{
// 构造HAL对象
HAL hal(
dma, // DMA接口(用于快速刷屏)
lcd, // LCD显示驱动
touch, // 触摸驱动
FRAME_BUFFER_ADDR, // 帧缓冲地址
128, // 显示宽度
128, // 显示高度
0, // 虚拟缓冲区(低内存不用)
0 // 预渲染缓冲区(低内存不用)
);
// 初始化HAL
hal.init();
// 设置帧缓冲策略
// 低内存平台建议使用单缓冲 + 局部刷新
hal.setFrameBufferStrategy(FrameBufferStrategy::SINGLE_BUFFER);
hal.setRefreshStrategy(RefreshStrategy::PARTIAL_REFRESH);
}
踩坑警告:我曾经在一个项目里把帧缓冲地址写错了,指向了代码区。结果屏幕死活不亮,调试器一看,HAL初始化直接HardFault。检查地址是否可写、是否对齐、是否被其他模块占用,这是第一步。
2.4 低内存平台的HAL配置要点
低内存平台和普通平台最大的区别在哪?说白了就三个字:省着用。
我整理了一个对比表,帮你快速理解差异:
| 配置项 | 普通平台(>256KB RAM) | 低内存平台(≤64KB RAM) |
|---|---|---|
| 帧缓冲策略 | 双缓冲或三缓冲 | 单缓冲 |
| 刷新策略 | 全屏刷新 | 局部刷新 |
| 帧缓冲位置 | 外部SDRAM | 内部SRAM或PSRAM |
| 像素格式 | RGB888或RGB565 | RGB565或L8 |
| DMA使用 | 必须,用于快速刷屏 | 可选,CPU直接刷也可 |
| 虚拟缓冲区 | 需要,用于动画平滑 | 关闭 |
你看,低内存平台几乎每一项都在做减法。但减法不等于阉割功能,而是用更精巧的方式实现同样的效果。
我的建议:如果你用的是内部SRAM,帧缓冲大小要精打细算。128x128的RGB565刚好32KB,很多MCU的SRAM也就64KB,剩下32KB给堆栈和全局变量。再大就得上外部PSRAM了。
2.5 初始化后的验证方法
代码写完了,怎么知道HAL初始化成功了?别急着跑TouchGFX的Demo,先做两个简单测试:
测试一:纯色填充
直接往帧缓冲地址写一个颜色值,看屏幕是否显示对应颜色。这是最基础的硬件通路验证。
// 测试:将帧缓冲全部填充为红色(RGB565: 0xF800)
uint16_t* fb = (uint16_t*)FRAME_BUFFER_ADDR;
for (int i = 0; i < 128 * 128; i++) {
fb[i] = 0xF800;
}
// 触发一次刷屏
lcd.flushDisplayBuffer(FRAME_BUFFER_ADDR, 0, 0, 128, 128);
测试二:触摸坐标回显
在屏幕上显示触摸点的坐标值。如果触摸没反应,检查sampleTouch()的SPI读取时序。
// 测试:轮询触摸并打印坐标
while (1) {
int x, y;
if (touch.sampleTouch(x, y)) {
printf("Touch: (%d, %d)\r\n", x, y);
}
HAL_Delay(10);
}
这两个测试过了,你的HAL层就基本稳了。接下来就可以放心地跑TouchGFX的Demo了。
记住:HAL初始化不是一次性的工作。随着你后续添加DMA、双缓冲、局部刷新等功能,HAL的配置会不断调整。但基础框架——时钟、帧缓冲、LCD、触摸——这四根柱子必须一开始就立稳。
嗯,这一章的内容就到这。HAL初始化看起来代码不多,但每一行背后都有硬件细节。你动手做一遍,遇到问题再回来看,理解会更深刻。