FMC和SPI显示接口
以下方案介绍了利用TouchGFX Generator选择定制的通过FMC或SPI与LCD相连的显示接口,并为其编写TouchGFX驱动程序通常涉及的步骤。
Further reading
通过FMC与SPI为不带内置显示控制器的MCU编写TouchGFX显示驱动程序的过程是相同的。 本节描述的方案以ST7789H2 LCD控制器为例。
一旦根据CubeMX中的板规格配置了FMC或SPI,通过选择定制显示接口,TouchGFX Generator可用于生成HAL,开发人员可以编写自定义代码,将更新过的应用程序帧缓存内容传输到显示器。
下图显示了选择定制显示接口时的TouchGFX Generator配置。 该配置会向TouchGFX Generator指示,开发人员希望完成相关显示配置后手动将像素从帧缓存传输到显示器,并生成用于完成该操作的句柄。
Note
驱动程序必须能够将像素传输到显示器,并可以控制显示器的像素写入位置。 检查其数据手册以找到如下面所述的适当命令以及更多详细信息。
通常,对于自带GRAM的显示器(如8080或SPI接口显示器),驱动程序的工作方式如下:
- 根据要重绘的帧缓存区域,将“显示光标”和“活动窗口”移动到GRAM中与此区域对应的位置。
- 准备将传入的像素数据写入GRAM。
- 发送像素数据。
传输帧缓存内容
在帧缓存内容被更新后,TouchGFX Engine会调用HAL::flushFrameBuffer(Rect r)
。 当开发人员必须为定制的显示接口编写驱动程序时,此函数可被忽略。
void TouchGFXHAL::flushFrameBuffer(const Rect& rect)
{
/* Set Cursor */
__ST7789H2_SetDisplayWindow(rect.x, rect.y, rect.width, rect.height);
/* Prepare to write to LCD RAM */
ST7789H2_WriteReg(ST7789H2_WRITE_RAM, (uint8_t*)NULL, 0);
/* Send Pixels */
this->copyFrameBufferBlockToLCD(rect);
}
以下__ST7789H2_SetDisplayWindow
函数通过写入特定寄存器来设置GRAM中虚拟“光标”的x
和y
坐标,对于使用GRAM的显示器,这很常见的用法。
extern "C"
void __ST7789H2_SetDisplayWindow(uint16_t Xpos, uint16_t Ypos, uint16_t Width, uint16_t Height)
{
uint8_t parameter[4];
/* CASET: Column Address Set */
parameter[0] = 0x00;
parameter[1] = Xpos;
parameter[2] = 0x00;
parameter[3] = Xpos + Width - 1;
ST7789H2_WriteReg(ST7789H2_CASET, parameter, 4);
/* RASET: Row Address Set */
parameter[0] = 0x00;
parameter[1] = Ypos;
parameter[2] = 0x00;
parameter[3] = Ypos + Height - 1;
ST7789H2_WriteReg(ST7789H2_RASET, parameter, 4);
}
以下TouchGFXHAL::copyFrameBufferBlockToLCD
函数是一个私有函数,该函数一次发送一行更新过的帧缓存区域(Rect
) ,并相应地更新帧缓冲指针。
void TouchGFXHAL::copyFrameBufferBlockToLCD(const Rect& rect)
{
__IO uint16_t* ptr;
uint32_t height;
// This can be accelerated using regular DMA hardware
for (height = 0; height < rect.height ; height++)
{
ptr = getClientFrameBuffer() + rect.x + (height + rect.y) * BSP_LCD_GetXSize();
LCD_IO_WriteMultipleData((uint16_t*)ptr, rect.width);
}
}
TouchGFX Generator将生成一个函数 advanceFrameBufferToRect
,并根据Rect
在帧缓冲中的位置来推进ptr
指针,而不用手动推进ptr
指针。
inline uint8_t* TouchGFXGeneratedHAL::advanceFrameBufferToRect(uint8_t* fbPtr, const touchgfx::Rect& rect) const
{
// Advance vertically Advance horizontally
fbPtr += rect.y * lcd().framebufferStride() + rect.x * 2;
return fbPtr;
}
从HAL::flushFrameBuffer()返回
在函数返回后,TouchGFX Engine继续绘制剩余的帧内容。 如果开发人员希望使用DMA将像素传输到显示器,他们必须通过等待DMA完成中断发出的信号量来确保HAL::flushFrameBuffer(Rect& rect)
不会立即返回。
以下伪代码示例显示了在使用DMA的情况下如何构造HAL::flushFrameBuffer()
。 此代码使用了FreeRTOS信号量screen_frame_buffer_sem
。
void TouchGFXHAL::flushFrameBuffer(const touchgfx::Rect& rect)
{
uint16_t* fb = HAL::lockFrameBuffer();
//Prepare display
prepare();
//Try to take a display semaphore - Always free at this point
xSemaphoreTake(screen_frame_buffer_sem, portMAX_DELAY);
//Set up DMA
screenDMAEnable();
// Wait for the DMA transfer to complete
xSemaphoreTake(screen_frame_buffer_sem, portMAX_DELAY);
//Unlock framebuffer and give semaphore back
HAL::unlockFrameBuffer();
xSemaphoreGive(screen_frame_buffer_sem);
}
TouchGFX驱动程序/撕裂效果信号
从上面的TouchGFX Generator配置中可以看出,应用中的“滴答计时源”也被设为“定制”,对于不内置TFT控制器的MCU来说,这也算是常见设置。
如抽象层架构部分所述,通常在发出显示信号时,调用OSWrappers::signalVSync()
来解除TouchGFX Engine主循环阻塞。
对于具有串行或8080显示接口的显示器,内置显示控制器通常会产生一个周期性撕裂效果(TE)信号,该信号可以连接到MCU上的GPIO。 在这种情况下,通常将MCU配置为当GPIO收到该信号时触发中断。 然后,该“撕裂效果”中断将解除对TouchGFX Engine主循环的阻塞,以便渲染下一帧。 请记住将GPIO配置为输入,并在CubeMX中使能该脚的外部中断。
extern "C"
void TE_Handler(void)
{
...
/* Unblock TouchGFX Engine Main Loop to render next frame */
OSWrappers::signalVSync();
...
}
结论
开发人员可通过TouchGFX Generator选择定制显示接口,并自主编写代码实现将像素从应用程序帧缓存传输到显示器之目的。
TouchGFX Generator将生成functionTouchGFXHAL::flushFrameBuffer(Rect& rect)
函数,当渲染完成帧缓存的一片区域后,TouchGFX会自动调用该函数,同时,开发人员将更新过的帧缓存数据通过FMC、SPI或其它途径传送到显示器。
选择定制显示接口也需要开发人员实现定制的TouchGFX滴答计时驱动,该驱动程序发出OSWrappers::signalVSync()
信号,以解除对TouchGFX Engine主循环的阻塞。 通常,与不带TFT控制器的MCU一起使用的显示器会提供连接至MCU的撕裂效果信号。