OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 99|回复: 0

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

[复制链接]

1312

主题

1328

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5613
金钱
5613
注册时间
2019-5-8
在线时间
1498 小时
发表于 前天 10:18 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 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


2.jpg

3.png

在前面,我们介绍了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所示:


第二十七章 LTDC LCD543.png
表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所示:


第二十七章 LTDC LCD803.png
图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所示:


第二十七章 LTDC LCD1172.png
图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 所示:


第二十七章 LTDC LCD1689.png
图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 所示:

1.png
表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所示:


第二十七章 LTDC LCD3197.png
图27.1.2.1 LTDC控制器框图

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


第二十七章 LTDC LCD3305.png
表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 所示:


第二十七章 LTDC LCD4021.png
图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所示:


第二十七章 LTDC LCD4446.png
图27.1.2.3 LTDC同步时序框图

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

2.png
表27.1.2.2 LTDC驱动时序参数

如果 RGBLCD 使用的是DE模式, LTDC也只需要设置表27.1.2.2 所示的参数,然后 LTDC会根据这些设置,自动控制DE信号。这些参数通过相关寄存器来配置,接下来,我们介绍一下LTDC的一些相关寄存器。
首先,我们来看LTDC全局控制寄存器:LTDC_GCR,该寄存器各位描述如图27.1.2.4所示:


第二十七章 LTDC LCD5130.png
图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 所示:


第二十七章 LTDC LCD5616.png
图27.1.2.5 LTDC_SSCR寄存器各位描述

该寄存器用于设置垂直同步高度(VSH)和水平同步宽度(HSW),其中:
VSH:表示垂直同步高度(以水平扫描行为单位),表示垂直同步脉宽减1,即VSW-1。
HSW:表示水平同步宽度(以像素时钟为单位),表示水平同步脉宽减1,即HSW-1.
接下来,我们看看LTDC后沿配置寄存器:LTDC_BPCR,该寄存器各位描述如图27.1.2.6所示:


第二十七章 LTDC LCD5818.png
图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所示:


第二十七章 LTDC LCD6019.png
图27.1.2.7 LTDC_AWCR寄存器各位描述

该寄存器我们需要配置AAH和AAW:
AAH:累加有效高度(以水平扫描行为单位),表示:VSW+VBP+有效高度-1。
AAW:累加有效宽度(以像素时钟为单位),表示:HSW+HBP+有效宽度-1。
这里所说的有效高度和有效宽度,是指LCD面板的宽度和高度(构成分辨率,下同)
接下来,我们看看LTDC总宽度配置寄存器:LTDC_TWCR,该寄存器各位描述如图27.1.2.8所示:


第二十七章 LTDC LCD6240.png
图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所示:


第二十七章 LTDC LCD6437.png
图27.1.2.9 LTDC_BCCR 寄存器各位描述

该寄存器定义背景层的颜色(RGB888),通过低26位配置,我们一般设置为全0即可。
接下来,我们看看LTDC的层颜色帧缓冲区地址寄存器:LTDC_LxCFBAR(x=1/2),该寄存器各位描述如图27.1.2.10所示:


第二十七章 LTDC LCD6579.png
图27.1.2.10LTDC_LxCFBAR寄存器各位描述

该寄存器用来定义一层显存的起始地址。STM32H7R7的LTDC支持2个层,所以总共两个寄存器,分别设置层1和层2的显存起始地址。
接下来,我们看看LTDC的层像素格式配置寄存器:LTDC_LxPFCR(x=1/2),该寄存器各位描述如图27.1.2.11所示:


第二十七章 LTDC LCD6743.png
图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所示:


第二十七章 LTDC LCD7042.png
图27.1.2.12 LTDC_LxCACR寄存器各位描述

该寄存器低8位(CONSTA)有效,这些位配置混合时使用的恒定Alpha。恒定Alpha由硬件实现255分频。关于这个恒定Alpha的使用,我们将在介绍LTDC_LxBFCR寄存器的时候进行讲解。
接下来,我们看看LTDC的层默认颜色配置寄存器:LTDC_LxDCCR(x=1/2),该寄存器各位描述如图27.1.2.13所示:


第二十七章 LTDC LCD7240.png
图27.1.2.13 LTDC_LxDCCR寄存器各位描述

该寄存器定义采用ARGB8888格式的层的默认颜色。默认颜色在定义的层窗口外使用或在层禁止时使用。一般情况下,用不到,所以该寄存器一般设置为0即可。
接下来,我们看看LTDC的层混合系数配置寄存器:LTDC_LxBFCR(x=1/2),该寄存器各位描述如图27.1.2.14所示:


第二十七章 LTDC LCD7413.png
图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 所示:


第二十七章 LTDC LCD8308.png
图27.1.2.15 LTDC层窗口设置关系图

上图中,层中的第一个和最后一个可见像素通过配置LTDC_LxWVPCR寄存器中的WHSTPOS[11:0]和WHSPPOS[11:0]进行设置,配置完成后,即可确定窗口的大小。
接下来,我们来介绍这两个寄存器,首先是LTDC的层窗口水平位置配置寄存器:
LTDC_LxWHPCR(x=1/2),该寄存器各位描述如图27.1.2.16所示:


第二十七章 LTDC LCD8504.png
图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 所示:


第二十七章 LTDC LCD8749.png
图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所示:


第二十七章 LTDC LCD8983.png
图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所示:


第二十七章 LTDC LCD9350.png
图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所示:


第二十七章 LTDC LCD9885.png
图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所示:


第二十七章 LTDC LCD10151.png
图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 所示:


第二十七章 LTDC LCD10450.png
图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所示:

第二十七章 LTDC LCD11193.png
图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所示:


第二十七章 LTDC LCD11684.png
图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 时钟使能方法为:

  1.     __HAL_RCC_LTDC_CLK_ENABLE();                /* 使能LTDC时钟 */
复制代码
2) 设置 LCD_CLK 时钟。
此步需要配置 LCD 的像素时钟,根据LCD的面板参数进行设置,LCD_CLK由PLLSAI进行配置,前面我们已经讲解非常详细,配置使用到的HAL库函数为:

  1. 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的函数为:
  1. HAL_StatusTypeDef HAL_LTDC_Init(LTDC_HandleTypeDef *hltdc);
复制代码
接下来真正用来初始化LTDC的结构体变量,结构体LTDC_InitTypeDef定义如下:

  1. typedef struct
  2. {
  3.   uint32_t            HSPolarity;                /* 水平同步极性 */
  4.   uint32_t            VSPolarity;                /* 垂直同步极性 */
  5.   uint32_t            DEPolarity;                /* 数据使能极性 */
  6.   uint32_t            PCPolarity;                /* 像素时钟极性 */
  7.   uint32_t            HorizontalSync;            /* 水平同步宽度 */
  8.   uint32_t            VerticalSync;              /* 垂直同步宽度 */
  9.   uint32_t            AccumulatedHBP;            /* 水平同步后沿宽度 */
  10.   uint32_t            AccumulatedVBP;            /* 垂直同步后沿高度 */
  11.   uint32_t            AccumulatedActiveW;        /* 累加有效宽度 */
  12.   uint32_t            AccumulatedActiveH;        /* 累加有效高度 */
  13.   uint32_t            TotalWidth;                /* 总宽度 */
  14.   uint32_t            TotalHeigh;                /* 总宽度 */
  15.   LTDC_ColorTypeDef   Backcolor;                 /* 屏幕背景层颜色 */
  16. } LTDC_InitTypeDef;
复制代码
这些参数含义我们都在结构体成员变量之后注释了,具体含义大家可以参考前面第四点配
置和状态寄存器讲解。
和其他外设或接口初始化一样,HAL同样提供了LTDC初始化MSP回调函数,HAL_LTDC_MspInit,该函数一般用来使能时钟和初始化IO口:

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

  1. 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 层窗口配置函数为:

  1. HAL_StatusTypeDef HAL_LTDC_SetWindowSize(LTDC_HandleTypeDef *hltdc,
  2. uint32_t XSize, uint32_t YSize, uint32_t LayerIdx)/* 层窗口尺寸配置 */
  3. 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&#8482;,即: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 所示:


第二十七章 LTDC LCD15684.png
图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 所示:


第二十七章 LTDC LCD16263.png
图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 所示:


第二十七章 LTDC LCD16552.png
图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所示:


第二十七章 LTDC LCD16805.png
图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所示:


第二十七章 LTDC LCD17132.png
图27.1.3.5 DMA2D_OOR寄存器各位描述

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


第二十七章 LTDC LCD17312.png
图27.1.3.6 DMA2D_FGOR寄存器各位描述

该寄存器同 DMA2D_OOR 一样,也是低 14 位有效,用于控制前景层的行偏移,也是用于生成地址,添加到各行末尾,从而确定下一行的起始地址。
接下来,我们介绍 DMA2D 输出存储器地址寄存器 : DMA2D_OMAR,该寄存器各位描述如图 27.1.3.7 所示:


第二十七章 LTDC LCD17478.png
图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所示:


第二十七章 LTDC LCD17788.png
图27.1.3.8 DMA2D_ FGMAR寄存器各位描述

该寄存器同 DMA2D_OMAR 一样,不过是用于控制前景层的存储器地址,计算方法同 DMA2D_OMAR。
接下来,我们介绍 DMA2D 行数寄存器 : DMA2D_NLR,该寄存器各位描述如图 27.1.3.9所示:


第二十七章 LTDC LCD17931.png
图27.1.3.9 DMA2D_NLR寄存器各位描述

该寄存器用于控制每行的像素和行数,该寄存器的设置对前景层和显示层均有效,通过该寄存器的配置,就可以设置开窗的大小。其中:
NL[15: 0]:设置待传输区域的行数,用于确定窗口的高度。
PL[13: 0]:设置待传输区域的每行像素数,用于确定窗口的宽度。
接下来,我们介绍 DMA2D 输出颜色寄存器 : DMA2D_OCOLR,该寄存器各位描述如图27.1.3.10 所示:


第二十七章 LTDC LCD18149.png
图27.1.3.10 DMA2D_OCOLR寄存器各位描述

该寄存器用于配置在寄存器到存储器模式下,填充时所用的颜色值,该寄存器是一个 32位寄存器,可以支持 ARGB8888 格式,也可以支持 RGB565 格式。我们一般使用 RGB565 格式,比如要填充红色,那么直接设置 DMA2D_OCOLR=0XF800 就可以了。
接下来,我们介绍DMA2D中断状态寄存器:DMA2D_ISR,该寄存器各位描述如图 27.1.3.11所示:


第二十七章 LTDC LCD18371.png
图27.1.3.11 DMA2D_ISR寄存器各位描述

该寄存器表示了 DMA2D 的各种状态标识,这里我们只关心 TCIF 位,表示 DMA2D 的传输完成中断标志。当 DMA2D 传输操作完成(仅限数据传输)时此位置 1,表示可以开始下一次 DMA2D 传输了。
另外,还有一个 DMA2D 中断标志清零寄存器: DMA2D_IFCR,该寄存器各位描述如图 27.1.3.12所示:


第二十七章 LTDC LCD18567.png
图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 方法为

  1. __HAL_RCC_DMA2D_CLK_ENABLE();     /* 使能DM2D时钟 */
  2. DMA2D->CR &= ~(DMA2D_CR_START);   /* 先停止DMA2D */
复制代码

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

  1. DMA2D->CR = DMA2D_R2M;            /* 寄存器到存储器模式 */
复制代码

存储器到存储器模式设置:
  1. 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 传输,实现图像数据的拷贝填充,方法为:

  1. DMA2D->CR |= DMA2D_CR_START;                /* 启动DMA2D */
复制代码

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

  1. while((DMA2D->ISR & (DMA2D_FLAG_TC)) == 0)  /* 等待传输完成 */
  2. 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所示


第二十七章 LTDC LCD20289.png
图27.2.1 RGBLCD接口原理图

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

第二十七章 LTDC LCD20435.png
图27.2.2 RGBLCD与开发板连接实物图

27.3 程序设计

27.3.1 程序解析

1. LTDC驱动代码
        这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。ltdc.c代码比较多,这里就不一一贴出来了,只针对几个重要的函数进行讲解。
本实验,我们用到LTDC驱动RGBLCD,通过前面的介绍,我们知道不同的RGB屏,驱动参数有一些差异,为了方便兼容不同的RGBLCD,我们定义如下LTDC参数结构体(在ltdc.h里面定义):

  1. /* LCD LTDC重要参数集 */
  2. typedef struct  
  3. {
  4. uint32_t pwidth;      /* LCD面板的宽度,固定参数,
  5.                                                   不随显示方向改变,如果为0,说明没有任何RGB屏接入 */
  6.     uint32_t pheight;     /* LCD面板的高度,固定参数,不随显示方向改变 */
  7.     uint16_t hsw;         /* 水平同步宽度 */
  8.     uint16_t vsw;         /* 垂直同步宽度 */
  9.     uint16_t hbp;         /* 水平后廊 */
  10.     uint16_t vbp;         /* 垂直后廊 */
  11.     uint16_t hfp;         /* 水平前廊 */
  12.     uint16_t vfp;         /* 垂直前廊  */
  13.     uint8_t activelayer;  /* 当前层编号:0/1 */
  14.     uint8_t dir;          /* 0,竖屏;1,横屏; */
  15.     uint16_t width;       /* LCD宽度 */
  16.     uint16_t height;      /* LCD高度 */
  17.     uint32_t pixsize;     /* 每个像素所占字节数 */
  18. }_ltdc_dev;
复制代码
该结构体用于保存一些 RGBLCD 重要参数信息,比如 LCD 面板的长宽、水平后廊和垂直后廊等参数。这个结构体虽然占用了几十个字节的内存,但是却可以让我们的驱动函数支持不同尺寸的 LCD,同时可以实现 LCD 横竖屏切换等重要功能,所以还是利大于弊的。
接下来,我们来看两个很重要的数组:

  1. /* 定义LTDC帧缓冲区 */
  2. #if ((LTDC_PIXFORMAT == LTDC_PIXFORMAT_ARGB8888) || (LTDC_PIXFORMAT ==
  3. LTDC_PIXFORMAT_RGB888))
  4. #if !(__ARMCC_VERSION >= 6010050)
  5.     uint32_t g_ltdc_lcd_framebuf[1280][800] __attribute__((at(LTDC_FRAME_BUF_ADDR)));
  6. #else
  7.     uint32_t g_ltdc_lcd_framebuf[1280][800] __attribute__((section(".bss.ARM.__at_0xC0000000")));
  8. #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,该函数代码如下:

  1. /**
  2. * @brief   LTDC画点
  3. * [url=home.php?mod=space&uid=271674]@param[/url]   x: 点的X坐标
  4. * @param   y: 点的Y坐标
  5. * @param   color: 点的颜色
  6. * @retval  无
  7. */
  8. void ltdc_draw_point(uint16_t x, uint16_t y, uint32_t color)
  9. {
  10. #if ((LTDC_PIXFORMAT == LTDC_PIXFORMAT_ARGB8888) || (LTDC_PIXFORMAT == LTDC_PIXFORMAT_RGB888))
  11.     if (lcdltdc.dir == 0)
  12.     {
  13.         *(uint32_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] +
  14. lcdltdc.pixsize*(lcdltdc.pwidth*(lcdltdc.pheight-x-1)+y))=color;
  15.     }
  16.     else
  17.     {
  18.         *(uint32_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] +
  19. lcdltdc.pixsize * (lcdltdc.pwidth * y + x)) = color;
  20.     }
  21. #else
  22.     if (lcdltdc.dir == 0)
  23.     {
  24.         *(uint16_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] +
  25. lcdltdc.pixsize*(lcdltdc.pwidth*(lcdltdc.pheight-x-1)+y))=color;
  26.     }
  27.     else
  28.     {
  29.         *(uint16_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] +
  30.          lcdltdc.pixsize*(lcdltdc.pwidth*y+x))=color;
  31.     }
  32. #endif
  33. }
复制代码
该函数实现往 RGBLCD 上面画点的功能,根据 LTDC_PIXFORMAT 定义的颜色格式以及横竖屏状态,执行不同的操作。 RGBLCD 的画点,实际上就是往指定坐标的显存里面写数据,以7 寸 800*480 的屏幕, RGB565 格式,竖屏模式为例,画某个点对应到屏幕上面的关系如图 27.3.1所示:

第二十七章 LTDC LCD23389.png
图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 的读点函数代码如下:

  1. /**
  2. * @brief   读取个某点的颜色值
  3. * @param   x: 指定点的X坐标
  4. * @param   y: 指定点的Y坐标
  5. * @retval  指定点的颜色
  6. */
  7. uint32_t ltdc_read_point(uint16_t x, uint16_t y)
  8. {
  9. #if ((LTDC_PIXFORMAT == LTDC_PIXFORMAT_ARGB8888) || (LTDC_PIXFORMAT ==
  10. LTDC_PIXFORMAT_RGB888))
  11.     if (lcdltdc.dir == 0)
  12.     {
  13.         return *(uint32_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] +
  14.         lcdltdc.pixsize * (lcdltdc.pwidth * (lcdltdc.pheight - x - 1) + y));
  15.     }
  16.     else
  17.     {
  18.         return *(uint32_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] +
  19. lcdltdc.pixsize * (lcdltdc.pwidth * y + x));
  20.     }
  21. #else
  22.     if (lcdltdc.dir == 0)
  23.     {
  24.         return *(uint16_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] +
  25. lcdltdc.pixsize * (lcdltdc.pwidth * (lcdltdc.pheight - x - 1) + y));
  26.     }
  27.     else
  28.     {
  29.         return *(uint16_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] +
  30. lcdltdc.pixsize * (lcdltdc.pwidth * y + x));
  31.     }
  32. #endif
  33. }
复制代码
画点函数和读点函数十分类似,只是过程反过来了而已,坐标的计算,也是在 g_ltdc_framebuf数组内,根据坐标计算偏移量,完全和读点函数一模一样。
第三个介绍的函数是 LTDC 单色填充函数: ltdc_fill,该函数使用了 DMA2D 操作,使得填充速度大大加快,该函数代码如下:

  1. /**
  2. * @brief   LTDC在指定区域内填充单个颜色
  3. * @param   sx: 指定区域的起始X坐标
  4. * @param   sy: 指定区域的起始Y坐标
  5. * @param   ex: 指定区域的结束X坐标
  6. * @param   ey: 指定区域的结束Y坐标
  7. * @param   color: 要填充的颜色
  8. * @retval  无
  9. */
  10. void ltdc_fill(uint16_t sx,uint16_t sy,uint16_tex,uint16_t ey,uint32_t color)
  11. {
  12.     uint32_t psx;
  13.     uint32_t psy;
  14.     uint32_t pex;
  15.     uint32_t pey;
  16.     uint32_t timeout = 0;
  17.     uint16_t offline;
  18.     uint32_t addr;
  19.    
  20.     if (lcdltdc.dir == 0)
  21.     {
  22.         if (ex >= lcdltdc.pheight)
  23.         {
  24.             ex = lcdltdc.pheight - 1;
  25.         }
  26.         
  27.         if (sx >= lcdltdc.pheight)
  28.         {
  29.             sx = lcdltdc.pheight - 1;
  30.         }
  31.         
  32.         psx = sy;
  33.         psy = lcdltdc.pheight - ex - 1;
  34.         pex = ey;
  35.         pey = lcdltdc.pheight - sx - 1;
  36.     }
  37.     else
  38.     {
  39.         psx = sx;
  40.         psy = sy;
  41.         pex = ex;
  42.         pey = ey;
  43.     }
  44.    
  45.     offline = lcdltdc.pwidth - (pex - psx + 1);
  46. addr = ((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] + lcdltdc.pixsize
  47. *(lcdltdc.pwidth * psy + psx));
  48.    
  49.     /* 配置DMA2D */
  50.     DMA2D->CR &= ~(DMA2D_CR_START);
  51.     DMA2D->CR = DMA2D_R2M;
  52.     DMA2D->OPFCCR = LTDC_PIXFORMAT;
  53.     DMA2D->OOR = offline;
  54.     DMA2D->OMAR = addr;
  55.     DMA2D->NLR = (pey - psy + 1) | ((pex - psx + 1) << 16);
  56.     DMA2D->OCOLR = color;
  57.    
  58.     /* 启动DMA2D */
  59.     DMA2D->CR |= DMA2D_CR_START;
  60.    
  61.     /* 等待DMA2D传输结束 */
  62.     while ((DMA2D->ISR & (DMA2D_FLAG_TC)) == 0)
  63.     {
  64.         if (++timeout > 0x1FFFFF)
  65.         {
  66.             break;
  67.         }
  68.     }
  69.    
  70.     /* 清除DMA2D传输完成标志 */
  71.     DMA2D->IFCR |= DMA2D_FLAG_TC;
  72. }
复制代码
该函数使用 DMA2D 完成矩形色块的填充,其操作步骤,就是按 27.1.3 节最后的介绍来进行的,我们这里就不多说了,详见 27.1.3 节。另外,还有一个 LTDC 彩色填充函数,也是采用的 DMA2D 填充,函数名为 ltdc_color_fill,该函数代码同 ltdc_fill 非常接近,我们这里就不介绍了,请大家参考本例程源码。
第四个介绍的函数是清屏函数: ltdc_clear,该函数代码如下:

  1. /**
  2. * @brief       LCD清屏
  3. * @param       color:颜色值
  4. * @retval      无
  5. */
  6. void ltdc_clear(uint32_t color)
  7. {
  8.     ltdc_fill(0, 0, lcdltdc.width - 1, lcdltdc.height - 1, color);
  9. }
复制代码
该函数代码非常简单,清屏操作调用了我们前面介绍的 ltdc_fill 函数,采用 DMA2D 完成对 LCD 的清屏,提高了清屏速度。
第五个介绍的函数是 LCD_CLK 频率设置函数: ltdc_clk_set,该函数代码如下:

  1. /**
  2. * @brief   LTDC时钟设置
  3. * @param   pll3n: PLL3倍频系数
  4. * @param   pll3m: PLL3预分频系数
  5. * @param   pll3r: PLL3 R输出分频系数
  6. * @retval  设置结果
  7. * @arg     0: 设置成功
  8. * @arg     1: 设置失败
  9. */
  10. static uint8_t ltdc_clk_set(uint32_t pll3n, uint32_t pll3m, uint32_t pll3r)
  11. {
  12.     rcc_periph_clk_init_struct.PeriphClockSelection |= RCC_PERIPHCLK_LTDC;
  13.     rcc_periph_clk_init_struct.PLL3.PLL3M = pll3m;
  14.     rcc_periph_clk_init_struct.PLL3.PLL3N = pll3n;
  15.     rcc_periph_clk_init_struct.PLL3.PLL3R = pll3r;
  16.     if (HAL_RCCEx_PeriphCLKConfig(&rcc_periph_clk_init_struct) != HAL_OK)
  17.     {
  18.         return 1;
  19.     }
  20.    
  21.     return 0;
  22. }
复制代码
该函数完成对PLL3的配置,最终控制输出LCD_CLK 的频率,LCD_CLK 的频率设置方法,我们在27.1.2节进行了介绍,请大家参考前面的介绍进行学习。
第六个介绍的函数是LTDC层参数设置函数:ltdc_layer_parameter_config,该函数代码如下:

  1. /**
  2. * @brief   配置LTDC层参数
  3. * @param   layerx: LTDC层编号
  4. * @arg     0: 层1
  5. * @arg     1: 层2
  6. * @param   bufaddr: 层显存起始地址
  7. * @param   pixformat: 层颜色数据格式
  8. * @arg     0: ARGB8888
  9. * @arg     1: RGB888
  10. * @arg     2: RGB565
  11. * @arg     3: ARGB1555
  12. * @arg     4: ARGB4444
  13. * @arg     5: L8
  14. * @arg     6: AL44
  15. * @arg     7: AL88
  16. * @param   alpha: 层颜色透明度
  17. * @param   alpha0: 层默认颜色透明度
  18. * @param   bfac1: 层混合系数1
  19. * @param   bfac2: 层混合系数2
  20. * @param   bkcolor: 层默认颜色(RGB888格式)
  21. * @retval  无
  22. */
  23. void ltdc_layer_parameter_config(uint8_t layerx, uint32_t bufaddr, uint8_t
  24. pixformat, uint8_t alpha, uint8_t alpha0, uint8_t bfac1, uint8_t bfac2,
  25. uint32_t bkcolor)
  26. {
  27.     LTDC_LayerCfgTypeDef ltdc_layer_cfg_struct = {0};
  28.    
  29.     ltdc_layer_cfg_struct.WindowX0 = 0;
  30.     ltdc_layer_cfg_struct.WindowX1 = 0;
  31.     ltdc_layer_cfg_struct.WindowY0 = lcdltdc.pwidth;
  32.     ltdc_layer_cfg_struct.WindowY1 = lcdltdc.pheight;
  33.     ltdc_layer_cfg_struct.PixelFormat = pixformat;
  34.     ltdc_layer_cfg_struct.Alpha = alpha;
  35.     ltdc_layer_cfg_struct.Alpha0 = alpha0;
  36.     ltdc_layer_cfg_struct.BlendingFactor1 = (uint32_t)bfac1 << 8;
  37.     ltdc_layer_cfg_struct.BlendingFactor2 = (uint32_t)bfac2 << 0;
  38.     ltdc_layer_cfg_struct.FBStartAdress = bufaddr;
  39.     ltdc_layer_cfg_struct.ImageWidth = lcdltdc.pwidth;
  40.     ltdc_layer_cfg_struct.ImageHeight = lcdltdc.pheight;
  41.     ltdc_layer_cfg_struct.Backcolor.Red = (uint8_t)(bkcolor & 0X00FF0000)>>16;
  42.     ltdc_layer_cfg_struct.Backcolor.Green = (uint8_t)(bkcolor & 0X0000FF00)>>8;
  43.     ltdc_layer_cfg_struct.Backcolor.Blue = (uint8_t)bkcolor & 0X000000FF;
  44.     HAL_LTDC_ConfigLayer(&g_ltdc_handle, &ltdc_layer_cfg_struct, layerx);
  45. }
复制代码
该函数中主要调用 HAL 库函数 HAL_LTDC_ConfigLayer 设置 LTDC 层的基本参数,包括:层帧缓冲区首地址、颜色格式、 Alpha 值、混合系数和层默认颜色等,这些参数都需要根据大家的实际需要来进行设置。
第七个介绍的函数是 LTDC 层窗口设置函数: ltdc_layer_window_config,该函数代码如下:

  1. /**
  2. * @brief   配置LTDC层窗口
  3. * @param   layerx: LTDC层编号
  4. * @arg     0: 层1
  5. * @arg     1: 层2
  6. * @param   sx: 起始X坐标
  7. * @param   sy: 起始Y坐标
  8. * @param   width: 宽度
  9. * @param   height: 高度
  10. * @retval  无
  11. */
  12. void ltdc_layer_window_config(uint8_t layerx, uint16_t sx, uint16_t sy,
  13.      uint16_t width, uint16_t height)
  14. {
  15.     HAL_LTDC_SetWindowPosition(&g_ltdc_handle, sx, sy, layerx);
  16.     HAL_LTDC_SetWindowSize(&g_ltdc_handle, width, height, layerx);
  17. }
复制代码
该函数依次调用 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,该函数代码如下:

  1. /**
  2. * @brief   读取LTDC LCD ID
  3. * @param   无
  4. * @retval  LTDC LCD ID
  5. */
  6. uint16_t ltdc_panelid_read(void)
  7. {
  8.     GPIO_InitTypeDef gpio_init_struct = {0};
  9.     uint8_t id;
  10.    
  11.     __HAL_RCC_GPIOA_CLK_ENABLE();
  12.     __HAL_RCC_GPIOB_CLK_ENABLE();
  13.     __HAL_RCC_GPIOG_CLK_ENABLE();
  14.    
  15.     gpio_init_struct.Pin = GPIO_PIN_2;              /* LTDC_B7 */
  16.     gpio_init_struct.Mode = GPIO_MODE_INPUT;
  17.     gpio_init_struct.Pull = GPIO_PULLUP;
  18.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
  19.     HAL_GPIO_Init(GPIOA, &gpio_init_struct);
  20.    
  21.     gpio_init_struct.Pin = GPIO_PIN_10;             /* LTDC_G7 */
  22.     HAL_GPIO_Init(GPIOB, &gpio_init_struct);
  23.    
  24.     gpio_init_struct.Pin = GPIO_PIN_0;              /* LTDC_R7 */
  25.     HAL_GPIO_Init(GPIOG, &gpio_init_struct);
  26.    
  27.     id = ((HAL_GPIO_ReadPin(GPIOG, GPIO_PIN_0) == GPIO_PIN_RESET) ? 0 : 1);
  28.     id |= ((HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10) == GPIO_PIN_RESET)?0:1)<< 1;
  29.     id |= ((HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_2) == GPIO_PIN_RESET)?0:1)<< 2;
  30.    
  31.     switch (id)
  32.     {
  33.         case 0:return 0x4342;     /* ATK-MD0430R-480272 */
  34.         case 1:return 0x7084;     /* ATK-MD0700R-800480 */
  35.         case 2:return 0x7016;     /* ATK-MD0700R-1024600 */
  36.         case 3:return 0x7018;     /* ATK-MD0700R-1280800 */
  37.         case 4:return 0x4384;     /* ATK-MD0430R-800480 */
  38.         case 5:return 0x1018;     /* ATK-MD1018R-1280800 */
  39.         default:return 0;
  40.     }
复制代码
因为 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,该函数的简化代码如下:

  1. /**
  2. * @brief   初始化LTDC
  3. * @param   无
  4. * @retval  无
  5. */
  6. void ltdc_init(void)
  7. {
  8.     uint16_t id;
  9.     GPIO_InitTypeDef gpio_init_struct = {0};
  10.    
  11.     id = ltdc_panelid_read();
  12.     if (id == 0x4342)               /* ATK-MD0430R-480272 */
  13.     {
  14.         lcdltdc.pwidth = 480;       /* LCD面板的宽度 */
  15.         lcdltdc.pheight = 272;      /* LCD面板的高度 */
  16.         lcdltdc.hsw = 1;            /* 水平同步宽度 */
  17.         lcdltdc.vsw = 1;            /* 垂直同步宽度 */
  18.         lcdltdc.hbp = 40;           /* 水平后廊 */
  19.         lcdltdc.vbp = 8;            /* 垂直后廊 */
  20.         lcdltdc.hfp = 5;            /* 水平前廊 */
  21.         lcdltdc.vfp = 8;            /* 垂直前廊 */
  22.         ltdc_clk_set(300, 25, 33);  /* LTDC_CLK = 9MHz */
  23.     }
  24.     else if (id == 0x7084)          /* ATK-MD0700R-800480 */
  25.     {
  26.         lcdltdc.pwidth = 800;       /* LCD面板的宽度 */
  27.         lcdltdc.pheight = 480;      /* LCD面板的高度 */
  28.         lcdltdc.hsw = 1;            /* 水平同步宽度 */
  29.         lcdltdc.vsw = 1;            /* 垂直同步宽度 */
  30.         lcdltdc.hbp = 46;           /* 水平后廊 */
  31.         lcdltdc.vbp = 23;           /* 垂直后廊 */
  32.         lcdltdc.hfp = 210;          /* 水平前廊 */
  33.         lcdltdc.vfp = 22;           /* 垂直前廊 */
  34.         ltdc_clk_set(300, 25, 9);   /* LTDC_CLK = 33MHz */
  35.     }
  36.     else if (id == 0x7016)          /* ATK-MD0700R-1024600 */
  37.     {
  38.         lcdltdc.pwidth = 1024;      /* LCD面板的宽度 */
  39.         lcdltdc.pheight = 600;      /* LCD面板的高度 */
  40.         lcdltdc.hsw = 20;           /* 水平同步宽度 */
  41.         lcdltdc.vsw = 3;            /* 垂直同步宽度 */
  42.         lcdltdc.hbp = 140;          /* 水平后廊 */
  43.         lcdltdc.vbp = 20;           /* 垂直后廊 */
  44.         lcdltdc.hfp = 160;          /* 水平前廊 */
  45.         lcdltdc.vfp = 12;           /* 垂直前廊 */
  46.         ltdc_clk_set(300, 25, 6);   /* LTDC_CLK = 40MHz */
  47.     }
  48.     else if (id == 0x7018)          /* ATK-MD0700R-1280800 */
  49.     {
  50.         lcdltdc.pwidth = 1280;      /* LCD面板的宽度 */
  51.         lcdltdc.pheight = 800;      /* LCD面板的高度 */
  52.         /* 其他参数待定 */
  53.     }
  54.     else if (id == 0x4384)          /* ATK-MD0430R-800480 */
  55.     {
  56.         lcdltdc.pwidth = 800;       /* LCD面板的宽度 */
  57.         lcdltdc.pheight = 480;      /* LCD面板的高度 */
  58.         lcdltdc.hsw = 88;           /* 水平同步宽度 */
  59.         lcdltdc.vsw = 40;           /* 垂直同步宽度 */
  60.         lcdltdc.hbp = 48;           /* 水平后廊 */
  61.         lcdltdc.vbp = 32;           /* 垂直后廊 */
  62.         lcdltdc.hfp = 13;           /* 水平前廊 */
  63.         lcdltdc.vfp = 3;            /* 垂直前廊 */
  64.         ltdc_clk_set(300, 25, 9);   /* LTDC_CLK = 33MHz */
  65.     }
  66.     else if (id == 0x1018)          /* ATK-MD1018R-1280800 */
  67.     {
  68.         lcdltdc.pwidth = 1280;      /* LCD面板的宽度 */
  69.         lcdltdc.pheight = 800;      /* LCD面板的高度 */
  70.         lcdltdc.hsw = 140;          /* 水平同步宽度 */
  71.         lcdltdc.vsw = 10;           /* 垂直同步宽度 */
  72.         lcdltdc.hbp = 10;           /* 水平后廊 */
  73.         lcdltdc.vbp = 10;           /* 垂直后廊 */
  74.         lcdltdc.hfp = 10;           /* 水平前廊 */
  75.         lcdltdc.vfp = 3;            /* 垂直前廊 */
  76.         ltdc_clk_set(300, 25, 6);   /* LTDC_CLK = 45MHz */
  77.     }
  78.    
  79.     lcddev.width = lcdltdc.pwidth;
  80.     lcddev.height = lcdltdc.pheight;
  81.    
  82. #if ((LTDC_PIXFORMAT == LTDC_PIXFORMAT_ARGB8888) || (LTDC_PIXFORMAT ==
  83.       LTDC_PIXFORMAT_RGB888))
  84.     g_ltdc_framebuf[0] = (uint32_t *)g_ltdc_lcd_framebuf;
  85.     lcdltdc.pixsize = 4;
  86. #else
  87.     g_ltdc_framebuf[0] = (uint16_t *)g_ltdc_lcd_framebuf;
  88.     lcdltdc.pixsize = 2;
  89. #endif
  90.    
  91.     /* 使能时钟 */
  92.     LTDC_BL_GPIO_CLK_ENABLE();
  93.    
  94.     /* 配置LTDC LCD BL引脚 */
  95.     gpio_init_struct.Pin = LTDC_BL_GPIO_PIN;
  96.     gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;
  97.     gpio_init_struct.Pull = GPIO_PULLUP;
  98.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
  99.     HAL_GPIO_Init(LTDC_BL_GPIO_PORT, &gpio_init_struct);
  100.    
  101.     /* 初始化LTDC */
  102.     g_ltdc_handle.Instance = LTDC;
  103.     g_ltdc_handle.Init.HSPolarity = LTDC_HSPOLARITY_AL;
  104.     g_ltdc_handle.Init.VSPolarity = LTDC_VSPOLARITY_AL;
  105.     g_ltdc_handle.Init.DEPolarity = LTDC_DEPOLARITY_AL;
  106.     if (id == 0x1018)
  107.     {
  108.         g_ltdc_handle.Init.PCPolarity = LTDC_PCPOLARITY_IIPC;
  109.     }
  110.     else
  111.     {
  112.         g_ltdc_handle.Init.PCPolarity = LTDC_PCPOLARITY_IPC;
  113.     }
  114.     g_ltdc_handle.Init.HorizontalSync = lcdltdc.hsw - 1;
  115.     g_ltdc_handle.Init.VerticalSync = lcdltdc.vsw - 1;
  116.     g_ltdc_handle.Init.AccumulatedHBP = lcdltdc.hsw + lcdltdc.hbp - 1;
  117.     g_ltdc_handle.Init.AccumulatedVBP = lcdltdc.vsw + lcdltdc.vbp - 1;
  118. g_ltdc_handle.Init.AccumulatedActiveW = lcdltdc.hsw + lcdltdc.hbp +
  119. lcdltdc.pwidth - 1;
  120. g_ltdc_handle.Init.AccumulatedActiveH = lcdltdc.vsw + lcdltdc.vbp +
  121. lcdltdc.pheight - 1;
  122. g_ltdc_handle.Init.TotalWidth = lcdltdc.hsw + lcdltdc.hbp + lcdltdc.pwidth
  123. + lcdltdc.hfp - 1;
  124. g_ltdc_handle.Init.TotalHeigh = lcdltdc.vsw + lcdltdc.vbp + lcdltdc.pheight
  125. + lcdltdc.vfp - 1;
  126.     g_ltdc_handle.Init.Backcolor.Red = 0;
  127.     g_ltdc_handle.Init.Backcolor.Green = 0;
  128.     g_ltdc_handle.Init.Backcolor.Blue = 0;
  129.     HAL_LTDC_DeInit(&g_ltdc_handle);
  130.     HAL_LTDC_Init(&g_ltdc_handle);
  131.    
  132.     /* 配置LTDC层 */
  133. ltdc_layer_parameter_config(0,(uint32_t)g_ltdc_framebuf[0],LTDC_PIXFORMAT,
  134. 255, 0, 6, 7, 0);
  135.     ltdc_layer_window_config(0, 0, 0, lcdltdc.pwidth, lcdltdc.pheight);
  136.    
  137.     ltdc_select_layer(0);
  138.     LTDC_BL(1);
  139.     ltdc_clear(0xFFFFFFFF);
  140. }
复制代码
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屏的支持,由于篇幅所限,这里我们只挑几个重点的函数给大家介绍下。
首先读点函数,改为了:

  1. /**
  2. * @brief   读取个某点的颜色值
  3. * @param   x: 指定点的X坐标
  4. * @param   y: 指定点的Y坐标
  5. * @retval  指定点的颜色
  6. */
  7. uint32_t ltdc_read_point(uint16_t x, uint16_t y)
  8. {
  9. #if ((LTDC_PIXFORMAT == LTDC_PIXFORMAT_ARGB8888) || (LTDC_PIXFORMAT ==
  10. LTDC_PIXFORMAT_RGB888))
  11.     if (lcdltdc.dir == 0)
  12.     {
  13.         return *(uint32_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] +
  14. lcdltdc.pixsize * (lcdltdc.pwidth * (lcdltdc.pheight - x - 1) + y));
  15.     }
  16.     else
  17.     {
  18.         ……//省略部分代码
  19.     }
  20. #endif
  21. }
复制代码
当lcdltdc.pwidth!=0 的时候,说明接入的是 RGB 屏,所以调用 ltdc_read_point 函数,实现读点操作,其他情况,说明是 MCU 屏,执行 MCU 屏的读点操作(代码省略)。
然后是画点函数,改为了:

  1. /**
  2. * @brief   LTDC画点
  3. * @param   x: 点的X坐标
  4. * @param   y: 点的Y坐标
  5. * @param   color: 点的颜色
  6. * @retval  无
  7. */
  8. void ltdc_draw_point(uint16_t x, uint16_t y, uint32_t color)
  9. {
  10. #if ((LTDC_PIXFORMAT == LTDC_PIXFORMAT_ARGB8888) || (LTDC_PIXFORMAT ==
  11. LTDC_PIXFORMAT_RGB888))
  12.     if (lcdltdc.dir == 0)
  13.     {
  14.         *(uint32_t *)((uint32_t)g_ltdc_framebuf[lcdltdc.activelayer] +
  15.    lcdltdc.pixsize * (lcdltdc.pwidth * (lcdltdc.pheight - x - 1)+y))=color;
  16.     }
  17.     else
  18.     {
  19.         ……//省略部分代码
  20.     }
  21. #endif
  22. }
复制代码
当lcdltdc.pwidth!=0 的时候,说明接入的是 RGB 屏,所以调用ltdc_draw_point 函数,实现画点操作,其他情况,说明是 MCU 屏,执行 MCU屏的画点操作(代码省略)。同样的,lcd.c 里面的快速画点函数:lcd_fast_drawPoint,在使用RGB屏的时候,也是使用lcd_fast_drawpoint 来实现画点操作的。
最后,是 LCD 初始化函数,改为:

  1. /**
  2. * @brief       初始化LCD
  3. * [url=home.php?mod=space&uid=60778]@note[/url]        该初始化函数可以初始化各种型号的LCD(详见本.c文件最前面的描述)
  4. * @param       无
  5. * @retval      无
  6. */
  7. void lcd_init(void)
  8. {
  9.     GPIO_InitTypeDef gpio_init_struct;
  10.     FMC_NORSRAM_TimingTypeDef fmc_read_timing_struct={0};
  11.     FMC_NORSRAM_TimingTypeDef fmc_write_timing_struct={0};

  12.     lcddev.id = ltdc_panelid_read();  /* 检查是否有RGB屏接入 */
  13.     if (lcddev.id != 0)
  14.     {
  15.         ltdc_init();                  /* ID非零,说明有RGB屏接入 */
  16.     }
  17.     else
  18.     ……/* 省略部分代码 */
  19. }
复制代码
首先通过 ltdc_panelid_read 函数,读取 RGBLCD 模块的 ID 值,如果合法,则说明接入了 RGB 屏,调用 ltdc_init 函数,完成对 LTDC 的初始化。否则的话,执行 MCU 屏的初始化。
在 lcd.c 里面,其他还有一些函数进行了兼容 RGB 屏的修改,这里我们就不一一列举了,请大家参考本例程源码。在完成修改后,我们的例程就可以同时兼容 MCU 屏和 RGB 屏了,且RGB 屏的优先级较高。
2. main.c代码
main函数代码如下:

  1. int main(void)
  2. {
  3.     uint8_t x = 0;
  4.     uint8_t lcd_id[13];

  5.     sys_mpu_config();                   /* 配置MPU */
  6.     sys_cache_enable();                 /* 使能Cache */
  7.     HAL_Init();                         /* 初始化HAL库 */
  8.     sys_stm32_clock_init(300, 6, 2);    /* 配置时钟,600MHz */
  9.     delay_init(600);                    /* 初始化延时 */
  10.     usart_init(115200);                 /* 初始化串口 */
  11.     led_init();                         /* 初始化LED */
  12.     hyperram_init();                    /* 初始化HyperRAM */
  13.     lcd_init();                         /* 初始化LCD */
  14.   
  15. sprintf((char *)lcd_id, "LCD ID:%04X", lcddev.id);
  16. /* 将LCD ID打印到lcd_id数组 */

  17.     while (1)
  18.     {
  19.         switch (x)
  20.         {
  21.             case 0:lcd_clear(WHITE); break;
  22.             case 1: lcd_clear(BLACK); break;
  23.             ……/* 此处省略部分代码 */
  24.             case 11: lcd_clear(BROWN); break;
  25.         }
  26.         lcd_show_string(10, 40, 240, 32, 32, "STM32", RED);
  27.         lcd_show_string(10, 80, 240, 24, 24, "LTDC TEST", RED);
  28.         lcd_show_string(10, 110, 240, 16, 16, "ATOM@ALIENTEK", RED);
  29.         lcd_show_string(10, 130, 240, 16, 16, (char *)lcd_id, RED);
  30.                                                                         /* 显示LCD ID */
  31.         if (++x == 12) x = 0;
  32.         LED0_TOGGLE();                                 /* 红灯闪烁 */
  33.         delay_ms(1000);
  34.     }
  35. }
复制代码
该部分代码与第 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所示:

第二十七章 LTDC LCD41385.png
图27.4.1 RGBLCD显示效果图

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

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

如发现本坛存在违规或侵权内容, 请点击这里发送邮件举报 (或致电020-38271790)。请提供侵权说明和联系方式。我们将及时审核依法处理,感谢配合。

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-4-23 02:11

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表