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中最容易实现:
除了用于像素传输的24个GPIO(如LTDC_B0) 以外,我们还配置了4个显示控制信号:
信号 | 功能 |
---|---|
LTDC_CLK | 像素时钟。 当对24个线中的像素进行采样时,向显示器发出信号 |
LTDC_DE | 数据使能。 它被激活时进行像素传输 |
LTDC_HSYNC | 水平同步。 允许显示器找到像素行起点 |
LTDC_VSYNC | 垂直同步。 允许显示器找到帧起点 |
检查您的硬件设计并进行相应的配置。
LTDC 配置
LTDC配置位于多媒体-> LTDC-> 参数设置下的CubeMX中:
有效宽度和高度与显示器的分辨率对应。 有关同步脉冲宽度和沿宽度,请检查显示数据手册。 另请注意信号极性。 灰色的值根据其他值计算而来。 这些值被写入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格式的显示器数据手册(数据大小和位顺序) 。 请记住,16位字以小端字节序存储在帧缓冲中。 检查是否可以将显示器配置为此格式。 如果不可以,则必须在传输期间进行数据转换。
另请注意时钟极性和时钟相位。 这些参数已在显示器数据手册中指定。
SPI时钟(比特率) 由FCLK的分频器控制。 最小分频器为2。 如果MCU正在运行(如64MHz) ,则最大SPI比特率为32 MBit/s。
在GPIO选项卡上,您可以检查SPI外设的GPIO配置:
在右侧的引脚排列视图中选择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
检查显示器颜色
此时,您可以将帧缓存内容传输到显示器,建议您彻底检查显示器颜色。
该想法就是将颜色写入帧缓存,并通过视觉来检查显示器。 以下为一些示例:
测试 | 说明 |
---|---|
红色 | 在帧缓存中设置红色。 显示屏也必须为红色。 |
绿色 | 在帧缓存中设置绿色。 显示屏也必须为绿色。 |
蓝色 | 在帧缓存中设置蓝色。 显示屏也必须为蓝色。 |
深色 | 如果设置为深色(如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
}