Skip to main content

抽象层架构

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

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

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

抽象层类

TouchGFX引擎通过HAL的具体子类来访问HAL。 这些子类由TouchGFX Generator生成。 作为创建抽象层的主要工具,生成器可生成反应CubeMX的配置的HAL部分,以及基于CMSIS V1和V2的OSAL。 请阅读TouchGFX Generator章节,以获取更多详细信息。 通常,HAL的架构如下图所示。

生成的代码的层次结构

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

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

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

Tip
TouchGFX Generator可以创建基于CMSIS V1和V2的完整OSAL。

渲染完成

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

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

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

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

RTOS_OSWrappers.cpp
static osMessageQId vsync_queue = 0; //Queue identifier is assigned elsewhere

void OSWrappers::waitForVSync()
{
//Wait for next VSYNC to occur, by reading from the queue
osMessageGet(vsync_queue, osWaitForever);
}

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

NO_OS_OSWrappers.cpp
static volatile uint8_t vsync_sem = 0;

void OSWrappers::waitForVSync()
{
while(!vsync_sem)
{
// Perform other work while waiting
...
}
}
Tip
  • TouchGFX Engine等待生成下一帧时,其他任务可以执行重要的工作。
  • 显示就绪

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

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

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

    RTOS_OSWrappers.cpp
    void OSWrappers::signalVSync()
    {
    if (vsync_queue)
    {
    osMessagePut(vsync_queue, dummy, 0);
    }
    }

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

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

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

    报告触摸与物理按钮事件

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

    触摸坐标

    通过引擎将触摸控制器的坐标转换为点击、拖动和手势事件,并传递至应用程序。 以下代码由TouchGFX Generator生成:

    TouchGFXConfiguration.cpp
    static STM32TouchController tc;
    static STM32L4DMA dma;
    static LCD24bpp display;
    static ApplicationFontProvider fontProvider;
    static Texts texts;
    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
    通读方案以获取有关如何支持各种显示接口的具体示例。