跳转到主要内容

3. 显示屏的帧缓存位于内部RAM中

动机

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

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

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

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

宽 x 高 x bpp

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

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

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

目标

本节的目标是将帧缓冲内容传输到显示屏。 您还应验证是否可以修改帧缓冲器内容和连续重新发送帧缓冲器。

验证

以下是本节的验证点:

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

先决条件

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

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

执行

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

main.c
/* USER CODE BEGIN PV */
uint16_t framebuffer[480*272]; //16 bpp framebuffer
/* USER CODE END PV */

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

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

并行RGB显示屏

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

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

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

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

显示相关GPIO

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

配置显示相关GPIO

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

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

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

LTDC 配置

在STM32CubeMX中,LTDC配置位于“多媒体”->“LTDC”->“参数设置”

配置LTDC参数

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

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

配置LTDC层参数

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

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

时钟配置

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

时钟配置

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

设置帧缓冲地址

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

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

HAL函数中的层编号为1、2,而STM32CubeMX中的层编号为0、1。 另外,LTDC的配置可以完全由STM32CubeMX生成的函数MX_LTDC_Init(void)来完成。

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

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

启用LTDC中断

LTDC控制器为每帧提供一个中断,中断必须在NVIC(核心中断控制器)中使能,以便应用程序中能够处理中断。

LTDC中断配置

除了在STM32CubeMX中设置复选标记以外,还必须在代码中手动启用所有LTCC中断:

main.c
  /* USER CODE BEGIN LTDC_Init 2 */
LTDC->IER |= LTDC_IER_LIE; // Enable LTDC interrupts
/* USER CODE END LTDC_Init 2 */

在中断处理程序中,必须在每次触发后重新启用中断。

stm32f7xx_it.c
void LTDC_IRQHandler(void)
{
/* USER CODE BEGIN LTDC_IRQn 0 */
/* USER CODE END LTDC_IRQn 0 */
HAL_LTDC_IRQHandler(&hltdc);
/* USER CODE BEGIN LTDC_IRQn 1 */
HAL_LTDC_ProgramLineEvent(&hltdc,0);
/* USER CODE END LTDC_IRQn 1 */
}

检查帧率

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 = now - last;
last = now;
/* USER CODE END LTDC_IRQn 0 */
HAL_LTDC_IRQHandler(&hltdc);
...

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

SPI 显示屏

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

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

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

SPI 配置

从STM32CubeMX开始并使能SPI。 这些图像来自STM32G081项目:

SPI 配置

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

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

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

SPI GPIO配置

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

SPI GPIO选择

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

STM32Cube固件包含使用SPI通信的示例。 STM32Cube 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 *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
}

显示彩色帧缓存

通过使用LTDC行事件回调,每秒来修改一次帧缓存:

main.c
/* USER CODE BEGIN 4 */
uint8_t r = 0x00, g = 0x00, b = 0x00;
uint16_t col = 0;
uint8_t color = 1;
void HAL_LTDC_LineEventCallback(LTDC_HandleTypeDef* hltdc)
{
static int count = 0;
count++;
if (count >= 60)
{
count = 0;
switch (color)
{
case 1:
r = 0xFF; g = 0x00; b = 0x00;
color = 2;
break;
case 2:
r = 0x00; g = 0xFF; b = 0x00;
color = 3;
break;
case 3:
r = 0x00; g = 0x00; b = 0xFF;
color = 1;
break;
default:
break;
}
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;
}
}
}
/* USER CODE END 4 */