第3章 TouchGFX基础框架移植:从安装到驱动集成

TouchGFX的移植,说白了就是让这个GUI框架在你的硬件上跑起来。很多初学者一上来就盯着代码看,结果越看越懵。我个人的习惯是——先搞清楚工具链怎么用,再动手改代码。

这一章,我们就从TouchGFX Designer的安装开始,一步步走到触摸驱动的集成。嗯,内容不少,但每一步我都会把坑点讲清楚。

3.1 TouchGFX Designer安装与项目生成

TouchGFX Designer是ST官方提供的图形化配置工具。它不仅能帮你生成UI代码,还能直接输出一个完整的工程框架。

3.1.1 安装步骤

  1. 下载安装包:从ST官网或GitHub Release页面获取最新版本。注意区分Windows和Linux版本。
  2. 安装依赖:Windows下基本一路Next;Linux需要确保有Java Runtime Environment(JRE 11+)。
  3. 验证安装:打开命令行,输入 TouchGFXDesigner --version,能看到版本号就对了。
我的经验: 我曾经在Ubuntu 20.04上装过,卡在Java环境上。后来发现OpenJDK 11和Oracle JDK 11都行,但OpenJDK 17会报错。建议直接用ST官方推荐的JDK版本。

3.1.2 生成第一个项目

打开Designer后,你会看到一个模板选择界面。这里我建议选“Empty Application”,别选那些带例程的模板——那些模板绑定了特定的硬件配置,反而容易干扰你的移植思路。

项目生成后,Designer会输出一个完整的文件夹结构:

MyProject/
├── Application/          # UI代码(你主要改这里)
├── Target/               # 硬件抽象层(HAL)
│   ├── BoardConfiguration.cpp
│   ├── TouchController.cpp
│   └── ...
├── generated/            # 自动生成的代码(别手动改)
└── TouchGFX/             # 核心库文件

这里有个关键点:Target/ 目录下的文件,才是我们移植工作的主战场。

3.2 HAL层配置:让GUI认识你的硬件

HAL(硬件抽象层)是TouchGFX和MCU之间的桥梁。说白了,就是告诉TouchGFX:你的屏幕分辨率是多少、像素格式是什么、帧缓冲放在哪里。

3.2.1 核心配置项

打开 BoardConfiguration.cpp,你会看到类似这样的代码:

void TouchGFXHAL::initialize()
{
    // 设置帧缓冲地址
    setFrameBufferStartAddress((uint16_t*)0xC0000000);
    
    // 配置显示参数
    hal->setDisplayWidth(480);
    hal->setDisplayHeight(272);
    hal->setBitsPerPixel(16);  // RGB565
    
    // 配置帧缓冲策略
    hal->setFrameBufferStrategy(FB_STRATEGY_MMU);
}

这里我踩过一个坑:帧缓冲地址必须对齐到32字节边界,否则DMA传输会出问题。我当时用的是SDRAM,地址0xC0000000没问题,但如果你用内部SRAM,记得检查对齐。

配置项 说明 常见值
显示宽度/高度 物理像素尺寸 480x272, 800x480
像素格式 每个像素的位数和排列 RGB565, RGB888
帧缓冲策略 单缓冲/双缓冲/MMU FB_STRATEGY_MMU
VSync信号 是否使用垂直同步 true/false
注意: 像素格式必须和你的LCD控制器匹配。比如你的屏幕驱动只支持RGB565,你却配成RGB888,显示出来的颜色会完全错乱。我曾经因为这个排查了整整两天。

3.3 帧缓冲驱动:屏幕刷新的核心

帧缓冲驱动负责把TouchGFX渲染好的图像数据,传输到LCD屏幕上。这里有两种常见方案:

  • CPU直接传输:简单,但占用CPU资源高,适合小屏幕。
  • DMA2D传输:硬件加速,不占CPU,适合大屏幕或高帧率场景。

我个人强烈建议用DMA2D。为什么?因为TouchGFX的渲染引擎本身就很吃性能,你再让CPU去搬运数据,帧率直接掉一半。

3.3.1 实现DMA2D传输

在 BoardConfiguration.cpp 中,你需要重写 flushFrameBuffer() 方法:

void TouchGFXHAL::flushFrameBuffer(const Rect& rect)
{
    // 配置DMA2D源地址(帧缓冲)和目标地址(LCD GRAM)
    DMA2D->CR = DMA2D_CR_M2M;
    DMA2D->FGMAR = (uint32_t)getFrameBufferStartAddress();
    DMA2D->OMAR = (uint32_t)LCD_GRAM_ADDR;
    DMA2D->FGPFCCR = DMA2D_FGPFCCR_CM_RGB565;
    DMA2D->NLR = (rect.height << 16) | rect.width;
    
    // 启动传输
    DMA2D->CR |= DMA2D_CR_START;
    
    // 等待完成
    while (DMA2D->CR & DMA2D_CR_START);
}

这里有个细节:rect 参数表示需要刷新的区域。TouchGFX只会刷新变化的部分,而不是全屏刷新。这能大幅提升性能。

关键点: 如果你的LCD控制器支持局部刷新,一定要利用好 rect 参数。否则每次都全屏刷新,DMA2D的优势就浪费了。

3.4 触摸驱动集成:让交互活起来

触摸驱动是用户交互的入口。TouchGFX的触摸框架设计得很巧妙——它通过一个 TouchController 抽象类来解耦硬件。

3.4.1 实现TouchController

在 TouchController.cpp 中,你需要实现三个核心方法:

bool TouchController::init()
{
    // 初始化I2C或SPI接口
    // 复位触摸芯片
    // 读取设备ID确认通信正常
    return true;
}

bool TouchController::sampleTouch(int32_t& x, int32_t& y)
{
    uint8_t data[5];
    // 读取触摸坐标数据
    if (readTouchData(data)) {
        x = (data[1] << 8) | data[2];
        y = (data[3] << 8) | data[4];
        return true;
    }
    return false;
}

这里有个容易忽略的点:sampleTouch() 的返回值。如果返回 false,TouchGFX会认为没有触摸事件,不会触发任何回调。所以一定要确保在无触摸时返回 false,而不是返回上一次的坐标。

3.4.2 校准与坐标映射

触摸屏的物理坐标和屏幕像素坐标往往不是一一对应的。你需要做校准。我常用的方法是:

  1. 在屏幕四个角显示校准点。
  2. 记录用户点击时的触摸坐标。
  3. 用线性插值计算出映射系数。

不过在实际项目中,很多触摸芯片(如FT5x06、GT911)内部已经做了校准,你只需要读取坐标即可。但如果你用的是电阻屏,那就必须自己做校准了。

避坑指南: 我曾经在一个项目里用了电容屏,但触摸方向反了——点击左上角,响应在右下角。后来发现是触摸芯片的坐标原点和屏幕的扫描方向不一致。解决办法很简单:在 sampleTouch() 里做一次坐标翻转:x = displayWidth - x。

3.5 整体移植流程总结

为了让你更直观地理解整个移植流程,我画了一张图:

TouchGFX基础框架移植流程 安装TouchGFX Designer 生成空项目框架 HAL层配置 帧缓冲驱动 触摸驱动集成 编译验证与调试 提示:每一步完成后都建议编译一次,避免最后一次性排查大量错误

从图中你可以看到,整个流程是线性的,但HAL配置之后会分叉成两个并行任务——帧缓冲和触摸驱动。我建议先搞定帧缓冲,因为屏幕能亮了,你才能验证触摸坐标对不对。

3.6 常见问题与调试技巧

  • 屏幕不亮:检查帧缓冲地址是否正确,LCD初始化时序是否完整。用逻辑分析仪抓一下LCD的时钟和数据线。
  • 触摸无响应:先确认I2C/SPI通信正常。可以在 sampleTouch() 里加个串口打印,看看坐标数据是否在变化。
  • 显示花屏:大概率是像素格式不匹配。检查 setBitsPerPixel() 和LCD控制器的配置是否一致。
我的调试习惯: 先让屏幕显示一个纯色背景(比如红色),确认帧缓冲和LCD驱动没问题。然后显示一个移动的方块,验证刷新区域是否正确。最后再集成触摸。这样一步步来,出问题能快速定位。

好了,这一章的内容就到这里。TouchGFX的移植其实不复杂,关键是把每个环节的配置搞清楚。你只要按照我上面说的步骤来,基本不会出大问题。