跳转到主要内容

抽象层架构

如前一节所述,TouchGFX AL具有一套特殊的职责。 职责要么在AL (HAL)的硬件部分实现,要么在与TouchGFX Engine同步的AL部分实现,典型的做法是通过RTOS (OSAL)来实现。 下表总结了这些在前一节中概述的职责:

职责操作系统或硬件
将TouchGFX Engine主循环与显示屏传输同步操作系统与硬件
报告触摸与物理按钮事件硬件
同步帧缓冲访问操作系统
报告下一个可用的帧缓冲区硬件
执行渲染操作硬件
处理到显示屏的帧缓冲传输 硬件

以下每个小节重点介绍履行上述职责应采取的措施。 对于定制硬件平台,STM32CubeMX中的TouchGFX Generator可以生成大多数AL和相应的TouchGFX工程。 AL开发人员必须手动实现其余部分,这此部分会通过代码注释提示,并通过TouchGFX Generator通知。 在下一节中阅读有关TouchGFX Generator的更多信息。

抽象层类

TouchGFX引擎通过具体的子类和类实现文件(.cpp)来访问TouchGFX AL,实现了TouchGFX 引擎中定义的类成员函数。 这些子类和类实现文件由TouchGFX Generator生成。 TouchGFX Generator既可以生成反映STM32CubeMX配置的HAL部分,也可以生成所用RTOS的OSAL。 请阅读TouchGFX Generator章节,以获取更多详细信息。 通常,HAL的架构如下图所示。

生成的代码的层次结构

将TouchGFX Engine主循环与显示屏传输同步

此步骤背后的主要思想是,在渲染完成后阻塞TouchGFX 引擎主循环,从而确保不再产生其他帧。 一旦显示准备就绪,OSAL向被阻塞的Engine主循环发出信号,以继续产生显示帧。

为履行该职责,TouchGFX AL的典型方法是利用渲染完成引擎钩子和显示就绪中断,如抽象层的职责中所述。 OSAL定义了OSWrappers::signalVSync函数,开发人员可调用此函数给引擎发送信号量。

Tip
TouchGFX Generator可以为CMSIS V1、CMSIS V2、ThreadX以及不使用RTOS时创建完整的OSAL。

渲染完成

渲染完成后,TouchGFX Engine调用渲染完成钩子、OSWrappers::waitForVSync

在实现此OSAL方法时,抽象层必须阻塞图形引擎,直至渲染下一帧。 实现该阻塞的标准方法是阻止从消息队列进行读取。 如果此方法不可行,则HAL开发人员可自由使用任何方法来实现该阻塞。

Tip
如果没有此类软件,TouchGFX Generator也可以生成一个使用自旋锁来等待,而非使用RTOS基元的空OSAL。

OSWrappers::signalVSync发出信号时(或用于OSWrappers::waitForVSync的信号量/队列发出信号时) ,TouchGFX将开始渲染下一个应用帧。 基于CMSIS V2的以下代码会造成TouchGFX Engine阻塞,直至某元素被系统的另一个部分(通常为与显示同步的中断) 添加到队列中。

OSWrappers.cpp (CMSIS V2)
static osMessageQueueId_t vsync_queue = NULL; //Queue identifier is assigned elsewhere

void OSWrappers::waitForVSync()
{
uint32_t dummyGet;
// First make sure the queue is empty, by trying to remove an element with 0 timeout.
osMessageQueueGet(vsync_queue, &dummyGet, 0, 0);

// 然后等待下一次VSYNC。
osMessageQueueGet(vsync_queue, &dummyGet, 0, osWaitForever);
}

如果未使用RTOS,TouchGFX Generator使用易失性变量为waitForVSync提供以下实现。

OSWrappers.cpp (No OS)
static volatile uint32_t vsync_sem = 0;

void OSWrappers::waitForVSync()
{
if(vsync_sem)
{
vsync_sem = 0;
// Signal TouchGFX to start rendering next frame
...
}
}
Tip
  • TouchGFX Engine等待生成下一帧时,其他任务可以执行重要的工作。
  • 显示就绪

    用于解除主循环阻塞的显示就绪信号应来自显示控制器、显示屏本身或是硬件定时器的中断。 信号源取决于显示屏类型。

    OSWrappers类为该信号定义了一个函数:OSWrappers::signalVsync 该函数的实现必须通过满足OSWrappers::waitForVSync中使用的等待条件来解锁主循环阻塞。

    继续上面的CMSIS RTOS示例,以下代码将消息放入队列vsync_queue中,此消息队列将解除对TouchGFX Engine的阻塞。

    OSWrappers.cpp (CMSIS V2)
    void OSWrappers::signalVSync()
    {
    osMessageQueuePut(vsync_queue, &dummy, 0, 0);
    }

    OSWrappers::signalVSync方法必须在硬件层面上通过LTDC、显示屏的外部信号或硬件定时器等的中断调用。

    如果不使用RTOS,则使用变量,并分配一个非零值以打破while循环。

    OSWrappers.cpp (No OS)
    void OSWrappers::signalVSync()
    {
    vsync_sem = 1;
    }

    报告触摸与物理按钮事件

    在渲染新帧之前,TouchGFX Engine从TouchControllerButtonController接口收集外部输入。

    触摸坐标

    通过引擎将触摸控制器的坐标转换为点击、拖动和手势事件,并传递至应用程序。

    TouchGFX 引擎访问触摸控制器的方式是通过传递触摸类的实现

    以下代码由TouchGFX Generator生成:

    TouchGFXConfiguration.cpp
    static STM32TouchController tc;
    ...
    static TouchGFXHAL hal(dma, display, tc, 390, 390);

    在TouchGFX Engine渲染周期中,在收集输入时,引擎调用tc对象上的sampleTouch()函数。

    bool STM32TouchController::sampleTouch(int32_t& x, int32_t& y)

    AL开发人员提供的实现函数应将读取的触摸坐标值分配给x和y,并返回是否检测到触摸(真或假)。

    Tip
    TouchGFX Generator将生成一个将TouchController接口函数定义为空的类。 HAL开发人员必须填入具体实现代码。

    有多种实现此函数的方法:

    1. 在sampleTouch() 中轮询:通过发送请求并轮询结果,从硬件触摸控制器(通常为I2C) 读取触摸状态。 这会影响应用程序的总体渲染时间,因为I2C往返传输通常长达1ms,在此期间,图形引擎会被阻塞。
    2. 基于中断:另一种可能是使用中断。 I2C读取命令由定时器定期启动,或作为对触摸硬件外部中断的响应而启动。 按钮控制器接口touchgfx::ButtonController可用于将硬件信号(按钮或其他) 映射到应用程序事件。 以下STM32TouchController.cpp(由TouchGFX Generator创建) 代码显示了sampleTouch如何查找带RTOS的系统:
    STM32TouchController.cpp
    bool STM32TouchController::sampleTouch(int32_t& x, int32_t& y)
    {
    if (osMessageQueueGet(mid_MsgQueue, &msg, NULL, 0U) == osOK)
    {
    x = msg.x;
    y = msg.y;
    return true;
    }
    return false;
    }

    将在TouchGFX Generator的下一章中概述此文件的位置

    其他外部事件

    按钮控制器接口touchgfx::ButtonController可用于将硬件信号(按钮或其他) 映射到应用程序事件。 可在TouchGFX Designer中配置对这些事件的反应。

    该接口的使用与上述触摸控制器类似,只是并非必须具有ButtonController。 要使用该接口,请创建一个实现ButtonController接口的类实体,并将参数传递至HAL实体:

    MyButtonController.cpp
    class MyButtonController : public touchgfx::ButtonController
    {
    bool sample(uint8_t& key)
    {
    ... //Sample IO, set key, return true/false
    }
    };
    TouchGFXConfiguration.cpp
    static MyButtonController bc;
    void touchgfx_init()
    {
    ...
    hal.initialize();
    hal.setButtonController(&bc);
    }

    每帧之前都会调用ButtonController类中的样本方法。 如果返回真值,则键值将被传递至当前屏幕的handleKeyEvent事件处理程序。

    Further reading
    有关如何将通过ButtonController采集的值用作设计工具中的交互触发器的更多信息,请参见交互文章。

    同步帧缓冲访问

    多个执行体可能涉及对帧缓存区的访问。

    1CPU在渲染期间读取和写入像素
    2DMA2D在硬件辅助渲染期间读取和写入像素
    3LTDC在传输到并行RGB显示屏期间读取像素
    4DMA在传输到SPI显示屏期间读取像素

    TouchGFX Engine通过OSWrappers接口来同步帧缓存访问,同时希望访问帧缓存的外设(如DMA2D) 也必须执行相同操作。 常规设计是使用信号量来保证对帧缓冲的访问,但也可以使用其他同步机制。

    下表显示了OSWrappers类(OSWrappers.cpp) 中的函数列表,这些函数可由TouchGFX Generator生成或由用户手动生成。

    方法说明
    takeFrameBufferSemaphore由图形引擎调用,以获得对帧缓存的独占访问。 这将阻塞引擎,直至DMA2D完成(如果正在运行)
    tryTakeFrameBufferSemaphore确保已锁定。 该方法不会阻塞引擎,但对takeFrameBufferSemaphore的下次调用将被阻塞
    giveFrameBufferSemaphore解除帧缓存锁定
    giveFrameBufferSemaphoreFromISR从中断上下文解除帧缓存锁定
    Tip
    TouchGFX Generator可生成使用OSWrappers接口来同步的ChromART驱动程序,以及根据RTOS选择来执行该同步的函数实现。

    报告下一个可用的帧缓冲区

    无论采用哪种渲染策略,TouchGFX Engine都必须知道在每个时间片中应将像素渲染到哪个存储区。 使用单帧缓存或双帧缓存战略时,TouchGFX Engine将根据帧缓存的全宽、高度和位宽将像素数据写入存储区。 图形引擎负责双缓存设置中两个帧缓存之间的交换。

    可以将对帧缓存的访问限制为部分帧缓存。 可在HAL子类中重新实现HAL::getTFTCurrentLine()方法。 返回上面用于图形引擎绘制而保存的行号。

    使用部分帧缓存时,开发人员定义TouchGFX Engine在渲染时将使用的一个或多个存储器块。 在此处阅读更多相关信息。

    Tip
    TouchGFX Generator支持所有帧缓冲策略的配置。

    执行渲染操作

    渲染和显示图形很少是应用程序的唯一目的。 其他任务也需要使用CPU。 TouchGFX的目标之一尽可能少地占用CPU资源来绘制用户界面。 HAL类可对许多STM32微控制器(或其他硬件功能) 上的DMA2D功能进行抽象,并使其可用于图形引擎。

    将位图之类的资源渲染到帧缓冲时,TouchGFX Engine检查HAL是否具有将部分或全部位图传输到帧缓存的功能。 如果有此功能,则将绘图操作委托给HAL,而不是由CPU处理。

    引擎调用方法HAL::getBlitCaps(),以获取硬件功能描述。 HAL子类可重新实现此调用,以添加功能。

    引擎在绘制用户界面时调用HAL类上的操作(HAL::blitCopy) ,并对DMA操作排队。 如果HAL无法报告所需的功能,则图形引擎将退而使用软件方式来渲染。

    Tip
    许多STM32 MCU都具有ChromART芯片,在执行alpha像素混合时,可将数据从外部Flash存储器等移动到帧缓存。 对于许多MCU,TouchGFX Generator可生成ChromART驱动程序,该驱动程序使用ChromART芯片来增加几个“块位传输”操作功能。

    处理到显示屏的帧缓冲传输

    为将帧缓存内容传输到显示屏,TouchGFX AL经常使用“区域渲染完成”钩子。 一旦部分帧缓存渲染完成,引擎就会向AL发送信号。 AL可选择如何将此帧缓存部分内容传输到显示屏。

    区域渲染完成

    在代码中,此钩子为虚拟函数HAL::flushFrameBuffer(Rect& rect)

    在带有LTDC控制器的STM32微控制器上,我们无需在每次渲染后执行任何用于帧缓存传输的操作。 在LTDC初始化之后,该传输将以给定的频率连续发生,因此我们可以将此方法的实现留空。

    对于其他显示屏类型(如SPI或8080) ,您需要手动实现帧缓存内容传输。

    此函数的实现允许开发人员发起向带有GRAM的显示屏的帧缓冲区域的手动传输:

    void TouchGFXHAL::flushFrameBuffer(const touchgfx::Rect& r)
    {
    HAL::flushFrameBuffer(rect); //call superclass

    //start transfer if not running already!
    if (!IsTransmittingData())
    {
    const uint8_t* pixels = ...; // Calculate pixel address
    SendFrameBufferRect((uint8_t*)pixels, r.x, r.y, r.width, r.height);
    }
    else
    {
    ... // Queue rect for later or wait here
    }
    }
    Further reading
    通读方案以获取有关如何支持各种显示接口的具体示例。