我是靠谱客的博主 文艺大叔,这篇文章主要介绍TFTLCD显示实验_STM32F1开发指南_第十八章前言18.1 TFTLCD和FSMC简介18.2 硬件设计18.3 软件设计18.4下载验证,现在分享给大家,希望可以做个参考。
第十八章 TFTLCD显示实验
TFTLCD模块的RST信号线是直接接到stm32的复位脚上,并不由软件控制,这样可以节省一个
16位数据线与显存的对应关系
注意:上表中HADDR[25:0]的对应关系:
不论外部接的设备位宽是8位还是16位,FSMC_A[0]永远接在外部设备的地址A[0]。这里,TFTLCD
对于NOR FLASH控制器,主要是通过FSMC_BCRx、FSMC_BTRx和FSMC_BWTRx寄
在实际扩展时,根据选用存储器的特征确定时序模型,从而确定各时间参数与存储器读/
2、SRAM/NOR闪存片选时序寄存器:FSMC_BTRx(x=1~4),该寄存器各位描述如下:
3、SRAM/NOR闪存写时序寄存器:FSMC_BWTRx(x=1~4),该寄存器各位描述如下:
BWTR[7]:FSMC_BWTRx:
通过以上讲解,大家对FSMC的原理有了一个初步认识,如果还不熟悉,请搜索网络资料理解
FSMC_NORSRAMTimingInitTypeDef类型定义如下:
在LCD_ShowChar函数里面,我们采用快速画点函数LCD_Fast_DrawPoint来画点显示字
前言
上一章我们介绍了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并口如下所示:
序号 | 管脚 | 功能 |
1 | CS | TFTLCD片选信号 |
2 | WR | 向TFTLCD写入数据 |
3 | RD | 从TFTLCD读取数据 |
4 | D[15:0] | 16位双向数据线 |
5 | RST | 硬复位TFTLCD |
6 | RS | 命令/数据标志(0:读写命令;1:读写数据) |
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 |
从图中可以看出,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_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 |
Bank1接的存储器位宽 | HADDR[]和FSMC_A[]的关系 |
16 | HADDR[25:1] -> FSMC_A[24:0] |
8 | HADDR[25:0] -> FSMC_A[25:0] |
使用的是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 |
存器设置(其中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的读操作时序如下图:
模式A支持独立的读写时序控制,这个对我们驱动TFTLCD来说非常有用,因为TFTLCD
在读的时候,一般比较慢,而在写的时候可以比较快,如果读写用一样的时序,则只能以读
的时序为基准,从而导致写的速度变慢,或者在读数据时,重新配置FSMC的延时,在读操
作完成时,在配置回写的时序,这样频繁配置,虽然不会降低写的速度,但比较麻烦。而如果
有独立的读写时序控制,则只要初始化时配置好就行了,能同时满足速度和配置步骤的要求。
ModeA的写操作时序如下图:
从模式A的读写时序,可以看出读操作还存在额外的2个HCLK周期,用于数据存储,
所以同样的配置都操作一般比写操作慢一点。
上两幅图中的ADDSET和DATAST是通过不同的寄存器配置的。
接下来讲解一下
Bank1的几个控制寄存器。
1、SRAM/NOR闪存片选控制寄存器:FSMC_BCRx(x=1~4),该寄存器各位描述如下:
本章用到的几个位如下所示:
序号 | 位域 | 功能 |
1 | EXTMOD | 扩展模式使能位,即是否允许读写不同的时序。 本章需要,所以该位为1。 |
2 | WREN | 写使能位。 本章要向LCD写数据,所以该位为1。 |
3 | MWID[1:0] | 存储器数据总线宽度。00:8位;01:16位;10和11保留。 本章用16位数据线,所以该位为01。 |
4 | MTYP[1:0] | 存储器类型。00:SRAMROM;01:PSRAM;10:NOR FLASH;11:保留。 本章把LCD当作SRAM使用,所以设置为00。 |
5 | MBKEN | 存储块是能位。 本章需要用该存储块控制LCD,所以使能这个存储块。 |
这个寄存器包含了
每个存储器块的控制信息,可以用于SRAMROMNOR闪存存储器。
如果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。 |
该寄存器为写操作时序控制寄存器。
本章用到的几个位域和读操作时序一模一样,如下所示:
序号 | 位域 | 功能 |
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[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相关库函数
1.FSMC初始化函数:
根据前面讲解,初始化FSMC主要是初始化三个寄存器FSMC_BCRx、FSMC_BTRx和FSMC_BWTRx,
在固件库中怎么初始化他们的函数分别为:
复制代码
1
2
3
4FSMC_NORSRAMInit(); //初始化NOR和SRAM FSMC_NANDInit(); FSMC_PCCARDInit();
这三个函数分别用来初始化4种类型存储器,这里根据名字很好判断。
初始化NOR和SRAM的函数定义如下:
复制代码
1void FSMC_NORSRAMInit(FSMC_NORSRAMInitTypeDef* FSMC_NORSRAMInitStruct);
这个函数只有一个入参,FSMC_NORSRAMInitTypeDef类型指针变量,其成员变量如下:
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18typedefstruct { 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。 |
复制代码
1
2
3
4
5
6
7
8
9
10typedefstruct { 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对不同的存储器类型同样提供了不同的使能函数:
复制代码
1
2
3
4void 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口对应关系如下:
复制代码
1
2
3
4
5
6LCD_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里面定义):
复制代码
1
2
3
4
5
6
7
8
9
10
11
12//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写命令/数据时,可以这样写:
复制代码
1
2LCD->LCD_REG = CMD;//写命令 LCD->LCD_RAM = DATA;//写数据
而读时则反过来:
复制代码
1
2
3CMD = LCD->LCD_REG;//读LCD寄存器 DATA = LCD->LCD_RAM;//读LCD数据
这其中,CS、WR、RD和IO口方向都是由FSMC控制的,不需要手动设置。
lcd.h中另一个重要的结构体:
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14//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个简单,但很重要的函数:
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48//写寄存器函数 //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进行各种操作了。
第八个函数,坐标设置函数:
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69//设置光标位置 //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等的设置通其他屏有些不同,故区别对待。
第九个函数:画点函数:
复制代码
1
2
3
4
5
6
7
8
9//画点 //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代码如下:
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51//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模块的字符显示函数差不多,但此处字符显示函数多了一个功能,
即以叠加/非叠加方式显示。 叠加方式显示多用于在显示的图片上再显示字符。非叠加
方式一般用于普通的显示。
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50//在指定地址显示一个字符 //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_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驱动器的初始化代码。
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205//初始化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的详细用法,请百度。
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57int 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内容请搜索靠谱客的其他文章。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复