我是靠谱客的博主 文艺大叔,最近开发中收集的这篇文章主要介绍TFTLCD显示实验_STM32F1开发指南_第十八章前言18.1 TFTLCD和FSMC简介18.2 硬件设计18.3 软件设计18.4下载验证,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

                                                                 第十八章 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硬复位TFTLCD
6RS命令/数据标志(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位)
D15D14D13D12D11NCD10D9D8D7D6D5D4D3D2D1D0NC
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驱动器的型号。
命令格式如下:
 
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
PSRAM
FSMC_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 ffff
Bank40X9000 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、ADDSET
ModeASRAM/ CRAM OE选通时序DATAST、ADDSET
Mode2/BNOR FLASH时序DATAST、ADDSET
ModeCNOR FLASH OE选通时序DATAST、ADDSET
ModeD延长地址保持时间的异步时序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:SRAMROM;01:PSRAM;10:NOR FLASH;11:保留。
本章把LCD当作SRAM使用,所以设置为00。
5MBKEN存储块是能位。
本章需要用该存储块控制LCD,所以使能这个存储块。
     2、SRAM/NOR闪存片选时序寄存器:FSMC_BTRx(x=1~4),该寄存器各位描述如下:

     这个寄存器包含了 每个存储器块的控制信息,可以用于SRAMROMNOR闪存存储器。
    如果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一样,选择模式A
2DATAST[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和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下上面的相关参数怎么配置?
序号 参数 设置
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 -> 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_2412asc2_1206asc2_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(93209325932845314535等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:%xrn",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
模块都可以支持,直接插上去即可使用。
















最后

以上就是文艺大叔为你收集整理的TFTLCD显示实验_STM32F1开发指南_第十八章前言18.1 TFTLCD和FSMC简介18.2 硬件设计18.3 软件设计18.4下载验证的全部内容,希望文章能够帮你解决TFTLCD显示实验_STM32F1开发指南_第十八章前言18.1 TFTLCD和FSMC简介18.2 硬件设计18.3 软件设计18.4下载验证所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(61)

评论列表共有 0 条评论

立即
投稿
返回
顶部