抽象层架构
如前一节所述,TouchGFX AL具有一套特殊的职责。 Responsibilities are either implemented in the hardware part of the AL (HAL) or the part of the AL that synchronizes with TouchGFX Engine, typically through an RTOS (OSAL). The following table summarizes these responsibilities which were outlined in the previous section:
职责 | 操作系统或硬件 |
---|---|
将TouchGFX Engine主循环与显示屏传输同步 | 操作系统与硬件 |
报告触摸与物理按钮事件 | 硬件 |
同步帧缓冲访问 | 操作系统 |
报告下一个可用的帧缓冲区 | 硬件 |
执行渲染操作 | 硬件 |
处理到显示屏的帧缓冲传输 | 硬件 |
以下每个小节重点介绍履行上述职责应采取的措施。 For custom hardware platforms the TouchGFX Generator, inside STM32CubeMX, can generate most of the AL and accompanying TouchGFX project. The remaining parts, that the AL developer must implement manually, are pointed out through code comments and notifications through the TouchGFX Generator. Read more about the TouchGFX Generator in the next section.
抽象层类
The TouchGFX AL is accessed by the TouchGFX Engine through concrete sub-classes and through class implementation files (.cpp) where member functions of classes defined in the TouchGFX Engine are implemented. These sub-classes and class implementation files are generated by the TouchGFX Generator. The TouchGFX Generator can generate both the part of the HAL that reflects configurations from STM32CubeMX, as well as the OSAL for the used RTOS. Please read the section on TouchGFX Generator for further details. 通常,HAL的架构如下图所示。
将TouchGFX Engine主循环与显示屏传输同步
The main idea behind this step is to block the TouchGFX Engine main loop when rendering is done, ensuring that no further frames are produced. 一旦显示准备就绪,OSAL向被阻塞的Engine主循环发出信号,以继续产生显示帧。
In order to fulfill this responsibility the typical way of a TouchGFX AL is to utilize the engine hook Rendering done and the interrupt Display Ready, as outlined in Responsibilities of the Abstraction Layer. The OSAL defines a function OSWrappers::signalVSync
in which developers can signal the semaphore that the engine waits upon when called.
Tip
渲染完成
渲染完成后,TouchGFX Engine调用渲染完成钩子、OSWrappers::waitForVSync
。
When implementing this OSAL method, the Abstraction Layer must block the graphics engine until it is time to render the next frame. 实现该阻塞的标准方法是阻止从消息队列进行读取。 The HAL developer is free to use any method to implement the block if this is not feasible.
Tip
When OSWrappers::signalVSync
is signaled (or the semaphore/queue used in OSWrappers::waitForVSync
is signaled) TouchGFX will start rendering the next application frame. The following code based on CMSIS V2 causes the TouchGFX engine to block until an element is added to the queue by another part of the system, typically an interrupt synchronized with the display.
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
显示就绪
The Display ready signal to unblock the main loop should come from an interrupt from a display controller, from the display itself or even from a hardware timer. The source of the signal is dependent on the type of display.
OSWrappers
类为该信号定义了一个函数:OSWrappers::signalVsync
The implementation of the function must unblock the main loop by satisfying the wait condition used in OSWrappers::waitForVSync
.
Continuing from the above CMSIS V2 example, the following code puts a message into the message queue vsync_queue
which unblocks the TouchGFX Engine.
OSWrappers.cpp (CMSIS V2)
void OSWrappers::signalVSync()
{
osMessageQueuePut(vsync_queue, &dummy, 0, 0);
}
This OSWrappers::signalVSync
method must be called at hardware level from an interrupt for e.g. an LTDC, an external signal from the display, or a hardware timer.
如果不使用RTOS,则使用变量,并分配一个非零值以打破while循环。
OSWrappers.cpp (No OS)
void OSWrappers::signalVSync()
{
vsync_sem = 1;
}
报告触摸与物理按钮事件
Before rendering a new frame, the TouchGFX Engine collects external input from the TouchController
and ButtonController
interfaces.
触摸坐标
Coordinates from the touch controller are translated into click-, drag- and gesture events by the TouchGFX Engine and passed to the application.
The way touch coordinated from the touch controller is accessed by the TouchGFX Engine is by passing an implementation
The following code is generated by the 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
有多种实现此函数的方法:
- 在sampleTouch() 中轮询:通过发送请求并轮询结果,从硬件触摸控制器(通常为I2C) 读取触摸状态。 这会影响应用程序的总体渲染时间,因为I2C往返传输通常长达1ms,在此期间,图形引擎会被阻塞。
- 基于中断:另一种可能是使用中断。 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
同步帧缓冲访问
多个执行体可能涉及对帧缓存区的访问。
1 | CPU | 在渲染期间读取和写入像素 |
2 | DMA2D | 在硬件辅助渲染期间读取和写入像素 |
3 | LTDC | 在传输到并行RGB显示屏期间读取像素 |
4 | DMA | 在传输到SPI显示屏期间读取像素 |
TouchGFX Engine通过OSWrappers
接口来同步帧缓存访问,同时希望访问帧缓存的外设(如DMA2D) 也必须执行相同操作。 常规设计是使用信号量来保证对帧缓冲的访问,但也可以使用其他同步机制。
下表显示了OSWrappers
类(OSWrappers.cpp) 中的函数列表,这些函数可由TouchGFX Generator生成或由用户手动生成。
方法 | 说明 |
---|---|
takeFrameBufferSemaphore | 由图形引擎调用,以获得对帧缓存的独占访问。 这将阻塞引擎,直至DMA2D完成(如果正在运行) |
tryTakeFrameBufferSemaphore | 确保已锁定。 该方法不会阻塞引擎,但对takeFrameBufferSemaphore的下次调用将被阻塞 |
giveFrameBufferSemaphore | 解除帧缓存锁定 |
giveFrameBufferSemaphoreFromISR | 从中断上下文解除帧缓存锁定 |
Tip
报告下一个可用的帧缓冲区
无论采用哪种渲染策略,TouchGFX Engine都必须知道在每个时间片中应将像素渲染到哪个存储区。 使用单帧缓存或双帧缓存战略时,TouchGFX Engine将根据帧缓存的全宽、高度和位宽将像素数据写入存储区。 图形引擎负责双缓存设置中两个帧缓存之间的交换。
可以将对帧缓存的访问限制为部分帧缓存。 可在HAL子类中重新实现HAL::getTFTCurrentLine()
方法。 返回上面用于图形引擎绘制而保存的行号。
使用部分帧缓存时,开发人员定义TouchGFX Engine在渲染时将使用的一个或多个存储器块。 在此处阅读更多相关信息。
Tip
执行渲染操作
渲染和显示图形很少是应用程序的唯一目的。 其他任务也需要使用CPU。 TouchGFX的目标之一尽可能少地占用CPU资源来绘制用户界面。 HAL类可对许多STM32微控制器(或其他硬件功能) 上的DMA2D功能进行抽象,并使其可用于图形引擎。
将位图之类的资源渲染到帧缓冲时,TouchGFX Engine检查HAL是否具有将部分或全部位图传输到帧缓存的功能。 如果有此功能,则将绘图操作委托给HAL,而不是由CPU处理。
引擎调用方法HAL::getBlitCaps()
,以获取硬件功能描述。 HAL子类可重新实现此调用,以添加功能。
引擎在绘制用户界面时调用HAL类上的操作(HAL::blitCopy
) ,并对DMA操作排队。 如果HAL无法报告所需的功能,则图形引擎将退而使用软件方式来渲染。
Tip
处理到显示屏的帧缓冲传输
为将帧缓存内容传输到显示屏,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
}
}