跳转到主要内容

FMC和SPI显示接口

下面的场景展示了,将像素数据发送到FMC或SPI接口的LCD上的步骤;两个方法共享一些元素。 本节描述的方案以ST7789H2 LCD控制器为例。

在STM32CubeMX中,当根据开发板规范配置好FMC或SPI后,就可由TouchGFX Generator生成TouchGFX HAL,允许开发人员编写代码,将应用程序帧缓冲器的更新部分传输到连接的显示屏。

下图显示了选择Custom显示接口时的TouchGFX Generator配置。 此配置告诉TouchGFX Generator,开发者希望通过手动方式去配置并将像素数据从帧缓存传输到显示屏,并生成用于完成该操作的句柄。

TouchGFX Generator配置

Tip
对于通过SPI连接的显示屏,必须选择Custom显示接口

通常,对于内嵌GRAM的显示屏,用户在生成的TouchGFX HAL中编写的代码应该执行以下步骤:

  1. 根据要重绘的帧缓存区域,将“显示光标”和“活动窗口”移动到GRAM中与此区域对应的位置。
  2. 准备将传入的像素数据写入GRAM。
  3. 发送像素数据。

传输帧缓存内容

在帧缓存内容被更新后,TouchGFX Engine会调用HAL::flushFrameBuffer(Rect r)。 当开发人员必须实现代码以手动将像素数据发送到显示屏(例如,用到SPI和FMC)时,可重写此函数。 我们将看到,通过FMC Banks发送像素数据的函数由TouchGFX Generator生成。

Note
本节中显示的ST7789H2驱动程序代码将在板搭建阶段开发,一旦驱动有效工作,就可以或多或少地复制到TouchGFX Generator生成的HAL类中。

驱动程序必须能够将像素传输到显示屏,并可以控制显示屏的像素写入位置。 如需进一步的详细信息,请查看显示屏的数据手册。

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 - User defined function */
this->copyFrameBufferBlockToLCD(rect);
}

以下ST7789H2_SetDisplayWindow函数通过写入特定寄存器来设置GRAM中的虚拟“光标”的x<code>和<code>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) * HAL::DISPLAY_WIDTH;
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;
}

FMC

TouchGFX Generator也支持FMC显示接口,如果至少一个FMC Bank配置正确。 本例中,TouchGFX Generator生成的代码与Custom显示接口的代码类似,除了生成的LCD_IO_WriteMultipleData函数,用于与FMC Bank相连的显示屏进行交互。 重新查看前面的copyFrameBufferBlockToLCD函数代码,您将看到它使用了生成的函数。

Tip
对于SPI和FMC显示接口,开发人员将修改flushFrameBuffer() 函数为:1) 设置光标 2) 准备写入GRAM 3) 通过自定义SPI显示驱动或通过生成的FMC Bank函数传输像素数据。
    __weak void LCD_IO_WriteMultipleData(uint16_t* pData, uint32_t Size)
{
uint32_t i;

for (i = 0; i < Size; i++)
{
FMC_BANK1_WriteData(pData[i]);
}
}

下图显示了一个有效的16位(必需的)FMC bank2的配置(两者都可以使用)。

FMC Bank配置

一旦实现了有效配置,可以在TouchGFX Generator中选择该bank。 验证您MCU的FMC Bank寄存器的起始地址。

FMC接口选择

TouchGFX Generator验证FMC Banks的配置,并报告它可能发现的任何问题。

FMC 配置错误

从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: Set cursor, write to display gram as described previously in this scenario

//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);
}
Caution
由TouchGFX Generator生成的FMC代码不使用DMA。

TouchGFX驱动程序/撕裂效果信号

从上面的TouchGFX Generator配置中可以看出,应用中的“滴答计时源”也被设为“定制”,对于不内置TFT控制器的MCU来说,这也算是常见设置。

如抽象层架构部分所述,通常在发出显示信号时,调用OSWrappers::signalVSync()来解除TouchGFX Engine主循环阻塞。

对于具有串行或8080显示接口的显示屏,内置显示控制器通常会产生一个周期性撕裂效果(TE)信号,该信号可以连接到MCU上的GPIO。 在这种情况下,通常将MCU配置为当GPIO收到该信号时触发中断。 然后,该“撕裂效果”中断将解除对TouchGFX Engine主循环的阻塞,以便渲染下一帧。 请记住将GPIO配置为输入,并在STM32CubeMX中使能该引脚的外部中断。

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或其它途径传送到显示屏。 无论如何,在这两种情况下都必须完成以下步骤:

  1. 根据要重绘的帧缓存区域,将“显示光标”和“活动窗口”移动到GRAM中与此区域对应的位置。
  2. 准备将传入的像素数据写入GRAM。
  3. 发送像素数据。 如果是FMC显示接口,该函数就是为您生成的,并可用在flushFrameBuffer(rect& rect)函数中(参见本文前面的内容)。

选择CustomFMC显示接口也需要开发人员实现定制的TouchGFX应用Tick驱动程序,用于发出OSWrappers::signalVSync()信号,以解除对TouchGFX Engine主循环的阻塞。 通常,与不带TFT控制器的MCU一起使用的显示屏会提供连接至MCU的撕裂效果信号。