欢迎大家来到IT世界,在知识的湖畔探索吧!
1)实验平台:alientek 阿波罗 STM32F767 开发板
2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子
欢迎大家来到IT世界,在知识的湖畔探索吧!
第二十章 LTDC LCD(RGB 屏)实验
在第18章,我们介绍了TFTLCD模块(MCU屏)的使用,但是高分辨率的屏(超过800*480),
一般都没有 MCU 屏接口,而是使用 RGB 接口的,这种接口的屏,就需要用到 STM32F767 的
LTDC 来驱动了。在本章中,我们将使用阿波罗 STM32F767 开发板核心板上的 LCD 接口(仅
支持 RGB 屏,本章介绍 RGB 屏的使用),来点亮 LCD,并实现 ASCII 字符和彩色的显示等功
能,并在串口打印 LCD ID,同时在 LCD 上面显示。本章分为如下几个部分:
20.1 RGBLCD<DC 简介
20.2 硬件设计
20.3 软件设计
20.4 下载验证
20.1 RGBLCD<DC 简介
本章我们将通过 STM32F767 的 LTDC 接口来驱动 RGBLCD 的显示,另外,STM32F767
的 LTDC 还有DMA2D 图形加速,我们也顺带进行介绍。本节分为三个部分,分别介绍RGBLCD、
LTDC 和 DMA2D。
20.1.1 RGBLCD 简介
在第 18 章,我们已经介绍过 TFTLCD 液晶了,实际上 RGBLCD 也是 TFTLCD,只是接口
不同而已。接下来我们简单介绍一下 RGBLCD 的驱动。
(1)RGBLCD 的信号线
RGBLCD 的信号线如表 20.1.1.1 所示:
表 20.1.1.1 RGBLCD 信号线
一般的 RGB 屏都有如表 20.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 面板为例),如图 20.1.1.1 所示:
图 20.1.1.1 DE/HV 模式行扫描时序图
从图中可以看出,DE 和 HV 模式,时序基本一样,DEN 模式需要提供 DE 信号(DEN),
而 HV 模式,则无需 DE 信号。图中的 HSD 即 HS 信号,用于行同步,注意:在 DE 模式下面,
是可以不用 HS 信号的,即不接 HS 信号,液晶照样可以正常工作。
图中的 thpw 为水平同步有效信号脉宽,用于表示一行数据的开始;thb 为水平后廊,表示
从水平有效信号开始,到有效数据输出之间的像素时钟个数;thfp 为水平前廊,表示一行数据
结束后,到下一个水平同步信号开始之前的像素时钟个数;这几个时间非常重要,在配置 LTDC
的时候,需要根据 LCD 的数据手册,进行正确的设置。
图 20.1.1.1 仅是一行数据的扫描,输出 800 个像素点数据,而液晶面板总共有 480 行,这
就还需要一个垂直扫描时序图,如图 20.1.1.2 所示:
图 20.1.1.2 垂直扫描时序图
图中的 VSD 就是垂直同步信号,HSD 就是水平同步信号,DE 为数据使能信号。由图可知,
一个垂直扫描,刚好就是 480 个有效的 DE 脉冲信号,每一个 DE 时钟周期,扫描一行,总共
扫描 480 行,完成一帧数据的显示。这就是 800*480 的 LCD 面板扫描时序,其他分辨率的 LCD
面板,时序类似。
图中的 tvpw 为垂直同步有效信号脉宽,用于表示一帧数据的开始;tvb 为垂直后廊,表示
垂直同步信号以后的无效行数,tvfp 为垂直前廊,表示一帧数据输出结束后,到下一个垂直同
步信号开始之前的无效行数;这几个时间同样在配置 LTDC 的时候,需要进行设置。
(3)ALIENTEK RGBLCD 模块
ALIENTEK 目前提供大家三款 RGBLCD 模块:ATK-4342(4.3 寸,480*272)、ATK-7084
(7 寸,800*480)和 ATK-7016(7 寸,1024*600),这里我们以 ATK-7084 为例,给大家介绍。
该模块的接口原理图如图 20.1.1.3 所示:
图 20.1.1.3 ATK-7084 模块对外接口原理图
图中 J1 就是对外接口,是一个 40PIN 的 FPC 座(0.5mm 间距),通过 FPC 线,可以连接
到阿波罗 STM32F767 开发板的核心板上面,从而实现和 STM32F767 的连接。该接口十分完善,
采用 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 面
板的分辨率和相关参数,以提高程序兼容性。这几个位的设置关系如表 20.1.1.2 所示:
表 20.1.1.2 ALIENTEK RGBLCD 模块 ID 对应关系
ATK-7084 模块,就设置 M2:M0=001 即可。这样,我们在程序里面,读取 LCD_R7/G7/B7,
得到 M0:M2 的值,从而判断 RGBLCD 模块的型号,并执行不同的配置,即可实现不同 LCD
模块的兼容。
RGBLCD 我们就给大家介绍到这里,接下来,我们介绍 LTDC。
20.1.2 LTDC 简介
STM32F767xx 系列芯片都带有 TFT LCD 控制器,即 LTDC,通过这个 LTDC,STM32F767
可以直接外接 RGBLCD 屏,实现液晶驱动。STM32F767 的 LTDC 具有如下特点:
24 位 RGB 并行像素输出;每像素 8 位数据(RGB888)
2 个带有专用 FIFO 的显示层(FIFO 深度 64×32 位)
支持查色表 (CLUT),每层高达 256 种颜色(256×24 位)
可针对不同显示面板编程时序
可编程背景色
可编程 HSync、VSync 和数据使能(DE)信号的极性
每层有多达 8 种颜色格式可供选择:ARGB8888、RGB888、RGB565、ARGB1555、ARGB4444、
L8(8 位 Luminance 或 CLUT)、AL44(4 位 alpha+4 位 luminance)和 AL88(8 位 alpha+8
位 luminance)
每通道的低位采用伪随机抖动输出(红色、绿色、蓝色的抖动宽度为 2 位)
使用 alpha 值(每像素或常数)在两层之间灵活混合
色键(透明颜色)
可编程窗口位置和大小
支持薄膜晶体管 (TFT) 彩色显示器
AHB 主接口支持 16 个字的突发
高达 4 个可编程中断事件
LTDC 控制器主要包含:信号线、图像处理单元、AHB 接口、配置和状态寄存器以及时钟
部分,其框图如图 20.1.2.1 所示:
图 20.1.2.1 LTDC 控制器框图
① 信号线
这里就包含了我们前面提到的 RGBLCD 驱动所需要的所有信号线,这些信号线通过STM32F767 核心板板载的 LCD 接口引出,其信号说明和 IO 连接关系,见表 20.1.2.1:
表 20.1.2.1 LTDC 信号线及 IO 连接关系说明
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 就是我们 STM32F767
核心板上面,LCD 接口所连接的 IO。
② 图像处理单元
此部分先从 AHB 接口获取显存中的图像数据,然后经过层 FIFO(有 2 个,对应 2 个层)
缓存,每个层 FIFO 具有 64*32 位存储深度,然后经过像素格式转换器(PFC),把从层的所选
输入像素格式转换为 ARGB8888 格式,再通过混合单元,把两层数据合并,混合得到单层要显
示的数据,最后经过抖动单元处理(可选)后,输出给 LCD 显示。
这里的 ARGB8888,即带 8 位透明通道,即最高 8 位为透明通道参数,表示透明度,值越
大,则约不透明,值越小,越透明。比如 A=255 时,表示完全不透明,而 A=0 时,表示完全
透明。RGB888 就表示 R、G、B 各 8 位,可表示的颜色深度为 1600W 色。
STM32F767 的 LTDC 总共有三个层:背景层、第一层和第二层,其中,背景层只可以是纯
色(即单色),而第一层和第二层都可以用来显示信息,混合单元会将三个层混合起来,进行显
示,显示关系如图 20.1.2.2 所示:
图 20.1.2.2 三个层混合关系
从图中可以看出,第二层位于最顶端,背景层位于最低端,混合单元首先将第一层与背景
层进行混合,随后,第二层与第一层和第二层的混合颜色结果再次混合,完成混合后,送给 LCD
显示。
③ AHB 接口
由于 LTDC 驱动 RGBLCD 的时候,需要有很多内存来做显存,比如一个 800*480 的屏幕,
按一般的 16 位 RGB565 模式,一个像素需要 2 个字节的内存,总共需要:800*480*2=768K 字
节内存,STM32 内部是没有这么多内存的,所以必须借助外部 SDRAM,而 SDRAM 是挂在AHB 总线上的,LTDC 的 AHB 接口,就是用来将显存数据,从 SDRAM 存储器传输到 FIFO 里
面。
④ 配置和状态寄存器
此部分包含了 LTDC 的各种配置寄存器以及状态寄存器,用于控制整个 LTDC 的工作参数,
主要有:各信号的有效电平、垂直/水平同步时间参数、像素格式、数据使能等等。LTDC 的同
步时序(HV 模式)控制框图,如图 20.1.2.3 所示:
图 20.1.2.3 LTDC 同步时序框图
图中有效显示区域,就是我们 RGBLCD 面板的显示范围(即分辨率),有效宽度*有效高
度,就是 LCD 的分辨率。另外,这里还有的参数包括:HSYNC 的宽度(HSW)、VSYNC 的宽
度(VSW)、HBP、HFP、VBP 和 VFP 等,这些参数的说明,见表 20.1.2.2:
表 20.1.2.2 LTDC 驱动时序参数
如果 RGBLCD 使用的是 DE 模式,LTDC 也只需要设置表 20.1.2.2 所示的参数,然后 LTDC
会根据这些设置,自动控制 DE 信号。这些参数通过相关寄存器来配置,接下来,我们介绍一
下 LTDC 的一些相关寄存器。
首先,我们来看 LTDC 全局控制寄存器:LTDC_GCR,该寄存器各位描述如图 20.1.2.4 所
示:
图 20.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,该寄存器各位描述如图
20.1.2.5 所示:
图 20.1.2.5 LTDC_SSCR 寄存器各位描述
该寄存器用于设置垂直同步高度(VSH)和水平同步宽度(HSW),其中:
VSH:表示垂直同步高度(以水平扫描行为单位),表示垂直同步脉宽减 1,即 VSW-1。
HSW:表示水平同步宽度(以像素时钟为单位),表示水平同步脉宽减 1,即 HSW-1。
接下来,我们看看 LTDC 后沿配置寄存器:LTDC_BPCR,该寄存器各位描述如图 20.1.2.6
所示:
图 20.1.2.6 LTDC_BPCR 寄存器各位描述
该寄存器我们需要配置 AVBP 和 AHBP:
AVBP:累加垂直后沿(以水平扫描行为单位),表示:VSW+VBP-1(见表 20.1.2.2)。
AHBP:累加水平后沿(以像素时钟为单位),表示 HSW+HBP-1(见表 20.1.2.2,下同)。
接下来,我们看看 LTDC 有效宽度配置寄存器:LTDC_AWCR,该寄存器各位描述如图
20.1.2.7 所示:
图 20.1.2.7 LTDC_AWCR 寄存器各位描述
该寄存器我们需要配置 AAH 和 AAW:
AAH:累加有效高度(以水平扫描行为单位),表示:VSW+VBP+有效高度-1。
AAW:累加有效宽度(以像素时钟为单位),表示:HSW+HBP+有效宽度-1。
这里所说的有效高度和有效宽度,是指 LCD 面板的宽度和高度(构成分辨率,下同)。
接下来,我们看看 LTDC 总宽度配置寄存器:LTDC_TWCR,该寄存器各位描述如图 20.1.2.8
所示:
图 20.1.2.8 LTDC_TWCR 寄存器各位描述
该寄存器我们需要配置 TOTALH 和 TOTALW:
TOTALH:总高度(以水平扫描行为单位),表示:VSW+VBP+有效高度+VFP-1。
TOTALW:总宽度(以像素时钟为单位),表示:HSW+HBP+有效宽度+HFP-1。
接下来,我们看看 LTDC 背景色配置寄存器:LTDC_BCCR,该寄存器各位描述如图 20.1.2.9
所示:
图 20.1.2.9 LTDC_BCCR 寄存器各位描述
该寄存器定义背景层的颜色(RGB888),通过低 24 位配置,我们一般设置为全 0 即可。
接下来,我们看看 LTDC 的层颜色帧缓冲区地址寄存器:LTDC_LxCFBAR(x=1/2),该寄
存器各位描述如图 20.1.2.10 所示:
图 20.1.2.10 LTDC_LxCFBAR 寄存器各位描述
该寄存器用来定义一层显存的起始地址。STM32F767 的 LTDC 支持 2 个层,所以总共有两
个寄存器,分别设置层 1 和层 2 的显存起始地址。
接下来,我们看看 LTDC 的层像素格式配置寄存器:LTDC_LxPFCR(x=1/2),该寄存器只
有最低 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),该寄存
器各位描述如图 20.1.2.11 所示:
图 20.1.2.11 LTDC_LxCACR 寄存器各位描述
该寄存器低 8 位(CONSTA)有效,这些位配置混合时使用的恒定 Alpha。恒定 Alpha 由
硬件实现 255 分频。关于这个恒定 Alpha 的使用,我们将在介绍 LTDC_LxBFCR 寄存器的时候
进行讲解。
接下来,我们看看 LTDC 的层默认颜色配置寄存器:LTDC_LxDCCR(x=1/2),该寄存器
各位描述如图 20.1.2.12 所示:
图 20.1.2.12 LTDC_LxDCCR 寄存器各位描述
该寄存器定义采用 ARGB8888 格式的层的默认颜色。默认颜色在定义的层窗口外使用或在
层禁止时使用。一般情况下,用不到,所以该寄存器一般设置为 0 即可。
接下来,我们看看 LTDC 的层混合系数配置寄存器:LTDC_LxBFCR(x=1/2),该寄存器
各位描述如图 20.1.2.13 所示:
图 20.1.2.13 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 这两个寄存器
设置,可以调整显示区域的大小,如图 20.1.2.14 所示:
图 20.1.2.14 LTDC 层窗口设置关系图
上图中,层中的第一个和最后一个可见像素通过配置 LTDC_LxWHPCR 寄存器中的
WHSTPOS[11:0]和 WHSPPOS[11:0]进行设置。层中的第一个和最后一个可见行通过配置
LTDC_LxWVPCR 寄存器中的 WHSTPOS[11:0]和 WHSPPOS[11:0]进行设置,配置完成后,即
可确定窗口的大小。
接下来,我们来介绍这两个寄存器,首先是 LTDC 的层窗口水平位置配置寄存器:
LTDC_LxWHPCR(x=1/2),该寄存器各位描述如图 20.1.2.15 所示:
图 20.1.2.15 LTDC_LxWHPCR 寄存器各位描述
该寄存器定义第 1 层或第 2 层窗口的水平位置(第一个和最后一个像素),其中:
WHSTPOS:窗口水平起始位置,定义层窗口的一行的第一个可见像素,见图 20.1.2.14。
WHSPPOS:窗口水平停止位置,定义层窗口的一行的最后一个可见像素,见图 20.1.2.14。
然后,我们介绍 LTDC 的层窗口垂直位置配置寄存器:LTDC_LxWVPCR(x=1/2),该寄
存器各位描述如图 20.1.2.16 所示:
图 20.1.2.16 LTDC_LxWVPCR 寄存器各位描述
该寄存器定义第 1 层或第 2 层窗口的垂直位置(第一行或最后一行),其中:
WVSTPOS:窗口垂直起始位置,定义层窗口的第一个可见行,见图 20.1.2.14。
WVSPPOS:窗口垂直停止位置,定义层窗口的最后一个可见行,见图 20.1.2.14。
接下来,我们看看 LTDC 的层颜色帧缓冲区长度寄存器:LTDC_LxCFBLR(x=1/2),该寄
存器各位描述如图 20.1.2.17 所示:
图 20.1.2.17 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),该寄
存器各位描述如图 20.1.2.18 所示:
图 20.1.2.18 LTDC_LxCFBLNR 寄存器各位描述
该寄存器定义颜色帧缓冲区中的行数。CFBLNBR 用于定义帧缓冲区行数,比如,LCD 面
板的分辨率为 800*480,那么帧缓冲区的行数为 480 行,则设置 CFBLNBR=480 即可。
至此,LTDC 相关的寄存器,基本就介绍完了,通过这些寄存器的配置,我们就可以完成
对 LTDC 的初始化,控制 LCD 显示了。关于 LTDC 的详细介绍,和寄存器描述,请看《STM32F7
中文参考手册.pdf》第 18 章。
⑤ 时钟域
LTDC 有三个时钟域:AHB 时钟域(HCLK)、APB2 时钟域(PCLK2)和像素时钟域
(LCD_CLK),AHB 时钟域用于驱动 AHB 接口,读取存储器的数据到 FIFO 里面,APB2 时钟
域用于配置寄存器,像素时钟域则用于生成 LCD 接口信号,LCD_CLK 的输出应按照 LCD 面
板要求进行配置。
接下来,我们重点介绍下 LCD_CLK 的配置过程。LCD_CLK 的时钟来源,如图 20.1.2.19
所示:
图 20.1.2.19 LCD_CLK 时钟图
由图可知,LCD_CLK 的来源,为外部晶振(假定外部晶振作为系统时钟源),经过分频器
分频(/M),然后经过 PLLSAI 倍频器倍频(xN)后,经 R 分频因子输出分频后的时钟,得到
PLLLCDCLK,然后在经过 DIV 分频和时钟使能后,得到 LCD_CLK。接下来,我们简单介绍
下配置 LCD_CLK 需要用到的一些寄存器。
首先是 RCC PLL SAI 配置寄存器:RCC_PLLSAICFGR,该寄存器的各位描述如图 20.1.2.20
所示:
图 20.1.2.20 RCC_PLLSAICFGR 寄存器各位描述
这个寄存器主要对 PLLSAI 倍频器的:N、Q 和 R 等参数进行配置,他们的设置关系(假
定使用外部 HSE 作为时钟源)为:
f(VCO clock) = f(hse) × (PLLSAIN / PLLM)
f(PLLSACLK) = f(VCO clock) / PLLSAIQ
f(PLLLCDCLK) = f(VCO clock) / PLLSAIR
f(hse)为我们外部晶振的频率,PLLM 就是 M 分频因子,PLLSAIN 为 PLLSAI 的倍频数,
取值范围为:49~432;PLLSAIQ 为 PLLSAI 的 Q 分频系数,取值范围为:2~15;PLLSAIR 为
PLLSAI 的 R 分频系数,取值范围为:2~7;阿波罗 STM32F767 核心板所用的 HSE 晶振频率为
25Mhz,一般我们设置 PLLM 为 25,那么输入 PLLSAI 的时钟频率就是 1Mhz,然后可得:
f(PLLLCDCLK) =1Mhz* PLLSAIN/PLLSAIR
在 f(PLLLCDCLK)之后,还有一个分频器(DIV),分频后得到最终的 LCD_CLK 频率,该
分频由 RCC 专用时钟配置寄存器:RCC_DCKCFGR1 配置,该寄存器各位描述如图 20.1.2.21
所示:
图 20.1.2.21 RCC_ DCKCFGR1 寄存器各位描述
在本章,该寄存器我们只关心 PLLSAIDIVR 的配置,这两个位用于配置 f(PLLLCDCLK)
之后的分频,设置范围为:0~2,表示:2^(PLLSAIDIVR+1)分频。因此,我们最终得到 LCD_CLK
的频率计算公式为(前提:HSE=25Mhz,PLLM=25):
f(LCD_CLK)= 1Mhz* PLLSAIN/PLLSAIR/2^(PLLSAIDIVR+1)
以群创 AT070TN92 面板为例,查其数据手册,可知 DCLK 的频率典型值为:33.3Mhz,我
们需要设置:PLLSAIN=396,PLLSAIR=3,PLLSAIDIVR=1,得到:
f(LCD_CLK)= 1Mhz* 396/3/2^(1+1)=33Mhz
最后,我们来看看实现 LTDC 驱动 RGBLCD,需要对 LTDC 进行哪些配置。LTDC 相关
HAL 库操作分布在函数 stm32f7xx_hal_ltdc.c 和 stm32f7xx_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 前面讲解过,它来自
PLLSAI,根据前面讲解的 LCD_CLK 计算公式:
f(LCD_CLK)= 1Mhz* PLLSAIN/PLLSAIR/2^(PLLSAIDIVR+1);
可知,我们需要配置 PLLSAIN,PLLSAIR 和 PLLSAIDIVR 等参数,具体使用实例如下:
RCC_PeriphCLKInitTypeDef PeriphClkIniture;
PeriphClkIniture.PeriphClockSelection=RCC_PERIPHCLK_LTDC; //LTDC 时钟
PeriphClkIniture.PLLSAI.PLLSAIN= 288;
PeriphClkIniture.PLLSAI.PLLSAIR=4;
PeriphClkIniture.PLLSAIDivR= RCC_PLLSAIDIVR_8;
HAL_RCCEx_PeriphCLKConfig(&PeriphClkIniture);
3)设置 RGBLCD 的相关参数,并使能 LTDC。
这一步,我们需要完成对 LCD 面板参数的配置,包括:LTDC 使能、时钟极性、HSW、
VSW、HBP、HFP、VBP 和VFP 等(见表19.1.2.2),通过LTDC_GCR、LTDC_SSCR、LTDC_BPCR、
LTDC_AWCR 和 LTDC_TWCR 等寄存器配置。HAL 库配置 LTDC 参数并使能 LTDC 的函数为:
HAL_StatusTypeDef HAL_LTDC_Init(LTDC_HandleTypeDef *hltdc);
该函数只有一个入口参数就是 hltdc,为 LTDC_HandleTypeDef 结构体指针类型。接下来我们
看看 LTDC_HandleTypeDef 结构体定义如下:
typedef struct
{
LTDC_TypeDef *Instance;
LTDC_InitTypeDef Init;
LTDC_LayerCfgTypeDef LayerCfg[MAX_LAYER];
HAL_LockTypeDef Lock;
__IO HAL_LTDC_StateTypeDef State;
__IO uint32_t
ErrorCode;
} LTDC_HandleTypeDef;
该结构体有 5 个成员变量。成员变量 Lock 和 State,他们是 HAL 库用来标识一些状态过程
的变量,这里就不做过多讲解。Instance 变量是 LTDC_TypeDef 结构体指针类型,和其他初始
化结构体成员变量 Instance 一样都是用来设置配置寄存器的基地址,这在 HAL 库中已经通过宏
定义定好好了,设置值为 LTDC 即可。成员变量 LayerCfg 是一个数组,它是用来保存 LTDC 层
配置参数,下一步骤我们会讲解。最后我们重点看看成员变量 Init,它是真正用来初始化 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;
这些参数含义我们都在结构体成员变量之后注释了,具体含义大家可以参考前面第四点配
置和状态寄存器讲解。
LTDC_HandleTypeDef LTDC_Handler; //LTDC 句柄
LTDC_Handler.Instance=LTDC;
LTDC_Handler.Init.HSPolarity=LTDC_HSPOLARITY_AL; //水平同步极性
LTDC_Handler.Init.VSPolarity=LTDC_VSPOLARITY_AL; //垂直同步极性
LTDC_Handler.Init.DEPolarity=LTDC_DEPOLARITY_AL; //数据使能极性
LTDC_Handler.Init.PCPolarity=LTDC_PCPOLARITY_IPC; //像素时钟极性
LTDC_Handler.Init.HorizontalSync=10-1; //水平同步宽度
LTDC_Handler.Init.VerticalSync=2-1; //垂直同步宽度
LTDC_Handler.Init.AccumulatedHBP=10+20-1;
//水平同步后沿宽度
LTDC_Handler.Init.AccumulatedVBP=2+2-1;
//垂直同步后沿高度
LTDC_Handler.Init.AccumulatedActiveW=10+20+480-1; //有效宽度
LTDC_Handler.Init.AccumulatedActiveH=2+2+272-1;
//有效高度
LTDC_Handler.Init.TotalWidth=10+20+480+10-1;
//总宽度
LTDC_Handler.Init.TotalHeigh=2+2+272+4-1;
//总高度
LTDC_Handler.Init.Backcolor.Red=0;
//屏幕背景层红色部分
LTDC_Handler.Init.Backcolor.Green=0;
//屏幕背景层绿色部分
LTDC_Handler.Init.Backcolor.Blue=0;
//屏幕背景色蓝色部分
HAL_LTDC_Init(<DC_Handler); //设置 RGBLCD 的相关参数,并使能 LTDC
和其他外设或接口初始化一样,HAL 同样提供了 LTDC 初始化 MSP 回调函数,
HAL_LTDC_MspInit,该函数一般用来使能时钟和初始化 IO 口等于 MCU 相关操作:
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);
基于篇幅考虑,该函数具体的入口参数定义这里我们就不做过多讲解,具体使用方法请参
考 19.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。
20.1.3 DMA2D 简介
为了提高STM32F767的图像处理能力,ST公司设计了一个专用于图像处理的专业 DMA:
Chrom-Art Accelerator™,即:DMA2D,通过 DMA2D 对图像进行填充和搬运,可以完全不用
CPU 干预,从而提高效率,减轻 CPU 负担。它可以执行下列操作:
用特定颜色填充目标图像的一部分或全部(可用于快速单色填充)
将源图像的一部分(或全部)复制到目标图像的一部分(或全部)中(可用于快速图
像填充)
通过像素格式转换将源图像的一部分(或全部)复制到目标图像的一部分(或全部)
中
将像素格式不同的两个源图像部分和/ 或全部混合,再将结果复制到颜色格式不同的
部分或整个目标图像中。
DMA2D 有四种工作模式,通过 DMA2D_CR 寄存器的 MODE[1:0]位选择工作模式:
1, 寄存器到存储器
2, 存储器到存储器
3, 存储器到存储器并执行 PFC
4, 存储器到存储器并执行 PFC 和混合
本章,我们仅介绍前两种工作模式,后两种工作模式,请大家参考《STM32F7 中文参考手
册.pdf》第 9 章。
寄存器到存储器
寄存器到存储器模式用于以预定义颜色填充用户自定义区域,也就是可以实现快速的单色
填充显示,比如清屏操作。
在该模式下:颜色格式在 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 在层帧缓存里面的开窗关系都一样,如图 20.1.3.1 所示:
图 20.1.3.1 层帧缓冲开窗示意图
窗口显示区域的显存首地址由 DMA2D_OMAR 寄存器指定,窗口宽度和高度由
DMA2D_NRL 寄存器的 PL 和 NL 指定,行偏移(确定下一行的起始地址)由 DMA2D_OOR
寄存器指定,经过这三个寄存器的配置,就可以确定窗口的显示位置和大小。
在寄存器到存储器模式下,在开窗完成后,DMA2D 可以将 DMA2D_OCOLR 指定的颜色,
自动填充到开窗区域,完成单色填充。
在存储器到存储器模式下,需要完成两个开窗:前景层和显示层,完成配置后,图像数据
从前景层拷贝到显示层(仅限窗口范围内),从而显示到 LCD 上面。显示层的开窗,如图 20.1.3.1
所示,而前景层的开窗,则和图 20.1.3.1 所示相似,只是 DMA2D_OMAR 寄存器变成了
DMA2D_FGMAR,DMA2D_OOR寄存器变成了DMA2D_FGOR,DMA2D_NRL则两个层共用,
然后就可以完成对前景层的开窗,确定好两个窗口后,DMA2D 就将前景层窗口内的数据,拷
贝到显示层窗口,完成快速图像填充。
接下来,我们介绍一下 DMA2D 的一些相关寄存器。
首先,我们来看 DMA2D 控制寄存器:DMA2D_CR,该寄存器各位描述如图 20.1.3.2 所示:
图 20.1.3.2 DMA2D_CR 寄存器各位描述
该寄存器,我们主要关心 MODE 和 START 这两个设置,其中:
MODE:表示 DMA2D 的工作模式,00:存储器到存储器模式;01:存储器到存储器模式
并执行 PFC;10:存储器到存储器并执行混合;11,寄存器到存储器模式;本章我们需要用到
的设置为:00 或者 11。
START:该位控制 DMA2D 的启动,在配置完成后,设置该位为 1,启动 DMA2D 传输。
接下来,我们介绍 DMA2D 输出 PFC 控制寄存器:DMA2D_OPFCCR,该寄存器各位描述
如图 20.1.3.3 所示:
图 20.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,该寄存器各位描述如图 20.1.3.4
所示:
图 20.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,该寄存器各位描述如图 20.1.3.5
所示:
图 20.1.3.5 DMA2D_OOR 寄存器各位描述
该寄存器仅最低 14 位有效(LO[13:0]),用于设置输出行偏移,作用于显示层,以像素为
单位表示。此值用于生成地址。行偏移将添加到各行末尾,用于确定下一行的起始地址,参见
图 20.1.3.1。
同样的,还有前景层偏移寄存器:DMA2D_FGOR,该寄存器同 DMA2D_OOR 一样,也是
低 14 位有效,用于控制前景层的行偏移,也是用于生成地址,添加到各行末尾,从而确定下一
行的起始地址。
接下来,我们介绍 DMA2D 输出存储器地址寄存器 :DMA2D_OMAR,该寄存器各位描
述如图 20.1.3.6 所示:
图 20.1.3.6 DMA2D_OMAR 寄存器各位描述
该寄存器设置由 MA[31:0]设置输出存储器地址,也就是输出 FIFO 所存储的数据地址,该
地址需要根据开窗的起始坐标来进行设置。以 800*480 的 LCD 屏为例,行长度为 800 像素,假
定帧缓存数组为:ltdc_framebuf,我们设置窗口的起始地址为:sx(<800),sy(<480),颜色
格式为 RGB565,每个像素 2 个字节,那么 MA 的设置值应该为:
MA[31:0]= framebuf+2*(800*sy+sx)
同样的,还有前景层偏移寄存器:DMA2D_ FGMAR,该寄存器同 DMA2D_OMAR 一样,
不过是用于控制前景层的存储器地址,计算方法同 DMA2D_OMAR。
接下来,我们介绍 DMA2D 行数寄存器 :DMA2D_NLR,该寄存器各位描述如图 20.1.3.7
所示:
图 20.1.3.7 DMA2D_NLR 寄存器各位描述
该寄存器用于控制每行的像素和行数,该寄存器的设置对前景层和显示层均有效,通过该
寄存器的配置,就可以设置开窗的大小。其中:
NL[15: 0]:设置待传输区域的行数,用于确定窗口的高度。
PL[13: 0]:设置待传输区域的每行像素数,用于确定窗口的宽度。
接下来,我们介绍 DMA2D 输出颜色寄存器 :DMA2D_OCOLR,该寄存器各位描述如图
20.1.3.8 所示:
图 20.1.3.8 DMA2D_OCOLR 寄存器各位描述
该寄存器用于配置在寄存器到存储器模式下,填充时所用的颜色值,该寄存器是一个 32
位寄存器,可以支持 ARGB8888 格式,也可以支持 RGB565 格式。我们一般使用 RGB565 格式,
比如要填充红色,那么直接设置 DMA2D_OCOLR=0XF800 就可以了。
接下来,我们介绍 DMA2D 中断状态寄存器 :DMA2D_ISR,该寄存器各位描述如图 20.1.3.9所示:
图 20.1.3.9 DMA2D_ISR 寄存器各位描述
该寄存器表示了 DMA2D 的各种状态标识,这里我们只关心 TCIF 位,表示 DMA2D 的传
输完成中断标志。当 DMA2D 传输操作完成(仅限数据传输)时此位置 1,表示可以开始下一
次 DMA2D 传输了。
另外,还有一个 DMA2D 中断标志清零寄存器: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 的简介,我们就介绍完了,详
细的介绍请大家参考《STM32F7xx 中文参考手册-扩展章节.pdf》第十一章。
20.2 硬件设计
本实验用到的硬件资源有:
1) 指示灯 DS0
2) SDRAM
3) LTDC
4) RGBLCD 接口
这里的 1~3,我们在前面的介绍,都已经讲解完毕。所以,我们仅介绍 RGBLCD 接口,
RGBLCD 接口在 STM32F767 核心板上,原理图如图 20.2.1 所示:
图 20.2.1 RGBLCD 接口原理图
图中 RGB LCD 接口的接线关系见表 20.1.2.1。 这些线的连接,阿波罗 STM32F767 核心板
的内部已经连接好了,我们只需要将 RGBLCD 模块通过 40PIN 的 FPC 线连接这个 RGBLCD
接口即可。实物连接(7 寸 RGBLCD 模块)如图 20.2.2 所示:
图 20.2.2 RGBLCD 与开发板连接实物图
20.3 软件设计
打开本章实验工程可以看到,在 USER 分组下面添加了源文件 ltdc.c 并且包含了对应的头
文件 ltdc.h,用来存放我们编写的 LTDC 相关驱动函数。
ltdc.c 代码比较多,这里就不贴一一出来了,只针对几个重要的函数进行讲解。完整版的代
码见光盘4,程序源码标准例程-库函数实验 15 LTDC LCD(RGB 屏)实验 的 ltdc.c 文
件。
本实验,我们用到 LTDC 驱动 RGBLCD,通过前面的介绍,我们知道不同的 RGB 屏,驱
动参数有一些差异,为了方便兼容不同的 RGBLCD,我们定义如下 LTDC 参数结构体(在 ltdc.h
里面定义):
//LCD LTDC 重要参数集
typedef struct
{
u32 pwidth;
//LCD 面板的宽度,固定参数,不随显示方向改变
//如果为 0,说明没有任何 RGB 屏接入
u32 pheight;
//LCD 面板的高度,固定参数,不随显示方向改变
u16 hsw;
//水平同步宽度
u16 vsw;
//垂直同步宽度
u16 hbp;
//水平后廊
u16 vbp;
//垂直后廊
u16 hfp;
//水平前廊
u16 vfp;
//垂直前廊
u8 activelayer;
//当前层编号:0/1
u8 dir;
//0,竖屏;1,横屏;
u16 width;
//LCD 宽度
u16 height;
//LCD 高度
u32 pixsize;
//每个像素所占字节数
}_ltdc_dev;
extern _ltdc_dev lcdltdc;
该结构体用于保存一些 RGBLCD 重要参数信息,比如 LCD 面板的长宽、水平后廊和垂直
后廊等参数。这个结构体虽然占用了几十个字节的内存,但是却可以让我们的驱动函数支持不
同尺寸的 LCD,同时可以实现 LCD 横竖屏切换等重要功能,所以还是利大于弊的。
接下来,我们来看两个很重要的数组:
//根据不同的颜色格式,定义帧缓存数组
#if LCD_PIXFORMAT==LCD_PIXFORMAT_ARGB8888||
LCD_PIXFORMAT==LCD_PIXFORMAT_RGB888
u32 ltdc_lcd_framebuf[1280][800] __attribute__((at(LCD_FRAME_BUF_ADDR)));
//定义最大屏分辨率时,LCD 所需的帧缓存数组大小
#else
u16 ltdc_lcd_framebuf[1280][800] __attribute__((at(LCD_FRAME_BUF_ADDR)));
//定义最大屏分辨率时,LCD 所需的帧缓存数组大小
#endif
u32 *ltdc_framebuf[2];
//LTDC LCD 帧缓存数组指针,必须指向对应大小的内存区域
其中,ltdc_lcd_framebuf 的大小是 LTDC 一帧图像的显存大小,STM32F7 的 LTDC 最大可
以支持 1280*800 的 RGB 屏,该数组根据我们选择的颜色格式(ARGB8888/RGB565),自动确
定 数 组 类 型 。 另 外 , 我 们 采 用 __attribute__ 关 键 字 , 将 数 组 的 地 址 定 向 到
LCD_FRAME_BUF_ADDR,它在 ltdc.h 里面定义,其值为:0XC0000000,也就是 SDRAM 的
首地址。这样,我们就把 ltdc_lcd_framebuf 数组定义到了 SDRAM 的首地址,大小为 800*1280*2
字节(RGB565 格式时)。
而 ltdc_framebuf 则是 LTDC 的帧缓存数组指针,LTDC 支持 2 个层,所以数组大小为 2 。
该指针为 32 位类型,必须指向对应的数组,才可以正常使用。在实际使用的时候,我们编写代
码:
ltdc_framebuf[0]=(u32*)<dc_lcd_framebuf;
就将 LTDC 第一层的帧缓存,指向了 ltdc_lcd_framebuf 数组。往 ltdc_lcd_framebuf 里面写
入不同的数据,就可以修改 RGBLCD 上面显示的内容。
首先,我们来看画点函数:LTDC_Draw_Point,该函数代码如下:
//画点函数
//x,y:写入坐标
//color:颜色值
void LTDC_Draw_Point(u16 x,u16 y,u32 color)
{
#if LCD_PIXFORMAT==LCD_PIXFORMAT_ARGB8888||
LCD_PIXFORMAT==LCD_PIXFORMAT_RGB888
if(lcdltdc.dir)
//横屏
{
*(u32*)((u32)ltdc_framebuf[lcdltdc.activelayer]+lcdltdc.pixsize*
(lcdltdc.pwidth*y+x))=color;
}else
//竖屏
{
*(u32*)((u32)ltdc_framebuf[lcdltdc.activelayer]+lcdltdc.pixsize*
(lcdltdc.pwidth*(lcdltdc.pheight-x-1)+y))=color;
}
#else
if(lcdltdc.dir) //横屏
{
*(u16*)((u32)ltdc_framebuf[lcdltdc.activelayer]+lcdltdc.pixsize*
(lcdltdc.pwidth*y+x))=color;
}else
//竖屏
{
*(u16*)((u32)ltdc_framebuf[lcdltdc.activelayer]+lcdltdc.pixsize*
(lcdltdc.pwidth*(lcdltdc.pheight-x-1)+y))=color;
}
#endif
}
该函数实现往 RGBLCD 上面画点的功能,根据 LCD_PIXFORMAT 定义的颜色格式以及横
竖屏状态,执行不同的操作。RGBLCD 的画点,实际上就是往指定坐标的显存里面写数据,以
7 寸 800*480 的屏幕,RGB565 格式,竖屏模式为例,画某个点对应到屏幕上面的关系如图 19.3.1
所示:
图 19.3.1 画点与 LCD 显存对应关系
注意图中的 LTDC 扫描方向(LTDC 在显存 ltdc_framebuf 里面读取 GRAM 数据的顺序也是
这个方向),是从上到下,从右到左,而竖屏的时候,原点在左上角,所以有一个变换过程,经
过变换后的画点函数为:
*(u16*)((u32)ltdc_framebuf[lcdltdc.activelayer]+lcdltdc.pixsize*(lcdltdc.pwidth*(lcdltdc.pheight-x-1
)+y))=color;
其中 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 的读点函数代码如下:
//读点函数
//x,y:读取点的坐标
//返回值:颜色值
u32 LTDC_Read_Point(u16 x,u16 y)
{
#if LCD_PIXFORMAT==LCD_PIXFORMAT_ARGB8888||
LCD_PIXFORMAT==LCD_PIXFORMAT_RGB888
if(lcdltdc.dir) //横屏
{
return *(u32*)((u32)ltdc_framebuf[lcdltdc.activelayer]+lcdltdc.pixsize*
(lcdltdc.pwidth*y+x));
}else
//竖屏
{
return *(u32*)((u32)ltdc_framebuf[lcdltdc.activelayer]+lcdltdc.pixsize*
(lcdltdc.pwidth*(lcdltdc.pheight-x-1)+y));
}
#else
if(lcdltdc.dir) //横屏
{
return *(u16*)((u32)ltdc_framebuf[lcdltdc.activelayer]+lcdltdc.pixsize*
(lcdltdc.pwidth*y+x));
}else
//竖屏
{
return *(u16*)((u32)ltdc_framebuf[lcdltdc.activelayer]+lcdltdc.pixsize*
(lcdltdc.pwidth*(lcdltdc.pheight-x-1)+y));
}
#endif
}
画点函数和读点函数十分类似,只是过程反过来了而已,坐标的计算,也是在 ltdc_framebuf
数组内,根据坐标计算偏移量,完全和读点函数一模一样。
第三个介绍的函数是 LTDC 单色填充函数:LTDC_Fill,该函数使用了 DMA2D 操作,使得
填充速度大大加快,该函数代码如下:
//LTDC 填充矩形,DMA2D 填充
//(sx,sy),(ex,ey):填充矩形对角坐标,区域大小为:(ex-sx+1)*(ey-sy+1)
//注意:sx,ex,不能大于 lcddev.width-1;sy,ey,不能大于 lcddev.height-1!!!
//color:要填充的颜色
void LTDC_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u32 color)
{
u32 psx,psy,pex,pey;
//以 LCD 面板为基准的坐标系,不随横竖屏变化而变化
u32 timeout=0;
u16 offline;
u32 addr;
//坐标系转换
if(lcdltdc.dir) //横屏
{
psx=sx;psy=sy;
pex=ex;pey=ey;
}else
//竖屏
{
psx=sy;psy=lcdltdc.pheight-ex-1;
pex=ey;pey=lcdltdc.pheight-sx-1;
}
offline=lcdltdc.pwidth-(pex-psx+1);
addr=((u32)ltdc_framebuf[lcdltdc.activelayer]+lcdltdc.pixsize*(lcdltdc.pwidth*psy+psx));
__HAL_RCC_DMA2D_CLK_ENABLE(); //使能 DM2D 时钟
DMA2D->CR&=~(DMA2D_CR_START); //先停止 DMA2D
DMA2D->CR=DMA2D_R2M;
//寄存器到存储器模式
DMA2D->OPFCCR=LCD_PIXFORMAT; //设置颜色格式
DMA2D->OOR=offline;
//设置行偏移
DMA2D->OMAR=addr;
//输出存储器地址
DMA2D->NLR=(pey-psy+1)|((pex-psx+1)<<16);
//设定行数寄存器
DMA2D->OCOLR=color;
//设定输出颜色寄存器
DMA2D->CR|=DMA2D_CR_START; //启动 DMA2D
while((DMA2D->ISR&(DMA2D_FLAG_TC))==0) //等待传输完成
{
timeout++;
if(timeout>0X1FFFFF)break;//超时退出
}
DMA2D->IFCR|=DMA2D_FLAG_TC;//清除传输完成标志
}
该函数使用 DMA2D 完成矩形色块的填充,其操作步骤,就是按 19.1.3 节最后的介绍来进
行的,我们这了就不多说了,详见 19.1.3 接。另外,还有一个 LTDC 彩色填充函数,也是采用
的 DMA2D 填充,函数名为 LTDC_Color_Fill,该函数代码同 LTDC_Fill 非常接近,我们这里就
不介绍了,请大家参考本例程源码。
第四个介绍的函数是清屏函数:LTDC_Clear,该函数代码如下:
//LCD 清屏
//color:颜色值
void LTDC_Clear(u32 color)
{
LTDC_Fill(0,0,lcdltdc.width-1,lcdltdc.height-1,color);
}
该函数代码非常简单,清屏操作调用了我们前面介绍的 LTDC_Fill 函数,采用 DMA2D 完
成对 LCD 的清屏,提高了清屏速度。
第五个介绍的函数是 LCD_CLK 频率设置函数:LTDC_Clk_Set,该函数代码如下:
//LTDC 时钟(Fdclk)设置函数
//Fvco=Fin*pllsain;
//Fdclk=Fvco/pllsair/2*2^pllsaidivr=Fin*pllsain/pllsair/2*2^pllsaidivr;
//Fvco:VCO 频率
//Fin:输入时钟频率一般为 1Mhz(来自系统时钟 PLLM 分频后的时钟,见时钟树图)
//pllsain:SAI 时钟倍频系数 N,取值范围:50~432.
//pllsair:SAI 时钟的分频系数 R,取值范围:2~7
//pllsaidivr:LCD 时钟分频系数,取值范围:0~3,对应分频 2^(pllsaidivr+1)
//假设:外部晶振为 25M,pllm=25 的时候,Fin=1Mhz.
//例如:要得到 20M 的 LTDC 时钟,则可以设置:pllsain=400,pllsair=5,pllsaidivr=1
//Fdclk=1*396/3/2*2^1=396/12=33Mhz
//返回值:0,成功;1,失败。
u8 LTDC_Clk_Set(u32 pllsain,u32 pllsair,u32 pllsaidivr)
{
RCC_PeriphCLKInitTypeDef PeriphClkIniture;
//LTDC 输出像素时钟,需要根据自己所使用的 LCD 数据手册来配置!
PeriphClkIniture.PeriphClockSelection=RCC_PERIPHCLK_LTDC; //LTDC 时钟
PeriphClkIniture.PLLSAI.PLLSAIN=pllsain;
PeriphClkIniture.PLLSAI.PLLSAIR=pllsair;
PeriphClkIniture.PLLSAIDivR=pllsaidivr;
if(HAL_RCCEx_PeriphCLKConfig(&PeriphClkIniture)==HAL_OK) //配置像素时钟
{
return 0; //成功
}
else return 1; //失败
}
该函数完成对 PLLSAI 的配置,最终控制输出 LCD_CLK 的频率,LCD_CLK 的频率设置
方法,我们在 19.1.2 节进行了介绍,请大家参考前面的介绍进行学习。
第六个介绍的函数是 LTDC 层参数设置函数:
LTDC_Layer_Parameter_Config,该函数代码
如下:
//LTDC,基本参数设置.
//注意:此函数,必须在 LTDC_Layer_Window_Config 之前设置.
//layerx:层值,0/1.
//bufaddr:层颜色帧缓存起始地址
//pixformat:颜色格式.0,ARGB8888;1,RGB888;2,RGB565;3,ARGB1555;
// 4,ARGB4444;5,L8;6;AL44;7;AL88
//alpha:层颜色 Alpha 值,0,全透明;255,不透明
//alpha0:默认颜色 Alpha 值,0,全透明;255,不透明
//bfac1:混合系数 1,4(100),恒定的 Alpha;6(101),像素 Alpha*恒定 Alpha
//bfac2:混合系数 2,5(101),恒定的 Alpha;7(111),像素 Alpha*恒定 Alpha
//bkcolor:层默认颜色,32 位,低 24 位有效,RGB888 格式
//返回值:无
void LTDC_Layer_Parameter_Config(u8 layerx,u32 bufaddr,
u8 pixformat,u8 alpha,u8 alpha0,u8 bfac1,u8 bfac2,u32 bkcolor)
{
LTDC_LayerCfgTypeDef pLayerCfg;
pLayerCfg.WindowX0=0; //窗口起始 X 坐标
pLayerCfg.WindowY0=0; //窗口起始 Y 坐标
pLayerCfg.WindowX1=lcdltdc.pwidth; //窗口终止 X 坐标
pLayerCfg.WindowY1=lcdltdc.pheight; //窗口终止 Y 坐标
pLayerCfg.PixelFormat=pixformat;
//像素格式
pLayerCfg.Alpha=alpha;
//Alpha 值设置,0~255,255 为完全不透明
pLayerCfg.Alpha0=alpha0;
//默认 Alpha 值
pLayerCfg.BlendingFactor1=(u32)bfac1<<8; //设置层混合系数
pLayerCfg.BlendingFactor2=(u32)bfac2<<8;
//设置层混合系数
pLayerCfg.FBStartAdress=bufaddr;
//设置层颜色帧缓存起始地址
pLayerCfg.ImageWidth=lcdltdc.pwidth; //设置颜色帧缓冲区的宽度
pLayerCfg.ImageHeight=lcdltdc.pheight; //设置颜色帧缓冲区的高度
pLayerCfg.Backcolor.Red=(u8)(bkcolor&0X00FF0000)>>16; //背景颜色红色部分
pLayerCfg.Backcolor.Green=(u8)(bkcolor&0X0000FF00)>>8; //背景颜色绿色部分
pLayerCfg.Backcolor.Blue=(u8)bkcolor&0X000000FF; //背景颜色蓝色部分
HAL_LTDC_ConfigLayer(<DC_Handler,&pLayerCfg,layerx); //设置所选中的层
}
该函数中主要调用 HAL 库函数 HAL_LTDC_ConfigLayer 设置 LTDC 层的基本参数,包括:
层帧缓冲区首地址、颜色格式、Alpha 值、混合系数和层默认颜色等,这些参数都需要根据大
家的实际需要来进行设置。
第七个介绍的函数是 LTDC 层窗口设置函数:LTDC_Layer_Window_Config,该函数代码
如下:
//LTDC,层窗口设置,窗口以 LCD 面板坐标系为基准
//layerx:层值,0/1.
//sx,sy:起始坐标
//width,height:宽度和高度
//LTDC,层颜窗口设置,窗口以 LCD 面板坐标系为基准
//注意:此函数必须在
LTDC_Layer_Parameter_Config 之后再设置.
//layerx:层值,0/1.
//sx,sy:起始坐标
//width,height:宽度和高度
void LTDC_Layer_Window_Config(u8 layerx,u16 sx,u16 sy,u16 width,u16 height)
{
HAL_LTDC_SetWindowPosition(<DC_Handler,sx,sy,layerx); //设置窗口的位置
HAL_LTDC_SetWindowSize(<DC_Handler,width,height,layerx);//设置窗口大小
}
该函数依次调用 HAL 库 LTDC 窗口位置设置函数 HAL_LTDC_SetWindowPositio 和窗口大
小设置函数 HAL_LTDC_SetWindowSizel 来控制 LTDC 在某一层(1/2)上面的开窗操作,这个
我们在 19.1.2节也介绍过了,请参考前面的内容进行学习。这里我们一般设置层窗口为整个 LCD
的分辨率,也就是不进行开窗操作。注意:此函数必须在
LTDC_Layer_Parameter_Config 之后
再设置。另外,当设置的窗口值不等于面板的尺寸时,对层 GRAM 的操作(读/写点函数),也
要根据层窗口的宽高来进行修改,否则显示不正常(本例程就未做修改)。
第八个介绍的函数是 LTDC LCD ID 获取函数:LTDC_PanelID_Read,该函数代码如下:
//读取面板参数
//PG6=R7(M0);PI2=G7(M1);PI7=B7(M2);
//M2:M1:M0
//0 :0 :0 //4.3 寸 480*272 RGB 屏,ID=0X4342
//0 :0 :1 //7 寸 800*480 RGB 屏,ID=0X7084
//0 :1 :0 //7 寸 1024*600 RGB 屏,ID=0X7016
//0 :1 :1 //7 寸 1280*800 RGB 屏,ID=0X7018
//1 :0 :0 //8 寸 1024*600 RGB 屏,ID=0X8016
//返回值:LCD ID:0,非法;其他值,ID;
u16 LTDC_PanelID_Read(void)
{
u8 idx=0;
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOG_CLK_ENABLE();
//使能 GPIOG 时钟
__HAL_RCC_GPIOI_CLK_ENABLE();
//使能 GPIOI 时钟
GPIO_Initure.Pin=GPIO_PIN_6;
//PG6
GPIO_Initure.Mode=GPIO_MODE_INPUT; //输入
GPIO_Initure.Pull=GPIO_PULLUP;
//上拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH;
//高速
HAL_GPIO_Init(GPIOG,&GPIO_Initure);
//初始化
GPIO_Initure.Pin=GPIO_PIN_2|GPIO_PIN_7; //PI2,7
HAL_GPIO_Init(GPIOI,&GPIO_Initure); //初始化
idx=(u8)HAL_GPIO_ReadPin(GPIOG,GPIO_PIN_6); //读取 M0
idx|=(u8)HAL_GPIO_ReadPin(GPIOI,GPIO_PIN_2)<<1;//读取 M1
idx|=(u8)HAL_GPIO_ReadPin(GPIOI,GPIO_PIN_7)<<2;//读取 M2
if(idx==0)return 0X4342;
//4.3 寸屏,480*272 分辨率
else if(idx==1)return 0X7084;
//7 寸屏,800*480 分辨率
else if(idx==2)return 0X7016;
//7 寸屏,1024*600 分辨率
else if(idx==3)return 0X7018;
//7 寸屏,1280*800 分辨率
else if(idx==4)return 0X8017;
//8 寸屏,1024*768 分辨率
else return 0;
}
因为 RGBLCD 屏并没有读的功能,所以,一般情况,外接 RGB 屏的时候,MCU 是无法
获取屏幕的任何信息的。但是 ALIENTEK 在 RGBLCD 模块上面,利用数据线(R7/G7/B7)做
了一个巧妙的设计,可以让 MCU 读取到 RGBLCD 模块的 ID,从而执行不同的初始化,实现
对不同分辨率的 RGBLCD 模块的兼容。详细原理见:本章 19.1.1 节,第(3)部分:
ALIENTEK
RGBLCD 模块的说明。
LTDC_PanelID_Read 函数,就是用这样的方法来读取 M[2:0]的值,并将结果(转换成屏型
号了)返回给上一层。
最后要介绍的函数是 LTDC 初始化函数:LTDC_Init,该函数的简化代码如下:
//LTDC 初始化函数
void LTDC_Init(void)
{
u32 tempreg=0;
u16 lcdid=0;
lcdid=LTDC_PanelID_Read();
//读取 LCD 面板 ID
if(lcdid==0X7084)
{
lcdltdc.pwidth=800;
//面板宽度,单位:像素
lcdltdc.pheight=480;
//面板高度,单位:像素
lcdltdc.hsw=1;
//水平同步宽度
lcdltdc.vsw=1;
//垂直同步宽度
lcdltdc.hbp=46;
//水平后廊
lcdltdc.vbp=23;
//垂直后廊
lcdltdc.hfp=210;
//水平前廊
lcdltdc.vfp=22;
//垂直前廊
LTDC_Clk_Set(396,3,1); //设置像素时钟 33M(如果开双显需要
//降低 DCLK 到:18.75Mhz 300/4/4,才会比较好)
}else if(lcdid==0Xxxxx)
//其他面板
{
……//省略部分代码
}
//LTDC 配置
LTDC_Handler.Instance=LTDC;
LTDC_Handler.Init.HSPolarity=LTDC_HSPOLARITY_AL;
//水平同步极性
LTDC_Handler.Init.VSPolarity=LTDC_VSPOLARITY_AL;
//垂直同步极性
LTDC_Handler.Init.DEPolarity=LTDC_DEPOLARITY_AL;
//数据使能极性
LTDC_Handler.Init.PCPolarity=LTDC_PCPOLARITY_IPC;
//像素时钟极性
LTDC_Handler.Init.HorizontalSync=lcdltdc.hsw-1; //水平同步宽度
LTDC_Handler.Init.VerticalSync=lcdltdc.vsw-1; //垂直同步宽度
LTDC_Handler.Init.AccumulatedHBP=lcdltdc.hsw+lcdltdc.hbp-1; //水平同步后沿宽度
LTDC_Handler.Init.AccumulatedVBP=lcdltdc.vsw+lcdltdc.vbp-1; //垂直同步后沿高度
LTDC_Handler.Init.AccumulatedActiveW=lcdltdc.hsw+lcdltdc.hbp+lcdltdc.pwidth-1;
LTDC_Handler.Init.AccumulatedActiveH=lcdltdc.vsw+lcdltdc.vbp+lcdltdc.pheight-1;
LTDC_Handler.Init.TotalWidth=lcdltdc.hsw+lcdltdc.hbp+lcdltdc.pwidth+lcdltdc.hfp-1;
LTDC_Handler.Init.TotalHeigh=lcdltdc.vsw+lcdltdc.vbp+lcdltdc.pheight+lcdltdc.vfp-1;
LTDC_Handler.Init.Backcolor.Red=0; //屏幕背景层红色部分
LTDC_Handler.Init.Backcolor.Green=0; //屏幕背景层绿色部分
LTDC_Handler.Init.Backcolor.Blue=0; //屏幕背景色蓝色部分
HAL_LTDC_Init(<DC_Handler);
LTDC_Layer_Parameter_Config(0,(u32)ltdc_framebuf[0],LCD_PIXFORMAT,255,0,6,7,
0X000000);//层参数配置
LTDC_Layer_Window_Config(0,0,0,lcdltdc.pwidth,lcdltdc.pheight);
//层窗口配置,以 LCD 面板坐标系为基准,不要随便修改!
LTDC_Display_Dir(0);
//默认竖屏
LTDC_Select_Layer(0);
//选择第 1 层
LCD_LED=1;
//点亮背光
LTDC_Clear(0XFFFFFFFF);
//清屏
LTDC_Init 的初始化步骤,是按照 19.1.2 节最后介绍的步骤来进行的,该函数先读取
RGBLCD 的 ID,然后根据不同的 RGBLCD 型号,执行不同的面板参数初始化,然后调用
HAL_LTDC_Init 函数来设置 RGBLCD 的相关参数并使能 LTDC,最后配置层参数和层窗口完
成对 LTDC 的初始化。注意,代码里面的 lcdltdc.hsw、lcdltdc.vsw、lcdltdc.hbp 等参数的值,均
是来自对应 RGBLCD 屏的数据手册,其中 lcdid=0X7084 的配置参数,来自:AT070TN92.pdf。
接下来我们看看头文件 ltdc.h 关键内容:
//LCD LTDC 重要参数集
typedef struct
{
u32 pwidth;
//LCD 面板的宽度,固定参数,如果为 0,说明没有任何 RGB 屏接入
u32 pheight;
//LCD 面板的高度,固定参数,不随显示方向改变
u16 hsw;
//水平同步宽度
u16 vsw;
//垂直同步宽度
u16 hbp;
//水平后廊
u16 vbp;
//垂直后廊
u16 hfp;
//水平前廊
u16 vfp;
//垂直前廊
u8 activelayer;
//当前层编号:0/1
u8 dir;
//0,竖屏;1,横屏;
u16 width;
//LCD 宽度
u16 height;
//LCD 高度
u32 pixsize;
//每个像素所占字节数
}_ltdc_dev;
extern _ltdc_dev lcdltdc;
//管理 LCD LTDC 参数
#define LCD_PIXFORMAT_ARGB8888
0X00
//ARGB8888 格式
…//此处省略部分宏定义标识符
#define LCD_PIXFORMAT_AL88
0X07
//ARGB8888 格式
///////////////////////////////////////////////////////////////////////
//用户修改配置部分:
//定义颜色像素格式,一般用 RGB565
#define LCD_PIXFORMAT
LCD_PIXFORMAT_RGB565
#define LTDC_BACKLAYERCOLOR 0X00000000 //定义默认背景层颜色
#define LCD_FRAME_BUF_ADDR 0XC0000000 //LCD 帧缓冲区首地址,在 SDRAM 里面.
void LTDC_Switch(u8 sw);
//LTDC 开关
…//此处省略部分函数声明
void LTDC_Init(void);
//LTDC 初始化函数
#endif
这段代码主要定义了_ltdc_dev 结构体,用于保存 LCD 相关参数,另外,LCD_PIXFORMAT
定义了颜色格式,我们一般使用 RGB565 格式,LCD_FRAME_BUF_ADDR 定义了帧缓存的首
地址,我们定义在 SDRAM 的首地址,其他的就不多说了。
以上,就是 ltdc 驱动部分的代码,因为阿波罗 STM32F7 开发板还有 MCU 屏接口,为了可
以同时兼容 MCU 屏和 RGB 屏,我们对第 17 章介绍的 lcd.c 部分代码做了小改,添加对 RGB
屏的支持,由于篇幅所限,这里我们只挑几个重点的函数给大家介绍下。
首先读点函数,改为了:
//读取个某点的颜色值
//x,y:坐标
//返回值:此点的颜色
u32 LCD_ReadPoint(u16 x,u16 y)
{
u16 r=0,g=0,b=0;
if(x>=lcddev.width||y>=lcddev.height)return 0; //超过了范围,直接返回
if(lcdltdc.pwidth!=0)
//如果是 RGB 屏
{
return LTDC_Read_Point(x,y);
}
……//省略部分代码
}
当 lcdltdc.pwidth!=0 的时候,说明接入的是 RGB 屏,所以调用 LTDC_Read_Point 函数,实
现读点操作,其他情况,说明是 MCU 屏,执行 MCU 屏的读点操作(代码省略)。
然后是画点函数,改为了:
//画点
//x,y:坐标
//POINT_COLOR:此点的颜色
void LCD_DrawPoint(u16 x,u16 y)
{
if(lcdltdc.pwidth!=0)//如果是 RGB 屏
{
LTDC_Draw_Point(x,y,POINT_COLOR);
}else
……//省略部分代码
}
当 lcdltdc.pwidth!=0 的时候,说明接入的是 RGB 屏,所以调用 LTDC_Draw_Point 函数,
实现画点操作,其他情况,说明是 MCU 屏,执行 MCU 屏的画点操作(代码省略)。同样的,
lcd.c 里面的快速画点函数:LCD_Fast_DrawPoint,在使用 RGB 屏的时候,也是使用
LCD_Fast_DrawPoint 来实现画点操作的
最后,是 LCD 初始化函数,改为:
//初始化 lcd
//该初始化函数可以初始化各种型号的 LCD(详见本.c 文件最前面的描述)
void LCD_Init(void)
{
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 屏的优先级较高。
接下来,我们看看主函数内容:
int main(void)
{
u8 x=0;
u8 lcd_id[12];
Cache_Enable(); //打开 L1-Cache
HAL_Init();
//初始化 HAL 库
Stm32_Clock_Init(432,25,2,9); //设置时钟,216Mhz
delay_init(216); //延时初始化
uart_init();
//串口初始化
LED_Init(); //初始化 LED
KEY_Init(); //初始化按键
SDRAM_Init(); //初始化 SDRAM
LCD_Init(); //LCD 初始化
POINT_COLOR=RED;
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;
}
POINT_COLOR=RED;
LCD_ShowString(10,40,260,32,32,”Apollo STM32F4/F7″);
LCD_ShowString(10,80,240,24,24,”LTDC TEST”);
LCD_ShowString(10,110,240,16,16,”ATOM@ALIENTEK”);
LCD_ShowString(10,130,240,16,16,lcd_id);
//显示 LCD ID
LCD_ShowString(10,150,240,12,12,”2016/1/6″);
x++;
if(x==12)x=0;
LED0_Toggle;
delay_ms(1000);
}
}
该部分代码与第 18 章几乎一摸一样,显示一些固定的字符,字体大小包括 32*16、24*12、
16*8 和 12*6 等四种,同时显示 LCD 的型号,然后不停的切换背景颜色,每 1s 切换一次。而
LED0 也会不停的闪烁,指示程序已经在运行了。其中我们用到一个 sprintf 的函数,该函数用
法同 printf,只是 sprintf 把打印内容输出到指定的内存区间上,sprintf 的详细用法,请百度。
另外特别注意:uart_init 函数,不能去掉,因为在 LCD_Init 函数里面调用了 printf,所以
一旦你去掉这个初始化,就会死机了!实际上,只要你的代码有用到 printf,就必须初始化串口,
否则都会死机,即停在 usart.c 里面的 fputc 函数,出不来。
在编译通过之后,我们开始下载验证代码。
20.4 下载验证
将程序下载到阿波罗 STM32 后,可以看到 DS0 不停的闪烁,提示程序已经在运行了。同
时可以看到 RGBLCD 模块的显示如图 20.4.1 所示:
图 20.4.1 RGBLCD 显示效果图
我们可以看到屏幕的背景是不停切换的,同时 DS0 不停的闪烁,证明我们的代码被正确的
执行了,达到了我们预期的目的。最后,需要注意的是:本例程兼容 MCU 屏,所以,当插入
MCU 屏的时候(不插 RGB 屏),也可以显示同样的结果。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/128023.html