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设置”下进行配置:
除了用于像素传输的24个GPIO(如LTDC_B0) 以外,我们还配置了4个显示控制信号:
信号 | 功能 |
---|---|
LTDC_CLK | 像素时钟。 当对24个线中的像素进行采样时,向显示屏发出信号 |
LTDC_DE | 数据使能。 它被激活时进行像素传输 |
LTDC_HSYNC | 水平同步。 允许显示屏找到像素行起点 |
LTDC_VSYNC | 垂直同步。 允许显示屏找到帧起点 |
检查您的硬件设计并进行相应的配置。
LTDC 配置
在STM32CubeMX中,LTDC配置位于“多媒体”->“LTDC”->“参数设置”
有效宽度和高度与显示屏的分辨率对应。 有关同步脉冲宽度和沿宽度,请检查显示数据手册。 另请注意信号极性。 灰色的值根据其他值计算而来。 这些值被写入LTDC寄存器(也可在代码中找到) 。
现在请转到多媒体-> LTDC-> 层设置”下的LTDC层配置:
对于该测试,在TouchGFX中,我们通常仅使用一层。 第0层的分辨率应与帧缓冲大小匹配。 以后需要设置帧缓冲地址,这里先不理它。
如果您声明了小于显示屏分辨率的帧缓冲数组,则调整图层大小,以便与帧缓冲尺寸匹配。 LTDC将传输帧缓冲中没有的显示屏像素背景颜色。 It is recommended to set the background color (in the Parameter Settings tab) to something recognisable like red (Blue: 0x00, Green: 0x00, Red: 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(核心中断控制器)中使能,以便应用程序中能够处理中断。
In addition to setting the checkmark in STM32CubeMX, the LTDC interrupts must all also manually be enabled in the code:
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格式的显示屏数据手册(数据大小和位顺序) 。 请记住,16位字以小端字节序存储在帧缓冲中。 检查是否可以将显示屏配置为此格式。 如果不可以,则必须在传输期间进行数据转换。 另请注意时钟极性和时钟相位。 这些参数已在显示屏数据手册中指定。
SPI时钟(比特率) 由FCLK的分频器控制。 最小分频器为2。 如果MCU正在运行(如64MHz) ,则最大SPI比特率为32 MBit/s。
在GPIO选项卡上,您可以检查SPI外设的GPIO配置:
在右侧的引脚排列视图中选择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
检查显示屏颜色
此时,您可以将帧缓存内容传输到显示屏,建议您彻底检查显示屏颜色。
该想法就是将颜色写入帧缓存,并通过视觉来检查显示屏。 以下为一些示例:
测试 | 说明 |
---|---|
红色 | 在帧缓存中设置红色。 显示屏也必须为红色。 |
绿色 | 在帧缓存中设置绿色。 显示屏也必须为绿色。 |
蓝色 | 在帧缓存中设置蓝色。 显示屏也必须为蓝色。 |
深色 | 如果设置为深色(如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
}
通过使用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 */