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

来源:互联网 发布:java阅读软件 编辑:程序博客网 时间:2024/05/20 17:58
                                                                第十八章 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模块采用2*17的2.54公排针与外部连接,接口定义如下:
 
     从图中可以看出,ALIENTEK TFTLCD模块采用16位的并口方式与外部相连,之所以不采用
8位的方式,是因为彩屏的数据量较大,尤其是显示图像时,如果用8位数据线,会比16位方式
慢一倍以上。
    该模块的80并口如下所示:
序号管脚功能1CSTFTLCD片选信号2WR向TFTLCD写入数据3RD从TFTLCD读取数据4D[15:0]16位双向数据线5RST硬复位TFTLCD6RS命令/数据标志(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总线D17D16D15D14D13D12D11D10D9D8D7D6D5D4D3D2D1D0MCU数据线
(16位)D15D14D13D12D11NCD10D9D8D7D6D5D4D3D2D1D0NCLCD_GRAM
(16位)R[4]R[3]R[2]R[1]R[0]NCG[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驱动器的型号。
命令格式如下:
 
2)控制ILI9341存储器的读写方向:存储器访问控制指令:0x36.
    在连续写GRAM时,可以控制GRAM指针的增长方向,从而控制显示方式(读GRAM也是一样)。
命令格式如下:

     0x36指令后面跟着一个参数,这里主要关注3个位:MY、MX和MV。通过这三个位的设置,可
以控制整个ILI9341的全部扫描方向:

     这样,利用ILI9341显示内容时,就有很大的灵活性,比如显示BMP图片时,BMP解码数据,
就从图片左下角开始,慢慢显示到右上角。如果设置LCD扫描方向为从左到右、从下到上,则
只需要设置一次坐标,然后不停向LCD里面填充颜色数据即可。大大提高了显示速度。
3)列地址设置指令:0x2A。
    在从左到右,从上到下的扫描方式(默认)下面,该指令用于设置横坐标(x坐标)。
 
     在默认扫描方式时,该指令用于设置X坐标,该指令带有4个参数,实际是2个坐标值:SC
和EC,即列地址的起始值和结束值。SC必须小于等于EC,且0 ≤ SC/EC ≤ 239。一般在设置
X坐标时,只需要带2个参数即可,即只设置SC即可,因为如果EC未变化,只需要设置一次即
可(在初始化ILI9341时设置),从而提高速度。
4)页地址设置指令:0x2B。
    在从左到右,从上到下的扫描方式(默认)下面,该指令用于设置纵坐标(y坐标)。

    在默认扫描方式时,该指令用于设置y坐标。带4个参数,实际是2个坐标值:SP和EP,
即页地址的起始值和结束值,SP必须大于等于EP,且0 ≤ SP/EP ≤ 319。一般在设置Y坐标
时,我们只需要带2个参数即可,即只设置SP即可。因为如果EP无变化,则只需要设置一次即可
(在初始化ILI9341时设置),从而提高速度。
5)写GRAM指令:0x2C。
    在发送该指令后,即可向LCD的GRAM里面写颜色数据了,该指令支持连续写。格式如下:

    从上表可知,在收到指令0x2C后,数据有效宽度变为16位,我们可以连续写入LCD GRAM值,
而GRAM的地址将根据MY/MX/MV设置的扫描方向自增。例如:假设设置从左到右、从上到下扫描方式,
则设置好起始坐标(通过SC、SP设置)后,每写入一个颜色值,GRAM地址就会自动增加1(SC++),如
果碰到EC,则回到SC,同时SP++,一直到坐标:EC,EP结束,期间无需设置再次坐标,大大提高写
入速度。
6)读GRAM指令:0x2E。
    用于读取ILI9341的显存(GRAM)
    注:该指令在ILI9341数据手册上的描述有误,真实输出情况如下所示:

     该指令用于读取GRAM,上图所示,ILI9341在收到该指令后,第一次输出的是dummy数据,是
无效数据;第二次开始,读取到有效GRAM数据(从坐标:SC,SP开始),输出规律为:每种颜色占8
位,一次输出2个颜色分量。比如:第一次输出R1G1,随后为:B1R2->G2B2->R3G3->B3R4->
G4B4->R5G5...以此类推。
    如果只需要读取一个点的颜色值,则只需要接收到参数3即可。如果要连续读取(利用GRAM地址
自增,方法同上),则按照上述规律接收颜色数据。
    以上就是操作ILI9341常用的几个指令,通过这几个指令,可以很好的控制ILI9341显示我们需要
显示的内容。
    一般TFTLCD模块的使用流程如下:
 
    画点流程是:设置坐标 -> 写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的框图如下所示:

    从上图可以看出,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字节的四个存储块,如下图所示:

    从上图可以看出,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_NE10x6000 0000~0x63ff ffff00FSMC_A[25:0]第二区FSMC_NE20x6400 0000~0x67ff ffff01第三区FSMC_NE30x6800 0000~0x6Bff ffff10第四区FSMC_NE40x6c00 0000~0x6fff ffff11
   注意:上表中HADDR[25:0]的对应关系:
Bank1接的存储器位宽HADDR[]和FSMC_A[]的关系16HADDR[25:1]   -> FSMC_A[24:0]8HADDR[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
控制器Bank10x6000 0000~0x6fff ffffSRAM/ROM
NOR FLASH
PSRAMFSMC_BCR   1/2/3/4
FSMC_BTR    1/2/2/3
FSMC_BWTR 1/2/3/4
NAND FLASH/
PC CARD
控制器Bank20X7000 0000~0X7fff ffffNAND FLASH
FSMC_PCR    2/3/4
FSMC_SR       2/3/4
FSMC_PMEM 2/3/4
FSMC_PATT   2/3/4
FSMC_PIO      4
Bank30X8000 0000~0X8fff ffffBank40X9000 0000~0X9fff ffffPC 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控制器支持的时序模型如下表所示:
时序模型简单描述需要设置的时间参数异步Mode1SRAM/ CRAM时序DATAST、ADDSETModeASRAM/ CRAM OE选通时序DATAST、ADDSETMode2/BNOR FLASH时序DATAST、ADDSETModeCNOR FLASH OE选通时序DATAST、ADDSETModeD延长地址保持时间的异步时序DATAST、ADDSET、ADDHLK同步突发根据同步时钟FSMC_CK读取多个顺序单元的数据CLKDIV、DATLAT
    在实际扩展时,根据选用存储器的特征确定时序模型,从而确定各时间参数与存储器读/
写周期参数指标之间的计算关系。
    利用该计算关系和存储芯片数据手册中给定的参数指标,可以计算出FSMC所需要的各时
间参数,从而对时间参数寄存器进行合理配置。
    本章,我们使用异步模型A(ModeA)方式控制TFTLCD,
    ModeA的读操作时序如下图:

     模式A支持独立的读写时序控制,这个对我们驱动TFTLCD来说非常有用,因为TFTLCD
在读的时候,一般比较慢,而在写的时候可以比较快,如果读写用一样的时序,则只能以读
的时序为基准,从而导致写的速度变慢,或者在读数据时,重新配置FSMC的延时,在读操
作完成时,在配置回写的时序,这样频繁配置,虽然不会降低写的速度,但比较麻烦。而如果
有独立的读写时序控制,则只要初始化时配置好就行了,能同时满足速度和配置步骤的要求。
    ModeA的写操作时序如下图:

     从模式A的读写时序,可以看出读操作还存在额外的2个HCLK周期,用于数据存储,
所以同样的配置都操作一般比写操作慢一点。
    上两幅图中的ADDSET和DATAST是通过不同的寄存器配置的。
    接下来讲解一下Bank1的几个控制寄存器
    1、SRAM/NOR闪存片选控制寄存器:FSMC_BCRx(x=1~4),该寄存器各位描述如下:

     本章用到的几个位如下所示:
序号位域功能1EXTMOD扩展模式使能位,即是否允许读写不同的时序。
本章需要,所以该位为1。2WREN写使能位。
本章要向LCD写数据,所以该位为1。3MWID[1:0]存储器数据总线宽度。00:8位;01:16位;10和11保留。
本章用16位数据线,所以该位为01。4MTYP[1:0]存储器类型。00:SRAM\ROM;01:PSRAM;10:NOR FLASH;11:保留。
本章把LCD当作SRAM使用,所以设置为00。5MBKEN存储块是能位。
本章需要用该存储块控制LCD,所以使能这个存储块。
    2、SRAM/NOR闪存片选时序寄存器:FSMC_BTRx(x=1~4),该寄存器各位描述如下:

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

     该寄存器为写操作时序控制寄存器。
     本章用到的几个位域和读操作时序一模一样,如下所示:
序号位域功能1ACCMOD[1:0]同FSMC_BTRx一样,选择模式A2DATAST[7:0]对应低电平持续时间,设置为3.3ADDSET[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_BTR4FSMC_BCR4FSMC_BTR3FSMC_BCR3FSMC_BTR2FSMC_BCR2FSMC_BTR1FSMC_BCR1
BWTR[7]:FSMC_BWTRx:
BWTR[6]BWTR[5]BWTR[4]BWTR[3]BWTR[2]BWTR[1]BWTR[0]FSMC_BWTR4reservedFSMC_BWTR3reservedFSMC_BWTR2reservedFSMC_BWTR1
    通过以上讲解,大家对FSMC的原理有了一个初步认识,如果还不熟悉,请搜索网络资料理解
FSMC的原理。只有理解了原理,使用库函数才可以得心应手。
FSMC相关库函数
1.FSMC初始化函数:
    根据前面讲解,初始化FSMC主要是初始化三个寄存器FSMC_BCRx、FSMC_BTRx和FSMC_BWTRx,
在固件库中怎么初始化他们的函数分别为:
FSMC_NORSRAMInit();    //初始化NOR和SRAMFSMC_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下上面的相关参数怎么配置?
序号参数设置1FSMC_Bank设置使用到的存储块标号和区号。
此处使用的是存储块1区号4,所以选择值为FSMC_Bank1_NORSRAM4。2FSMC_MemoryType设置存储类型。
此处使用SRAM,所以选择值为FSMC_MemoryType_SRAM。3FSMC_MemoryDataWidth设置数据宽度。
这里用的16位,所以选择值为FSMC_MemoryDataWidth_16b。4FSMC_WriteOperation设置写是能。
此处要向TFT写数据,所以要写使能,选择值为FSMC_WriteOperation_Enable。5FSMC_ExtendedMode设置扩展模式使能,即是否允许读写不同的时序。
此处使用读写不同时序,所以设置值为FSMC_ExtendedMode_Enable。6FSMC_DataAddressMux设置地址/数据复用使能,若设置为使能,则地址的低16位和数据将共用数据总线,仅对NOR和PSRAM有效。
此处设置为默认值不复用,值为FSMC_DataAddressMux_Disable。上面这些参数都是和模式A相关。7FSMC_BurstAccessMode
FSMC_AsynchronousWait
FSMC_WaitSignalPolarity
FSMC_WaitSignalActive
FSMC_WrapMode
FSMC_WaitSignal
FSMC_WriteBurst这些参数在组成模式同步模式才需要设置,参考中文参考手册。8FSMC_ReadWriteTimingStruct初始化片选控制寄存器FSMC_BTRx。9FSMC_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模块与开发板上单片机的IO口对应关系如下:
LCD_BL -> PB0LCD_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();//返回读到的值}//开始写GRAMvoid 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模块显示如下图所示:

     可以看到屏幕背景不停地切换,同时DS0不停地闪烁,证明我们的代码被正确地
执行了。
    另外,本例程除了不支持CPLD方案的7寸屏模块,其余所有的ALIENTEK TFTLCD
模块都可以支持,直接插上去即可使用。