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。
将帧缓冲传输到显示屏的方法取决于显示屏类型。 我们现在来了解一下这点。
Caution
Therefore, the RAM start address must be changed beforehand. This can be done by modifying the linker script. You can find the usable memory addresses and additional details in your chip’s datasheet.
并行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将传输帧缓冲中没有的显示屏像素背景颜色。 建议将背景颜色(参数设置选项卡)设置为可识别的颜色,如红色(蓝: 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(核心中断控制器)中使能,以便应用程序中能够处理中断。
除了在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格式的显示屏数据手册(数据大小和位顺序) 。 请记住,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 *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 */