3. 第一个移植工程:裸机工程创建、TouchGFX HAL层初始化、OSAL层选择

好,咱们正式开始动手。这一章是整个移植工作的地基,说白了就是让 TouchGFX 在你的硬件上先「亮」起来。很多新手一上来就急着调动画、搞特效,结果发现屏幕根本不亮,或者亮了但触摸没反应——嗯,这种坑我踩过不止一次。

所以这一章,咱们稳扎稳打,把裸机工程搭好,把 HAL 层初始化做对,再选好 OSAL 层。屏幕点亮的那一刻,你心里就有底了。

3.1 裸机工程创建——别急着上 RTOS

我个人习惯,第一个移植工程永远用裸机(Bare Metal)。为什么?因为少一个 RTOS 的干扰项。如果屏幕点不亮,你不需要怀疑是不是任务调度出了问题,直接查硬件和 HAL 层就行。

创建一个裸机工程,其实就三步:

  1. 准备芯片 SDK:把厂商提供的 HAL 库或者 LL 库加进来。比如 STM32 就用 STM32CubeMX 生成一个基础工程,选好芯片型号,配好时钟和 GPIO。
  2. 配置 LCD 接口:根据你的屏幕接口(RGB/SPI/MCU 8080)配好对应的外设。我建议先把 LTDC(如果芯片有)或者 FMC/SPI 调通,能单独刷一个纯色屏再说。
  3. 添加 TouchGFX 核心库:从 TouchGFX Designer 导出的工程里,把 TouchGFX 文件夹整个复制过来。里面包含了核心渲染引擎和 HAL 抽象层。
我的经验: 在 CubeMX 里生成工程时,记得把 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 层提供的接口来刷屏、读触摸。

初始化流程,我画了一张图,你看完就清楚了:

TouchGFX HAL 层初始化流程 系统时钟配置 LCD 接口初始化 帧缓冲地址配置 HAL::initialize() TouchGFXHAL::initialize() 帧缓冲地址必须 32 字节对齐,否则 DMA2D 传输会出问题

你看,流程并不复杂。但有几个关键点,我得重点说一下:

3.2.1 帧缓冲地址对齐

帧缓冲(Frame Buffer)是 TouchGFX 刷屏用的内存区域。这个地址必须 32 字节对齐。为什么?因为很多芯片的 DMA2D 控制器要求源地址和目标地址按 32 字节对齐,否则会触发硬件错误或者传输数据错位。

我曾经在一个项目里,因为帧缓冲地址只做了 4 字节对齐,结果屏幕刷出来全是花屏。查了两天,最后用逻辑分析仪抓 LTDC 时序才发现是地址问题。从那以后,我每次分配帧缓冲都用 memalign(32, size) 或者直接在链接脚本里指定对齐。

注意: 如果你用 CubeMX 生成的工程,默认的 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
小技巧: 如果你不确定当前用的是哪个 OSAL,可以在 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();
}

如果屏幕显示红色,恭喜你,移植成功了!如果屏幕不亮或者花屏,别急,按下面的步骤排查:

  1. 检查硬件连接:用示波器看 LCD 的 VSYNC、HSYNC、DE、CLK 信号有没有输出。
  2. 检查帧缓冲地址:确认地址是否正确,并且 32 字节对齐。
  3. 检查 LTDC 配置:时序参数(HBP、HFP、VBP、VFP)是否和屏幕规格书一致。
  4. 检查像素格式:TouchGFX 默认用 RGB565,你的 LTDC 配置也要匹配。
我曾经踩过的坑: 有一次屏幕死活不亮,查了三天,最后发现是 LTDC 的时钟极性配反了。屏幕规格书里写的是「数据在时钟上升沿采样」,我配成了下降沿。所以,一定要仔细看屏幕的数据手册,别想当然。

屏幕点亮之后,你还可以加一个简单的触摸测试:在触摸中断里打印坐标,看看触摸屏是否工作正常。这一步虽然简单,但能帮你提前发现触摸驱动的问题,避免后面调 UI 时才发现触摸没反应。

好了,这一章的内容就到这里。裸机工程搭好了,HAL 层初始化对了,屏幕也亮了——你已经完成了 TouchGFX 移植最基础也最关键的一步。后面的章节,咱们会在这个基础上,逐步加入 FreeRTOS、优化动画性能、解决掉帧问题。