Skip to main content

3. 内部RAM做帧缓冲

动机

在这一步中,通过将像素数据从内部RAM传输到显示器,我们可以看到显示器上会显示内容。 此步骤可确保我们能将数据传输到显示器,并且可以不断更新显示器内容。

除了将图像数据传输到显示器以外,我们还必须确保可连续将新数据发送到显示器,且不会在显示器上出现错误。 我们还将测量传输速度,因为这会影响显示器的帧率。

正如我们从上一节中学到的那样,我们将在内部RAM中放置一个帧缓冲,该RAM可读写。 我们将重复更新该帧缓冲,并将其传输到显示器。

回顾此 帧缓冲 是由此 公式计算的:

宽 x 高 x bpp

例如,分辨率为480 x 272的16位普通显示器将占用480x272x16/8字节 即61120字节的缓存。

即使显示器尺寸过大导致所需整帧缓存无法存储在内部RAM中,也不应跳过此步骤。 而应将显示控制器配置为仅更新显示器的一部分。 这样一来,我们可以调整帧缓冲所需的RAM量,并使其适合内部RAM大小。

显示接口的类型对传输帧缓冲所需的设置和代码有很大影响。 在本节中,我们将首先讨论连接至LTDC的显示器。 如果您使用诸如SPI显示器之类的显示器,代码会有很大区别,但任务和目标相同。

目标

本节的目标是将帧缓冲内容传输到显示器。 You should also verify that you can modify the framebuffer content and resend the framebuffer continuously.

验证

以下是本节的验证点:

验证点基本原理
显示帧缓存显示控制器或SPI已配置并正在运行
显示更新的帧缓存我们知道如何重复发送帧缓存数据
颜色正确LTDC相关的GPIO配置正确,显示器的数据格式与我们的帧缓冲匹配
帧率正确配置像素时钟和同步沿,以获得所需帧率

先决条件

以下是此步骤的先决条件:

  • 有关显示器的信息,通常为数据手册
  • 有关MCU和显示器之间的连接的信息。

执行

根据显示器类型,所需设置会有所不同。 但对于所有类型的显示器,我们都需要内部RAM中有一个帧缓冲。 一种分配该存储器的简单方法是只声明一个大小合适的全局数组:

main.c
uint16_t framebuffer[480*272];  //16 bpp framebuffer

如果内部RAM不足以容纳该数组,请声明一个对应于较小分辨率的数组,比方480x200。

将帧缓冲传输到显示器的方法取决于显示器类型。 我们现在来了解一下这点。

并行RGB显示器

我们首先将讨论连接至MCU的LTDC控制器的并行RGB显示器。

这类显示器的配置任务如下:

  • 配置与显示器有关的GPIO连接
  • 配置LTDC控制器
  • 配置LTDC像素时钟
  • 设置帧缓冲地址
  • 检查帧率

作为说明性示例,我们将使用STM32F746Discovery评估套件。 此板自带480*272显示器。

显示相关GPIO

该显示器以24 BPP模式运行,所以我们为LTDC和显示器之间的连接配置24个GPIO。 这在多媒体-> LTDC-> GPIO设置下的CubeMX中最容易实现:

配置显示相关GPIO

除了用于像素传输的24个GPIO(如LTDC_B0) 以外,我们还配置了4个显示控制信号:

信号功能
LTDC_CLK像素时钟。 当对24个线中的像素进行采样时,向显示器发出信号
LTDC_DE数据使能。 它被激活时进行像素传输
LTDC_HSYNC水平同步。 允许显示器找到像素行起点
LTDC_VSYNC垂直同步。 允许显示器找到帧起点

检查您的硬件设计并进行相应的配置。

LTDC 配置

LTDC配置位于多媒体-> LTDC-> 参数设置下的CubeMX中:

配置LTDC参数

有效宽度和高度与显示器的分辨率对应。 有关同步脉冲宽度和沿宽度,请检查显示数据手册。 另请注意信号极性。 灰色的值根据其他值计算而来。 这些值被写入LTDC寄存器(也可在代码中找到) 。

现在请转到多媒体-> LTDC-> 层设置”下的LTDC层配置:

配置LTDC层参数

对于该测试,在TouchGFX中,我们通常仅使用一层。 第0层的分辨率应与帧缓冲大小匹配。 以后需要设置帧缓冲地址,这里先不理它。

如果您声明了小于显示器分辨率的帧缓冲数组,则调整图层大小,以便与帧缓冲尺寸匹配。 LTDC将传输帧缓冲中没有的显示器像素背景颜色。 建议将背景颜色设置为可识别的颜色,如红色(蓝色).

时钟配置

时钟配置也很重要。 必须使能所有GPIO和LTDC的时钟。 像素时钟必须在显示器可接受的范围内。

时钟配置

LTDC取决于3个时钟: HCLK, PCLK2, and LCD_CLK.

设置帧缓冲地址

在CubeMX中,我们将第0层的帧缓冲地址配置为0xC0000000。 我们需要将其更改为内部RAM中的数组地址。 这可以使用其中一个Cube固件HAL函数来轻松实现:

main.c
  /* USER CODE BEGIN 2 */
HAL_LTDC_SetAddress(&hltdc, framebuffer, LTDC_LAYER_1);
/* USER CODE END 2 */

HAL函数中的层编号为1、2,而CubeMX中的层编号为0、1。 另外,通过CubeMX生成代码中的MX_LTDC_Init(void)函数全面配置LTDC。

LTDC控制器将帧缓冲重复发送至显示器。 显示的图像取决于帧缓冲中的值。 尝试帧缓冲中的不同值或模式。 例如,使用memset将帧缓冲清除为0xFF,以显示白屏。

Note
在某些显示器上,必须开启背光才能使图形帧可见。

检查帧率

LTDC控制器为每个帧触发一个中断。 该中断可被应用程序所利用。

您应使用调试器来检查是否触发了该中断。

这些中断间的时间是所有像素传输和同步沿的耗时总和。 您可以通过调节同步沿来调节帧率。 同步沿参数是LTDC配置的一部分。 通常通过增强场前沿来降低帧率。

一种测量帧率的简单方法为使用中断处理程序中的HAL_GetTick():

stm32f7xx_it.c
volatile int last = 0;
volatile int diff = 0;
void LTDC_IRQHandler(void)
{
/* USER CODE BEGIN LTDC_IRQn 0 */
int now = HAL_GetTick();
diff = last - now;
last = now;
/* USER CODE END LTDC_IRQn 0 */
HAL_LTDC_IRQHandler(&hltdc);
...

请记住,在每秒60帧的情况下,各个帧之间应该有1000 ms / 60 = 16 ms。

SPI 显示器

现在我们将讨论与SPI总线连接的显示器。

这类显示器的配置任务如下:

  • 配置SPI外设和GPIO
  • 检查时钟
  • 编写或找到必要的驱动程序代码

SPI 配置

在CubeMX中启动并使能SPI。 这些图像来自STM32G081项目:

SPI 配置

检查使用的SPI格式的显示器数据手册(数据大小和位顺序) 。 请记住,16位字以小端字节序存储在帧缓冲中。 检查是否可以将显示器配置为此格式。 如果不可以,则必须在传输期间进行数据转换。
另请注意时钟极性和时钟相位。 这些参数已在显示器数据手册中指定。

SPI时钟(比特率) 由FCLK的分频器控制。 最小分频器为2。 如果MCU正在运行(如64MHz) ,则最大SPI比特率为32 MBit/s。

在GPIO选项卡上,您可以检查SPI外设的GPIO配置:

SPI GPIO配置

在右侧的引脚排列视图中选择GPIO:

SPI GPIO选择

现在剩下的任务为配置显示器并在SPI通道上传输帧缓冲。 CubeMX无法为您生成此代码,因为它在很大程度上取决于显示器。
对于许多显示器,必须发送一系列命令,并遵循特定的上电时序。 之后,通常需要设置颜色模式,然后将显示器打开。 所有这些均必须在供应商提供的数据手册或示例中指定。

Cube固件包含使用SPI通信的示例。 Cube HAL包含各种助手功能。 用于发送数据的基本功能:

stm32g0xx_hal_spi.h
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);

我们建议使用这些库函数,直至通信稳定运行。 然后,可通过编写专用函数来提高性能。

在编写SPI显示驱动程序的过程中,SPI示波器或SPI USB记录器可能非常有用。

Note
从SPI上的低频开始,以避免噪声问题。

检查显示器颜色

此时,您可以将帧缓存内容传输到显示器,建议您彻底检查显示器颜色。

该想法就是将颜色写入帧缓存,并通过视觉来检查显示器。 以下为一些示例:

测试说明
红色在帧缓存中设置红色。 显示屏也必须为红色。
绿色在帧缓存中设置绿色。 显示屏也必须为绿色。
蓝色在帧缓存中设置蓝色。 显示屏也必须为蓝色。
深色如果设置为深色(如0x8000)(50%红色),则必须在显示屏上显示黑色。
更改颜色每秒更改一次帧缓存,然后查看显示屏是否更新。

要将颜色放入RGB565帧缓冲中,可使用以下方案:

uint8_t r    = 0xff, g = 0x00, b = 0x00;             // Solid red
uint16_t col = ((r>>3)<<11) | ((g>>2)<<5) | (b>>3); // Convert colors to RGB565
// put colors into the framebuffer
for(int i = 0; i < W*H; i++) {
framebuffer[i] = col;
}

对于24BPP显示器,最好使用字节指针来表示代码(颜色以BGR顺序存储):

uint8_t* framebuffer[480*272*3];  //24 bit framebuffer
...
uint8_t *fb = framebuffer;
while(fb < (uint8_t*)(framebuffer + (480*272*3))) {
*fb++ = 0x00; // Write blue color and increment pointer by one byte
*fb++ = 0x00; // Write green color
*fb++ = 0xFF; // Write red color
}

显示彩色帧缓存