3. 第一个移植工程:裸机工程创建、TouchGFX HAL层初始化、OSAL层选择
好,咱们正式开始动手。这一章是整个移植工作的地基,说白了就是让 TouchGFX 在你的硬件上先「亮」起来。很多新手一上来就急着调动画、搞特效,结果发现屏幕根本不亮,或者亮了但触摸没反应——嗯,这种坑我踩过不止一次。
所以这一章,咱们稳扎稳打,把裸机工程搭好,把 HAL 层初始化做对,再选好 OSAL 层。屏幕点亮的那一刻,你心里就有底了。
3.1 裸机工程创建——别急着上 RTOS
我个人习惯,第一个移植工程永远用裸机(Bare Metal)。为什么?因为少一个 RTOS 的干扰项。如果屏幕点不亮,你不需要怀疑是不是任务调度出了问题,直接查硬件和 HAL 层就行。
创建一个裸机工程,其实就三步:
- 准备芯片 SDK:把厂商提供的 HAL 库或者 LL 库加进来。比如 STM32 就用 STM32CubeMX 生成一个基础工程,选好芯片型号,配好时钟和 GPIO。
- 配置 LCD 接口:根据你的屏幕接口(RGB/SPI/MCU 8080)配好对应的外设。我建议先把 LTDC(如果芯片有)或者 FMC/SPI 调通,能单独刷一个纯色屏再说。
- 添加 TouchGFX 核心库:从 TouchGFX Designer 导出的工程里,把
TouchGFX文件夹整个复制过来。里面包含了核心渲染引擎和 HAL 抽象层。
Heap Size 设大一点。我一般设 0x10000(64KB),因为 TouchGFX 的帧缓冲和动态对象分配都需要堆空间。曾经有个项目因为堆太小,屏幕刷到一半就卡死了,查了两天才发现是 malloc 失败。
裸机工程的目录结构,我通常这样组织:
Project/
├── Core/
│ ├── Inc/
│ └── Src/
├── Drivers/
│ ├── BSP/
│ └── CMSIS/
├── TouchGFX/
│ ├── App/
│ ├── Target/
│ └── generated/
├── Middlewares/
│ └── ST/
└── .cproject / .uvprojx
注意 TouchGFX/Target 这个文件夹,里面放的是你硬件相关的 HAL 层实现,后面咱们要重点改它。
3.2 TouchGFX HAL 层初始化——核心中的核心
HAL 层是 TouchGFX 和硬件之间的桥梁。说白了,TouchGFX 不知道你的 LCD 是怎么接的,它只调用 HAL 层提供的接口来刷屏、读触摸。
初始化流程,我画了一张图,你看完就清楚了:
你看,流程并不复杂。但有几个关键点,我得重点说一下:
3.2.1 帧缓冲地址对齐
帧缓冲(Frame Buffer)是 TouchGFX 刷屏用的内存区域。这个地址必须 32 字节对齐。为什么?因为很多芯片的 DMA2D 控制器要求源地址和目标地址按 32 字节对齐,否则会触发硬件错误或者传输数据错位。
我曾经在一个项目里,因为帧缓冲地址只做了 4 字节对齐,结果屏幕刷出来全是花屏。查了两天,最后用逻辑分析仪抓 LTDC 时序才发现是地址问题。从那以后,我每次分配帧缓冲都用 memalign(32, size) 或者直接在链接脚本里指定对齐。
LCD_FB_START_ADDRESS 可能没有对齐。一定要手动检查,或者在 TouchGFXHAL.cpp 里重新赋值。
3.2.2 HAL::initialize() 做了什么
这个函数是 TouchGFX 核心库提供的。它会初始化内部渲染引擎、设置帧缓冲指针、配置显示驱动回调。你不需要改它,但需要确保在调用它之前,硬件已经准备好了。
调用时机:在 main() 函数里,先初始化所有硬件外设,然后调用 HAL::initialize(),最后进入主循环。
int main(void)
{
HAL_Init(); // STM32 HAL 库初始化
SystemClock_Config(); // 系统时钟配置
MX_LTDC_Init(); // LTDC 初始化
MX_FMC_Init(); // FMC 初始化(如果用了SDRAM)
touchgfx::HAL::initialize(); // TouchGFX HAL 初始化
while(1)
{
touchgfx::HAL::getInstance()->vSync(); // 等待垂直同步
touchgfx::HAL::getInstance()->swapFrameBuffers(); // 交换帧缓冲
}
}
3.3 OSAL 层选择——FreeRTOS 还是 BareMetal?
OSAL(Operating System Abstraction Layer)是 TouchGFX 用来抽象操作系统接口的一层。它提供了任务创建、信号量、互斥量、定时器等基础功能。
TouchGFX 支持两种 OSAL 模式:
| 特性 | BareMetal | FreeRTOS |
|---|---|---|
| 实现复杂度 | 低,直接 while 循环 | 高,需要配置任务和调度 |
| 多任务支持 | 无 | 有,可并行处理触摸、动画、后台任务 |
| 调试难度 | 低,单线程容易跟踪 | 中,需要处理任务优先级和资源竞争 |
| 适用场景 | 原型验证、简单 UI | 复杂 UI、多传感器、网络通信 |
我个人建议:第一个工程先用 BareMetal。你想想看,如果屏幕都还没点亮,你还要去配置 FreeRTOS 的任务栈大小、优先级、调度策略,那不是给自己添乱吗?
等裸机工程跑通了,屏幕亮了,触摸能用了,再切换到 FreeRTOS。那时候你心里有底,知道问题出在 UI 层还是 OS 层。
3.3.1 如何切换 OSAL 模式
在 TouchGFX Designer 生成的工程里,有一个 OSAL 文件夹,里面有两个子文件夹:
OSALBareMetal:裸机实现OSALFreeRTOS:FreeRTOS 实现
你只需要在编译器里包含对应的文件夹路径,排除另一个就行。以 IAR 为例:
// 在预定义宏里添加:
USE_BAREMETAL_OS // 使用裸机
// 或者
USE_FREERTOS_OS // 使用 FreeRTOS
然后在 TouchGFXHAL.cpp 里,根据宏定义选择对应的 OSAL 头文件:
#ifdef USE_BAREMETAL_OS
#include "touchgfx/hal/OSALBareMetal.hpp"
#elif defined(USE_FREERTOS_OS)
#include "touchgfx/hal/OSALFreeRTOS.hpp"
#endif
main() 里加一句打印:printf("OSAL: %s\n", USE_BAREMETAL_OS ? "BareMetal" : "FreeRTOS");。这样一眼就能看出来。
3.4 屏幕点亮——验证你的移植是否成功
所有配置都做好了,怎么验证?很简单,让屏幕显示一个纯色背景,或者画一个简单的图形。
在 main() 的主循环里,加一段测试代码:
while(1)
{
// 等待 VSYNC
touchgfx::HAL::getInstance()->vSync();
// 获取当前帧缓冲地址
uint16_t* fb = (uint16_t*)touchgfx::HAL::getInstance()->getFrameBuffer();
// 填充红色 (RGB565: 0xF800)
for(uint32_t i = 0; i < 480 * 272; i++)
{
fb[i] = 0xF800;
}
// 交换帧缓冲
touchgfx::HAL::getInstance()->swapFrameBuffers();
}
如果屏幕显示红色,恭喜你,移植成功了!如果屏幕不亮或者花屏,别急,按下面的步骤排查:
- 检查硬件连接:用示波器看 LCD 的 VSYNC、HSYNC、DE、CLK 信号有没有输出。
- 检查帧缓冲地址:确认地址是否正确,并且 32 字节对齐。
- 检查 LTDC 配置:时序参数(HBP、HFP、VBP、VFP)是否和屏幕规格书一致。
- 检查像素格式:TouchGFX 默认用 RGB565,你的 LTDC 配置也要匹配。
屏幕点亮之后,你还可以加一个简单的触摸测试:在触摸中断里打印坐标,看看触摸屏是否工作正常。这一步虽然简单,但能帮你提前发现触摸驱动的问题,避免后面调 UI 时才发现触摸没反应。
好了,这一章的内容就到这里。裸机工程搭好了,HAL 层初始化对了,屏幕也亮了——你已经完成了 TouchGFX 移植最基础也最关键的一步。后面的章节,咱们会在这个基础上,逐步加入 FreeRTOS、优化动画性能、解决掉帧问题。