欢迎来到广东TFT屏幕厂家官方网站!
contact us

联系我们

广东TFT屏幕厂家 > 新闻资讯 > ALIENTEK 阿波罗 STM32F767 开发板资料连载第十八章 TFTLCD实验

ALIENTEK 阿波罗 STM32F767 开发板资料连载第十八章 TFTLCD实验

编辑 :

广东TFT屏幕

时间 : 2021-12-22 15:27 浏览量 : 26

1)试验服务平台:alientek 阿波罗 STM32F767 单片机开发板2)节选自《STM32F7 开发设计指引(HAL 库版)》关心官微号微信公众号,获得大量材料:正点原子



第十八章 TFTLCD(MCU 屏)试验

在第 16 章大家详细介绍了 OLED 模块以及显示,可是该模块只有显示纯色/两色,不可以显示彩

色,并且规格也较小。此章大家将详细介绍 ALIENTEK 的 TFT LCD 模块(MCU 屏),该模块选用

TFTLCD 控制面板,可以显示 16 位色的真彩照片。在这章中,大家将应用阿波罗 STM32F767 开发设计

板底版上的 TFTLCD 插口(仅适用 MCU 屏,此章仅详细介绍 MCU 屏的应用),来照亮 TFTLCD,

并完成 ASCII 标识符和彩色的显示等作用,并在串口通信打印出 LCD 控制板 ID,与此同时在 LCD 上边显示。

此章分成如下所示一些一部分:

18.1 TFTLCD&FMC 介绍

18.2 硬件开发

18.3 软件开发

18.4 在线下载认证

18.5 STM32CubeMX 配备 FMC(SRAM)

18.1 TFTLCD&FMC 介绍

此章大家将根据 STM32F767的 FMC 插口来操纵TFTLCD 的显示,因此这节分成2个一部分,

各自详细介绍 TFTLCD 和 FMC。

18.1.1 TFTLCD 介绍

TFT-LCD 即塑料薄膜晶体三极管液晶显示器。其英语全称之为:Thin Film Transistor-Liquid Crystal

Display。TFT-LCD 与微波感应器 TN-LCD、STN-LCD 的简易引流矩阵不一样,它在液晶显示屏的每一个象

素上面设定有一个塑料薄膜晶体三极管(TFT),可合理地摆脱非选通时的串扰,使显示液晶显示屏的静态数据特

性与扫描线数不相干,因而进一步提高了图象品质。TFT-LCD 也被称为真彩液晶显示器。

上一章详细介绍了 OLED 模块,此章,大家给各位详细介绍 ALIENTEK TFTLCD 模块(MCU 插口),

该模块有以下特性:

1,2.8’/3.5’/4.3’/7’等 4 种尺寸的显示屏可选。

2,320×240 的屏幕分辨率(3.5’屏幕分辨率为:320*480,4.3’和 7’屏幕分辨率为:800*480)。

3,16 位真彩显示。

4,内置触摸显示屏,可以拿来做为操纵键入。

此章,大家以 2.8 寸(别的 3.5 寸/4.3 寸等 LCD 方式相近,请参照 2.8 的就可以)的 ALIENTEK

TFTLCD 模块为例子详细介绍,该模块适用 65K 色显示,显示屏幕分辨率为 320×240,插口为 16 位的 80

并口,内置触摸显示屏。

该模块的外型图如下图 18.1.1.1 所显示

图 18.1.1.1 ALIENTEK 2.8 寸 TFTLCD 外型图


模块电路原理图如下图 18.1.1.2 所显示:

图 18.1.1.2 ALIENTEK 2.8 寸 TFTLCD 模块电路原理图


TFTLCD 模块选用 2*17 的 2.54 公排针与外界联接,接口定义如下图 18.1.1.3 所显示:

图 18.1.1.3 ALIENTEK 2.8 寸 TFTLCD 模块插口图


从图 18.1.1.3 可以看得出,ALIENTEK TFTLCD 模块选用 16 位的并方法与外界联接,往往

不选用 8 位的方法,是由于显示屏的信息量较为大,特别是在在显示照片的情况下,假如用 8 位手机充电线,

便会比 16 位方法慢一倍以上,大家自然期待速率越是快就越好,因此大家挑选 16 位的插口。图

18.1.1.3 还列举了触摸显示屏集成ic的插口,有关触摸显示屏此章大家很少详细介绍,后边的章节目录会出现详尽的介

绍。该模块的 80 并口有以下一些电源线:

CS:TFTLCD 片选数据信号。

WR:向 TFTLCD 载入数据信息。

RD:从 TFTLCD 接收数据。

D[15:0]:16 位双重手机充电线。

RST:硬校准 TFTLCD。

RS:指令/数据信息标示(0,读写能力指令;1,读写能力数据信息)。

80 并口在上一节大家早已有完整的讲解了,这儿大家就不会再详细介绍,必须表明的是,TFTLCD

模块的 RST 电源线是立即收到 STM32F767 的校准脚底,并不由自主APP操纵,那样可以省出来一

个 IO 口。此外大家还要一个led背光控线来操纵 TFTLCD 的led背光。因此,大家一共必须的 IO

口数额为 21 个。这儿还要留意,大家标明的 DB1~DB8,DB10~DB17,是相比于 LCD 操纵

IC 标明的,事实上大伙儿可以把她们就相当于 D0~D15,那样解释起來就相对简单一点。

ALIENTEK给予 2.8/3.5/4.3/7 寸等 4种不一样规格和分辩率的TFTLCD 模块,其推动集成ic为:

ILI9341/NT35310/NT35510/SSD1963 等(实际的型号规格,大伙儿可以根据在线下载此章试验编码,根据串

口或是 LCD 显示查询),这儿大家仅以 ILI9341 控制板为例子开展详细介绍,别的的操纵基本上都相近,

大家也不详尽论述了。

ILI9341 液晶控制板内置独显存储,其独显存储总尺寸为 172800(240*320*18/8),即 18 位方式(26

万色)下的显总量。在 16 位方式下,ILI9341 选用 RGB565 文件格式储存色调数据信息,这时 ILI9341

的 18 位手机充电线与 MCU 的 16 位手机充电线及其 LCD GRAM 的对应关系如下图 18.1.1.4 所显示:

图 18.1.1.4 16 位数据信息与独显存储对应关系图

从图内可以看得出,ILI9341 在 16 位方式下边,手机充电线有效的是:D17~D13 和 D11~D1,D0

和 D12 沒有使用,事实上在大家 LCD 模块里边,ILI9341 的 D0 和 D12 根本就沒有引过来,这

样,ILI9341 的 D17~D13 和 D11~D1 相匹配 MCU 的 D15~D0。

那样 MCU 的 16 位数据信息,最少 5 位意味着深蓝色,正中间 6 位为翠绿色,最大 5 位为鲜红色。标值越

大,表明该色调越重。此外,需注意 ILI9341 全部的命令全是 8 位的(高 8 位失效),且主要参数

除开读写能力 GRAM 的那时候是 16 位,别的实际操作主要参数,全是 8 位的。

下面,大家介绍一下 ILI9341 的一些关键指令,由于 ILI9341 的指令许多,大家这儿就

不所有详细介绍了,有感兴趣的各位可以寻找 ILI9341 的 datasheet 看一下。里边对这种指令有完整的介

绍。大家将详细介绍:0XD3,0X36,0X2A,0X2B,0X2C,0X2E 等 6 条命令。

最先看来命令:0XD3,这个是读 ID4 命令,用以载入 LCD 控制板的 ID,该命令如表 18.1.1.1

所显示:

表 18.1.1.1 0XD3 命令叙述


从以上可以看得出,0XD3 命令后边跟了 4 个主要参数,最终 2 个主要参数,读出是 0X93 和 0X41,

恰好是大家控制板 ILI9341 的小数一部分,进而,根据该命令,就可以辨别常用的 LCD 控制器是什

么型号规格,那样,大家的编码,就可以依据控制板的型号规格去实行相匹配推动 IC 的复位编码,进而

兼容不一样推动 IC 的屏,促使一个编码适用几款 LCD。

下面看命令:0X36,这也是储存浏览程序控制,可以操纵 ILI9341 储存器的读写能力方位,简

单的说,便是在持续写 GRAM 的情况下,可以操纵 GRAM 表针的提高方位,进而操纵显示方法

(读 GRAM 也是一样)。该命令如表 18.1.1.2 所显示:

表 18.1.1.2 0X36 命令叙述


从以上可以看得出,0X36 命令后边,紧随一个主要参数,这儿大家关键关心:MY、MX、MV

这三个位,根据这三个位的设定,我们可以操纵全部 ILI9341 的所有扫描仪方位,如表 18.1.1.3

所显示:



表 18.1.1.3 MY、MX、MV 设定与 LCD 扫描仪方位关系表


那样,我们在运用 ILI9341 显示內容的情况下,就会有非常大灵敏性了,例如显示 BMP 照片,

BMP 编解码数据信息,就是以照片的左下方逐渐,渐渐地显示到右上方,假如设定 LCD 扫描仪方位为从

左到右,从下向上,那麼大家只必须设定一次座标,随后就不断的往 LCD 填充颜色数据信息就可以,

那样可以进一步提高显示速率。

下面看命令:0X2A,这也是列详细地址设定命令,在从左往右,自上而下的扫描仪方法(默认设置)

下边,该命令用以设定横坐标轴(x 座标),该命令如表 18.1.1.4 所显示:

表 18.1.1.4 0X2A 命令叙述

在默认设置扫描仪方法时,该命令用以设定 x 座标,该命令含有 4 个主要参数,事实上是 2 个平面坐标:

SC 和 EC,即列详细地址的起始值和完毕值,SC 务必不大于 EC,且 0≤SC/EC≤239。一般在设

置 x 座标的情况下,大家只必须带 2 个主要参数就可以,也就是设定 SC 就可以,由于假如 EC 沒有转变,

大家只必须设定一次就可以(在复位 ILI9341 的过程中设定),进而提高速度。

与 0X2A 命令相近,命令:0X2B,是页详细地址设定命令,在从左往右,自上而下的扫描仪方法

(默认设置)下边,该命令用以设定纵轴(y 座标)。该命令如表 18.1.1.5 所显示:

表 18.1.1.5 0X2B 命令叙述


在默认设置扫描仪方法时,该命令用以设定 y 座标,该命令含有 4 个主要参数,事实上是 2 个平面坐标:

SP 和 EP,即页详细地址的起始值和完毕值,SP 务必不大于 EP,且 0≤SP/EP≤319。一般在设定

y 座标的情况下,大家只必须带 2 个主要参数就可以,也就是设定 SP 就可以,由于假如 EP 沒有转变,我

们只必须设定一次就可以(在复位 ILI9341 的过程中设定),进而提高速度。

下面看命令:0X2C,该命令是写 GRAM 命令,在推送该命令以后,大家便可以往 LCD

的 GRAM 里边载入色调数据信息了,该命令适用持续写,命令叙述如表 18.1.1.6 所显示:


表 18.1.1.6 0X2C 命令叙述



从以上得知,在接到命令 0X2C 以后,数据信息合理位宽变成 16 位,我们可以持续载入 LCD

GRAM 值,而 GRAM 的详细地址将依据 MY/MX/MV 设定的扫描仪方位开展自增。比如:假定设定

的是从左往右,自上而下的扫描仪方法,那麼设定好起止座标(根据 SC,SP 设定)后,每载入

一个颜色值,GRAM 详细地址可能全自动自增 1(SC  ),假如遇到 EC,则返回 SC,与此同时 SP  ,一

直到座标:EC,EP 完毕,期间不用再度设定的座标,进而进一步提高载入速率。

最终,一起来看看命令:0X2E,该命令是读 GRAM 命令,用以载入 ILI9341 的独显存储(GRAM),

该命令在 ILI9341 的数据信息指南上边的叙述是不正确的,真正的导出状况如表 18.1.1.7 所显示:


表 18.1.1.7 0X2E 命令叙述


该命令用以载入 GRAM,如表 18.1.1.7 所显示,ILI9341 在得到该命令后,第一次导出的是

dummy 数据信息,也就是失效的数据信息,第二次逐渐,载入到的才算是合理的 GRAM 数据信息(从座标:

SC,SP 逐渐),导出规律性为:每一个色调份量占 8 个位数,一次导出 2 个色调份量。例如:第一次

导出是 R1G1,接着的规律性为:B1R2G2B2R3G3B3R4G4B4R5G5... 依此类推。假如

大家只必须载入一个点的颜色值,那麼只必须接受到主要参数 3 就可以,假如要持续载入(运用 GRAM

详细地址自增,方式跟上面一样),那麼就依照以上规律性去接受色调数据信息。

以上,便是实际操作 ILI9341 常见的好多个命令,根据这好多个命令,大家便可以不错的操纵 ILI9341

显示大家所要显示的內容了。

一般 TFTLCD 模块的应用步骤如下图 18.1.1.5:

图 18.1.1.5 TFTLCD 应用步骤


一切 LCD,应用步骤都能够简易的用以上流程表表明。在其中硬校准和复位编码序列,只必须

实行一次就可以。而画点步骤便是:设定座标写 GRAM 命令载入色调数据信息,随后在 LCD 上

面,大家就可以见到相应的点显示大家载入的色调了。读点步骤为:设定座标读 GRAM 命令

载入色调数据信息,那样就可以获得到对应的点的色调数据信息了。

以上仅仅非常简单的实际操作,也是最常见的实际操作,拥有这种实际操作,一般就可以正常的应用 TFTLCD

了。下面人们将该模块用于来显示标识符和数据,根据以上详细介绍,我们可以得到 TFTLCD 显示

必须的有关设定流程如下所示:

1)设定 STM32F767 与 TFTLCD 模块相连接的 IO。

这一步,先将我们与 TFTLCD 模块相连的 IO 口进行初始化,以便驱动 LCD。这里我们用

到的是 FMC,FMC 将在 18.1.2 节向大家详细介绍。

2)初始化 TFTLCD 模块。

即图 18.1.1.5 的初始化序列,这里我们没有硬复位 LCD,因为阿波罗 STM32F767 开发板

的 LCD 接口,将 TFTLCD 的 RST 同 STM32F767 的 RESET 连接在一起了,只要按下开发板的

RESET 键,就会对 LCD 进行硬复位。初始化序列,就是向 LCD 控制器写入一系列的设置值(比

如伽马校准),这些初始化序列一般 LCD 供应商会提供给客户,我们直接使用这些序列即可,

不需要深入研究。在初始化之后,LCD 才可以正常使用。

3)通过函数将字符和数字显示到 TFTLCD 模块上。

这一步则通过图 18.1.1.5 左侧的流程,即:设置坐标写 GRAM 指令写 GRAM 来实现,

但是这个步骤,只是一个点的处理,我们要显示字符/数字,就必须要多次使用这个步骤,从而

达到显示字符/数字的目的,所以需要设计一个函数来实现数字/字符的显示,之后调用该函数,

就可以实现数字/字符的显示了。

STM32F767xx 系列芯片都带有 FMC 接口,即可变存储存储控制器,能够与同步或异步存

储器、SDRAM 存储器和 NAND FLASH 等连接,STM32F767 的 FMC 接口支持包括 SRAM、

SDRAM、NAND FLASH、NOR FLASH 和 PSRAM 等存储器。FMC 的框图如图 18.1.2.1 所示:

图 18.1.2.1 FMC 框图


从上图我们可以看出,STM32F767 的 FMC 将外部设备分为 3 类:NOR/PSRAM 设备、NAND

设备和 SDRAM 设备。他们共用地址数据总线等信号,他们具有不同的 CS 以区分不同的设备,

比如本章我们用到的 TFTLCD 就是用的 FMC_NE1 做片选,其实就是将 TFTLCD 当成 SRAM

来控制。

这里我们介绍下为什么可以把 TFTLCD 当成 SRAM 设备用:首先我们了解下外部 SRAM

的连接,外部 SRAM 的控制一般有:地址线(如 A0~A18)、数据线(如 D0~D15)、写信号(WE)、

读信号(OE)、片选信号(CS),如果 SRAM 支持字节控制,那么还有 UB/LB 信号。而 TFTLCD

的信号我们在 18.1.1 节有介绍,包括:RS、D0~D15、WR、RD、CS、RST 和 BL 等,其中真

正在操作 LCD 的时候需要用到的就只有:RS、D0~D15、WR、RD 和 CS。其操作时序和 SRAM

的控制完全类似,唯一不同就是 TFTLCD 有 RS 信号,但是没有地址信号。

TFTLCD 通过 RS 信号来决定传送的数据是数据还是命令,本质上可以理解为一个地址信

号,比如我们把 RS 接在 A0 上面,那么当 FMC 控制器写地址 0 的时候,会使得 A0 变为 0,

对 TFTLCD 来说,就是写命令。而 FMC 写地址 1 的时候,A0 将会变为 1,对 TFTLCD 来说,

就是写数据了。这样,就把数据和命令区分开了,他们其实就是对应 SRAM 操作的两个连续地

址。当然 RS 也可以接在其他地址线上,阿波罗 STM32F767 开发板是把 RS 连接在 A18 上面的。

STM32F767 的 FMC 支持 8/16/32 位数据宽度,我们这里用到的 LCD 是 16 位宽度的,所

以在设置的时候,选择 16 位宽就 OK 了。我们再来看看 FMC 的外部设备地址映像,STM32F767

的 FMC 将外部存储器划分为 6 个固定大小为 256M 字节的存储区域,如图 18.1.2.2 所示:

图 18.1.2.2 FMC 存储块地址映像


从上图可以看出,FMC 总共管理 1.5GB 空间,拥有 6 个存储块(Bank),本章,我们用到

的是块 1,所以在本章我们仅讨论块 1 的相关配置,其他块的配置,请参考《STM32F7 中文参

考手册》第 13 章(286 页)的相关介绍。

STM32F767 的 FMC 存储块 1(Bank1)被分为 4 个区,每个区管理  ** M 字节空间,每个

区都有独立的寄存器对所连接的存储器进行配置。Bank1 的 256M 字节空间由 28 根地址线

(HADDR[27:0])寻址。

这里 HADDR 是内部AHB地址总线,其中HADDR[25:0]来自外部存储器地址 FMC_A[25:0],

而 HADDR[26:27]对 4 个区进行寻址。如表 18.1.2.1 所示:

表 18.1.2.1 Bank1 存储区选择表

HADDR[25:0]位包含外部存储器的地址,由于 HADDR 为字节地址,而存储器按字寻址,

所以,根据存储器数据宽度的不同,实际上向存储器发送的地址也有所不同,如表 18.1.2.2 所

示:

表 18.1.2.2 NOR/PSRAM 外部存储器地址


因此,FMC 内部 HADDR 与存储器寻址地址的实际对应关系就是:

当接的是 32 位宽度存储器的时候:HADDR[25:2] FMC_A [23:0]。

当接的是 16 位宽度存储器的时候:HADDR[25:1] FMC_A [24:0]。

当接的是 8 位宽度存储器的时候:HADDR[25:0] FMC_A [25:0]。

不论外部接 8 位/16 位/32 位宽设备,FMC_A[0]永远接在外部设备地址 A[0]。 这里,

TFTLCD 使用的是 16 位数据宽度,所以 HADDR[0]并没有用到,只有 HADDR[25:1]是有效的,

对应关系变为:HADDR[25:1] FMC_A[24:0],相当于右移了一位,这里请大家特别留意。另

外,HADDR[27:26]的设置,是不需要我们干预的,比如:当你选择使用 Bank1 的第一个区,

即使用 FMC_NE1 来连接外部设备的时候,即对应了 HADDR[27:26]=00,我们要做的就是配置

对应第 1 区的寄存器组,来适应外部设备即可。STM32F767 的 FMC 各 Bank 配置寄存器如表

18.1.2.3 所示:

表 18.1.2.3 FMC 各 Bank 配置寄存器表


对于 NOR FLASH 控制器,主要是通过 FMC_BCRx、FMC_BTRx 和 FMC_BWTRx 寄存器

设置(其中 x=1~4,对应 4 个区)。通过这 3 个寄存器,可以设置 FMC 访问外部存储器的时序

参数,拓宽了可选用的外部存储器的速度范围。FMC 的 NOR FLASH 控制器支持同步和异步突

发两种访问方式。选用同步突发访问方式时,FMC 将 HCLK(系统时钟)分频后,发送给外部存

储器作为同步时钟信号 FMC_CLK。此时需要的设置的时间参数有 2 个:

1,HCLK 与 FMC_CLK 的分频系数(CLKDIV),可以为 2~16 分频;

2,同步突发访问中获得第 1 个数据所需要的等待延迟(DATLAT)。

对于异步突发访问方式,FMC 主要设置 3 个时间参数:地址建立时间(ADDSET)、数据建

立时间(DATAST)和地址保持时间(ADDHLD)。FMC 综合了 SRAM、PSRAM 和 NOR Flash 产品

的信号特点,定义了 4 种不同的异步时序模型。选用不同的时序模型时,需要设置不同的时序

参数,如表 18.1.2.4 所列:

表 18.1.2.4 NOR FLASH/PSRAM 控制器支持的时序模型


在实际扩展时,根据选用存储器的特征确定时序模型,从而确定各时间参数与存储器读/

写周期参数指标之间的计算关系;利用该计算关系和存储芯片数据手册中给定的参数指标,可

计算出 FMC 所需要的各时间参数,从而对时间参数寄存器进行合理的配置。

本章,我们使用异步模式 A(ModeA)方式来控制 TFTLCD,模式 A 的读操作时序如图

18.1.2.3 所示:

图 18.1.2.3 模式 A 读操作时序图


模式 A 支持独立的读写时序控制,这个对我们驱动 TFTLCD 来说非常有用,因为 TFTLCD

在读的时候,一般比较慢,而在写的时候可以比较快,如果读写用一样的时序,那么只能以读

的时序为基准,从而导致写的速度变慢,或者在读数据的时候,重新配置 FMC 的延时,在读

操作完成的时候,再配置回写的时序,这样虽然也不会降低写的速度,但是频繁配置,比较麻

烦。而如果有独立的读写时序控制,那么我们只要初始化的时候配置好,之后就不用再配置,

既可以满足速度要求,又不需要频繁改配置。

模式 A 的写操作时序如图 18.1.2.4 所示:


图 18.1.2.4 模式 A 写操作时序

图 18.1.2.3 和图 18.1.2.4 中的 ADDSET 与 DATAST,是通过不同的寄存器设置的,接下来

我们讲解一下 Bank1 的几个控制寄存器

首先,我们介绍 SRAM/NOR 闪存片选控制寄存器:FMC_BCRx(x=1~4),该寄存器各位

描述如图 18.1.2.5 所示:

图 18.1.2.5 FMC_BCRx 寄存器各位描述


该寄存器我们在本章用到的设置有:EXTMOD、WREN、MWID、MTYP 和 MBKEN 这几

个设置,我们将逐个介绍。

EXTMOD:扩展模式使能位,也就是是否允许读写不同的时序,很明显,我们本章需要读

写不同的时序,故该位需要设置为 1。

WREN:写使能位。我们需要向 TFTLCD 写数据,故该位必须设置为 1。

MWID[1:0]:存储器数据总线宽度。00,表示 8 位数据模式;01 表示 16 位数据模式;10

表示 32 位数据模式;11 保留。我们的 TFTLCD 是 16 位数据线,所以设置 WMID[1:0]=01。

MTYP[1:0]:存储器类型。00 表示 SRAM;01 表示 PSRAM;10 表示 NOR FLASH/OneNAND

FLASH;11 保留。前面提到,我们把 TFTLCD 当成 SRAM 用,所以需要设置 MTYP[1:0]=00。

MBKEN:存储块使能位。这个容易理解,我们需要用到该存储块控制 TFTLCD,当然要

使能这个存储块了。

接下来,我们看看 SRAM/NOR 闪存片选时序寄存器:FMC_BTRx(x=1~4),该寄存器各

位描述如图 18.1.2.6 所示:

图 18.1.2.6 FMC_BTRx 寄存器各位描述


这个寄存器包含了每个存储器块的控制信息,可以用于 SRAM 和 NOR 闪存存储器等。如

果 FMC_BCRx 寄存器中设置了 EXTMOD 位,则有两个时序寄存器分别对应读(本寄存器)和写

操作(FMC_BWTRx 寄存器)。因为我们要求读写分开时序控制,所以 EXTMOD 是使能了的,

也就是本寄存器是读操作时序寄存器,控制读操作的相关时序。本章我们要用到的设置有:

ACCMOD、DATAST 和 ADDSET 这三个设置。

ACCMOD[1:0]:访问模式。00 表示访问模式 A;01 表示访问模式 B;10 表示访问模式 C;

11 表示访问模式 D,本章我们用到模式 A,故设置为 00。

DATAST[7:0]:数据保持时间。0 为保留设置,其他设置则代表保持时间为: DATAST 个

HCLK 时钟周期,最大为 255 个 HCLK 周期。对 ILI9341 来说,其实就是 RD 低电平持续时间,

一般为 355ns。而一个 HCLK 时钟周期为 4.6ns 左右(1/216Mhz),为了兼容其他屏,我们这里

设置 DATAST 为 80,也就是 80 个 HCLK 周期,时间大约是 368ns。

ADDSET[3:0]:地址建立时间。其建立时间为:ADDSET 个 HCLK 周期,最大为 15 个 HCLK

周期。对 ILI9341 来说,这里相当于 RD 高电平持续时间,为 90ns,我们设置 ADDSET 为最大

15,即 15*4.6=69ns(略超)。

最后,我们再来看看 SRAM/NOR 闪写时序寄存器:FMC_BWTRx(x=1~4),该寄存器各

位描述如图 18.1.2.7 所示:

图 18.1.2.7 FMC_BWTRx 寄存器各位描述


该寄存器在本章用作写操作时序控制寄存器,需要用到的设置同样是:ACCMOD、DATAST

和 ADDSET 这三个设置。这三个设置的方法同 FMC_BTRx 一模一样,只是这里对应的是写操

作的时序,ACCMOD 设置同 FMC_BTRx 一模一样,同样是选择模式 A,另外 DATAST 和

ADDSET 则对应低电平和高电平持续时间,对 ILI9341 来说,这两个时间只需要 15ns 就够了,

比读操作快得多。所以我们这里设置 DATAST 为 4,即 4 个 HCLK 周期,时间约为 18.4ns。然

后 ADDSET 设置为 4,即 4 个 HCLK 周期,时间为 18.4ns。

至此,我们对 STM32F767 的 FMC 介绍就差不多了,关于 FMC 的详细介绍,请大家参考

《STM32F7 中文参考手册》第 13 章。通过以上两个小节的了解,我们可以开始写 LCD 的驱动

代码了。不过,这里还要给大家做下科普,在 MDK 的寄存器定义里面,并没有定义 FMC_BCRx、

FMC_BTRx、FMC_BWTRx 等这个单独的寄存器,而是将他们进行了一些组合。

FMC_BCRx 和 FMC_BTRx,组合成 BTCR[8]寄存器组,他们的对应关系如下:

BTCR[0]对应 FMC_BCR1,BTCR[1]对应 FMC_BTR1

BTCR[2]对应 FMC_BCR2,BTCR[3]对应 FMC_BTR2

BTCR[4]对应 FMC_BCR3,BTCR[5]对应 FMC_BTR3

BTCR[6]对应 FMC_BCR4,BTCR[7]对应 FMC_BTR4

FMC_BWTRx 则组合成 BWTR[7],他们的对应关系如下:

BWTR[0]对应 FMC_BWTR1,BWTR[2]对应 FMC_BWTR2,

BWTR[4]对应 FMC_BWTR3,BWTR[6]对应 FMC_BWTR4,

BWTR[1]、BWTR[3]和 BWTR[5]保留,没有用到。

通过上面的讲解,通过对 FSC 相关的寄存器的描述,大家对 FMC 的原理有了一个初步的

认识,如果还不熟悉的朋友,请一定要搜索网络资料理解 FMC 的原理。只有理解了原理,使

用库函数才可以得心应手。那么在库函数中是怎么实现 FMC 的配置的呢?FMC_BCRx,

FMC_BTRx 寄存器在库函数是通过什么函数来配置的呢?下面我们来讲解一下使用 FMC 接口

驱动 LCD(SRAM)相关的库函数操作过程。与 SRAM 和 FMC 相关的库函数定义和声明在源

文件 stm32f7xx_hal_fmc.c/stm32f7xx_hal_sram.c 以及头文件

stm32f7xx_hal_fmc.h/stm32f7xx_hal_sram.h 中。

1) 使能 FMC 和 GPIO 时钟,初始化 IO 口配置,设置映射关系

这个步骤在前面实验已多次讲解。这里我们主要列出 FMC 时钟使能方法:

__HAL_RCC_FMC_CLK_ENABLE ();

//使能 FMC 时钟

对于 IO 配置,调用函数 HAL_GPIO_Init 配置即可,具体 请参考实验源码。

2) 初始化 FMC 接口读写时序参数,初始化 LCD(SRAM)控制接口

根据前面的讲解,我们把 LCD 当 SRAM 使用,连接在 FMC 接口之上,所以我们要初始化

FMC 读写时序参数以及 LCD 数据接口,也就是初始化三个寄存器 FMC_BCRx,FMC_BTRx

和 FMC_BWTRx。HAL 库提供了 SRAM 初始化函数 HAL_SRAM_Init,该函数声明如下:

HAL_StatusTypeDef HAL_SRAM_Init(SRAM_HandleTypeDef *hsram,

FMC_NORSRAM_TimingTypeDef *Timing,

FMC_NORSRAM_TimingTypeDef *ExtTiming);

该函数有三个入口参数,首先我们来看看第一个入口参数 hsram,它是

SRAM_HandleTypeDef 结构体指针类型,该参数用来初始化当 FMC 接口当 SRAM 使用时的控

制接口参数。结构体 SRAM_HandleTypeDef 定义如下:

typedef struct

{

FMC_NORSRAM_TypeDef

*Instance;

FMC_NORSRAM_EXTENDED_TypeDef

*Extended;

FMC_NORSRAM_InitTypeDef

Init;

HAL_LockTypeDef

Lock;

__IO HAL_SRAM_StateTypeDef

State;

DMA_HandleTypeDef

*hd ** ;

}SRAM_HandleTypeDef;

成员变量 Instance 和成员变量 Extended 实际上是用来在指定的时序模型下,寄存器基地址

和扩展模式寄存器基地址。这个怎么理解呢,本实验我们使用异步模式 A(ModeA)方式来控

制 TFTLCD,使用的存储块是 Bank1,所以寄存器基地址 Instance 我们直接写 FMC_Bank1 即可,

当然,HAL 库定义好了宏定义 FMC_NORSRAM_DEVICE,也就是如果是 SRAM 设备,直接

填写这个宏定义标识符即可。因为我们要配置的读写时序是不一样的,也就是我们前面讲解的

FMC_BCRx 寄存器的 EXTMOD 位我们会配置为 1 允许读写不同的时序,所以我们这里还要指

定写操作时序寄存器地址,也就是通过参数 Extended 来指定的,这里我们设置为 FMC_Bank1E

即可,同样 MDK 定义好了宏定义标识符 FMC_NORSRAM_EXTENDED_DEVICE,所以这里

我们填写这个宏定义标识符也是一样的。对于写时序参数配置,是在函数 HAL_SRAM_Init 的

第三个参数 ExtTiming 来配置的,这个我们后面会讲解。

成员变量 Init 是 FMC_NORSRAM_InitTypeDef 结构体指针类型,改变量才是真正用来设置

SRAM 控制接口参数的。我们接下来看看这个结构体定义:

typedef struct

{

uint32_t NSBank;

//存储区块号

uint32_t DataAddressMux;

//地址/数据复用使能

uint32_t MemoryType;

//存储器类型

uint32_t MemoryDataWidth; //存储器数据宽度

uint32_t BurstAccessMode;

uint32_t WaitSignalPolarity;

uint32_t WaitSignalActive;

uint32_t WriteOperation;

//存储器写使能

uint32_t WaitSignal;

uint32_t ExtendedMode;

//是否使能扩展模式

uint32_t AsynchronousWait;

uint32_t WriteBurst;

uint32_t ContinuousClock;

//启用/禁止 FMC 时钟输出到外部存储设备

uint32_t WriteFifo;

uint32_t PageSize;

}FMC_NORSRAM_InitTypeDef;

NSBank 用来指定使用到的存储块区号,前面讲过,我们是使用的存储块区号 1,所以选择

值为 FMC_NORSRAM_BANK1。DataAddressMux 用来设置是否使能地址/数据复用,该变量仅对

NOR/PSRAM 有 效 , 所 以 这 里 我 们 选 择 不 使 能 地 址 / 数据复用值

FMC_DATA_ADDRESS_MUX_DISABLE 即可。MemoryType 用来设置存储器类型,这里我们

把 LCD 当 SRAM 使用,所以设置为 FMC_MEMORY_TYPE_SRAM 即可。MemoryDataWidth

用来设置存储器数据总线宽度,可选 8 位还是 16 位,这里我们选择 16 位数据宽度

FMC_NORSRAM_MEM_BUS_WIDTH_16。WriteOperation 用来设置存储器写使能,也就是是

否允许写入。毫无疑问我们会进行存储器写操作,所以这里设置为

FMC_WRITE_OPERATION_ENABLE。ExtendedMode 用来设置是否使能扩展模式,也就是是

否允许读写使用不同时序,前面讲解过本实验读写采用不同时序,所以设置值为使能值

FMC_EXTENDED_MODE_ENABLE。ContinuousClock 用来设置启用/禁止 FMC 时钟输出到外

部存储设备 ,这里 仅 当 使 用 FMC_BCR1 寄 存 器 的 时 候 需 要 启 用 , 启 用 值 为

FMC_CONTINUOUS_CLOCK_SYNC_ASYNC 。 其 他 参 数 WriteBurst , BurstAccessMode ,

WaitSignalPolarity,WaitSignalActive,WaitSignal,AsynchronousWait 等是用在突发访问和异步

时序情况下,这里我们不做过多讲解。

成员变量 Lock 和 State 是 HAL 库处理状态标识变量。这里就不做过多讲解。

成员变量 hd **  在使用 DMA 时候才使用,这里就先不讲解了。

函数 HAL_SRAM_Init 的第一个入口参数就给大家讲解到这里。

接下来看看后面 2 个参数 Timing 和 ExtTiming,它们都是 FMC_NORSRAM_TimingTypeDef

结构体指针类型,分别用来设置 FMC 接口读和写时序,主要涉及地址建立保持时间,数据建

立时间等等配置,对于我们的实验中,读写时序不一样,读写速度要求不一样,所以对于参数

Timing 和 ExtTiming 设置了不同的值。

FMC_NORSRAM_TimingTypeDef 结构体定义如下:

typedef struct

{

uint32_t AddressSetupTime;

//地址建立时间

uint32_t AddressHoldTime;

//地址保持时间

uint32_t DataSetupTime;

//数据简历时间

uint32_t BusTurnAroundDuration; //总线周转阶段的持续时间

uint32_t CLKDivision;

//CLK 时钟输出信号的周期

uint32_t DataLatency;

//同步突发 NOR FLASH 的数据延迟

uint32_t AccessMode;

//异步模式配置

}FMC_NORSRAM_TimingTypeDef;

成员变量 AddressSetupTime 用来设置地址建立时间。AddressHoldTime 用来设置地址保持

时间。DataSetupTime 用来设置数据建立时间。BusTurnAroundDuration 用来配置总线周转阶段

的持续时间。CLKDivision 用来配置 CLK 时钟输出信号的周期,以 HCLK 周期数表示。

DataLatency 用来设置同步突发 NOR FLASH 的数据延迟。AccessMode 用来设置异步模式,取

值范围为 FMC_ACCESS_MODE_A,FMC_ACCESS_MODE_B, FMC_ACCESS_MODE_C 和

FMC_ACCESS_MODE_D,这里我们用是异步模式 A,所以取值为 FMC_ACCESS_MODE_A。

HAL_SRAM_Init 函数各个入口参数含义和配置就给大家讲解到这里。

和其他外设一样,HAL 库也提供了 SRAM 的初始化 MSP 回调函数,函数声明如下:

void HAL_SRAM_MspInit(SRAM_HandleTypeDef *hsram) ;

关于 MSP 函数的使用方法相信大家已经非常熟悉。该函数内部一般用来使能时钟以及初

始化 IO 口这些与 MCU 相关的步骤。

前面我们讲解过,FMC 接口支持多种存储器,包括 SDRAM,NOR,NAND 和 PC CARD

等。HAL 库为每种支持的存储器类型都定义了一个独立的 HAL 库文件,并且在文件中定义了

独立的初始化函数。这里以 SDRAM 为例,HAL 提供库支持文件 stm32f7xx_hal_sdram.c 和头文

件 stm32f7xx_hal_sdram.h,同时还提供了独立的初始化函数 HAL_SDRAM_Init,这里我们就列

出几种存储器的初始化函数:

HAL_SDRAM_Init();//SDRAM 初始化函数,省略入口参数

HAL_NOR_Init();//NOR 初始化函数,省略入口参数

HAL_NAND_Init();//NAND 初始化函数,省略入口参数

3)存储区使能

实际上,当我们调用了存储器初始化函数之后,相应的使用到的存储区就已经被使能。

SRAM 存储区使能方法为:

__FMC_NORSRAM_ENABLE(FMC_Bank1,FMC_NORSRAM_BANK1);

18.2 硬件设计

本实验用到的硬件资源有:

1) 指示灯 DS0

2) TFTLCD 模块

TFTLCD 模块的电路见图 18.1.1.2,这里我们介绍 TFTLCD 模块与 ALIETEK 阿波罗

STM32F767 开发板的连接,阿波罗 STM32F767 开发板底板的 LCD 接口和 ALIENTEK TFTLCD

模块直接可以对插,连接关系如图 18.2.1 所示:

图 18.2.1 TFTLCD 与开发板连接示意图


图 18.2.1 中圈出来的部分就是连接 TFTLCD 模块的接口,液晶模块直接插上去即可。

在硬件上,TFTLCD 模块与阿波罗 STM32F767 开发板的 IO 口对应关系如下:

LCD_BL(背光控制)对应 PB5;

LCD_CS 对应 PD7 即 FMC_NE1;

LCD _RS 对应 PD13 即 FMC_A18;

LCD _WR 对应 PD5 即 FMC_NWE;

LCD _RD 对应 PD4 即 FMC_NOE;

LCD _D[15:0]则直接连接在 FMC_D15~FMC_D0;

这些线的连接,阿波罗 STM32F767 开发板的内部已经连接好了,我们只需要将 TFTLCD

模块插上去就好了。实物连接(4.3 寸 TFTLCD 模块)如图 18.2.2 所示:

图 18.2.2 TFTLCD 与开发板连接实物图


18.3 软件设计

打开我们光盘的实验 13 TFTLCD(MCU 屏)工程可以看到我们添加了两个文件 lcd.c 和头

文 件 lcd.h 。 同 时 , FMC 和 SRAM 相 关 的 库 函 数 和 声 明 定 义 在 源 文 件

stm32f7xx_hal_fmc.c/stm32f7xx_hal_sdram.c 和头文件 stm32f7xx_hal_fmc.h

/stm32f7xx_hal_sram.h 中。

在 lcd.c 里面要输入的代码比较多,我们这里就不贴出来了,只针对几个重要的函数进行讲

解。完整版的代码见光盘4,程序源码标准例程-寄存器版本实验 13 TFTLCD(MCU 屏)

实验 的 lcd.c 文件。

本实验,我们用到 FMC 驱动 LCD,通过前面的介绍,我们知道 TFTLCD 的 RS 接在 FMC

的 A18 上面,CS 接在 FMC_NE1 上,并且是 16 位数据总线。即我们使用的是 FMC 存储器 1

的第 1 区,我们定义如下 LCD 操作结构体(在 lcd.h 里面定义):

//LCD 地址结构体

typedef struct

{

vu16 LCD_REG;

vu16 LCD_RAM;

} LCD_TypeDef;

//使用 NOR/SRAM 的 Bank1.sector1,地址位 HADDR[27,26]=00 A18 作为数据命令区分线

//注意设置时 STM32 内部会右移一位对其!

#define LCD_BASE ((u32)(0x | 0x0007FFFE))

#define LCD ((LCD_TypeDef *) LCD_BASE)

其中 LCD_BASE,必须根据我们外部电路的连接来确定,我们使用 Bank1.sector1 就是从

地址 0X 开始,而 0x0007FFFE,则是 A18 的偏移量,这里很多朋友不理解这个偏移量

的概念,简单说明下:以 A18 为例,0x0007FFFE 转换成二进制就是:0111 1111 1111 1111 1110,

而 16 位数据时,地址右移一位对齐,那么实际对应到地址引脚的时候,就是:A18:A0=011 1111

1111 1111 1111,此时 A18 是 0,但是如果 16 位地址再加 1(注意:对应到 8 位地址是加 2,即

0x0007FFFE +0X02),那么:A18:A0=100 0000 0000 0000 0000,时 A18 就是 1 了,即实现了对

RS 的 0 和 1 的控制。

我们将这个地址强制转换为 LCD_TypeDef 结构体地址,那么可以得到 LCD->LCD_REG 的

地址就是 0X6007,FFFE,对应 A18 的状态为 0(即 RS=0),而 LCD->LCD_RAM 的地址就是

0X6008,0000(结构体地址自增),对应 A18 的状态为 1(即 RS=1)。

所以,有了这个定义,当我们要往 LCD 写命令/数据的时候,可以这样写:

LCD->LCD_REG=CMD; //写命令

LCD->LCD_RAM=DATA; //写数据

而读的时候反过来操作就可以了,如下所示:

CMD= LCD->LCD_REG; //读 LCD 寄存器

DATA = LCD->LCD_RAM; //读 LCD 数据

这其中,CS、WR、RD 和 IO 口方向都是由 FMC 硬件自动控制,不需要我们手动设置了。

接下来,我们先介绍一下 lcd.h 里面的另一个重要结构体:

//LCD 重要参数集

typedef struct

{

u16 width;

//LCD 宽度

u16 height;

//LCD 高度

u16 id;

//LCD ID

u8 dir;

//横屏还是竖屏控制:0,竖屏;1,横屏。

u16 wramcmd;

//开始写 gram 指令

u16 setxcmd;

//设置 x 坐标指令

u16 setycmd;

//设置 y 坐标指令

}_lcd_dev;

//LCD 参数

extern _lcd_dev lcddev; //管理 LCD 重要参数

该结构体用于保存一些 LCD 重要参数信息,比如 LCD 的长宽、LCD ID(驱动 IC 型号)、

LCD 横竖屏状态等,这个结构体虽然占用了十几个字节的内存,但是却可以让我们的驱动函数

支持不同尺寸的 LCD,同时可以实现 LCD 横竖屏切换等重要功能,所以还是利大于弊的。有

了以上了解,下面我们开始介绍 lcd.c 里面的一些重要函数。

先看 7 个简单,但是很重要的函数:

//写寄存器函数

//regval:寄存器值

void LCD_WR_REG(vu16 regval)

{

regval=regval;

//使用-O2 优化的时候,必须插入的延时

LCD->LCD_REG=regval;//写入要写的寄存器序号

}

//写 LCD 数据

//data:要写入的值

void LCD_WR_DATA(vu16 data)

{

data=data;

//使用-O2 优化的时候,必须插入的延时

LCD->LCD_RAM=data;

}

//读 LCD 数据

//返回值:读到的值

u16 LCD_RD_DATA(void)

{

vu16 ram;

//防止被优化

ram=LCD->LCD_RAM;

return ram;

}

//写寄存器

//LCD_Reg:寄存器地址

//LCD_RegValue:要写入的数据

void LCD_WriteReg(u16 LCD_Reg, u16 LCD_RegValue)

{

LCD->LCD_REG = LCD_Reg;

//写入要写的寄存器序号

LCD->LCD_RAM = LCD_RegValue; //写入数据

}

//读寄存器

//LCD_Reg:寄存器地址

//返回值:读到的数据

u16 LCD_ReadReg(u16 LCD_Reg)

{

LCD_WR_REG(LCD_Reg);

//写入要读的寄存器序号

delay_us(5);

return LCD_RD_DATA();

//返回读到的值

}

//开始写 GRAM

void LCD_WriteRAM_Prepare(void)

{

LCD->LCD_REG=lcddev.wramcmd;

}

//LCD 写 GRAM

//RGB_Code:颜色值

void LCD_WriteRAM(u16 RGB_Code)

{

LCD->LCD_RAM = RGB_Code;//写十六位 GRAM

}

因为 FMC 自动控制了 WR/RD/CS 等这些信号,所以这 7 个函数实现起来都非常简单,我

们就不多说,注意,上面有几个函数,我们添加了一些对 MDK –O2 优化的支持,去掉的话,

在-O2 优化的时候会出问题。这些函数实现功能见函数前面的备注,通过这几个简单函数的组

合,我们就可以对 LCD 进行各种操作了。

第七个要介绍的函数是坐标设置函数,该函数代码如下:

//设置光标位置

//Xpos:横坐标

//Ypos:纵坐标

void LCD_SetCursor(u16 Xpos, u16 Ypos)

{

if(lcddev.id==0X9341||lcddev.id==0X5310)

{

LCD_WR_REG(lcddev.setxcmd);

LCD_WR_DATA(Xpos>>8);LCD_WR_DATA(Xpos&0XFF);

LCD_WR_REG(lcddev.setycmd);

LCD_WR_DATA(Ypos>>8);LCD_WR_DATA(Ypos&0XFF);

}else if(lcddev.id==0X1963)

{

if(lcddev.dir==0)//x 坐标需要变换

{

Xpos=lcddev.width-1-Xpos;

LCD_WR_REG(lcddev.setxcmd);

LCD_WR_DATA(0);LCD_WR_DATA(0);

LCD_WR_DATA(Xpos>>8);LCD_WR_DATA(Xpos&0XFF);

}else

{

LCD_WR_REG(lcddev.setxcmd);

LCD_WR_DATA(Xpos>>8);LCD_WR_DATA(Xpos&0XFF);

LCD_WR_DATA((lcddev.width-1)>>8);

LCD_WR_DATA((lcddev.width-1)&0XFF);

}

LCD_WR_REG(lcddev.setycmd);

LCD_WR_DATA(Ypos>>8);LCD_WR_DATA(Ypos&0XFF);

LCD_WR_DATA((lcddev.height-1)>>8);LCD_WR_DATA((lcddev.height-1)&0XFF);

}else if(lcddev.id==0X5510)

{

LCD_WR_REG(lcddev.setxcmd);LCD_WR_DATA(Xpos>>8);

LCD_WR_REG(lcddev.setxcmd+1);LCD_WR_DATA(Xpos&0XFF);

LCD_WR_REG(lcddev.setycmd);LCD_WR_DATA(Ypos>>8);

LCD_WR_REG(lcddev.setycmd+1);LCD_WR_DATA(Ypos&0XFF);

}

}

该函数实现将 LCD 的当前操作点设置到指定坐标(x,y)。因为 9341/5310/1963/5510 等的设

置有些不太一样,所以进行了区别对待。

接下来我们介绍第八个函数:画点函数。该函数实现代码如下:

//画点

//x,y:坐标

//POINT_COLOR:此点的颜色

void LCD_DrawPoint(u16 x,u16 y)

{

LCD_SetCursor(x,y);

//设置光标位置

LCD_WriteRAM_Prepare(); //开始写入 GRAM

LCD->LCD_RAM=POINT_COLOR;

}

该函数实现比较简单,就是先设置坐标,然后往坐标写颜色。其中 POINT_COLOR 是我们

定义的一个全局变量,用于存放画笔颜色,顺带介绍一下另外一个全局变量:BACK_COLOR,

该变量代表 LCD 的背景色。LCD_DrawPoint 函数虽然简单,但是至关重要,其他几乎所有上

层函数,都是通过调用这个函数实现的。

有了画点,当然还需要有读点的函数,第九个介绍的函数就是读点函数,用于读取 LCD

的 GRAM,这里说明一下,为什么 OLED 模块没做读 GRAM 的函数,而这里做了。因为 OLED

模块是单色的,所需要全部 GRAM 也就 1K 个字节,而 TFTLCD 模块为彩色的,点数也比 OLED

模块多很多,以 16 位色计算,一款 320×240 的液晶,需要 320×240×2 个字节来存储颜色值,

也就是也需要 150K 字节,这对任何一款单片机来说,都不是一个小数目了。而且我们在图形

叠加的时候,可以先读回原来的值,然后写入新的值,在完成叠加后,我们又恢复原来的值。

这样在做一些简单菜单的时候,是很有用的。这里我们读取 TFTLCD 模块数据的函数为

LCD_ReadPoint,该函数直接返回读到的 GRAM 值。该函数使用之前要先设置读取的 GRAM

地址,通过 LCD_SetCursor 函数来实现。LCD_ReadPoint 的代码如下:

//读取个某点的颜色值

//x,y:坐标

//返回值:此点的颜色

u16 LCD_ReadPoint(u16 x,u16 y)

{

u16 r=0,g=0,b=0;

if(x>=lcddev.width||y>=lcddev.height)return 0; //超过了范围,直接返回

LCD_SetCursor(x,y);

if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X1963)

LCD_WR_REG(0X2E);//9341/3510/1963 发送读 GRAM 指令

else if(lcddev.id==0X5510)LCD_WR_REG(0X2E00);//5510 发送读 GRAM 指令

r=LCD_RD_DATA();

//dummy Read

if(lcddev.id==0X1963)return r;

//1963 直接读就可以

opt_delay(2);

r=LCD_RD_DATA();

//实际坐标颜色

//9341/NT35310/NT35510 要分 2 次读出

opt_delay(2);

b=LCD_RD_DATA();

g=r&0XFF; //对于 9341/5310/5510,第一次读取的是 RG 的值,R 在前,G 在后,各占 8 位

g<<=8;

return (((r>>11)<<11)|((g>>10)<<5)|(b>>11));

//需要公式转换一下

}

在 LCD_ReadPoint 函数中,因为我们的代码不止支持一种 LCD 驱动器,所以,我们根据

不同的 LCD 驱动器((lcddev.id)型号,执行不同的操作,以实现对各个驱动器兼容,提高函数

的通用性。

第十个要介绍的是字符显示函数 LCD_ShowChar,该函数同前面 OLED 模块的字符显示函

数差不多,但是这里的字符显示函数多了 1 个功能,就是可以以叠加方式显示,或者以非叠加

方式显示。叠加方式显示多用于在显示的图片上再显示字符。非叠加方式一般用于普通的显示。

该函数实现代码如下:

//在指定位置显示一个字符

//x,y:起始坐标

//num:要显示的字符:" "--->"~"

//size:字体大小 12/16/24/32

//mode:叠加方式(1)还是非叠加方式(0)

void LCD_ShowChar(u16 x,u16 y,u8 num,u8 size,u8 mode)

{

u8 temp,t1,t;

u16 y0=y;

u8 csize=(size/8+((size%8)?1:0))*(size/2);//得到字体一个字符对应点阵集所占的字节数

num=num-' ';//ASCII 字库是从空格开始取模,所以-' '就是对应字符的字库

for(t=0;t<csize;t++)

{

if(size==12)temp=asc2_1206[num][t];

//调用 1206 字体

else if(size==16)temp=asc2_1608[num][t]; //调用 1608 字体

else if(size==24)temp=asc2_2412[num][t]; //调用 2412 字体

else if(size==32)temp=asc2_3216[num][t]; //调用 3216 字体

else return;

//没有的字库

for(t1=0;t1<8;t1++)

{

if(temp&0x80)LCD_Fast_DrawPoint(x,y,POINT_COLOR);

else if(mode==0)LCD_Fast_DrawPoint(x,y,BACK_COLOR);

temp<<=1;

y++;

if(y>=lcddev.height)return;

//超区域了

if((y-y0)==size)

{

y=y0;

x++;

if(x>=lcddev.width)return; //超区域了

break;

}

}

}

}

在 LCD_ShowChar 函数里面,我们采用快速画点函数 LCD_Fast_DrawPoint 来画点显示字

符,该函数同 LCD_DrawPoint 一样,只是带了颜色参数,且减少了函数调用的时间,详见本例

程源码。该代码中我们用到了四个字符集点阵数据数组 asc2_3216、asc2_2412、asc2_1206 和

asc2_1608,这几个字符集的点阵数据的提取方式,同十六章介绍的提取方法是一模一样的。详

细请参考第十六章。

最后,我们再介绍一下 TFTLCD 模块的初始化函数 LCD_Init,该函数先配置 FMC 控制器,

然后读取 LCD 控制器的型号,根据控制 IC 的型号执行不同的初始化代码,其简化代码如下:

//初始化 lcd

//该初始化函数可以初始化各种型号的 LCD(详见本.c 文件最前面的描述)

void LCD_Init(void)

{

GPIO_InitTypeDef GPIO_Initure;

FMC_NORSRAM_TimingTypeDef FMC_ReadWriteTim;

FMC_NORSRAM_TimingTypeDef FMC_WriteTim;

__HAL_RCC_GPIOB_CLK_ENABLE();

//开启 GPIOB 时钟

GPIO_Initure.Pin=GPIO_PIN_5;

//PB5,背光控制

GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出

GPIO_Initure.Pull=GPIO_PULLUP;

//上拉

GPIO_Initure.Speed=GPIO_SPEED_HIGH;

//高速

HAL_GPIO_Init(GPIOB,&GPIO_Initure);

LCD_MPU_Config(); //使能 MPU 保护 LCD 区域

SRAM_Handler.Instance= FMC_NORSRAM_DEVICE;

//SRAM BANK1

SRAM_Handler.Extended= FMC_NORSRAM_EXTENDED_DEVICE;

SRAM_Handler.Init.NSBank=FMC_NORSRAM_BANK1;

//使用 NE1

SRAM_Handler.Init.DataAddressMux=FMC_DATA_ADDRESS_MUX_DISABLE;

//地址/数据线不复用

SRAM_Handler.Init.MemoryType=FMC_MEMORY_TYPE_SRAM; //SRAM

SRAM_Handler.Init.MemoryDataWidth=FMC_NORSRAM_MEM_BUS_WIDTH_16;

//16 位数据宽度

SRAM_Handler.Init.BurstAccessMode=FMC_BURST_ACCESS_MODE_DISABLE;

//是否使能突发访问,仅对同步突发存储器有效,此处未用到

SRAM_Handler.Init.WaitSignalPolarity=FMC_WAIT_SIGNAL_POLARITY_LOW;

//等待信号的极性,仅在突发模式访问下有用

SRAM_Handler.Init.WaitSignalActive=FMC_WAIT_TIMING_BEFORE_WS;

//存储器是在等待周期之前的一个时钟周期还是等待周期期间使能 NWAIT

SRAM_Handler.Init.WriteOperation=FMC_WRITE_OPERATION_ENABLE;

//存储器写使能

SRAM_Handler.Init.WaitSignal=FMC_WAIT_SIGNAL_DISABLE;

//等待使能位,此处未用到

SRAM_Handler.Init.ExtendedMode=FMC_EXTENDED_MODE_ENABLE;

//读写使用不同的时序

SRAM_Handler.Init.AsynchronousWait=FMC_ASYNCHRONOUS_WAIT_DISABLE;

//是否使能同步传输模式下的等待信号,此处未用到

SRAM_Handler.Init.WriteBurst=FMC_WRITE_BURST_DISABLE;

//禁止突发写

SRAM_Handler.Init.ContinuousClock=FMC_CONTINUOUS_CLOCK_SYNC_ASYNC;

//FMC 读时序控制寄存器

FMC_ReadWriteTim.AddressSetupTime=0x011; //地址建立时间为 17 个 HCLK

FMC_ReadWriteTim.AddressHoldTime=0x00;

FMC_ReadWriteTim.DataSetupTime=0x55; //数据保存时间(DATAST)为 85 个 HCLK

FMC_ReadWriteTim.AccessMode=FMC_ACCESS_MODE_A; //模式 A

//FMC 写时序控制寄存器

FMC_WriteTim.AddressSetupTime=0x15; //地址建立时间(ADDSET)为 21 个 HCLK

FMC_WriteTim.AddressHoldTime=0x00;

FMC_WriteTim.DataSetupTime=0x015; //数据保存时间(DATAST)为 21 个 HCLK

FMC_WriteTim.AccessMode=FMC_ACCESS_MODE_A; //模式 A

HAL_SRAM_Init(&SRAM_Handler,&FMC_ReadWriteTim,&FMC_WriteTim);

delay_ms(50); // delay 50 ms

//尝试 9341 ID 的读取

LCD_WR_REG(0XD3);

lcddev.id=LCD_RD_DATA(); //dummy read

lcddev.id=LCD_RD_DATA(); //读到 0X00

lcddev.id=LCD_RD_DATA();

//读取 93

lcddev.id<<=8;

lcddev.id|=LCD_RD_DATA();

//读取 41

if(lcddev.id!=0X9341)

//非 9341,尝试看看是不是 NT35310

{

LCD_WR_REG(0XD4);

lcddev.id=LCD_RD_DATA();//dummy read

lcddev.id=LCD_RD_DATA();//读回 0X01

lcddev.id=LCD_RD_DATA();//读回 0X53

lcddev.id<<=8;

lcddev.id|=LCD_RD_DATA();

//这里读回 0X10

if(lcddev.id!=0X5310)

//也不是 NT35310,尝试看看是不是 NT35510

{

LCD_WR_REG(0XDA00);

lcddev.id=LCD_RD_DATA();

//读回 0X00

LCD_WR_REG(0XDB00);

lcddev.id=LCD_RD_DATA();

//读回 0X80

lcddev.id<<=8;

LCD_WR_REG(0XDC00);

lcddev.id|=LCD_RD_DATA();

//读回 0X00

if(lcddev.id==0x8000)lcddev.id=0x5510;

//NT35510 读回的 ID 是 8000H,为方便区分,我们强制设置为 5510

if(lcddev.id!=0X5510)

//也不是 NT5510,尝试看看是不是 SSD1963

{

LCD_WR_REG(0XA1);

lcddev.id=LCD_RD_DATA();

lcddev.id=LCD_RD_DATA();

//读回 0X57

lcddev.id<<=8;

lcddev.id|=LCD_RD_DATA();

//读回 0X61

if(lcddev.id==0X5761)lcddev.id=0X1963;

//SSD1963 读回的 ID 是 5761H,为方便区分,我们强制设置为 1963

}

}

}

printf(" LCD ID:%x",lcddev.id); //打印 LCD ID

if(lcddev.id==0X9341)

//9341 初始化

{

……//9341 初始化代码

}else if(lcddev.id==0xXXXX) //其他 LCD 初始化代码

{

……//其他 LCD 驱动 IC,初始化代码

}

//初始化完成以后,提速

if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X5510||lcddev.id==0X1963)

{

//重新配置写时序控制寄存器的时序

FMC_Bank1E->BWTR[0]&=~(0XF<<0);

//地址建立时间(ADDSET)清零

FMC_Bank1E->BWTR[0]&=~(0XF<<8);

//数据保存时间清零

FMC_Bank1E->BWTR[0]|=5<<0; //地址建立时间(ADDSET)为 5 个 HCLK =21ns

FMC_Bank1E->BWTR[0]|=5<<8;//数据保存时间(DATAST) 为 21ns

}

LCD_Display_Dir(0);

//默认为竖屏显示

LCD_LED(1);

//点亮背光

LCD_Clear(WHITE);

}

该函数先对 FMC 相关 IO 进行初始化,然后是 FMC 的初始化,这个我们在前面都有介绍,

最后根据读到的 LCD ID,对不同的驱动器执行不同的初始化代码,从上面的代码可以看出,

这个初始化函数针对多款不同的驱动 IC 执行初始化操作,这样提高了整个程序的通用性。大家

在以后的学习中应该多使用这样的方式,以提高程序的通用性、兼容性。

这里还要提醒大家,在 LCD_Init 函数中有如下一行代码:

LCD_MPU_Config(); //使能 MPU 保护 LCD 区域

这行代码的作用是调用函数 LCD_MPU_Config 使能 MPU 保护 LCD 区域,而函数

LCD_MPU_Config 定义的内容实际上是我们上一章给大家讲解的使能 MPU 保护 LCD 区域。这

里我们之所以直接在 LCD 程序中加入 MPU 保护,是因为方便大家在移植 LCD 相关代码到自

己的工程中的时候不会因为没有引入 MPU 相关配置而导致 LCD 无 ** 常工作。

特别注意:本函数使用了 printf 来打印 LCD ID,所以,如果你在主函数里面没有初始化串

口,那么将导致程序死在 printf 里面!!如果不想用 printf,那么请注释掉它。

SRAM 初始化 MSP 回调函数 HAL_SRAM_MspInit 内容比较简单,主要是进行时钟使能以

及 IO 口映射配置,这里就不做过多讲解。

LCD 驱动相关的函数就给大家讲解到这里。接下来,我们看看主函数代码如下:

int  ** in(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

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 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,"TFTLCD 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/7/11");

x++;

if(x==12)x=0;

LED0_Toggle;

delay_ms(1000);

}

}

该部分代码将显示一些固定的字符,字体大小包括 32*16、24*12、16*8 和 12*6 等四种,

同时显示 LCD 驱动 IC 的型号,然后不停的切换背景颜色,每 1s 切换一次。而 LED0 也会不停

的闪烁,指示程序已经在运行了。其中我们用到一个 sprintf 的函数,该函数用法同 printf,只

是 sprintf 把打印内容输出到指定的内存区间上,sprintf 的详细用法,请百度学习。

另外特别注意:uart_init 函数,不能去掉,因为在 LCD_Init 函数里面调用了 printf,所以

一旦你去掉这个初始化,就会死机了!实际上,只要你的代码有用到 printf,就必须初始化串口,

否则都会死机,即停在 usart.c 里面的 fputc 函数,出不来。

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

18.4 下载验证

将程序下载到阿波罗 STM32 后,可以看到 DS0 不停的闪烁,提示程序已经在运行了。同

时可以看到 TFTLCD 模块的显示如图 18.4.1 所示:

图 18.4.1 TFTLCD 显示效果图


我们可以看到屏幕的背景是不停切换的,同时 DS0 不停的闪烁,证明我们的代码被正确的

执行了,达到了我们预期的目的。

18.5 STM32CubeMX 配置 FMC(SRAM)

当大家了解了 FMC 的基本工作原理,那么使用 STM32CubeMX 配置 FMC 相关参数就会非

常简单。如果大家对 FMC 没有理解,请仔细看教程学习。这里我们们不再详细讲解每个配置

项的含义。使用 STM32CubeMX 配置 FMC 的一般步骤为:

① 进入 Pinout->FMC 配置栏,配置 FMC 基本参数。根据前面的讲解,这里我们使用的是

BANK1 的第一个分区 NE1,同时吧 LCD 作为 SRAM 使用,19 位地址线,16 位数据线。

配置参数如下图 18.5.1 所示:

图 18.5.1 FMC 配置参数


② 点击 Configuration->FMC 进入 FMC 配置界面,在 NOR/SRAM 1 选项卡之下配置相关参

数。这些参数的含义这里我们不累赘,在 18.1 小节讲解 HAL_SRAM_Init 函数的时候都

有讲解。配置方法如下图 18.5.2 所示:

图 18.5.2 FMC Configuration 配置界面 NOR/PSRAM1 选项卡


在该配置界面,点击右边的 GPIO Settigns 选项卡,还可以配置相关 IO 口的信息。

经过上面配置步骤,我们就可以生成相应的初始化代码,大家生成后和本章实验工

程对比学习。

热门推荐:

cache
Processed in 0.008643 Second.