TFTLCD显示实验_STM32F1开发指南_第十八章

                                                                 第十八章 TFTLCD显示实验

前言

    上一章我们介绍了OLED模块及其显示,但是该模块只能显示单色/双色,不能显示彩色,
而且尺寸也较小。本章我们将介绍ALIENTEK 2.8寸TFT LCD模块,该模块采用TFTLCD面板
,可以显示16位真彩色图片。
    本章将利用stm32开发板上的LCD接口,点亮LCD,并实现ASCII字符和彩色的显示等功能,
并在串口上打印LCD控制器ID,同时在LCD上面显示。
    本章分为如下几个部分:
18.1 TFTLCD和FSMC简介
18.2 硬件设计
18.3 软件设计
18.4 下载验证

18.1 TFTLCD和FSMC简介

    本章通过STM32的FSMC接口来控制TFTLCD的显示,所以本节分为两部分,分别介绍
TFTLCD和FSMC。

18.1.1 TFTLCD简介

    TFTLCD即薄膜晶体管液晶显示器(真彩液晶显示器)。与无源的TN_LCD、STN_LCD的简
单矩阵不同,它在液晶显示屏的每一个像素上都设置有一个薄膜晶体管(TFT),可有效地克
服非选通时的串扰,使液晶屏的静态特性与扫描线数无关,因此大大提高了图像质量。
    TFTLCD的特点:
    1、2.4‘、2.8’、3.5‘、4.3‘、7’ 5种大小的屏幕可选;
    2、320x240的分辨率(3.5'分辨率为320*480、4.3‘和7’分辨率为800*480);
    3、16位真彩色显示;
    4、自带触摸屏,可以用来作为控制输入。
    本章使用2.8寸的ALIENTEK TFTLCD模块为例介绍,该模块支持65k色显示,其中分辨率
为320x240,接口为16位的80并口,自带触摸屏。
TFTLCD显示实验_STM32F1开发指南_第十八章_第1张图片
    模块原理图:
  TFTLCD显示实验_STM32F1开发指南_第十八章_第2张图片  
     TFTLCD模块采用2*17的2.54公排针与外部连接,接口定义如下:
  TFTLCD显示实验_STM32F1开发指南_第十八章_第3张图片
     从图中可以看出,ALIENTEK TFTLCD模块采用16位的并口方式与外部相连,之所以不采用
8位的方式,是因为彩屏的数据量较大,尤其是显示图像时,如果用8位数据线,会比16位方式
慢一倍以上。
     该模块的80并口如下所示:
序号 管脚 功能
1 CS TFTLCD片选信号
2 WR 向TFTLCD写入数据
3 RD 从TFTLCD读取数据
4 D[15:0] 16位双向数据线
5 RST 硬复位TFTLCD
6 RS 命令/数据标志(0:读写命令;1:读写数据)
    TFTLCD模块的RST信号线是直接接到stm32的复位脚上,并不由软件控制,这样可以节省一个
IO口。另外我们还需要一个背光控制线来控制TFTLCD的背光。所以总共需要21个IO口。
    注意:我们标注的DB1~DB8,DB10~DB17,是相对LCD控制IC标注的,实际上可以把他们等
同于D0~D15,这样容易理解。
    ALIENTEK提供2.8/3.5/4.3/7寸等不同尺寸的TFTLCD模块,其驱动芯片有很多类型,比如:
ILI9341/ILI9325/RM68042/RM68021/ILI9320/ILI9328/LGDP4531/LGDP4535/SPFD5408/SSD1289/
1505/B505/C505/NT35310/NT35510等(具体型号,大家可以通过下载本章实验代码,通过串口或者
LCD显示查看),这里仅以ILI9341控制器为例进行介绍。
    ILI9341液晶控制器自带显存,大小为240*320*18/8 = 172800,即18位模式(26万色)下的显存量。
在16位模式下,ILI9341采用RGB565格式存储颜色数据,此时ILI9341的18位数据线与MCU的16位
数据线以及LCD_GRAM的对应关系如下:
9341总线 D17 D16 D15 D14 D13 D12 D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0
MCU数据线
(16位)
D15 D14 D13 D12 D11 NC D10 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0 NC
LCD_GRAM
(16位)
R[4] R[3] R[2] R[1] R[0] NC G[5] G[4] G[3] G[2] G[1] G[0] B[4] B[3] B[2] B[1] B[0] NC
                                                                    16位数据线与显存的对应关系
    从图中可以看出,ILI9341在16位模式下,有用的数据线有:D17~13和D11~1,D0和D12没有使用。
    如上表所示,MCU的16位数据,最低5位表示蓝色,中间6位代表绿色,最高5位代表红色。数值
越大,颜色越深。
    注意:ILI9341所有的指令都是8位的(高8位无效),且参数除了读写GRAM时是16位,其他操作参数,
都是8位,这个和ILI9320等驱动器不一样,必须加以注意。
ILI9341几个重要命令(0xD3, 0x36, 0x2A, 0x2B, 0x2C, 0x2E ):
1)读取LCD控制器的ID号:读取ID4指令:0xD3。
    0xD3后面跟着4个参数,最后两个参数,都出来的是控制器ILI9341的数字部分,从而可以判断所使用
的LCD驱动器的型号。
命令格式如下:
  TFTLCD显示实验_STM32F1开发指南_第十八章_第4张图片
2)控制ILI9341存储器的读写方向:存储器访问控制指令:0x36.
    在连续写GRAM时,可以控制GRAM指针的增长方向,从而控制显示方式(读GRAM也是一样)。
命令格式如下:
TFTLCD显示实验_STM32F1开发指南_第十八章_第5张图片
     0x36指令后面跟着一个参数,这里主要关注3个位:MY、MX和MV。通过这三个位的设置,可
以控制整个ILI9341的全部扫描方向:
TFTLCD显示实验_STM32F1开发指南_第十八章_第6张图片
     这样,利用ILI9341显示内容时,就有很大的灵活性,比如显示BMP图片时,BMP解码数据,
就从图片左下角开始,慢慢显示到右上角。如果设置LCD扫描方向为从左到右、从下到上,则
只需要设置一次坐标,然后不停向LCD里面填充颜色数据即可。大大提高了显示速度。
3)列地址设置指令:0x2A。
    在从左到右,从上到下的扫描方式(默认)下面,该指令用于设置横坐标(x坐标)。
  TFTLCD显示实验_STM32F1开发指南_第十八章_第7张图片
     在默认扫描方式时,该指令用于设置X坐标,该指令带有4个参数,实际是2个坐标值:SC
和EC,即列地址的起始值和结束值。SC必须小于等于EC,且0 ≤ SC/EC ≤ 239。一般在设置
X坐标时,只需要带2个参数即可,即只设置SC即可,因为如果EC未变化,只需要设置一次即
可(在初始化ILI9341时设置),从而提高速度。
4)页地址设置指令:0x2B。
    在从左到右,从上到下的扫描方式(默认)下面,该指令用于设置纵坐标(y坐标)。
TFTLCD显示实验_STM32F1开发指南_第十八章_第8张图片
    在默认扫描方式时,该指令用于设置y坐标。带4个参数,实际是2个坐标值:SP和EP,
即页地址的起始值和结束值,SP必须大于等于EP,且0 ≤ SP/EP ≤ 319。一般在设置Y坐标
时,我们只需要带2个参数即可,即只设置SP即可。因为如果EP无变化,则只需要设置一次即可
(在初始化ILI9341时设置),从而提高速度。
5)写GRAM指令:0x2C。
    在发送该指令后,即可向LCD的GRAM里面写颜色数据了,该指令支持连续写。格式如下:
TFTLCD显示实验_STM32F1开发指南_第十八章_第9张图片
    从上表可知,在收到指令0x2C后,数据有效宽度变为16位,我们可以连续写入LCD GRAM值,
而GRAM的地址将根据MY/MX/MV设置的扫描方向自增。例如:假设设置从左到右、从上到下扫描方式,
则设置好起始坐标(通过SC、SP设置)后,每写入一个颜色值,GRAM地址就会自动增加1(SC++),如
果碰到EC,则回到SC,同时SP++,一直到坐标:EC,EP结束,期间无需设置再次坐标,大大提高写
入速度。
6)读GRAM指令:0x2E。
    用于读取ILI9341的显存(GRAM)
    注:该指令在ILI9341数据手册上的描述有误,真实输出情况如下所示:
TFTLCD显示实验_STM32F1开发指南_第十八章_第10张图片
     该指令用于读取GRAM,上图所示,ILI9341在收到该指令后,第一次输出的是dummy数据,是
无效数据;第二次开始,读取到有效GRAM数据(从坐标:SC,SP开始),输出规律为:每种颜色占8
位,一次输出2个颜色分量。比如:第一次输出R1G1,随后为:B1R2->G2B2->R3G3->B3R4->
G4B4->R5G5...以此类推。
    如果只需要读取一个点的颜色值,则只需要接收到参数3即可。如果要连续读取(利用GRAM地址
自增,方法同上),则按照上述规律接收颜色数据。
    以上就是操作ILI9341常用的几个指令,通过这几个指令,可以很好的控制ILI9341显示我们需要
显示的内容。
    一般TFTLCD模块的使用流程如下:
  TFTLCD显示实验_STM32F1开发指南_第十八章_第11张图片
    画点流程是:设置坐标 -> 写GRAM指令 -> 写入颜色数据,然后在LCD上既可以看到写入的颜色了。
    读点流程是:设置坐标 -> 读GRAM指令 -> 读取颜色数据,这样就可以获取对应点的颜色数据了。
    以上只是最简单的操作,也是最常用的操作。接下来我们将该模块(2.8寸屏模块)用来显示字符和数字,
通过以上介绍,我们可以得出TFTLCD显示需要的相关步骤如下:
1)设置stm32f1与TFTLCD模块相连的IO
    先初始化stm32相关管脚。这里用到FSMC,在下面介绍。
2)初始化TFTLCD模块
    此处没有硬件复位,因为lcd的RST同stm32的Reset连在一起了。
    初始化序列就是向LCD控制器写入一系列设置值(比如伽马校准),这些初始化序列一般LCD供应商
会提供给客户。
3)通过函数将字符和数字显示到TFTLCD模块上
    处理一个点:设置坐标 -> 写GRAM指令 -> 写GRAM。
    处理字符和数字:需要设计一个函数来实现。

18.1.2 FSMC简介

    大容量,且引脚数目在100以上的stm32f103芯片都带有FSMC接口,ALIENTEK战舰stm32开发板
的主芯片为stm32f103zet6,是带有FSMC接口的。
    FSMC,即灵活的 静态存储控制器,能够与同步或异步存储器和16位PC存储卡连接。
    STM32的FSMC接口支持包括SRAM、NAND FLASH、NOR FLASH和PSRAM等存储器。
    FSMC的框图如下所示:
TFTLCD显示实验_STM32F1开发指南_第十八章_第12张图片
    从上图可以看出,stm32的FSMC将外部设备分为3类:NOR/PSRAM设备、NAND
设备、PC卡设备。他们公用地址数据总线等信号,他们具有不同的CS以区分不同的
设备,比如本章的TFTLCD就是用到FSMC_NE4做片选,其实就是将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上面,那么当FSMC控制器写地址0的时候,会使得A0变为0,对于TFTLCD
来说,就是写命令。反之就是写数据了。这样数据和命令就分开来了,他们其实就是对应SRAM
操作的两个连续地址。当然当然RS也可以接在其他地址线上,战舰STM32开发板是把RS连接在
A10上面。
    STM32的FSMC支持8/16/32位数据宽度,我们这里用到的LCD是16位宽度的,所以在设置的时
候,选择16位宽即可。
     FSMC的外部设备地址映像:
    STM32的FSMC将外部存储器划分为固定大小的256M字节的四个存储块,如下图所示:
TFTLCD显示实验_STM32F1开发指南_第十八章_第13张图片
    从上图可以看出,FSMC总共管理1GB空间,拥有4个存储块(Bank),本章用到的是块1,
所以本章仅讨论块1的相关配置,其他块的配置,请参考《STM32参考手册》第19章(324页)
的相关介绍。
    STM32的FSMC存储块1(Bank)被分成4个区,每个区管理64M字节空间,每个区都有独立
的寄存器对应所连接的存储器进行配置。Bank1的256字节空间由28根地址线(HADDR[27:0])寻址。
    这里HADDR是内部AHB地址总线,其中HADDR[25:0]来自外部存储器地址FSMC_A[25:0],而
HADDR[26:27]对4个区进行寻址。如下表所示:
Bank1所选区 片选信号 地址范围 HADDR
[27:26] [25:0]
第一区 FSMC_NE1 0x6000 0000~0x63ff ffff 00 FSMC_A[25:0]
第二区 FSMC_NE2 0x6400 0000~0x67ff ffff 01
第三区 FSMC_NE3 0x6800 0000~0x6Bff ffff 10
第四区 FSMC_NE4 0x6c00 0000~0x6fff ffff 11
   注意:上表中HADDR[25:0]的对应关系:
Bank1接的存储器位宽 HADDR[]和FSMC_A[]的关系
16 HADDR[25:1]   -> FSMC_A[24:0]
8 HADDR[25:0]   -> FSMC_A[25:0]
    不论外部接的设备位宽是8位还是16位,FSMC_A[0]永远接在外部设备的地址A[0]。这里,TFTLCD
使用的是16位数据宽度,所以HADDR[0]并没有用到,只有[25:1]有效。相当于右移一位。
    另外,HADDR[27:26]的设置,不需要我们干预,比如:当选择使用Bank1的第三区,即用FSMC_NE3
来连接外部设备的时候,HADDR[27:26]就为10。
    我们 需要做的就是配置对应第三区的寄存器组,来适应外部设备即可
    STM32的 FSMC各Bank配置寄存器如下表所示:
内部控制器 存储块 管理的地址范围 支持的设备类型 配置寄存器
NOR FLASH
控制器
Bank1 0x6000 0000~0x6fff ffff SRAM/ROM
NOR FLASH
PSRAM
FSMC_BCR   1/2/3/4
FSMC_BTR    1/2/2/3
FSMC_BWTR 1/2/3/4
NAND FLASH/
PC CARD
控制器
Bank2 0X7000 0000~0X7fff ffff NAND FLASH
FSMC_PCR    2/3/4
FSMC_SR       2/3/4
FSMC_PMEM 2/3/4
FSMC_PATT   2/3/4
FSMC_PIO      4
Bank3 0X8000 0000~0X8fff ffff
Bank4 0X9000 0000~0X9fff ffff PC Card
     对于NOR FLASH控制器,主要是通过FSMC_BCRx、FSMC_BTRx和FSMC_BWTRx寄
存器设置(其中x=1~4,对应4个区)。
    通过这3个寄存器,可以设置FSMC访问外部存储器的时序参数,拓宽可选用的外部存储器的速度范围。
    FSMC的NOR FLASH控制器支持同步和异步突发两种 访问方式
    选用 同步突发访问方式时,FSMC将HCLK(系统时钟)分频后,发送给外部存储器作为同步
时钟信号(FSMC_CLK)。此时需要的设置的时间参数有2个:
1、HCLK和FSMC_CLK的分频系数CLKDIV,可以分为2~16分频;
2、同步突发访问中获得第一个数据所需要的等待延迟DATLAT。
    选用 异步突发访问方式时,FSMC主要设置3个时间参数:
1、地址建立时间ADDSET;
2、数据建立时间DATAST;
3、地址保持时间ADDHLD。
    FSMC综合了SRAM/ROM 、PSRAM和NOR FLASH产品的信号特点,定义了四种不同的
异步时序模型。选用不同时序模型时, 需要设置不同的 时序参数
    NOR FLASH控制器支持的时序模型如下表所示:
时序模型 简单描述 需要设置的时间参数
异步 Mode1 SRAM/ CRAM时序 DATAST、ADDSET
ModeA SRAM/ CRAM OE选通时序 DATAST、ADDSET
Mode2/B NOR FLASH时序 DATAST、ADDSET
ModeC NOR FLASH OE选通时序 DATAST、ADDSET
ModeD 延长地址保持时间的异步时序 DATAST、ADDSET、ADDHLK
同步突发 根据同步时钟FSMC_CK读取多个顺序单元的数据 CLKDIV、DATLAT
    在实际扩展时,根据选用存储器的特征确定时序模型,从而确定各时间参数与存储器读/
写周期参数指标之间的计算关系。
    利用该计算关系和存储芯片数据手册中给定的参数指标,可以计算出FSMC所需要的各时
间参数,从而对时间参数寄存器进行合理配置。
    本章,我们使用异步模型A(ModeA)方式控制TFTLCD,
     ModeA的读操作时序如下图:
TFTLCD显示实验_STM32F1开发指南_第十八章_第14张图片
     模式A支持独立的读写时序控制,这个对我们驱动TFTLCD来说非常有用,因为TFTLCD
在读的时候,一般比较慢,而在写的时候可以比较快,如果读写用一样的时序,则只能以读
的时序为基准,从而导致写的速度变慢,或者在读数据时,重新配置FSMC的延时,在读操
作完成时,在配置回写的时序,这样频繁配置,虽然不会降低写的速度,但比较麻烦。而如果
有独立的读写时序控制,则只要初始化时配置好就行了,能同时满足速度和配置步骤的要求。
     ModeA的写操作时序如下图:
TFTLCD显示实验_STM32F1开发指南_第十八章_第15张图片
     从模式A的读写时序,可以看出读操作还存在额外的2个HCLK周期,用于数据存储,
所以同样的配置都操作一般比写操作慢一点。
    上两幅图中的ADDSET和DATAST是通过不同的寄存器配置的。
    接下来讲解一下 Bank1的几个控制寄存器
     1、SRAM/NOR闪存片选控制寄存器:FSMC_BCRx(x=1~4),该寄存器各位描述如下:
TFTLCD显示实验_STM32F1开发指南_第十八章_第16张图片
     本章用到的几个位如下所示:
序号 位域 功能
1 EXTMOD 扩展模式使能位,即是否允许读写不同的时序。
本章需要,所以该位为1。
2 WREN 写使能位。
本章要向LCD写数据,所以该位为1。
3 MWID[1:0] 存储器数据总线宽度。00:8位;01:16位;10和11保留。
本章用16位数据线,所以该位为01。
4 MTYP[1:0] 存储器类型。00:SRAM\ROM;01:PSRAM;10:NOR FLASH;11:保留。
本章把LCD当作SRAM使用,所以设置为00。
5 MBKEN 存储块是能位。
本章需要用该存储块控制LCD,所以使能这个存储块。
     2、SRAM/NOR闪存片选时序寄存器:FSMC_BTRx(x=1~4),该寄存器各位描述如下:

     这个寄存器包含了 每个存储器块的控制信息,可以用于SRAM\ROM\NOR闪存存储器。
    如果FSMC_BCRx中使能了扩展模式,则有两个时序寄存器分别对应读(本寄存器)和写操作
(FSMC_BWTRx寄存器)。
    因为我们要求读写分开时序控制,所以EXTMODE使能了。即本寄存器是读操作时序寄存器,
控制读操作的相关时序。
    本章用到的几个位域如下所示:
序号 位域 功能
1 ACCMOD[1:0] 访问模式。
00:模式A;01:模式B;10:模式C;11:模式D。
本章用到模式A,所以设置为00.
2 DATAST[7:0] 数据保持时间。
0为保留设置,其他设置则代表保持时间为:DATAST个HCLK时钟周期,最大为255个HCLK周期。
对于ILI9341来说,其实就是RD低电平持续时间,一般为355ns。而一个HCLK时钟周期为13.8ns左右(1/72Mhz),为了兼容其它屏,这里设置DATAST为15,即16个HCLK周期,时间大约为234ns(未计算数据存储的2个HCLK时间,对于9341来说超频了,但实际可以正常使用)。
3 ADDSET[3:0] 地址建立时间。
其建立时间为:ADDSET个HCLK周期,最大为15个HCLK周期。对ILI9341来说,这里相当于RD高电平持续时间,为90ns,本来这里应该设置为和DATAST一样,但由于stm32f103 FSMC的性能问题,就算设置ADDSET为0,RD的高电平持续时间也达到了190ns以上,故此处设置ADDSET为较小值。
本章设置ADDSET为1,即2个HCLK周期,实际RD高电平大于200ns。
    3、SRAM/NOR闪存写时序寄存器:FSMC_BWTRx(x=1~4),该寄存器各位描述如下:

     该寄存器为写操作时序控制寄存器。
     本章用到的几个位域和读操作时序一模一样,如下所示:
序号 位域 功能
1 ACCMOD[1:0] 同FSMC_BTRx一样,选择模式A
2 DATAST[7:0] 对应低电平持续时间,设置为3.
3 ADDSET[3:0] 对应高电平持续时间,设置为0.
    注:对于ILI9341来说,DATAST[7:0]和ADDSET[3:0]的持续时间只需要15ns就够了,比读操作
快得多。
    所以我们这里设置DATAST为3,即4个HCLK周期,时间约55ns(因为9320等控制器,这个时间
要求比较长,要50ns)。设置ADDSET(也存在性能问题)为0,即1个HCLK周期,实际WR电平时间
大于100ns。
    至此,我们对STM32的FSMC介绍差不多了,通过上面两节,可以开始写LCD驱动代码了。
    注意:在MDK的寄存器里面,没有定义FSMC_BCRx、FSMC_BTRx、FSMC_BWTRx等这些
单独的寄存器,而是将他们进行了组合。
BTCR[8]:FSMC_BCRx和FSMC_BTRx:
BTCR[7] BTCR[6] BTCR[5] BTCR[4] BTCR[3] BTCR[2] BTCR[1] BTCR[0]
FSMC_BTR4 FSMC_BCR4 FSMC_BTR3 FSMC_BCR3 FSMC_BTR2 FSMC_BCR2 FSMC_BTR1 FSMC_BCR1
BWTR[7]:FSMC_BWTRx:
BWTR[6] BWTR[5] BWTR[4] BWTR[3] BWTR[2] BWTR[1] BWTR[0]
FSMC_BWTR4 reserved FSMC_BWTR3 reserved FSMC_BWTR2 reserved FSMC_BWTR1
    通过以上讲解,大家对FSMC的原理有了一个初步认识,如果还不熟悉,请搜索网络资料理解
FSMC的原理。只有理解了原理,使用库函数才可以得心应手。
FSMC相关库函数
1.FSMC初始化函数:
    根据前面讲解,初始化FSMC主要是初始化三个寄存器FSMC_BCRx、FSMC_BTRx和FSMC_BWTRx,
在固件库中怎么初始化他们的函数分别为:
FSMC_NORSRAMInit();    //初始化NOR和SRAM
FSMC_NANDInit();
FSMC_PCCARDInit();
    这三个函数分别用来初始化4种类型存储器,这里根据名字很好判断。
初始化NOR和SRAM的函数定义如下:
void FSMC_NORSRAMInit(FSMC_NORSRAMInitTypeDef* FSMC_NORSRAMInitStruct);
    这个函数只有一个入参,FSMC_NORSRAMInitTypeDef类型指针变量,其成员变量如下:
typedefstruct
{
uint32_t FSMC_Bank;
uint32_t FSMC_DataAddressMux;
uint32_t FSMC_MemoryType;
uint32_t FSMC_MemoryDataWidth;
uint32_t FSMC_BurstAccessMode;
uint32_t FSMC_AsynchronousWait;
uint32_t FSMC_WaitSignalPolarity;
uint32_t FSMC_WrapMode;
uint32_t FSMC_WaitSignalActive;
uint32_t FSMC_WriteOperation;
uint32_t FSMC_WaitSignal;
uint32_t FSMC_ExtendedMode;
uint32_t FSMC_WriteBurst;
    FSMC_NORSRAMTimingInitTypeDef* FSMC_ReadWriteTimingStruct;
    FSMC_NORSRAMTimingInitTypeDef* FSMC_WriteTimingStruct;
}FSMC_NORSRAMInitTypeDef;
    从这个结构体可以看出,前面有13个基本类型(uint32_t)的成员变量,用来配置片选控制寄存器
FSMC_BCRx。
    最后还有两个FSMC_NORSRAMTimingInitTypeDef指针类型的成员变量。前面讲到过,FSMC
有读时序和写时序之分,这里就是用来设置读时序和写时序的参数。这两个参数是用来配置寄存器
FSMC_BTRx和FSMC_BWTRx。
     模式A下上面的相关参数怎么配置?
序号 参数 设置
1 FSMC_Bank 设置使用到的存储块标号和区号。
此处使用的是存储块1区号4,所以选择值为FSMC_Bank1_NORSRAM4。
2 FSMC_MemoryType 设置存储类型。
此处使用SRAM,所以选择值为FSMC_MemoryType_SRAM。
3 FSMC_MemoryDataWidth 设置数据宽度。
这里用的16位,所以选择值为FSMC_MemoryDataWidth_16b。
4 FSMC_WriteOperation 设置写是能。
此处要向TFT写数据,所以要写使能,选择值为FSMC_WriteOperation_Enable。
5 FSMC_ExtendedMode 设置扩展模式使能,即是否允许读写不同的时序。
此处使用读写不同时序,所以设置值为FSMC_ExtendedMode_Enable。
6 FSMC_DataAddressMux 设置地址/数据复用使能,若设置为使能,则地址的低16位和数据将共用数据总线,仅对NOR和PSRAM有效。
此处设置为默认值不复用,值为FSMC_DataAddressMux_Disable。
上面这些参数都是和模式A相关。
7 FSMC_BurstAccessMode
FSMC_AsynchronousWait
FSMC_WaitSignalPolarity
FSMC_WaitSignalActive
FSMC_WrapMode
FSMC_WaitSignal
FSMC_WriteBurst
这些参数在组成模式同步模式才需要设置,参考中文参考手册。
8 FSMC_ReadWriteTimingStruct 初始化片选控制寄存器FSMC_BTRx。
9 FSMC_WriteTimingStruct 初始化写操作时序寄存器FSMC_BWTRx。
FSMC_NORSRAMTimingInitTypeDef类型定义如下:
typedefstruct
{
uint32_t FSMC_AddressSetupTime;
uint32_t FSMC_AddressHoldTime;
uint32_t FSMC_DataSetupTime;
uint32_t FSMC_BusTurnAroundDuration;
uint32_t FSMC_CLKDivision;
uint32_t FSMC_DataLatency;
uint32_t FSMC_AccessMode;
}FSMC_NORSRAMTimingInitTypeDef;
    这个结构体有7个参数用来设置FSMC读写时序。这些参数主要是设计地址建立保持时间,数据建立时间等配置。
这些参数的意义在前面有讲解。
    本章读写时序不同,读写速度要求不同,所以参数FSMC_DataSetupTime设置不同的值。
2.FSMC使能函数:
    FSMC对不同的存储器类型同样提供了不同的使能函数:
void FSMC_NORSRAMCmd(uint32_t FSMC_Bank,FunctionalStateNewState);
void FSMC_NANDCmd   (uint32_t FSMC_Bank,FunctionalStateNewState);
void FSMC_PCCARDCmd (FunctionalStateNewState);
本章使用SRAM,所以使用第一个函数。

18.2 硬件设计

    本实验用到的硬件有:
1)指示灯DS0;
2)TFTLCD模块;
    TFTLCD模块的电路图如下所示:
TFTLCD显示实验_STM32F1开发指南_第十八章_第17张图片
    在硬件上,TFTLCD模块与开发板上单片机的IO口对应关系如下:
LCD_BL -> PB0
LCD_CS -> PG12(FSMC_NE4)
LCD_RS -> PG0 (FSMC_A10) 
LCD_WR -> PD5 (FSMC_NWE)
LCD_RD -> PD4 (FSMC_NOE)
LCD_D[15:0] -> FSMC_D15:FSMC_D0

18.3 软件设计

    本实验,LCD的RS接FSMC的A10上面,CS接FSMC_NE4,16位数据总线。即使用FSMC存储器
1的第四区,我们定义如下LCD操作结构体(在lcd.h里面定义):
//LCD操作结构体
typedef sturct
{
    vu16 LCD_REG;
    vu16 LCD_RAM;
}LCD_TypeDef;
//使用NOR/SRAM的Bank1.sector4,地址位HADDR[27,26] = 11,A10作为数据命令区分线
//注意:16位数据总线时,stm32内部地址会右移一位对齐!
#define LCD_BASE    ((u32)(0x6C000000 | 0x000007fe))
#define LCD         ((LCD_TypeDef*)LCD_BASE)
    其中,LCD_BASE,必须根据外部电路的连接确定,我们使用Bank1.sector4就是从
地址0x6c000000开始,而0x000007fe,则是A10的偏移量。我们把这个地址强制转换成
LCD_TypeDef结构体地址,则可以得到LCD->LCD_REG的地址就是0x6c00, 07fe,对
应A10的状态为0(即RS=0),而LCD->LCD_RAM的地址就是0x6c00 0800(结构体地址
自增),对应A10的状态为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口方向都是由FSMC控制的,不需要手动设置。

    lcd.h中另一个重要的结构体:
//LCD重要参数集
typedefstruct
{
    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横竖屏状态等,这个结构体虽然占用了10个字节的内容,但是却可以让我们的
驱动函数支持不同尺寸的LCD,同时可以实现LCD横竖屏切换等重要功能,所以还
是利大于弊的。

    lcd.c里面的一些重要函数:
先看 7个简单,但很重要的函数
//写寄存器函数
//regval:寄存器值
void LCD_WR_REG(u16 regval)
{
    LCD->LCD_REG = regval;//写入要写的寄存器序号
}
//写LCD数据
//data:要写入的数据
void LCD_WR_DATA(u16 data)
{
    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    
}
    因为FSMC自动控制了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);
  }
  elseif(lcddev.id ==0x6804)
  {
    if(lcddev.dir ==1)
	Xpos= lcddev.width -1-Xpos;//横屏时处理
        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);
  }
  elseif(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);
    }
    elseif(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);
    }
    else
    {
        if(lcddev.dir==1)
            Xpos=lcddev.width-1-Xpos;//横屏其实就是调转 x,y 坐标 
         LCD_WriteReg(lcddev.setxcmd,Xpos);
         LCD_WriteReg(lcddev.setycmd,Ypos);
    }
}
    该函数非常重要,其实现了将LCD的当前操作点设置到指定坐标(x,y),有了该函数,
我们可以在液晶上任意作图了。
    这里面的lcddev.setxcmd、lcddev.setycmd、lcddev.width、lcddev.height等指令/
参数都是在LCD_Display_Dir函数里面初始化的,该函数根据lcddev.id的不同,执行不同设置。
    由于9341/5310/6804/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的GRAM。
    注:为什么OLED模块没有读GRAM函数,而此处需要?
    因为OLED模块是单色的,所需要全部GRAM也就1k字节,而TFTLCD模块是彩色的,点数也比
OLED模块多很多,以16位色计算,一款320x240的液晶,需要320x240x2个字节来存储颜色值,也
就是需要150k字节,这对任何一款单片机来说,都不是小数目。
    而我们在图形叠加时,可以先读回原来的值,再写入新值,在完成叠加后,又恢复原来的值。这样
在做一些简单菜单时,很有用。
    这里的读点函数返回值为读到的GRAM的值。使用之前要先设置读取的GRAM地址,通过
LCD_SetCursor函数实现。
    LCD_ReadPoint代码如下:
//func:读取某个点的颜色值
//入参:x, y:坐标
//出参:此点的颜色
u16 LCD_ReadPoint(u16 x, u16 y)
{
    vu16 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 == 0x6804 ||
       lcddev.id == 0x5310 || lcddev.id == 0x1963)
        LCD_WR_REG(0x2e);      //9341、6804、3510、1963 发送读GRAM指令
    else if(lcddev.id == 0x5510)
        LCD_WR_REG(0x2e00);    //5510 发送读GRAM指令
    else
        LCD_WR_REG(0x22);      //其他IC,发送读GRAM指令
 
    if(lcddev.id == 0x9320)
        opt_delay(2);          //for 9320,延时2us
    r = LCD_RD_DATA();         //dummy Read 假读
    
    if(lcddev.id == 0x1963)
        return r;              //1963直接读就可以
    opt_delay(2);              
    r = LCD_RD_DATA();         //实际坐标颜色
    if(lcddev.id == 0x9341 || lcddev.id == 0x5310 || lcddev.id == 0x5510)    //这些LCD要分2次读出
    {
        opt_delay(2);
        b = LCD_RD_DATA();
        g = r & 0xff;          //对于9341、5310、5510,第一次读取的是RG值,先R后G,各占8位
        g <<= 8;
    }    
    if(lcddev.id == 0x9325 || lcddev.id == 0x4535 || lcddev.id == 0x4531 ||
       lcddev.id == 0xb505 || lcddev.id == 0xc505)
        return r;              //这几种IC,直接返回颜色值
    else if(lcddev.id == 0x9341 || lcddev.id == 0x5310 || lcddev.id == 0x5510)
        return (((r >> 11) << 11) | ((g >> 10) << 5) | (b >> 11));    //ILI9341、NT35310、NT3510需要公式转换一下
    else
        return LCD_BGR2RGB(r);    //其他IC
}
第十一个函数:LCD_ShowChar: 
    该函数同前面OLED模块的字符显示函数差不多,但此处字符显示函数多了一个功能,
即以叠加/非叠加方式显示。    叠加方式显示多用于在显示的图片上再显示字符。非叠加
方式一般用于普通的显示。
//在指定地址显示一个字符
//x,y:起始坐标
//num:要显示的字符:“ ”--->"~"
//size:字体大小 12/16/24
//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
            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一样,只是带了颜色参数,且减少了函数的调用时间。该代码
在我们用到的3个字符集点阵数据数组asc2_2412\asc2_1206\asc2_1608,这几个字符集的点
阵数据的提取方式,同17章相同。
最后,介绍TFTLCD模块的初始化函数LCD_Init:
    该函数先初始化stm32与TFTLCD连接的IO口,并配置FSMC控制器,然后读取LCD控制器
的型号,根据型号来执行不同的初始化代码。
      从初始化代码可以看出,LCD初始化步骤为①~⑤在代码中的标注:
① GPIO、FSMC、AFIO时钟使能;
② GPIO初始化:GPIO_Init()函数;
③ FSMC初始化:FSMC_NORSRAMInit()函数;
④ FSMC使能:FSMC_NORSRAMCmd()函数;
⑤ 不同的LCD驱动器的初始化代码。
//初始化LCD
//注:改初始化函数可以初始化各种ILI93XX液晶,但是其他函数是基于ILI9320的!!!
//在其他型号的驱动芯片上没有测试!
void LCD_Init(void)
{
    GPIO_InitTypeDef                  GPIO_InitStruct;
    FSMC_NORSRAMInitTypeDef           FSMC_NSInitStructure;
    FSMC_NORSRAMTimingInitTypeDef     readWriteTiming;
    FSMC_NORSRAMTimingInitTypeDef     writeTiming;
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE);                   //使能 FSMC 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOD|
                           RCC_APB2Periph_GPIOE|RCC_APB2Periph_GPIOG|
                           RCC_APB2Periph_AFIO, ENABLE);                 // ①使能GPIO 以及AFIO 复用功能时钟   
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;              //PB0 推挽输出 背光
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;       //推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);                             //②初始化 PB0
    //PORTD 复用推挽输出
    GPIO_InitStructure.GPIO_Pin= GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5|
    GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_14|GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;         //复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOD, &GPIO_InitStructure);                             //②初始化 PORTD
    //PORTE 复用推挽输出
    GPIO_InitStructure.GPIO_Pin =GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|
    GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;     //复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOE, &GPIO_InitStructure);              //②初始化 PORTE
    //PORTG12 复用推挽输出 A0
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_12; //PORTD 
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_12; //PORTD 复用推挽输出
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;      //复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOG, &GPIO_InitStructure);                //②初始化 PORTG 
    readWriteTiming.FSMC_AddressSetupTime = 0x01;       //地址建立时间 2 个 HCLK 1
    readWriteTiming.FSMC_AddressHoldTime = 0x00;        //地址保持时间模式 A 未用到
    readWriteTiming.FSMC_DataSetupTime = 0x0f;          // 数据保存时间为 16 个 HCLK
    readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
    readWriteTiming.FSMC_CLKDivision = 0x00;
    readWriteTiming.FSMC_DataLatency = 0x00;
    readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A; //模式 A
   
    writeTiming.FSMC_AddressSetupTime = 0x00;         //地址建立时间为 1 个 HCLK    
    writeTiming.FSMC_AddressHoldTime = 0x00;          //地址保持时间( A
    writeTiming.FSMC_DataSetupTime = 0x03;            //数据保存时间为 4 个 HCLK
    writeTiming.FSMC_BusTurnAroundDuration = 0x00;
    writeTiming.FSMC_CLKDivision = 0x00;
    writeTiming.FSMC_DataLatency = 0x00;
    writeTiming.FSMC_AccessMode = FSMC_AccessMode_A; //模式 A 
    FSMC_NSInitStructure.FSMC_Bank              = FSMC_Bank1_NORSRAM4;         //这里我们使用NE4,也就对应BTCR[6],[7]。
    FSMC_NSInitStructure.FSMC_DataAddressMux    = FSMC_DataAddressMux_Disable;  //不复用数据地址
    FSMC_NSInitStructure.FSMC_MemoryType        = FSMC_MemoryType_SRAM;         // SRAM
    FSMC_NSInitStructure.FSMC_MemoryDataWidth   = FSMC_MemoryDataWidth_16b;     //存储器数据宽度为 16bit
    FSMC_NSInitStructure.FSMC_BurstAccessMode   = FSMC_BurstAccessMode_Disable;
    FSMC_NSInitStructure.FSMC_WaitSignalPolarity  = FSMC_WaitSignalPolarity_Low;
    FSMC_NSInitStructure.FSMC_AsynchronousWait  = FSMC_AsynchronousWait_Disable;
    FSMC_NSInitStructure.FSMC_WrapMode          =  FSMC_WrapMode_Disable;
    FSMC_NSInitStructure.FSMC_WaitSignalActive  =  FSMC_WaitSignalActive_BeforeWaitState;
    FSMC_NSInitStructure.FSMC_WriteOperation    =  FSMC_WriteOperation_Enable;            //存储器写使能
    FSMC_NSInitStructure.FSMC_WaitSignal        = FSMC_WaitSignal_Disable;
    FSMC_NSInitStructure.FSMC_ExtendedMode      = FSMC_ExtendedMode_Enable;                        
    // 读写使用不同的时序
    FSMC_NSInitStructure.FSMC_WriteBurst            = FSMC_WriteBurst_Disable;
    FSMC_NSInitStructure.FSMC_ReadWriteTimingStruct = &readWriteTiming;
    FSMC_NSInitStructure.FSMC_WriteTimingStruct     = &writeTiming;      //写时序
    FSMC_NORSRAMInit(&FSMC_NSInitStructure);                             //③初始化FSMC 配置
    FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4, ENABLE);                        //④使能BANK1
    delay_ms(50); // delay 50 ms 

    lcddev.id = LCD_ReadReg(0x0000);    //读ID(9320\9325\9328\4531\4535等IC)
    if(lcddev.id < 0xff || lcddev.id == 0xffff || lcddev.id == 0x9300)
    //ID不正确,新增0x9300判断,因为9341在未被复位时,会被读成9300
    {
        //尝试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,尝试是不是6804
        {
            LCD_WR_REG(0xBF);
            lcddev.id = LCD_RD_DATA();    //dummy read
            lcddev.id = LCD_RD_DATA();    //读到0x01
            lcddev.id = LCD_RD_DATA();    //读到0xD0
            lcddev.id = LCD_RD_DATA();    //读到0x68
            lcddev.id <<= 8;
            lcddev.id |= LCD_RD_DATA();    //读取0x04
            if(lcddev.id != 0x6804)  //也不是6804,尝试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,尝试NT35110
                {
                    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)  //也不是5510,尝试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\r\n",lcddev.id); //打印 LCD ID
    if(lcddev.id==0X9341) //9341 初始化
    {
    ……//9341 初始化代码
    }
    else if(lcddev.id==0xXXXX) //其他 LCD 初始化代码
    {
    ……//其他 LCD 驱动 IC,初始化代码
    }
    LCD_Display_Dir(0); //默认为竖屏显示
    LCD_LED=1; //点亮背光
    LCD_Clear(WHITE); 
}
main.c:
    该部分代码将显示一些固定的字符,字体大小包括24*12、16*8和12*6等三种,同时显示
LCD驱动IC的型号,然后不停地切换背景颜色,每秒切换一次。而LED0也不停地闪烁,指示
程序已经运行了。
    其中用到了一个sprintf函数,该函数用法同printf,只是sprintf把打印内容输出到指定的内存
区间上,sprintf的详细用法,请百度。
int main(void)
{
    u8 x = 0;
    u8 lcd_id[12];        //存放LCD ID字符串
    delay_init();         //延时函数初始化
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);    //设置NVIC中断分组2
    uart_init(115200);    //串口初始化波特率115200
    LED_Init();           //LED初始化
    LCD_Init();                
    POINT_COLOR = RED;
    sprintf((char *)lcd_id, "LCD ID:%04X", lcddev.id);        //将LCD ID打印到lcd_id数组
    while(1)
    {
        case 0:LCD_Clear(WHITE);    break;
        case 1:LCD_Clear(BLACK);    break;
        case 2:LCD_Clear(BLUE);     break;
        case 3:LCD_Clear(RED);      break;
        case 4:LCD_Clear(MAGENTA);  break;
        case 5:LCD_Clear(GREEN);    break;
        case 6:LCD_Clear(CYAN);     break;
        case 7:LCD_Clear(YELLOW);   break;
        case 8:LCD_Clear(BRRED);    break;
        case 9:LCD_Clear(GRAY);     break;
        case 10:LCD_Clear(LGRAY);   break;
        case 11:LCD_Clear(BROWN);   break; 
    }
    POINT_COLOR=RED;
    LCD_ShowString(30,40,210,24,24,"WarShip STM32 ^_^");
    LCD_ShowString(30,70,200,16,16,"TFTLCD TEST");
    LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
    LCD_ShowString(30,110,200,16,16,lcd_id);                             //显示 LCD ID 
    LCD_ShowString(30,130,200,12,12,"2014/5/4"); 
    x++;
    if(x == 12)
        x = 0;
    LED0 = !LED0
    delay_ms(1000);
}

18.4下载验证

    将程序下载到战舰STM32后,可以看到DS0不停地闪烁,提示程序已经在运行了。同时可以
看到TFTLCD模块显示如下图所示:
TFTLCD显示实验_STM32F1开发指南_第十八章_第18张图片
     可以看到屏幕背景不停地切换,同时DS0不停地闪烁,证明我们的代码被正确地
执行了。
    另外,本例程除了不支持CPLD方案的7寸屏模块,其余所有的ALIENTEK TFTLCD
模块都可以支持,直接插上去即可使用。
















你可能感兴趣的:(应用-,stm32)