完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:alientek 阿波罗 STM32F767 开发板
2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子 第二十章 LTDC LCD(RGB 屏)实验 在第18章,我们介绍了TFTLCD模块(MCU屏)的使用,但是高分辨率的屏(超过800*480), 一般都没有 MCU 屏接口,而是使用 RGB 接口的,这种接口的屏,就需要用到 STM32F767 的 LTDC 来驱动了。在本章中,我们将使用阿波罗 STM32F767 开发板核心板上的 LCD 接口(仅 支持 RGB 屏,本章介绍 RGB 屏的使用),来点亮 LCD,并实现 ASCII 字符和彩色的显示等功 能,并在串口打印 LCD ID,同时在 LCD 上面显示。本章分为如下几个部分: 20.1 RGBLCD 20.3 软件设计 20.4 下载验证 20.1 RGBLCD 本章我们将通过 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 深度 64x32 位) 支持查色表 (CLUT),每层高达 256 种颜色(256x24 位) 可针对不同显示面板编程时序 可编程背景色 可编程 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( 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*) 入不同的数据,就可以修改 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( 该函数中主要调用 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( 该函数依次调用 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( 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(115200); //串口初始化 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 屏),也可以显示同样的结果。 |
|
相关推荐
|
|
AD7686芯片不传输数据给STM32,但是手按住就会有数据。
313 浏览 0 评论
1927 浏览 0 评论
如何解决MPU-9250与STM32通讯时,出现HAL_ERROR = 0x01U
1044 浏览 1 评论
hal库中i2c卡死在HAL_I2C_Master_Transmit
1457 浏览 1 评论
LL库F030进行3个串口收发,2个串口为232,一个为485,长时间后,会出现串口1停止运行,另外两个正常,只有重启复原
1897 浏览 1 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-18 10:22 , Processed in 0.679564 second(s), Total 66, Slave 48 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号