《STM32H7R7开发指南 V1.1 》第二十七章 LTDC LCD(RGB屏)实验

第二十七章 LTDC LCD(RGB屏)实验


       在前面,我们介绍了TFTLCD模块(MCU屏)的使用,但是高分辨率的屏(超过800*480),一般都没有MCU屏接口,而是使用RGB接口的,这种接口的屏,就需要用到STM32H7R7的LTDC来驱动了。在本章中,我们将使用STM32H7R7开发板核心板上的LCD接口(仅支持RGB屏,本章介绍RGB屏的使用),来点亮LCD,并实现ASCII字符和彩色的显示等功能,并在串口打印LCD ID,同时在LCD上面显示。

       本章分为如下几个小节:

       27.1 RGBLCD<DC简介

       27.2 硬件设计

       27.3 程序设计

       27.4 下载验证


        27.1 RGBLCD<DC简介

       本章我们将通过STM32H7R7的LTDC接口来驱动RGBLCD的显示,另外,STM32H7R7的LTDC还有DMA2D图形加速,我们也顺带进行介绍。本节分为三个部分,分别介绍RGBLCD、LTDC和DMA2D。


       27.1.1 RGBLCD简介

       在第25章,我们已经介绍过TFTLCD液晶了,实际上RGBLCD也是TFTLCD,只是接口不同而已。接下来我们简单介绍一些RGBLCD的驱动。


       (1) RGBLCD的信号线

       RGBLCD的信号线如表27.1.1.1所示:


表27.1.1.1 RGBLCD信号线


       一般的RGB屏都有如表27.1.1.1所示的信号线,有24根颜色数据线(RGB各8根,即RGB888格式),这样可以表示最多1600W色,DE、VS、HS和DCLK,用于控制数据传输。


       (2) RGBLCD的驱动模式

       RGB屏一般有2种驱动模式:DE模式和HV模式。DE模式使用DE信号来确定有效数据(DE为高/低时,数据有效),而HV模式,则需要行同步和场同步,来表示扫描的行和列。

DE模式和HV模式的行扫描时序图(以800*480的LCD面板为例),如图27.1.1.1所示:


图27.1.1.1 DE/HV模式行扫描时序图


       从图中可以看出,DE和HV模式,时序基本一样,DEN模式需要提供DE信号(DEN),而HV模式,则无需DE信号。图中的HSD即HS信号,用于行同步,注意:在DE模式下面,是可以不用HS信号的,即不接HS信号,液晶照样可以正常工作。

       图中的thpw为水平同步有效信号脉宽,用于表示一行数据的开始;thb为水平后廊,表示从水平有效信号开始,到有效数据输出之间的像素时钟个数;thfp为水平前廊,表示一行数据结束后,到下一个水平同步信号开始之前的像素时钟个数;这几个时间非常重要,在配置LTDC的时候,需要根据LCD的数据手册,进行正确的设置。

       图27.1.1.1仅是一行数据的扫描,输出800个像素点数据,而液晶面板总共有480行,这就还需要一个垂直扫描时序图,如图27.1.1.2所示:


图27.1.1.2 垂直扫描时序图


       图中的VSD就是垂直同步信号,HSD就是水平同步信号,DE为数据使能信号。如图可知,一个垂直扫描,刚好就是480个有效的DE脉冲信号,每一个DE时钟周期,扫描一行,总共扫描480行,完成一帧数据的显示。这就是800*480的LCD面板扫描时序,其他分辨率的LCD面板,时序类似。

       图中的tvpw为垂直同步有效信号脉宽,用于表示一帧数据的开始;tvb为垂直后廊,表示垂直同步信号以后的无效行数,tvfp为垂直前廊,表示一帧数据输出结束后,到下一个垂直同步信号开始之前的无效行数;这几个时间同样在配置LTDC的时候,需要进行设置。


       (3) 正点原子 RGBLCD模块

       正点原子 目前提供大家六款 RGBLCD 模块:ATK-4342(4.3 寸,480*272)、 ATK-7084( 7寸, 800*480)、 ATK-7016( 7 寸,1024*600)、ATK-7018( 7 寸, 1280*800)、ATK-4384(4.3寸,800*480)和ATK-1018(10.1寸,1280*800),这里我们以 ATK-7084 为例,给大家介绍。该模块的接口原理图如图 27.1.1.3 所示:


图27.1.1.3 RGBLCD模块对外接口原理图


       图中 J3就是对外接口,是一个 40PIN 的 FPC 座( 0.5mm 间距),通过 FPC 线,可以连接到 STM32H7R7 开发板的核心板上面,从而实现和 STM32H7R7的连接。该接口十分完善,采用 RGB888 格式,并支持 DE&HV 模式,还支持触摸屏(电阻/电容)和背光控制。右侧的几个电阻,并不是都焊接的,而是可以用户自己选择。默认情况, R1 和 R6 焊接,设置 LCD_LR和 LCD_UD,控制 LCD 的扫描方向,是从左到右,从上到下(横屏看)。而 LCD_R7/G7/B7 则用来设置 LCD 的 ID,由于 RGBLCD 没有读写寄存器,也就没有所谓的 ID,这里我们通过在模块上面,控制 R7/G7/B7 的上/下拉,来自定义 LCD 模块的 ID,帮助 MCU 判断当前 LCD 面板的分辨率和相关参数,以提高程序兼容性。这几个位的设置关系如表 27.1.1.2 所示:


表27.1.1.2 正点原子 RGBLCD模块ID对应关系


       ATK-7084 模块,就设置 M2:M0=001 即可。这样我们在程序里面,读取 LCD_R7/G7/B7,得到 M0:M2 的值,从而判断 RGBLCD 模块的型号,并执行不同的配置,即可实现不同 LCD模块的兼容。

RGBLCD 我们就给大家介绍到这里,接下来,我们介绍 LTDC。


       27.1.2 LTDC简介

       STM32H7R7xx 系列芯片都带有TFTLCD 控制器,即LTDC,通过这个 LTDC, STM32H7R7可以直接外接 RGBLCD 屏,实现液晶驱动。 STM32H7R7 的 LTDC 具有如下特点:

       ·24位RGB并行像素输出:每像素8位数据(RGB888)

       ·2 个带有专用 FIFO 的显示层( FIFO 深度 64x32 位)

       ·支持查色表 (CLUT),每层高达 256 种颜色( 256x24 位)

       ·可针对不同显示面板编辑时序

       ·可编程背景色

       ·可编程HSync、VSync和数据使能(DE)信号的极性

       ·每层有多达8种颜色格式可供选择:ARG8888、RGB8888、RGB565、ARGB1555、ARGB4444、L8(8位Luminance或CLUT)、AL44(4位alpha+4位luminance)和AL88(8位alpha+8位luminance)

       ·每通道的低位采用伪随机抖动输出(红色、绿色、蓝色的抖动宽度为2位)

       ·使用alpha值(每像素或常数)在两层之间灵活混合

       ·色键(透明颜色)

       ·可编程窗口位置和大小

       ·支持薄膜晶体管(TFT)彩色显示器

       ·AXI主接口支持16个字的突发

       ·高达4个可编程中断事件

       LTDC控制器主要包含:信号线、图像处理单元、AXI接口、配置和状态寄存器以及时钟部分,其框图如图27.1.2.1所示:


图27.1.2.1 LTDC控制器框图


       ① 信号线

       这里就包含了我们前面提到的RGBLCD驱动所需要的所有信号线,这些信号线通过STM32H7R7核心板板载的LCD接口引出,其信号线说明,见表27.1.2.1所示:


表27.1.2.1 LTDC信号线说明


       LTDC总共有24位数据线,支持RGB888格式,但是我们为了节省IO,并提高图片显示速度,使用RGB565颜色格式,这样的话,只需16个IO口,当使用RGB565格式的时候,LCD面板的数据线,必须连接到LTDC数据线的MSB,即:LTDC的LCD_R[7:3]接RGBLCD的R[7:3],LTDC的LCD_G[7:2]接RGBLCD的G[7:2],LTDC的LCD_B[7:3]接RGBLCD的B[7:3],这样,RGB数据线分别是5:6:5,即RGB565格式。表中对应IO就是我们STM32H7R7核心板上面,LCD接口所连接的IO。


       ② 图像处理单元

       此部分先从 AXI 接口获取显存中的图像数据,然后经过层 FIFO(有2个,对应2个层)缓存, 每个层FIFO具有64*32 位存储深度,然后经过像素格式转换器( PFC),把从层的所选输入像素格式转换为ARGB8888格式,再通过混合单元,把两层数据合并,混合得到单层要显示的数据,最后经过抖动单元处理(可选)后,输出给 LCD 显示。

       这里的 ARGB8888,即带 8 位透明通道,即最高8 位为透明通道参数,表示透明度,值越大,则约不透明,值越小,越透明。比如 A=255 时,表示完全不透明,而 A=0 时,表示完全透明。 RGB888 就表示 R、G、B各8 位,可表示的颜色深度为1600W 色。

       STM32H7R7的LTDC总共有三个层:背景层、第一层和第二层,其中,背景层只可以是纯色(即单色),而第一层和第二层都可以用来显示信息,混合单元会将三个层混合起来,进行显示,显示关系如图 27.1.2.2 所示:


图27.1.2.2 三个层混合关系


       从图中可以看出,第二层位于最顶端,背景层位于最低端,混合单元首先将第一层与背景层进行混合,随后,第二层与第一层和第二层的混合颜色结果再次混合,完成混合后,送给 LCD显示。


       ③AXI接口

       由于LTDC驱动RGBLCD的时候,需要有很多内存来做显存,比如一个800*480的屏幕,按一般的16位RGB565模式,一个像素需要2个字节的内存,总共需要:800*480*2=768K 字节内存,STM32内部是没有这么多内存的,所以必须借助外部SDRAM,而SDRAM是挂在AXI总线上的,LTDC的AXI接口,就是用来将显存数据,从SDRAM存储器传输到 FIFO里面。


       ④配置和状态寄存器

       此部分包含了LTDC的各种配置寄存器以及状态寄存器,用于控制整个LTDC的工作参数,主要有:各信号的有效电平、垂直/水平同步时间参数、像素格式、数据使能等等。LTDC的同步时序(HV模式)控制框图,如图27.1.2.3所示:


图27.1.2.3 LTDC同步时序框图


       图中有效显示区域,就是我们RGBLCD面板的显示范围(即分辨率),有效宽度*有效高度,就是LCD的分辨率。另外,这里还有的参数包括:HSYNC的宽度(HSW)、VSYNC的宽度(VSW)、HBP、HFP、VBP和VFP等,这些参数的说明,见表27.1.2.2所示:


表27.1.2.2 LTDC驱动时序参数


       如果 RGBLCD 使用的是DE模式, LTDC也只需要设置表27.1.2.2 所示的参数,然后 LTDC会根据这些设置,自动控制DE信号。这些参数通过相关寄存器来配置,接下来,我们介绍一下LTDC的一些相关寄存器。

       首先,我们来看LTDC全局控制寄存器:LTDC_GCR,该寄存器各位描述如图27.1.2.4所示:


图27.1.2.4 LTDC_GCR寄存器各位描述


       该寄存器我们在本章用到的设置有: LTDCEN、 PCPOL、 DEPOL、 VSPOL 和 HSPOL 这几个设置,我们将逐个介绍。

       LTDCEN: TFT LCD 控制器使能位,也就是 LTDC 的开关,该位需要设置为 1。

       PCPOL:像素时钟极性。控制像素时钟的极性,根据 LCD 面板的特性来设置,我们所用的 LCD 一般设置为 0 即可,表示低电平有效。

       DEPOL:数据使能极性。控制 DE 信号的极性,根据 LCD 面板的特性来设置,我们所用的 LCD 一般设置为 0 即可,表示低电平有效。

       VSPOL:垂直同步极性。控制 VSYNC 信号的极性,根据 LCD面板的特性来设置,我们所用的 LCD 一般设置为 0 即可,表示低电平有效。

       HSPOL:水平同步极性。控制 HSYNC 信号的极性,根据 LCD面板的特性来设置,我们所用的 LCD 一般设置为 0 即可,表示低电平有效。

       接下来,我们看看 LTDC同步大小配置寄存器: LTDC_SSCR,该寄存器各位描述如图27.1.2.5 所示:


图27.1.2.5 LTDC_SSCR寄存器各位描述


       该寄存器用于设置垂直同步高度(VSH)和水平同步宽度(HSW),其中:

       VSH:表示垂直同步高度(以水平扫描行为单位),表示垂直同步脉宽减1,即VSW-1。

       HSW:表示水平同步宽度(以像素时钟为单位),表示水平同步脉宽减1,即HSW-1.

       接下来,我们看看LTDC后沿配置寄存器:LTDC_BPCR,该寄存器各位描述如图27.1.2.6所示:


图27.1.2.6 LTDC_BPCR寄存器各位描述


       该寄存器我们需要配置AVBP和AHBP:

       AVBP:累加垂直后沿(以水平扫描行为单位),表示:VSW+VBP-1(见表27.1.2.2)

       AHBP:累加水平后沿(以像素时钟为单位),表示HSW+HBP-1(见表27.1.2.2,下同)

       接下来,我们看看LTDC有效宽度配置寄存器:LTDC_AWCR,该寄存器各位描述如图27.1.2.7所示:


图27.1.2.7 LTDC_AWCR寄存器各位描述


       该寄存器我们需要配置AAH和AAW:

       AAH:累加有效高度(以水平扫描行为单位),表示:VSW+VBP+有效高度-1。

       AAW:累加有效宽度(以像素时钟为单位),表示:HSW+HBP+有效宽度-1。

       这里所说的有效高度和有效宽度,是指LCD面板的宽度和高度(构成分辨率,下同)

       接下来,我们看看LTDC总宽度配置寄存器:LTDC_TWCR,该寄存器各位描述如图27.1.2.8所示:


图27.1.2.8 LTDC_TWCR 寄存器各位描述


       该寄存器我们需要配置TOTALH和TOTALW:

       TOTALH:总高度(以水平扫描行为单位),表示:VSW+VBP+有效高度+VFP-1。

       TOTALW:总宽度(以像素时钟为单位),表示:HSW+HBP+有效宽度+HFP-1。

       接下来,我们看看LTDC背景色配置寄存器:LTDC_BCCR,该寄存器各位描述如图27.1.2.9所示:


图27.1.2.9 LTDC_BCCR 寄存器各位描述


       该寄存器定义背景层的颜色(RGB888),通过低26位配置,我们一般设置为全0即可。

       接下来,我们看看LTDC的层颜色帧缓冲区地址寄存器:LTDC_LxCFBAR(x=1/2),该寄存器各位描述如图27.1.2.10所示:


图27.1.2.10LTDC_LxCFBAR寄存器各位描述


       该寄存器用来定义一层显存的起始地址。STM32H7R7的LTDC支持2个层,所以总共两个寄存器,分别设置层1和层2的显存起始地址。

       接下来,我们看看LTDC的层像素格式配置寄存器:LTDC_LxPFCR(x=1/2),该寄存器各位描述如图27.1.2.11所示:


图27.1.2.11 LTDC_LxPFCR寄存器各位描述


       该寄存器只有最低3位有效,用于设置层颜色的像素格式:000:ARGB8888;001:RGB888;010:RGB565;011:ARGB1555;100:ARGB4444;101:L8(8位Luminance);110:AL44(4位Alpha,4位Luminance);111:AL88(8位Alpha,8位Luminance)。我们一般使用RGB565格式,即该寄存器设置为:010即可。

       接下来,我们看看LTDC的层恒定Alpha配置寄存器:LTDC_LxCACR(x=1/2),该寄存器各位描述如图27.1.2.12所示:


图27.1.2.12 LTDC_LxCACR寄存器各位描述


       该寄存器低8位(CONSTA)有效,这些位配置混合时使用的恒定Alpha。恒定Alpha由硬件实现255分频。关于这个恒定Alpha的使用,我们将在介绍LTDC_LxBFCR寄存器的时候进行讲解。

       接下来,我们看看LTDC的层默认颜色配置寄存器:LTDC_LxDCCR(x=1/2),该寄存器各位描述如图27.1.2.13所示:


图27.1.2.13 LTDC_LxDCCR寄存器各位描述


       该寄存器定义采用ARGB8888格式的层的默认颜色。默认颜色在定义的层窗口外使用或在层禁止时使用。一般情况下,用不到,所以该寄存器一般设置为0即可。

       接下来,我们看看LTDC的层混合系数配置寄存器:LTDC_LxBFCR(x=1/2),该寄存器各位描述如图27.1.2.14所示:


图27.1.2.14 LTDC_LxBFCR寄存器各位描述


       该寄存器用于定义混合系数:BF1和BF2。BF1=100的时候,使用恒定的Alpha混合系数(由LTDC_LxCACR寄存器设置恒定Alpha值),BF1=110的时候,使用像素Alpha*恒定Alpha。像素Alpha即ARGB格式像素的A值(Alpha值),仅限ARGB颜色格式时使用。在RGB565格式下,我们设置BF1=100即可。BF2同BF1类似,BF2=101的时候,使用恒定的Alpha混合系数,BF2=111的时候,使用像素Alpha*恒定Alpha。在RGB565格式下,我们设置BF2=101即可。

       通用的混合公式为:

BC=BF1*C+BF2*Cs

       其中: BC=混合后的颜色; BF1=混合系数 1; C=当前层颜色,即我们写入层显存的颜色值;BF2=混合系数 2; Cs=底层混合后的颜色,对于层 1 来说, Cs=背景层的颜色,对于层 2 来说,Cs=背景层和层 1 混合后的颜色。

       以使用恒定的 Alpha 值,并仅使能第一层为例,给大家讲解一下混色的计算方式。恒定 Alpha的值由 LTDC_LxCACR 寄存器设置,恒定 Alpha=LTDC_LxCACR 设置值/255 。假设:LTDC_LxCACR=240; C=128; Cs(背景色) =48;那么恒定 Alpha=240/255=0.94,则:

BC=0.94*128+(1-0.94)*48=123

       则混合后,颜色值变成了 123。另外,需要注意的是: BF1 和 BF2 的恒定 Alpha 值互补,他们之和为 1,且 BF1 使用的是恒定 Alpha 值,BF2 使用的是互补值。一般情况下,我们设置LTDC_LxCACR 的值为 255,这样,在使用恒定 Alpha 值的时候,可以得到 BC=C,即混合后的颜色,就是显存里面的颜色(不进行混色)。

       LTDC 的层支持窗口设置功能,通过 LTDC_LxWHPCR 和 LTDC_LxWVPCR 这两个寄存器设置,可以调整显示区域的大小,如图 27.2.15 所示: 


图27.1.2.15 LTDC层窗口设置关系图


       上图中,层中的第一个和最后一个可见像素通过配置LTDC_LxWVPCR寄存器中的WHSTPOS[11:0]和WHSPPOS[11:0]进行设置,配置完成后,即可确定窗口的大小。

       接下来,我们来介绍这两个寄存器,首先是LTDC的层窗口水平位置配置寄存器:

       LTDC_LxWHPCR(x=1/2),该寄存器各位描述如图27.1.2.16所示:


图27.1.2.16 LTDC_LxWHPCR寄存器各位描述


       该寄存器定义第 1 层或第 2 层窗口的水平位置(第一个和最后一个像素),其中:

       WHSTPOS:窗口水平起始位置,定义层窗口的一行的第一个可见像素,见图 27.1.2.14。

       WHSPPOS:窗口水平停止位置,定义层窗口的一行的最后一个可见像素,见图 27.1.2.14。

       然后,我们介绍 LTDC 的层窗口垂直位置配置寄存器: LTDC_LxWVPCR( x=1/2),该寄存器各位描述如图 27.1.2.17 所示:


图27.1.2.17 LTDC_LxWVPCR寄存器各位描述


       该寄存器定义第 1 层或第 2 层窗口的垂直位置(第一行或最后一行),其中:

       WVSTPOS:窗口垂直起始位置,定义层窗口的第一个可见行,见图 27.1.2.15。

       WVSPPOS:窗口垂直停止位置,定义层窗口的最后一个可见行,见图 27.1.2.15。

       接下来,我们看看 LTDC 的层颜色帧缓冲区长度寄存器: LTDC_LxCFBLR(x=1/2),该寄存器各位描述如图 27.1.2.18所示:


图27.1.2.18 LTDC_LxCFBLR寄存器各位描述


       该寄存器定义颜色帧缓冲区的行长和行间距。其中:

       CFBLL:这些位定义一行像素的长度(以字节为单位)+3。行长的计算方法为:有效宽度*每像素的字节数+3。比如,LCD 面板的分辨率为800*480,有效宽度为 800,采用 RGB565 格式,那么 CFBLL 需要设置为: 800*2+3=1603。

       CFBP: 这些位定义从像素某行的起始处到下一行的起始处的增量(以字节为单位)。这个设置,其实同样是一行像素的长度,对于800*480的LCD面板, RGB565 格式,设置 CFBP 为:800*2=1600 即可。

       最后,我们看看 LTDC 的层颜色帧缓冲区行数寄存器:LTDC_LxCFBLNR(x=1/2),该寄存器各位描述如图 27.1.2.19所示:


图27.1.2.19 LTDC_LxCFBLNR寄存器各位描述


       该寄存器定义颜色帧缓冲区中的行数。 CFBLNBR 用于定义帧缓冲区行数,比如, LCD 面板的分辨率为 800*480,那么帧缓冲区的行数为 480 行,则设置 CFBLNBR=480 即可。

       至此,LTDC 相关的寄存器,基本就介绍完了,通过这些寄存器的配置,我们就可以完成对LTDC的初始化,控制LCD显示了。关于 LTDC 的详细介绍,和寄存器描述,请看《STM32H7Rx参考手册_V6(英文版).pdf》第 33 章。


       ⑤时钟域

       LTDC 有三个时钟域: AXI 时钟域(ltdc_aclk)、 APB5 时钟域(ltdc_plck)和像素时钟域(ltdc_ker_ck),时钟域用于驱动:接口读取存储器的数据到 FIFO 里面,APB5时钟域用于配置寄存器,像素时钟域则用于生成 LCD 接口信号(LCD_HSYNC、LCD_VSYNC和LCD_CLK等),这些时钟信号需根据LCD面板的要求进行配置。

       接下来,我们重点介绍下 LCD_CLK 的配置过程。 LCD_CLK 的时钟来自ltdc_ker_ck,而ltdc_ker_ck则来自PLL3的R分频,如图 27.1.2.20所示:


图27.1.2.20 ltdc_ker_ck时钟图


       由图可知, ltdc_ker_ck的来源为clk_in,一般为外部晶振(开发板是24MHz),然后经过DIVM3分频,输入锁相环进行倍频(DIVN3)和分频(DIVR3),输出pll3_r_ck,然后通过PKSU和PKEU得到ltdc_ker_ck,最终输出到LTDC,产生LCD_CK,驱动液晶面板。接下来,我们简单介绍下配置 LCD_CLK 需要用到的一些寄存器。

       首先是PLL时钟选择寄存器:RCC_PLLCKSELR,该寄存器的各位描述如图 27.1.2.21所示:


图27.1.2.21 RCC_PLLCKSELR寄存器各位描述


       其中,PLLSRC[1:0]用于选择所有PLL的输入时钟源,也就是图27.1.2.20中clk_in的来源,我们在SYSTEM文件夹介绍的时候,就已经设置了这两个位,设置PLLSRC[1:0]=2,即选择PLL输入时钟源为HSE(hse_ck,外部晶振频率为24MHz)。

       DIVM3[5:0]用于设置PLL3输入时钟的分频系数,一般我们设置为6,这样就可以得到PLL3的输入时钟频率为24MHz/6= 4Mh。

       接下来,我们看PLL3分频配置寄存器:RCC_PLL3DIVR1,该寄存器各位描述如图 27.1.2.22 所示:


图27.1.2.22 RCC_PLL3DIVR1寄存器各位描述


       这个寄存器主要对PLL3倍频器的:N、P、Q和R等参数进行配置,他们的设置关系(假定使用外部HSE作为时钟源)为:

f(VCO clock) = f(hse) × (DIVN3[8:0] / DIVM3[5:0])

f(PLL3_P) = f(VCO clock) / DIVP3[6:0]

f(PLL3_Q) = f(VCO clock) / DIVQ3[6:0]

f(PLL3_R) = f(VCO clock) / DIVR3[6:0]

       f(hse)为我们外部晶振的频率,DIVM3就PLL3的M分频因子,DIVN3为PLL3的倍频数,取值范围为:3~511(表示4~512倍频);DIVP3、DIVQ3和DIVR3为PLL3的P、Q和R分频系数,取值范围为:2~128;STM32H7R7核心板所用的HSE晶振频率为24Mhz,一般我们设置PLLM3为6,那么输入PLL3的输入时钟频率就是4Mhz,然后可得:

 f(ltdc_ker_ck) =4Mhz* DIVN3[8:0] / DIVR3[6:0]

       LCD_CLK的时钟频率就等于ltdc_ker_ck,因此LCD_CLK时钟频率计算公式就是上式。以群创AT070TN92面板为例,查其数据手册,可知DCLK的频率典型值为:33.3Mhz,我们需要设置:DIVN3[8:0]=300,DIVR3[6:0]=2,DIVM3[5:0]=6,得到:

f(LCD_CLK)= 4Mhz* 300/2=600Mhz

       为了让PLL3正常工作,我们还需要配置RCC_PLLCFGR和RCC_CR。RCC_PLLCFGR寄存器各位描述如图27.1.2.23所示:


图27.1.2.23 RCC_PLLCFGR寄存器各位描述


       因为ltdc_ker_ck时钟来自PLL3的R分频,因此RCC_PLLCFGR寄存器我们只需要关心PLL3FRACEN、PLL3VCOSEL、PLL3RGE[1:0]和PLL3QEN这些位。接下来我们分别介绍:

       PLL3FRACEN位,用于设置是否使能分数倍频器,本章我们不需要分数倍频,因此设置该位为0即可。

       PLL3VCOSEL位,用于选择VCO的范围。当VCO的范围在384~1672Mhz之间时,设置该位为0;当VCO范围在150~420Mhz之间时,设置该位为1;本章我们设置该位为0,使用192~836Mhz的倍频范围。

       PLL3RGE[1:0]位,用于设置PLL3输入频率范围。当输入频率范围在4~8Mhz之间时,设置这两个位为10即可,本章输入范围为4Mhz(24M/6),因此设置这两个位为10即可。

       PLL3QEN位,用于使能PLL3的R分频输出。本章需要用到R分频输出,因此必须设置该位为1。

       最后,我们看看RCC控制寄存器:RCC_CR,该寄存器各位描述如图27.1.2.24所示:


图27.1.2.24 RCC_CR寄存器各位描述


       该寄存器我们只关心PLL3ON和PLL3RDY两个位:

       PLL3ON位,用于使能PLL3,我们需要用到PLL3的R分频来产生LCD_CLK时钟,因此必须使能PLL3,该位需要设置为1。

       PLL3RDY为,用于查询PLL3锁相环是否锁定了,如果锁定了,该位为1,否则该位为0。

我们在开启PLL3之后,需要查询该位来判断PLL3是否准备好(锁定)。

       最后,我们来看看实现LTDC驱动RGBLCD,需要对LTDC进行哪些配置。LTDC相关HAL 库操作分布在函数stm32h7rsxx_hal_ltdc.c 和 stm32h7rsxx_hal_ltdc_ex.c 以及他们对应的头文件中。操作步骤如下:


       1) 使能LTDC时钟,并配置LTDC相关的 IO 及其时钟使能。

       要使用 LTDC,当然首先得开启其时钟。然后需要把 LCD_R/G/B数据线、LCD_HSYNC和 LCD_VSYNC等相关IO口,全部配置为复用输出,并使能各IO组的时钟。GPIO配置这里我们就不做讲解,LTDC 时钟使能方法为:

 __HAL_RCC_LTDC_CLK_ENABLE();                /* 使能LTDC时钟 */


       2) 设置 LCD_CLK 时钟。

       此步需要配置 LCD 的像素时钟,根据LCD的面板参数进行设置,LCD_CLK由PLLSAI进行配置,前面我们已经讲解非常详细,配置使用到的HAL库函数为:

HAL_StatusTypeDef HAL_RCCEx_PeriphCLKConfig(RCC_PeriphCLKInitTypeDef                    *PeriphClkInit)

       该函数是HAL库提供的用来配置扩展外设时钟通用函数。LCD_CLK前面讲解过,它来自PLL3R,可知,我们需要配置PLL3M、PLL3N和PLL3R等参数。


       3)设置 RGBLCD 的相关参数,并使能 LTDC。

       这一步,我们需要完成对LCD面板参数的配置,包括:LTDC使能、时钟极性、HSW、VSW、HBP、HFP、VBP和VFP等(见表27.1.2.2),通过LTDC_GCR、LTDC_SSCR、LTDC_BPCR、LTDC_AWCR和LTDC_TWCR等寄存器配置。HAL库配置LTDC参数并使能LTDC的函数为:

HAL_StatusTypeDef HAL_LTDC_Init(LTDC_HandleTypeDef *hltdc);

       接下来真正用来初始化LTDC的结构体变量,结构体LTDC_InitTypeDef定义如下:

typedef struct
{
  uint32_t            HSPolarity;                /* 水平同步极性 */
  uint32_t            VSPolarity;                /* 垂直同步极性 */
  uint32_t            DEPolarity;                /* 数据使能极性 */
  uint32_t            PCPolarity;                /* 像素时钟极性 */
  uint32_t            HorizontalSync;            /* 水平同步宽度 */
  uint32_t            VerticalSync;              /* 垂直同步宽度 */
  uint32_t            AccumulatedHBP;            /* 水平同步后沿宽度 */
  uint32_t            AccumulatedVBP;            /* 垂直同步后沿高度 */
  uint32_t            AccumulatedActiveW;        /* 累加有效宽度 */
  uint32_t            AccumulatedActiveH;        /* 累加有效高度 */
  uint32_t            TotalWidth;                /* 总宽度 */
  uint32_t            TotalHeigh;                /* 总宽度 */
  LTDC_ColorTypeDef   Backcolor;                 /* 屏幕背景层颜色 */
} LTDC_InitTypeDef;

       这些参数含义我们都在结构体成员变量之后注释了,具体含义大家可以参考前面第四点配

置和状态寄存器讲解。

       和其他外设或接口初始化一样,HAL同样提供了LTDC初始化MSP回调函数,HAL_LTDC_MspInit,该函数一般用来使能时钟和初始化IO口:

void HAL_LTDC_MspInit(LTDC_HandleTypeDef* hltdc)


       4)设置 LTDC 层参数。

       此步,我们需要设置LTDC 某一层的相关参数,包括:帧缓存首地址、颜色格式、混合系数和层默认颜色等。通过LTDC_LxCFBAR、LTDC_LxPFCR、LTDC_LxCACR、LTDC_LxDCCR和LTDC_LxBFCR等寄存器配置。HAL库提供的LTDC层参数配置函数为:

HAL_StatusTypeDef HAL_LTDC_ConfigLayer(LTDC_HandleTypeDef *hltdc,           LTDC_LayerCfgTypeDef *pLayerCfg, uint32_t LayerIdx)

       基于篇幅考虑,该函数具体的入口参数定义这里我们就不做过多讲解,具体使用方法请参

考27.3小节函数讲解以及实验工程。


       5) 设置 LTDC层窗口,并使能层。

       这一步,完成对LTDC某个层的显示窗口设置(一般设置为整层显示,不开窗),通过LTDC_LxWHPCR、LTDC_LxWVPCR、LTDC_LxCFBLR和LTDC_LxCFBLNR等寄存器配置。层使能通过配置LTDC_LxCR寄存器的最低位实现,使能层以后,RGBLCD就可以正常工作了。

       HAL 库提供的 LTDC 层窗口配置函数为: 

HAL_StatusTypeDef HAL_LTDC_SetWindowSize(LTDC_HandleTypeDef *hltdc, 
uint32_t XSize, uint32_t YSize, uint32_t LayerIdx)/* 层窗口尺寸配置 */
HAL_StatusTypeDef HAL_LTDC_SetWindowPosition(LTDC_HandleTypeDef *hltdc, uint32_t X0, uint32_t Y0, uint32_t LayerIdx)      /* 层窗口位置配置 */

       这两个函数使用方法非常简单,这里我们就不累赘了。

       通过以上几个步骤,我们就完成了 LTDC 的配置,可以控制 RGBLCD 显示了。 LTDC 我们就给大家介绍到这里,接下来,我们介绍DMA2D。


       27.1.3 DMA2D简介

       为了提高 STM32H7R7 的图像处理能力, ST公司设计了一个专用于图像处理的专业 DMA:Chrom-Art Accelerator™,即:DMA2D,通过DMA2D 对图像进行填充和搬运,可以完全不用CPU 干预,从而提高效率,减轻 CPU 负担。它可以执行下列操作:

       ·用特定颜色填充目标图像的一部分或全部(可用于快速单色填充)

       ·将源图像的一部分(或全部)复制到目标图像的一部分(或全部)中(可用于快速图像填充)

       ·通过像素格式转换将源图像的一部分(或全部)复制到目标图像的一部分(或全部)中

       ·将像素格式不同的两个源图像部分和/ 或全部混合,再将结果复制到颜色格式不同的

部分或整个目标图像中。

       DMA2D 有四种工作模式,通过 DMA2D_CR 寄存器的 MODE[1:0]位选择工作模式:

       1, 寄存器到存储器

       2, 存储器到存储器

       3, 存储器到存储器并执行 PFC

       4, 存储器到存储器并执行 PFC 和混合

       本章,我们仅介绍前两种工作模式,后两种工作模式,请大家参考《STM32H7Rx参考手册_V6(英文版).pdf》第15章。

       寄存器到存储器

       寄存器到存储器模式用于以预定义颜色填充用户自定义区域,也就是可以实现快速的单色填充显示,比如清屏操作。

       在该模式下:颜色格式在 DMA2D_OPFCCR 中设置, DMA2D 不从任何源获取数据,它只是将 DMA2D_OCOLR 寄存器中定义的颜色写入通过 DMA2D_OMA 寻址以及 DMA2D_NLR 和DMA2D_OOR 定义的区域。

       存储器到存储器

       该模式下, DMA2D 不执行任何图形数据转换。前景层输入 FIFO 充当缓冲区,数据从DMA2D_FGMAR 中定义的源存储单元传输到 DMA2D_OMAR 寻址的目标存储单元,可用于快速图像填充。 DMA2D_FGPFCCR 寄存器的 CM[3:0]位中编程的颜色模式决定输入和输出的每像素位数。对于要传输的区域大小,源区域大小由 DMA2D_NLR 和 DMA2D_FGOR 寄存器定义,目标区域大小则由 DMA2D_NLR 和 DMA2D_OOR 寄存器定义。

       以上两个工作模式, LTDC 在层帧缓存里面的开窗关系都一样,如图 27.1.3.1 所示:


图27.1.3.1 层帧缓冲开窗示意图


       窗 口 显 示 区 域 的 显 存 首 地 址 由 DMA2D_OMAR 寄 存 器 指 定 , 窗 口 宽 度 和 高 度 由DMA2D_NRL 寄存器的 PL 和 NL 指定,行偏移(确定下一行的起始地址)由DMA2D_OOR寄存器指定,经过这三个寄存器的配置,就可以确定窗口的显示位置和大小。

       在寄存器到存储器模式下,在开窗完成后, DMA2D 可以将 DMA2D_OCOLR 指定的颜色,自动填充到开窗区域,完成单色填充。

       在存储器到存储器模式下,需要完成两个开窗:前景层和显示层,完成配置后,图像数据从前景层拷贝到显示层(仅限窗口范围内),从而显示到 LCD 上面。显示层的开窗,如图 27.1.3.1所示,而前景层的开窗,则和图 27.1.3.1 所示相似,只是 DMA2D_OMAR 寄存器变成了DMA2D_FGMAR, DMA2D_OOR 寄存器变成了 DMA2D_FGOR, DMA2D_NRL 则两个层共用,然后就可以完成对前景层的开窗,确定好两个窗口后, DMA2D 就将前景层窗口内的数据,拷贝到显示层窗口,完成快速图像填充。

       接下来,我们介绍一下 DMA2D 的一些相关寄存器。

       首先,我们来看 DMA2D 控制寄存器: DMA2D_CR,该寄存器各位描述如图 27.1.3.2 所示:


图27.1.3.2  DMA2D_CR寄存器各位描述


       该寄存器,我们主要关心 MODE 和 START 这两个设置,其中:

       MODE:表示 DMA2D 的工作模式, 00:存储器到存储器模式; 01:存储器到存储器模式并执行 PFC; 10:存储器到存储器并执行混合; 11,寄存器到存储器模式;本章我们需要用到的设置为: 00 或者 11。

       START:该位控制 DMA2D 的启动,在配置完成后,设置该位为 1,启动 DMA2D 传输。

       接下来,我们介绍 DMA2D 输出 PFC 控制寄存器: DMA2D_OPFCCR,该寄存器各位描述如图 27.1.3.3 所示:


图27.1.3.3 DMA2D_OPFCCR寄存器各位描述


       该寄存器用于设置寄存器到存储器模式下的颜色格式,只有最低 3 位有效(CM[2:0]),表示的颜色格式有: 000, ARGB8888; 001: RGB888; 010: RGB565; 011: ARGB1555; 100:ARGB1444。我们一般使用的是 RGB565 格式,所以设置 CM[2:0]=010 即可。

       同样的,还有前景层 PFC 控制寄存器: DMA2D_FGPFCCR,该寄存器各位描述如图 27.1.3.4所示:


图27.1.3.4 DMA2D_FGPFCCR寄存器各位描述


       该寄存器,我们只关心最低 4 位: CM[3:0],用于设置存储器到存储器模式下的颜色格式,这四个位表示的颜色格式为: 0000: ARGB8888; 0001: RGB888; 0010: RGB565; 0011: ARGB1555;0100: ARGB4444; 0101: L8; 0110: AL44; 0111: AL88; 1000: L4; 1001: A8; 1010: A4;我们一般使用 RGB565 格式,所以设置 CM[3:0]=0010 即可。

       接下来,我们介绍 DMA2D 输出偏移寄存器: DMA2D_OOR,该寄存器各位描述如图 27.1.3.5所示:


图27.1.3.5 DMA2D_OOR寄存器各位描述


       该寄存器仅最低 14 位有效( LO[13:0]),用于设置输出行偏移,作用于显示层,以像素为单位表示。此值用于生成地址。行偏移将添加到各行末尾,用于确定下一行的起始地址,参见图 27.1.3.1。

       同样的,还有前景层偏移寄存器: DMA2D_FGOR,该寄存器各位描述如图 27.1.3.6 所示:


图27.1.3. 7 DMA2D_OMAR寄存器各位描述


       该寄存器设置由 MA[31:0]设置输出存储器地址,也就是输出 FIFO 所存储的数据地址,该地址需要根据开窗的起始坐标来进行设置。以 800*480 的 LCD 屏为例,行长度为 800 像素,假定帧缓存数组为:g_ltdc_framebuf,我们设置窗口的起始地址为: sx(<800),sy(<480),颜色格式为 RGB565,每个像素 2 个字节,那么 MA 的设置值应该为:

MA[31:0]= framebuf+2*(800*sy+sx)

       同样的,还有前景层偏移寄存器: DMA2D_ FGMAR,该寄存器各位描述如图 27.1.3.8所示:


图27.1.3.8 DMA2D_ FGMAR寄存器各位描述


       该寄存器同 DMA2D_OMAR 一样,不过是用于控制前景层的存储器地址,计算方法同 DMA2D_OMAR。

       接下来,我们介绍 DMA2D 行数寄存器 : DMA2D_NLR,该寄存器各位描述如图 27.1.3.9所示:


图27.1.3.9 DMA2D_NLR寄存器各位描述


       该寄存器用于控制每行的像素和行数,该寄存器的设置对前景层和显示层均有效,通过该寄存器的配置,就可以设置开窗的大小。其中:

       NL[15: 0]:设置待传输区域的行数,用于确定窗口的高度。

       PL[13: 0]:设置待传输区域的每行像素数,用于确定窗口的宽度。

       接下来,我们介绍 DMA2D 输出颜色寄存器 : DMA2D_OCOLR,该寄存器各位描述如图27.1.3.10 所示:


图27.1.3.10 DMA2D_OCOLR寄存器各位描述


       该寄存器用于配置在寄存器到存储器模式下,填充时所用的颜色值,该寄存器是一个 32位寄存器,可以支持 ARGB8888 格式,也可以支持 RGB565 格式。我们一般使用 RGB565 格式,比如要填充红色,那么直接设置 DMA2D_OCOLR=0XF800 就可以了。

       接下来,我们介绍DMA2D中断状态寄存器:DMA2D_ISR,该寄存器各位描述如图 27.1.3.11所示:


图27.1.3.11 DMA2D_ISR寄存器各位描述


       该寄存器表示了 DMA2D 的各种状态标识,这里我们只关心 TCIF 位,表示 DMA2D 的传输完成中断标志。当 DMA2D 传输操作完成(仅限数据传输)时此位置 1,表示可以开始下一次 DMA2D 传输了。

       另外,还有一个 DMA2D 中断标志清零寄存器: DMA2D_IFCR,该寄存器各位描述如图 27.1.3.12所示:


图27.1.3.12 DMA2D_ IFCR寄存器各位描述


       用于清除 DMA2D_ISR 寄存器对应位的标志。通过向该寄存器的第 1 位( CTCIF)写 1,可以用于清除 DMA2D_ISR 寄存器的 TCIF 位标志。

       最后,我们来看看利用 DMA2D 完成颜色填充,需要哪些步骤。这里需要说明一下,使用官方提供的 HAL 库 DMA2D 相关库函数进行颜色填充效率极为低下,大量时间浪费在函数的入栈出栈以及过程处理,所以在项目开发中一般都不会使用 DMA2D 库函数进行颜色填充,包括 ST 官方提供的 STEMWIN 实例关于 DMA2D 部分,均采用的寄存器操作。 具体操作步骤如下:


       1) 使能 DMA2D 时钟,并先停止 DMA2D。

       要使用 DMA2D,先得开启其时钟。然后 DMA2D 在配置其相关参数的时候,需要先停DMA2D 传输。 使能 DMA2D 时钟和停止 DMA2D 方法为

__HAL_RCC_DMA2D_CLK_ENABLE();     /* 使能DM2D时钟 */
DMA2D->CR &= ~(DMA2D_CR_START);   /* 先停止DMA2D */


       2) 设置 DMA2D 工作模式。

       通过 DMA2D_CR 寄存器,配置 DMA2D 的工作模式。我们用了寄存器到存储器模式和存储器到存储器这两个模式。

       寄存器到存储器模式设置:

DMA2D->CR = DMA2D_R2M;            /* 寄存器到存储器模式 */

       存储器到存储器模式设置:

DMA2D->CR = DMA2D_M2M;            /* 存储器到存储器模式 */


       3)设置DMA2D的相关参数

       这一步,我们需要设置:颜色格式、输出窗口、输出存储器地址、前景层地址(仅存储器到存储器模式需要设置)、颜色寄存器(仅寄存器到存储器模式需要设置)等,由:DMA2D_OPFCCR、 DMA2D_FGPFCCR、 DMA2D_OOR、 DMA2D_FGOR 、 DMA2D_OMAR、DMA2D_FGMAR 和 DMA2D_NLR 等寄存器进行配置。 具体配置过程请参考实验源码。


       4)启动 DMA2D 传输。

       通过 DMA2D_CR 寄存器配置开启 DMA2D 传输,实现图像数据的拷贝填充,方法为:

DMA2D->CR |= DMA2D_CR_START;                /* 启动DMA2D */


       5) 等待 DMA2D 传输完成,清除相关标识。

       最后,在传输过程中,不要再次设置 DMA2D,否则会打乱显示,所以一般在启动 DMA2D后,需要等待 DMA2D 传输完成(判断 DMA2D_ISR),在传输完成后,清除传输完成标识(设置 DMA2D_IFCR),以便启动下一次 DMA2D 传输。 方法为:

while((DMA2D->ISR & (DMA2D_FLAG_TC)) == 0)  /* 等待传输完成 */
DMA2D->IFCR |= DMA2D_FLAG_TC;               /* 清除传输完成标志 */

       通过以上几个步骤,我们就完成了 DMA2D 填充,DMA2D 的简介,我们就介绍完了,详细的介绍请大家参考《STM32H7Rx参考手册_V6(英文版).pdf》第18章。


        27.2 硬件设计


       1. 例程功能

       本实验利用STM32H7R7的LTDC接口来驱动RGB屏,RGBLCD模块的接口在核心板上,通过40P的FPC排线连接RGBLCD模块,实现RGBLCD模块的驱动和显示,下载成功后,按下复位之后,就可以看到RGBLCD模块不停的显示一些信息并不断切换底色。同时,屏幕上会显示LCD的ID。


       2. 硬件资源


       1)LED灯

              LED0.:LED0 – PD14


       2)串口1(PB14/PB15连接在板载USB转串口芯片CH340上面)


       3)正点原子 4.3寸/7寸、8寸RGBLCD模块


       4)外部SDRAM(W958D8NBYA)


       3. 原理图

       这里RGBLCD接口在STM32H7R7核心板上,原理图如图27.2.1所示


图27.2.1 RGBLCD接口原理图


       图中RGBLCD接口的接线关系见表27.1.2.1.这些线的连接, STM32H7R7核心板的内部已经连接好了,我们只需将RGBLCD模块通过40PIN的FPC线连接这个RGBLCD接口即可。实物连接(7寸RGBLCD模块)如图27.2.2所示:


图27.2.2 RGBLCD与开发板连接实物图


        27.3 程序设计 


       27.3.1 程序解析


       1. LTDC驱动代码

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。ltdc.c代码比较多,这里就不一一贴出来了,只针对几个重要的函数进行讲解。

       本实验,我们用到LTDC驱动RGBLCD,通过前面的介绍,我们知道不同的RGB屏,驱动参数有一些差异,为了方便兼容不同的RGBLCD,我们定义如下LTDC参数结构体(在ltdc.h里面定义):

/* LCD LTDC重要参数集 */
typedef struct  
{
uint32_t pwidth;      /* LCD面板的宽度,固定参数,
  不随显示方向改变,如果为0,说明没有任何RGB屏接入 */
    uint32_t pheight;     /* LCD面板的高度,固定参数,不随显示方向改变 */
    uint16_t hsw;         /* 水平同步宽度 */
    uint16_t vsw;         /* 垂直同步宽度 */
    uint16_t hbp;         /* 水平后廊 */
    uint16_t vbp;         /* 垂直后廊 */
    uint16_t hfp;         /* 水平前廊 */
    uint16_t vfp;         /* 垂直前廊  */
    uint8_t activelayer;  /* 当前层编号:0/1 */
    uint8_t dir;          /* 0,竖屏;1,横屏; */
    uint16_t width;       /* LCD宽度 */
    uint16_t height;      /* LCD高度 */
    uint32_t pixsize;     /* 每个像素所占字节数 */
}_ltdc_dev;

       该结构体用于保存一些 RGBLCD 重要参数信息,比如 LCD 面板的长宽、水平后廊和垂直后廊等参数。这个结构体虽然占用了几十个字节的内存,但是却可以让我们的驱动函数支持不同尺寸的 LCD,同时可以实现 LCD 横竖屏切换等重要功能,所以还是利大于弊的。

       接下来,我们来看两个很重要的数组:

/* 定义LTDC帧缓冲区 */
#if ((LTDC_PIXFORMAT == LTDC_PIXFORMAT_ARGB8888) || (LTDC_PIXFORMAT ==
LTDC_PIXFORMAT_RGB888))
#if !(__ARMCC_VERSION >= 6010050)
    uint32_t g_ltdc_lcd_framebuf[1280][800] __attribute__((at(LTDC_FRAME_BUF_ADDR)));
#else
    uint32_t g_ltdc_lcd_framebuf[1280][800] __attribute__((section(".bss.ARM.__at_0xC0000000")));
#endif

       其中,g_ltdc_lcd_framebuf 的大小是 LTDC 一帧图像的显存大小, STM32H7R7的 LTDC 最大可以支持 1280*800 的 RGB 屏,该数组根据我们选择的颜色格式( ARGB8888/RGB565),自动确 定 数 组 类 型 。 另 外 , 我 们 采 用 __attribute__ 关 键 字 , 将 数 组 的 地 址 定 向 到LTDC_FRAME_BUF_ADDR,它在 ltdc.h 里面定义,其值为: 0XC0000000,也就是 HyperRAM 的首地址。这样,我们就把 g_ltdc_lcd_framebuf 数组定义到了 HyperRAM 的首地址,大小为 800*1280*2字节( RGB565 格式时)。

       而 g_ltdc_framebuf 则是 LTDC 的帧缓存数组指针, LTDC 支持 2 个层,所以数组大小为 2。该指针为 32 位类型,必须指向对应的数组,才可以正常使用。在实际使用的时候,我们编写代码:

uint32_t * g_ltdc_framebuf[2];/* LTDC LCD帧缓存数组指针,必须指向对应大小的内存区域 */

       接下来看画点函数:ltdc_draw_point,该函数代码如下:

/**
 * @brief   LTDC画点
 * @param   x: 点的X坐标
 * @param   y: 点的Y坐标
 * @param   color: 点的颜色
 * @retval  无
 */
void ltdc_draw_point(uint16_t x, uint16_t y, uint32_t color)
{
#if ((LTDC_PIXFORMAT == LTDC_PIXFORMAT_ARGB8888) || (LTDC_PIXFORMAT == LTDC_PIXFORMAT_RGB888))
    if (lcdltdc.dir == 0)
    {
        *(uint32_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] +
 lcdltdc.pixsize*(lcdltdc.pwidth*(lcdltdc.pheight-x-1)+y))=color;
    }
    else
    {
        *(uint32_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] + 
lcdltdc.pixsize * (lcdltdc.pwidth * y + x)) = color;
    }
#else
    if (lcdltdc.dir == 0)
    {
        *(uint16_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] +
 lcdltdc.pixsize*(lcdltdc.pwidth*(lcdltdc.pheight-x-1)+y))=color;
    }
    else
    {
        *(uint16_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] +
         lcdltdc.pixsize*(lcdltdc.pwidth*y+x))=color;
    }
#endif
}

       该函数实现往 RGBLCD 上面画点的功能,根据 LTDC_PIXFORMAT 定义的颜色格式以及横竖屏状态,执行不同的操作。 RGBLCD 的画点,实际上就是往指定坐标的显存里面写数据,以7 寸 800*480 的屏幕, RGB565 格式,竖屏模式为例,画某个点对应到屏幕上面的关系如图 27.3.1所示:


图27.3.1 画点与LCD显存对应关系


       注意图中的LTDC扫描方向(LTDC在显存g_ltdc_framebuf里面读取GRAM数据的顺序也是这个方向),是从上到下,从右到左,而竖屏的时候,原点在左上角,所以有一个变换的过程,经过变换后的画点函数为:

*(uint16_t*)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] + lcdltdc.pixsize * (lcdltdc.pwidth * (lcdltdc.pheight - x - 1) + y)) = color;

       其中g_ltdc_framebuf,就是层帧缓冲的首地址;lcdltdc.activelayer 表示层编号:0/1 代表第 1/2层; lcdltdc.pixsize 表示每个像素的字节数,对于RGB565,它的值为 2; lcdltdc.pwidth 和lcdltdc.pheight 为 LCD 面板的宽度和高度,lcdltdc.pwidth=800,lcdltdc.pheight=480;x,y 就是要写入显存的坐标(也就是显示在LCD上面的坐标);color 为要写入的颜色值。

       有画点函数,就有读点函数,LTDC 的读点函数代码如下:

/**
 * @brief   读取个某点的颜色值
 * @param   x: 指定点的X坐标
 * @param   y: 指定点的Y坐标
 * @retval  指定点的颜色
 */
uint32_t ltdc_read_point(uint16_t x, uint16_t y)
{
#if ((LTDC_PIXFORMAT == LTDC_PIXFORMAT_ARGB8888) || (LTDC_PIXFORMAT ==
LTDC_PIXFORMAT_RGB888))
    if (lcdltdc.dir == 0)
    {
        return *(uint32_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] +
        lcdltdc.pixsize * (lcdltdc.pwidth * (lcdltdc.pheight - x - 1) + y));
    }
    else
    {
        return *(uint32_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] + 
lcdltdc.pixsize * (lcdltdc.pwidth * y + x));
    }
#else
    if (lcdltdc.dir == 0)
    {
        return *(uint16_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] + 
lcdltdc.pixsize * (lcdltdc.pwidth * (lcdltdc.pheight - x - 1) + y));
    }
    else
    {
        return *(uint16_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] +
 lcdltdc.pixsize * (lcdltdc.pwidth * y + x));
    }
#endif
}

       画点函数和读点函数十分类似,只是过程反过来了而已,坐标的计算,也是在 g_ltdc_framebuf数组内,根据坐标计算偏移量,完全和读点函数一模一样。

       第三个介绍的函数是 LTDC 单色填充函数: ltdc_fill,该函数使用了 DMA2D 操作,使得填充速度大大加快,该函数代码如下:

/**
 * @brief   LTDC在指定区域内填充单个颜色
 * @param   sx: 指定区域的起始X坐标
 * @param   sy: 指定区域的起始Y坐标
 * @param   ex: 指定区域的结束X坐标
 * @param   ey: 指定区域的结束Y坐标
 * @param   color: 要填充的颜色
 * @retval  无
 */
void ltdc_fill(uint16_t sx,uint16_t sy,uint16_tex,uint16_t ey,uint32_t color)
{
    uint32_t psx;
    uint32_t psy;
    uint32_t pex;
    uint32_t pey;
    uint32_t timeout = 0;
    uint16_t offline;
    uint32_t addr;
    
    if (lcdltdc.dir == 0)
    {
        if (ex >= lcdltdc.pheight)
        {
            ex = lcdltdc.pheight - 1;
        }
        
        if (sx >= lcdltdc.pheight)
        {
            sx = lcdltdc.pheight - 1;
        }
        
        psx = sy;
        psy = lcdltdc.pheight - ex - 1;
        pex = ey;
        pey = lcdltdc.pheight - sx - 1;
    }
    else
    {
        psx = sx;
        psy = sy;
        pex = ex;
        pey = ey;
    }
    
    offline = lcdltdc.pwidth - (pex - psx + 1);
addr = ((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] + lcdltdc.pixsize
*(lcdltdc.pwidth * psy + psx));
    
    /* 配置DMA2D */
    DMA2D->CR &= ~(DMA2D_CR_START);
    DMA2D->CR = DMA2D_R2M;
    DMA2D->OPFCCR = LTDC_PIXFORMAT;
    DMA2D->OOR = offline;
    DMA2D->OMAR = addr;
    DMA2D->NLR = (pey - psy + 1) | ((pex - psx + 1) << 16);
    DMA2D->OCOLR = color;
    
    /* 启动DMA2D */
    DMA2D->CR |= DMA2D_CR_START;
    
    /* 等待DMA2D传输结束 */
    while ((DMA2D->ISR & (DMA2D_FLAG_TC)) == 0)
    {
        if (++timeout > 0x1FFFFF)
        {
            break;
        }
    } 
    
    /* 清除DMA2D传输完成标志 */
    DMA2D->IFCR |= DMA2D_FLAG_TC;
}

       该函数使用 DMA2D 完成矩形色块的填充,其操作步骤,就是按 27.1.3 节最后的介绍来进行的,我们这里就不多说了,详见 27.1.3 节。另外,还有一个 LTDC 彩色填充函数,也是采用的 DMA2D 填充,函数名为 ltdc_color_fill,该函数代码同 ltdc_fill 非常接近,我们这里就不介绍了,请大家参考本例程源码。

       第四个介绍的函数是清屏函数: ltdc_clear,该函数代码如下:

/**
 * @brief       LCD清屏
 * @param       color:颜色值
 * @retval      无
 */
void ltdc_clear(uint32_t color)
{
    ltdc_fill(0, 0, lcdltdc.width - 1, lcdltdc.height - 1, color);
}

       该函数代码非常简单,清屏操作调用了我们前面介绍的 ltdc_fill 函数,采用 DMA2D 完成对 LCD 的清屏,提高了清屏速度。

       第五个介绍的函数是 LCD_CLK 频率设置函数: ltdc_clk_set,该函数代码如下:

/**
 * @brief   LTDC时钟设置
 * @param   pll3n: PLL3倍频系数
 * @param   pll3m: PLL3预分频系数
 * @param   pll3r: PLL3 R输出分频系数
 * @retval  设置结果
 * @arg     0: 设置成功
 * @arg     1: 设置失败
 */
static uint8_t ltdc_clk_set(uint32_t pll3n, uint32_t pll3m, uint32_t pll3r)
{
    rcc_periph_clk_init_struct.PeriphClockSelection |= RCC_PERIPHCLK_LTDC;
    rcc_periph_clk_init_struct.PLL3.PLL3M = pll3m;
    rcc_periph_clk_init_struct.PLL3.PLL3N = pll3n;
    rcc_periph_clk_init_struct.PLL3.PLL3R = pll3r;
    if (HAL_RCCEx_PeriphCLKConfig(&rcc_periph_clk_init_struct) != HAL_OK)
    {
        return 1;
    }
    
    return 0;
}

       该函数完成对PLL3的配置,最终控制输出LCD_CLK 的频率,LCD_CLK 的频率设置方法,我们在27.1.2节进行了介绍,请大家参考前面的介绍进行学习。

       第六个介绍的函数是LTDC层参数设置函数:ltdc_layer_parameter_config,该函数代码如下:

/**
 * @brief   配置LTDC层参数
 * @param   layerx: LTDC层编号
 * @arg     0: 层1
 * @arg     1: 层2
 * @param   bufaddr: 层显存起始地址
 * @param   pixformat: 层颜色数据格式
 * @arg     0: ARGB8888
 * @arg     1: RGB888
 * @arg     2: RGB565
 * @arg     3: ARGB1555
 * @arg     4: ARGB4444
 * @arg     5: L8
 * @arg     6: AL44
 * @arg     7: AL88
 * @param   alpha: 层颜色透明度
 * @param   alpha0: 层默认颜色透明度
 * @param   bfac1: 层混合系数1
 * @param   bfac2: 层混合系数2
 * @param   bkcolor: 层默认颜色(RGB888格式)
 * @retval  无
 */
void ltdc_layer_parameter_config(uint8_t layerx, uint32_t bufaddr, uint8_t
 pixformat, uint8_t alpha, uint8_t alpha0, uint8_t bfac1, uint8_t bfac2,
 uint32_t bkcolor)
{
    LTDC_LayerCfgTypeDef ltdc_layer_cfg_struct = {0};
    
    ltdc_layer_cfg_struct.WindowX0 = 0;
    ltdc_layer_cfg_struct.WindowX1 = 0;
    ltdc_layer_cfg_struct.WindowY0 = lcdltdc.pwidth;
    ltdc_layer_cfg_struct.WindowY1 = lcdltdc.pheight;
    ltdc_layer_cfg_struct.PixelFormat = pixformat;
    ltdc_layer_cfg_struct.Alpha = alpha;
    ltdc_layer_cfg_struct.Alpha0 = alpha0;
    ltdc_layer_cfg_struct.BlendingFactor1 = (uint32_t)bfac1 << 8;
    ltdc_layer_cfg_struct.BlendingFactor2 = (uint32_t)bfac2 << 0;
    ltdc_layer_cfg_struct.FBStartAdress = bufaddr;
    ltdc_layer_cfg_struct.ImageWidth = lcdltdc.pwidth;
    ltdc_layer_cfg_struct.ImageHeight = lcdltdc.pheight;
    ltdc_layer_cfg_struct.Backcolor.Red = (uint8_t)(bkcolor & 0X00FF0000)>>16;
    ltdc_layer_cfg_struct.Backcolor.Green = (uint8_t)(bkcolor & 0X0000FF00)>>8;
    ltdc_layer_cfg_struct.Backcolor.Blue = (uint8_t)bkcolor & 0X000000FF;
    HAL_LTDC_ConfigLayer(&g_ltdc_handle, <dc_layer_cfg_struct, layerx);
}

       该函数中主要调用 HAL 库函数 HAL_LTDC_ConfigLayer 设置 LTDC 层的基本参数,包括:层帧缓冲区首地址、颜色格式、 Alpha 值、混合系数和层默认颜色等,这些参数都需要根据大家的实际需要来进行设置。

       第七个介绍的函数是 LTDC 层窗口设置函数: ltdc_layer_window_config,该函数代码如下:

/**
 * @brief   配置LTDC层窗口
 * @param   layerx: LTDC层编号
 * @arg     0: 层1
 * @arg     1: 层2
 * @param   sx: 起始X坐标
 * @param   sy: 起始Y坐标
 * @param   width: 宽度
 * @param   height: 高度
 * @retval  无
 */
void ltdc_layer_window_config(uint8_t layerx, uint16_t sx, uint16_t sy,
     uint16_t width, uint16_t height)
{
    HAL_LTDC_SetWindowPosition(&g_ltdc_handle, sx, sy, layerx);
    HAL_LTDC_SetWindowSize(&g_ltdc_handle, width, height, layerx);
}

       该函数依次调用 HAL 库 LTDC 窗口位置设置函数 HAL_LTDC_SetWindowPosition 和窗口大小设置函数 HAL_LTDC_SetWindowSizel 来控制 LTDC 在某一层(1/2)上面的开窗操作,这个我们在 27.1.2 节也介绍过了,请参考前面的内容进行学习。这里我们一般设置层窗口为整个 LCD的分辨率,也就是不进行开窗操作。注意:此函数必须在 ltdc_layer_parameter_config 之后再设置。另外, 当设置的窗口值不等于面板的尺寸时,对层 GRAM 的操作(读/写点函数),也要根据层窗口的宽高来进行修改,否则显示不正常( 本例程就未做修改)。

       第八个介绍的函数是 LTDC LCD ID 获取函数: ltdc_panelid_read,该函数代码如下:

/**
 * @brief   读取LTDC LCD ID
 * @param   无
 * @retval  LTDC LCD ID
 */
uint16_t ltdc_panelid_read(void)
{
    GPIO_InitTypeDef gpio_init_struct = {0};
    uint8_t id;
    
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_GPIOG_CLK_ENABLE();
    
    gpio_init_struct.Pin = GPIO_PIN_2;              /* LTDC_B7 */
    gpio_init_struct.Mode = GPIO_MODE_INPUT;
    gpio_init_struct.Pull = GPIO_PULLUP;
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &gpio_init_struct);
    
    gpio_init_struct.Pin = GPIO_PIN_10;             /* LTDC_G7 */
    HAL_GPIO_Init(GPIOB, &gpio_init_struct);
    
    gpio_init_struct.Pin = GPIO_PIN_0;              /* LTDC_R7 */
    HAL_GPIO_Init(GPIOG, &gpio_init_struct);
    
    id = ((HAL_GPIO_ReadPin(GPIOG, GPIO_PIN_0) == GPIO_PIN_RESET) ? 0 : 1);
    id |= ((HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10) == GPIO_PIN_RESET)?0:1)<< 1;
    id |= ((HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2) == GPIO_PIN_RESET)?0:1)<< 2;
    
    switch (id)
    {
        case 0:return 0x4342;     /* ATK-MD0430R-480272 */
        case 1:return 0x7084;     /* ATK-MD0700R-800480 */
        case 2:return 0x7016;     /* ATK-MD0700R-1024600 */
        case 3:return 0x7018;     /* ATK-MD0700R-1280800 */
        case 4:return 0x4384;     /* ATK-MD0430R-800480 */
        case 5:return 0x1018;     /* ATK-MD1018R-1280800 */
        default:return 0;
    }

       因为 RGBLCD 屏并没有读的功能,所以,一般情况,外接 RGB 屏的时候, MCU 是无法获取屏幕的任何信息的。但是正点原子在 RGBLCD 模块上面,利用数据线( R7/G7/B7)做了一个巧妙的设计,可以让 MCU 读取到 RGBLCD 模块的 ID,从而执行不同的初始化,实现对不同分辨率的 RGBLCD 模块的兼容。详细原理见:本章 27.1.1 节,第(3)部分:正点原子RGBLCD 模块的说明。

       ltdc_panelid_read 函数,就是用这样的方法来读取 M[2:0]的值,并将结果(转换成屏型号了)返回给上一层。

       最后要介绍的函数是 LTDC 初始化函数: ltdc_init,该函数的简化代码如下:

/**
 * @brief   初始化LTDC
 * @param   无
 * @retval  无
 */
void ltdc_init(void)
{
    uint16_t id;
    GPIO_InitTypeDef gpio_init_struct = {0};
    
    id = ltdc_panelid_read();
    if (id == 0x4342)               /* ATK-MD0430R-480272 */
    {
        lcdltdc.pwidth = 480;       /* LCD面板的宽度 */
        lcdltdc.pheight = 272;      /* LCD面板的高度 */
        lcdltdc.hsw = 1;            /* 水平同步宽度 */
        lcdltdc.vsw = 1;            /* 垂直同步宽度 */
        lcdltdc.hbp = 40;           /* 水平后廊 */
        lcdltdc.vbp = 8;            /* 垂直后廊 */
        lcdltdc.hfp = 5;            /* 水平前廊 */
        lcdltdc.vfp = 8;            /* 垂直前廊 */
        ltdc_clk_set(300, 25, 33);  /* LTDC_CLK = 9MHz */
    }
    else if (id == 0x7084)          /* ATK-MD0700R-800480 */
    {
        lcdltdc.pwidth = 800;       /* LCD面板的宽度 */
        lcdltdc.pheight = 480;      /* LCD面板的高度 */
        lcdltdc.hsw = 1;            /* 水平同步宽度 */
        lcdltdc.vsw = 1;            /* 垂直同步宽度 */
        lcdltdc.hbp = 46;           /* 水平后廊 */
        lcdltdc.vbp = 23;           /* 垂直后廊 */
        lcdltdc.hfp = 210;          /* 水平前廊 */
        lcdltdc.vfp = 22;           /* 垂直前廊 */
        ltdc_clk_set(300, 25, 9);   /* LTDC_CLK = 33MHz */
    }
    else if (id == 0x7016)          /* ATK-MD0700R-1024600 */
    {
        lcdltdc.pwidth = 1024;      /* LCD面板的宽度 */
        lcdltdc.pheight = 600;      /* LCD面板的高度 */
        lcdltdc.hsw = 20;           /* 水平同步宽度 */
        lcdltdc.vsw = 3;            /* 垂直同步宽度 */
        lcdltdc.hbp = 140;          /* 水平后廊 */
        lcdltdc.vbp = 20;           /* 垂直后廊 */
        lcdltdc.hfp = 160;          /* 水平前廊 */
        lcdltdc.vfp = 12;           /* 垂直前廊 */
        ltdc_clk_set(300, 25, 6);   /* LTDC_CLK = 40MHz */
    }
    else if (id == 0x7018)          /* ATK-MD0700R-1280800 */
    {
        lcdltdc.pwidth = 1280;      /* LCD面板的宽度 */
        lcdltdc.pheight = 800;      /* LCD面板的高度 */
        /* 其他参数待定 */
    }
    else if (id == 0x4384)          /* ATK-MD0430R-800480 */
    {
        lcdltdc.pwidth = 800;       /* LCD面板的宽度 */
        lcdltdc.pheight = 480;      /* LCD面板的高度 */
        lcdltdc.hsw = 88;           /* 水平同步宽度 */
        lcdltdc.vsw = 40;           /* 垂直同步宽度 */
        lcdltdc.hbp = 48;           /* 水平后廊 */
        lcdltdc.vbp = 32;           /* 垂直后廊 */
        lcdltdc.hfp = 13;           /* 水平前廊 */
        lcdltdc.vfp = 3;            /* 垂直前廊 */
        ltdc_clk_set(300, 25, 9);   /* LTDC_CLK = 33MHz */
    }
    else if (id == 0x1018)          /* ATK-MD1018R-1280800 */
    {
        lcdltdc.pwidth = 1280;      /* LCD面板的宽度 */
        lcdltdc.pheight = 800;      /* LCD面板的高度 */
        lcdltdc.hsw = 140;          /* 水平同步宽度 */
        lcdltdc.vsw = 10;           /* 垂直同步宽度 */
        lcdltdc.hbp = 10;           /* 水平后廊 */
        lcdltdc.vbp = 10;           /* 垂直后廊 */
        lcdltdc.hfp = 10;           /* 水平前廊 */
        lcdltdc.vfp = 3;            /* 垂直前廊 */
        ltdc_clk_set(300, 25, 6);   /* LTDC_CLK = 45MHz */
    }
    
    lcddev.width = lcdltdc.pwidth;
    lcddev.height = lcdltdc.pheight;
    
#if ((LTDC_PIXFORMAT == LTDC_PIXFORMAT_ARGB8888) || (LTDC_PIXFORMAT ==
      LTDC_PIXFORMAT_RGB888))
    g_ltdc_framebuf[0] = (uint32_t *)g_ltdc_lcd_framebuf;
    lcdltdc.pixsize = 4;
#else
    g_ltdc_framebuf[0] = (uint16_t *)g_ltdc_lcd_framebuf;
    lcdltdc.pixsize = 2;
#endif
    
    /* 使能时钟 */
    LTDC_BL_GPIO_CLK_ENABLE();
    
    /* 配置LTDC LCD BL引脚 */
    gpio_init_struct.Pin = LTDC_BL_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
    gpio_init_struct.Pull = GPIO_PULLUP;
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(LTDC_BL_GPIO_PORT, &gpio_init_struct);
    
    /* 初始化LTDC */
    g_ltdc_handle.Instance = LTDC;
    g_ltdc_handle.Init.HSPolarity = LTDC_HSPOLARITY_AL;
    g_ltdc_handle.Init.VSPolarity = LTDC_VSPOLARITY_AL;
    g_ltdc_handle.Init.DEPolarity = LTDC_DEPOLARITY_AL;
    if (id == 0x1018)
    {
        g_ltdc_handle.Init.PCPolarity = LTDC_PCPOLARITY_IIPC;
    }
    else
    {
        g_ltdc_handle.Init.PCPolarity = LTDC_PCPOLARITY_IPC;
    }
    g_ltdc_handle.Init.HorizontalSync = lcdltdc.hsw - 1;
    g_ltdc_handle.Init.VerticalSync = lcdltdc.vsw - 1;
    g_ltdc_handle.Init.AccumulatedHBP = lcdltdc.hsw + lcdltdc.hbp - 1;
    g_ltdc_handle.Init.AccumulatedVBP = lcdltdc.vsw + lcdltdc.vbp - 1;
g_ltdc_handle.Init.AccumulatedActiveW = lcdltdc.hsw + lcdltdc.hbp +
lcdltdc.pwidth - 1;
g_ltdc_handle.Init.AccumulatedActiveH = lcdltdc.vsw + lcdltdc.vbp +
lcdltdc.pheight - 1;
g_ltdc_handle.Init.TotalWidth = lcdltdc.hsw + lcdltdc.hbp + lcdltdc.pwidth
 + lcdltdc.hfp - 1;
g_ltdc_handle.Init.TotalHeigh = lcdltdc.vsw + lcdltdc.vbp + lcdltdc.pheight
 + lcdltdc.vfp - 1;
    g_ltdc_handle.Init.Backcolor.Red = 0;
    g_ltdc_handle.Init.Backcolor.Green = 0;
    g_ltdc_handle.Init.Backcolor.Blue = 0;
    HAL_LTDC_DeInit(&g_ltdc_handle);
    HAL_LTDC_Init(&g_ltdc_handle);
    
    /* 配置LTDC层 */
ltdc_layer_parameter_config(0,(uint32_t)g_ltdc_framebuf[0],LTDC_PIXFORMAT,
 255, 0, 6, 7, 0);
    ltdc_layer_window_config(0, 0, 0, lcdltdc.pwidth, lcdltdc.pheight);
    
    ltdc_select_layer(0);
    LTDC_BL(1);
    ltdc_clear(0xFFFFFFFF);
}

       ltdc_init 的初始化步骤,是按照27.1.2节最后介绍的步骤来进行的,该函数先读取RGBLCD的ID,然后根据不同的 RGBLCD 型号,执行不同的面板参数初始化,然后调用HAL_LTDC_Init 函数来设置 RGBLCD 的相关参数并使能 LTDC,最后配置层参数和层窗口完成对 LTDC 的初始化。注意,代码里面的 lcdltdc.hsw、lcdltdc.vsw、lcdltdc.hbp 等参数的值,均是来自对应RGBLCD屏的数据手册,其中lcdid=0X7084 的配置参数,来自:AT070TN92.pdf。

       以上,就是ltdc驱动部分的代码,因为STM32H7R7开发板还有MCU屏接口,为了同时兼容MCU屏和RGB屏,我们对第25章介绍的lcd.c部分代码做了小改,添加对RGB屏的支持,由于篇幅所限,这里我们只挑几个重点的函数给大家介绍下。

       首先读点函数,改为了:

/**
 * @brief   读取个某点的颜色值
 * @param   x: 指定点的X坐标
 * @param   y: 指定点的Y坐标
 * @retval  指定点的颜色
 */
uint32_t ltdc_read_point(uint16_t x, uint16_t y)
{
#if ((LTDC_PIXFORMAT == LTDC_PIXFORMAT_ARGB8888) || (LTDC_PIXFORMAT ==
LTDC_PIXFORMAT_RGB888))
    if (lcdltdc.dir == 0)
    {
        return *(uint32_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] + 
lcdltdc.pixsize * (lcdltdc.pwidth * (lcdltdc.pheight - x - 1) + y));
    }
    else
    {
        ……//省略部分代码
    }
#endif
}

       当lcdltdc.pwidth!=0 的时候,说明接入的是 RGB 屏,所以调用 ltdc_read_point 函数,实现读点操作,其他情况,说明是 MCU 屏,执行 MCU 屏的读点操作(代码省略)。

       然后是画点函数,改为了:

/**
 * @brief   LTDC画点
 * @param   x: 点的X坐标
 * @param   y: 点的Y坐标
 * @param   color: 点的颜色
 * @retval  无
 */
void ltdc_draw_point(uint16_t x, uint16_t y, uint32_t color)
{
#if ((LTDC_PIXFORMAT == LTDC_PIXFORMAT_ARGB8888) || (LTDC_PIXFORMAT ==
LTDC_PIXFORMAT_RGB888))
    if (lcdltdc.dir == 0)
    {
        *(uint32_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] +
   lcdltdc.pixsize * (lcdltdc.pwidth * (lcdltdc.pheight - x - 1)+y))=color;
    }
    else
    {
        ……//省略部分代码
    }
#endif
}

       当lcdltdc.pwidth!=0 的时候,说明接入的是 RGB 屏,所以调用ltdc_draw_point 函数,实现画点操作,其他情况,说明是 MCU 屏,执行 MCU屏的画点操作(代码省略)。同样的,lcd.c 里面的快速画点函数:lcd_fast_drawPoint,在使用RGB屏的时候,也是使用lcd_fast_drawpoint 来实现画点操作的。

       最后,是 LCD 初始化函数,改为:

/**
 * @brief       初始化LCD
 * @note        该初始化函数可以初始化各种型号的LCD(详见本.c文件最前面的描述)
 * @param       无
 * @retval      无
 */
void lcd_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    FMC_NORSRAM_TimingTypeDef fmc_read_timing_struct={0};
    FMC_NORSRAM_TimingTypeDef fmc_write_timing_struct={0};
 
    lcddev.id = ltdc_panelid_read();  /* 检查是否有RGB屏接入 */
    if (lcddev.id != 0)
    {
        ltdc_init();                  /* ID非零,说明有RGB屏接入 */
    }
    else
    ……/* 省略部分代码 */
}

       首先通过 ltdc_panelid_read 函数,读取 RGBLCD 模块的 ID 值,如果合法,则说明接入了 RGB 屏,调用 ltdc_init 函数,完成对 LTDC 的初始化。否则的话,执行 MCU 屏的初始化。

       在 lcd.c 里面,其他还有一些函数进行了兼容 RGB 屏的修改,这里我们就不一一列举了,请大家参考本例程源码。在完成修改后,我们的例程就可以同时兼容 MCU 屏和 RGB 屏了,且RGB 屏的优先级较高。


       2. main.c代码

       main函数代码如下:

int main(void)
{
    uint8_t x = 0;
    uint8_t lcd_id[13];
 
    sys_mpu_config();                   /* 配置MPU */
    sys_cache_enable();                 /* 使能Cache */
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(300, 6, 2);    /* 配置时钟,600MHz */
    delay_init(600);                    /* 初始化延时 */
    usart_init(115200);                 /* 初始化串口 */
    led_init();                         /* 初始化LED */
    hyperram_init();                    /* 初始化HyperRAM */
    lcd_init();                         /* 初始化LCD */
  
sprintf((char *)lcd_id, "LCD ID:%04X", lcddev.id);
/* 将LCD ID打印到lcd_id数组 */
 
    while (1)
    {
        switch (x)
        {
            case 0:lcd_clear(WHITE); break;
            case 1: lcd_clear(BLACK); break;
            ……/* 此处省略部分代码 */
            case 11: lcd_clear(BROWN); break;
        }
        lcd_show_string(10, 40, 240, 32, 32, "STM32", RED);
        lcd_show_string(10, 80, 240, 24, 24, "LTDC TEST", RED);
        lcd_show_string(10, 110, 240, 16, 16, "ATOM@ALIENTEK", RED);
        lcd_show_string(10, 130, 240, 16, 16, (char *)lcd_id, RED); 
/* 显示LCD ID */
        if (++x == 12) x = 0;
        LED0_TOGGLE();     /* 红灯闪烁 */
        delay_ms(1000);
    }
}

       该部分代码与第 25章几乎一摸一样,显示一些固定的字符,字体大小包括 24*12、 16*8和 12*6 等三种,同时显示 LCD 的型号,然后不停的切换背景颜色,每 1s 切换一次。而 LED0也会不停的闪烁,指示程序已经在运行了。其中我们用到一个 sprintf 的函数,该函数用法同 printf,只是 sprintf 把打印内容输出到指定的内存区间上, sprintf 的详细用法,请百度。

       另外特别注意:uart_init 函数,不能去掉,因为在 lcd_init 函数里面调用了 printf,所以一旦你去掉这个初始化,就会死机了!实际上,只要你的代码有用到 printf,就必须初始化串口,否则都会死机,即停在 usart.c 里面的 fputc 函数,出不来。

       在编译通过之后,我们开始下载验证代码 。


        27 .4 下载验证

       将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。同时,可以看到RGBLCD模块的显示如图27.4.1所示:  


图27.4.1 RGBLCD显示效果图


       我们可以看到屏幕的背景是不停切换的,同时 DS0 不停的闪烁,证明我们的代码被正确的执行了,达到了我们预期的目的。 最后,需要注意的是:本例程兼容 MCU 屏,所以,当插入MCU 屏的时候(不插 RGB 屏),也可以显示同样的结果。


请使用浏览器的分享功能分享到微信等