抽象层架构
如前一节所述,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生成。 作为创建抽象层的__主要工具,generator可生成反应STM32CubeMX的配置的HAL部分,以及适用于CMSIS V1和V2的OSAL。 请阅读TouchGFX Generator章节,以获取更多详细信息。 通常,HAL的架构如下图所示。
将TouchGFX Engine主循环与显示屏传输同步
此步骤背后的主要思想是,在渲染完成后阻塞TouchGFX Engine主循环,从而确保不再产生其他帧。 一旦显示准备就绪,OSAL向被阻塞的Engine主循环发出信号,以继续产生显示帧。
为履行该职责,TouchGFX AL的典型方法如前一节中所述,就是利用渲染完成引擎钩子和显示就绪中断。 OSAL定义了OSWrappers::signalVSync
函数,开发人员可通过它发信号给引擎调用OSWrappers::signalVSync
。
Tip
渲染完成
渲染完成后,TouchGFX Engine调用渲染完成钩子、OSWrappers::waitForVSync
。
在实现此AL方法时,AL必须阻塞图形引擎,直至渲染下一帧。 实现该阻塞的标准方法是阻止从消息队列进行读取。 如果此方法不可行,则HAL开发人员可自由使用任何方法来实现该阻塞。
Tip
当OSWrappers::signalVSync
发出信号时(或用于OSWrappers::waitForVSync
的信号量/队列发出信号时) ,TouchGFX将开始渲染下一个应用帧。 基于CMSIS V2的以下代码会造成TouchGFX Engine阻塞,直至某元素被系统的另一个部分(通常为与显示同步的中断) 添加到队列中。
RTOS_OSWrappers.cpp
static osMessageQId vsync_queue = 0; //Queue identifier is assigned elsewhere
void OSWrappers::waitForVSync()
{
uint32_t dummyGet;
// 首先,通过尝试删除0超时元素,确保队列为空。
osMessageQueueGet(vsync_queue, &dummyGet, 0, 0);
// 然后等待下一次VSYNC。
osMessageQueueGet(vsync_queue, &dummyGet, 0, 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
显示就绪
用于解除主循环阻塞的显示就绪信号应来自显示控制器、显示屏本身或是硬件定时器的中断。 信号源取决于显示类型。
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从TouchController
和ButtonController
接口收集外部输入。
触摸坐标
通过引擎将触摸控制器的坐标转换为点击、拖动和手势事件,并传递至应用程序。 以下代码由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
有多种实现此函数的方法:
- 在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
}
}