本帖最后由 正点原子运营 于 2026-4-21 10:18 编辑
第二十七章 LTDC LCD(RGB屏)实验
1)实验平台:正点原子STM32H7R7开发板
2)章节摘自【正点原子】STM32H7R7开发指南 V1.1
3)购买链接: https://detail.tmall.com/item.htm?id=820823382459
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h7rx.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子STM32开发板技术交流群:756580169
在前面,我们介绍了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.6 DMA2D_FGOR寄存器各位描述
该寄存器同 DMA2D_OOR 一样,也是低 14 位有效,用于控制前景层的行偏移,也是用于生成地址,添加到各行末尾,从而确定下一行的起始地址。
接下来,我们介绍 DMA2D 输出存储器地址寄存器 : DMA2D_OMAR,该寄存器各位描述如图 27.1.3.7 所示:
图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画点
- * [url=home.php?mod=space&uid=271674]@param[/url] 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
- * [url=home.php?mod=space&uid=60778]@note[/url] 该初始化函数可以初始化各种型号的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 屏),也可以显示同样的结果。 |