stm32学习笔记-ALINETKTFTLCD驱动

一、基础知识

1.1 TFTLCD显示图像原理

TFTLCD液晶显示屏的显示图像原理 - TFTLCD的成像介绍

显存GRAM:
我们了解LCD成像原理后,知道我们可以控制每个像素点的子像素RGB的电压大小来显示不同颜色的数据。故液晶屏中的每个像素点都是数据,在实际应用中需要把每个像素点的数据缓存起来,再传输给液晶屏,这种存储显示数据的存储器被称为显存。显存一般至少要能存储液晶屏的一帧显示数据(一个画面的数据)。
如:分辨率为800x480的液晶屏,使用RGB888格式显示,它的一帧显示数据大小为:3x800x480=1152000字节;若使用RGB565格式显示,一帧显示数据大小为: 2x800x480=768000字节。

为什么需要显存呢?
原因是LCD一帧数据所需的空间比较大,而stm32内部的SRAM不够大。
若把LCD一帧的数据放在SRAM,计算一下:使用分辨率为800x480的液晶屏,使用RGB656格式显示,它的一帧显示数据需要多少K的空间?
768000/1024=750,足足需要750K的空间,而zet6芯片的SRAM最大才64K,若不使用显存GRAM,zet6芯片的SRAM不可能一次存储下这么大的数据。

1.2 ILI9341 控制器

上面,我们说到控制LCD需要显存GRAM的帮助。这里我们使用ILI9341控制器控制LCD。而ILI9341就拥有用来存储显示数据图像的GRAM。其GRAM中一个像素点的颜色数据用18bit表示,GRAM的大小为172800字节(240 * 320 * 18/8),所有像素数据都存储在GRAM中。当TFT-LCD开显示时,LCD源极驱动会根据GRAM中存储的数据对液晶像素进行刷新,从而实现图像显示。如果改变GRAM中单个像素点的数据,刷新显示时,TFT-LCD对应的像素状态也会改变。
如何才能准确的向每个像素对应的数据位写入显示数据呢?ILI9341控制器内部设计了一个“控制寄存器”,用户只需要按照要求发送相应的控制指令,“控制寄存器”收到控制指令后,通过“地址计数器”在对应的像素位置写入像素数据,最终通过LCD源极驱动LCD刷新显示,ILI9341控制器内部结构如下图所示。
在这里插入图片描述

i. 8080并口总线

LCD显示模块的外部接口一般采用并行方式,并行接口接口线的读写时序常见有以下两种模式:8080(Intel总线)、6800(moto总线)。

由于STM32的I/O口资源丰富,为了提高数据的读写效率,采用16位的8080并行接口,可以比8位的8080并行接口的传输速率高出一倍(数据线多出一倍),由于是并行接口,比SPI串行接口的传输速率更快,并且通讯时序简单,因此这里将TFT-LCD 的ILI9341 的IM2~IM0引脚接地,选择8080MCU 16bit bus interface II 接口类型。
在这里插入图片描述

80并口使用到如下一些信号线。末尾附加X代表低电平有效。

管脚名称功能描述
Reset复位信号总线
CSX芯片选择管脚
RS(D/CX)数据/命令选择管脚
WRX向TFT写入数据管脚
RDX从TFT读取数据管脚
DB0-DB17数据总线

ii. ILI9341 16位的数据格式

ILI9341 液晶控制器自带显存,其显存总大小为 172800字节,即 18 位模式( 26万色)下的显存量。通常使用16位的8080并行接口与MCU通信。在 16 位模式下, ILI9341 采用 RGB565 格式存储颜色数据,此时 ILI9341的 18 位数据线与 MCU 的 16 位数据线以及 LCD GRAM 的对应关系如图:
在这里插入图片描述
由上图可以知道,ILI9341 在 16 位模式下面,数据线有用的是: D17 -D13 和 D11-D1, D0和 D12 没有用到,实际上在我们 LCD 模块里面, ILI9341 的 D0 和 D12 压根就没有引出来,这样, ILI9341 的 D17-D13 和 D11-D1 对应 MCU 的 D15-D0
MCU 的 16 位数据, 最低 5 位代表蓝色,中间 6 位为绿色,最高 5 位为红色。数值越大,表示该颜色越深

iii. ILI9341常用命令。

讲完ILI9341的数据格式,接下来我们将常用指令。
ILI9431指令是8位的(高 8 位无效),指令后面带有参数,参数除了读写 GRAM 的时候是 16 位,其他操作参数都是 8 位。。

  1. 0XD3:读 ID指令,参数4个
    用于读取LCD控制器的ID,该指令如表1所示,可以看出0XD3指令后面跟了4个参数,最后2个参数读出来是 0X93和0 X41,刚好是控制器ILI9341的数字部分,通过该指令即可判别所用的LCD驱动器是什么型号,这样就可以根据控制器的型号去执行对应驱动IC的初始化代码,从而兼容不同驱动 IC 的屏,使得一个代码支持多款 LCD。
    在这里插入图片描述
  2. 0X36:存储访问控制指令,参数1个
    用来控制ILI9341存储器的读写方向,就是在连续写GRAM的时候,可以控制GRAM指针的增长方向,从而控制显示方式(读GRAM也是一样),该指令如表2所示:
    在这里插入图片描述
    从上表可以看出0X36指令后面紧跟一个参数,控制这个参数的MY,MX,MV三个位,就可以控制ILI9431的全部扫描方向如表3所示:
    在这里插入图片描述
    常常采取第一个:从左到右,从上到下的扫描方向。
    在这里插入图片描述
    在这里插入图片描述
  3. 0X2A:列地址(X轴起始、结束坐标)设置指令,参数4个
    用于设置LCD显示内容的X坐标,指令如下表所示,在从左到右,从上到下的扫描方式(默认)下,用于设置横坐标(x坐标)。4个参数,实际上是2个坐标值:SC(start column)和EC(end column),即列地址的起始值和结束值,SC必须小于等于EC,且 0≤SC/EC≤239。一般只设SC即可,EC可以不管:一般在设置x坐标的时候,只需要带2个参数即可,也就是设置 SC即可,因为如果EC没有变化,只需要设置一次即可(在初始化ILI9341的时候设置),从而提高速度。
    在这里插入图片描述
    在这里插入图片描述
  4. 0X2B:页(Y轴起始、结束坐标)地址设置指令,参数4个
    用于设置LCD显示内容的Y坐标,指令如下表所示,在从左到右,从上到下的扫描方式(默认)下,用于设置纵坐标(y坐标)。4个参数,实际上是2个坐标值:SP和EP,即页地址的起始值和结束值,SP必须小于等于EP,且 0≤SP/EP≤319。一般也只设置SP即可:一般在设置y坐标的时候,只需要带2个参数即可,也就是设置 SP即可,因为如果EP没有变化,只需要设置一次即可(在初始化ILI9341的时候设置),从而提高速度。
    在这里插入图片描述
    在这里插入图片描述
  5. 0X2C:写GRAM 指令,参数n个
    用于向ILI9341的GRAM写入数据(即设置LCD像素点的颜色)。在发送该指令之后,便可以往LCD的GRAM 里面写入颜色数据了,该指令支持连续写,指令描述如下表所示:
    在这里插入图片描述
    从上表可知,在收到指令0X2C之后,数据有效位宽变为16位,可以连续写入LCDGRAM值,而GRAM的地址将根据MY/MX/MV设置的扫描方向进行自增。
    例如:假设设置的是从左到右,从上到下的扫描方式,那么设置好起始坐标(通过SC,SP设置)后,每写入一个颜色值,GRAM地址将会自动自增1(SC++),如果碰到EC,则回到SC,同时SP++,一直到坐标:EC,EP结束,其间无需再次设置的坐标,从而大大提高写入速度。
  6. 0X2E:读 GRAM 指令
    用于读取ILI9341的GRAM数据(即读取LCD像素点的颜色),输出情况如表7所示:ILI9341在收到该指令后,第一次输出的是dummy数据,也就是无效的数据,第二次开始,读取到的才是有效的 GRAM数据(从坐标:SC,SP 开始),输出规律为:每个颜色分量占8个位,一次输出2个颜色分量。比如:第一次输出是R1G1,随后的规律为:B1R2àG2B2àR3G3àB3R4àG4B4à R5G5… 以此类推。如果只需要读取一个点的颜色值,那么只需要接收到参数3即可,如果要连续读取(利用GRAM地址自增,方法同上),那么就按照上述规律去接收颜色数据。
    在这里插入图片描述

以上就是操作ILI9341常用的几个指令,通过这几个指令便可以很好的控制 ILI9341显示需要的内容了。

iv. 读写时序

  • 写时序
    在这里插入图片描述
    片选使能:CSX拉低,表示选中ILI9341这个芯片。
    复位失能:RESX保存高电平,表示不用复位。
    命令信号:D/CX、WRX拉低表示写入命令,发送命令地址;然后D/CX保持高、WRX拉低表示写入数据,写入命令数据。

  • 读属性
    在这里插入图片描述
    片选使能:CSX拉低,表示选中ILI9341这个芯片。
    复位失能:RESX保存高电平,表示不用复位。
    读取信号:D/CX、WRX先拉低,表示写入读取命令;然后D/CX、WRX拉高、RDX拉低,表示读取数据。

1.3 FSMC

我们一般使用stm32的FSMC来模拟8080时序。

i. 为什么用stm32的FSMC来驱动TFTLCD?

首先,stm32没有8080接口的硬件驱动,所以stm32要想通过驱动ILI9341控制TFTLCD,就需要模拟8080接口时序。我们可以使用 GPIO 或者 fsmc 来模拟8080接口时序。
因为fsmc是stm32用来外接存储芯片的专用接口,它的通信速度是GPIO口的好几倍。故这里我们选择使用fsmc来模拟8080接口时序。

FSMC百度定义:
FSMC (Flexible Static Memory Controller,可变静态存储控制器) 是STM32系列采用的一种新型的存储器扩展技术。在外部存储器扩展方面具有独特的优势,可根据系统的应用需要,方便地进行不同类型大容量静态存储器的扩展。
它可以用于驱动包括SRAM、NOR FLASH以及NAND FLSAH类型的存储器,但不能驱动如SDRAM这种动态的存储器。

在这里插入图片描述
由框图可知,FSMC引出的引脚可以控制NorFlash/PSRAM、NandFlash、PC卡等3类外围存储设备。

  • 时钟控制逻辑(编程没用到,跳)
    FSMC外设挂载在AHB总线上,时钟信号来自于HCLK(默认72MHz),控制器的同步时钟输出就是由它分频得到。例如,NOR控制器的FSMC_CLK引脚输出的时钟,它可用于与同步类型的SRAM芯片进行同步通讯,它的时钟频率可通过FSMC_ BTR寄存器的CL KDIV位配置,可以配置为HCLK的1/2或1/3,也就是说,若它与同步类型的SRAM通讯时,同步时钟最高频率为36MHz。若使用异步类型的存储器(异步类型存储器不适应同步时钟信号),时钟分频配置不起作用。
  • FSMC的地址映射
    在使用FSMC时,配置模式后,使用指针就可以访问到外设存储器的内容,不用我们去控制产生时序,FSMC自动完成,很是方便。
    使用FSMC外接存储器时,其存储单元是映射到STM32的内部寻址空间的;在程序里,定义一个指向这些地址的指针,然后就可以通过指针直接修改该存储单元的内容,FSMC外设会自动完成数据访问过程,读写命令之类的操作不需要程序控制(配置好工作模式的前提下)。
    在这里插入图片描述
  • FSMC_NE[X]片选
    NE[X]片选就是选择存储块中的哪一个区。存储块1分为 4个区,每个区管理 64M 字节空间,每个区都有 独立的寄存器 对所连接的存储器进行配置。如下图的Bank1:
    在这里插入图片描述

ii. FSMC模式B时序简介

FSMC外设支持输出多种不同的时序以便于控制不同的存储器,它具有ABCD四种模式。控制LCD时,适合使用FSMC的NOR\PSRAM模式,FSMC具有A、B、C、D模式,针对编程LCD使用到的模式B进行讲解。当内核发出访问某个指向外部存储器地址时,FSMC外设会根据配置控制信号线产生时序访问存储器.不需我们直接控制引脚产生时序。

  • 信号线
    NOR FLASH为异步存储器,故不需要CLK线。当我们不需要地址 / 数据线复用时,NADV就不用管。
    在这里插入图片描述

  • FSMC模式B读时序
    在这里插入图片描述
    以读时序为例,该图表示-一个 存储器操作周期地址建立周期(ADDSET+1)数据建立周期(DATAST+1) 以及 2个HCLK周期 组成。在地址建立周期中,地址线发出要访问的地址,数据掩码信号线NBL[1]、NBL[0]指示出要读取地址的高、低字节部分,片选信号使能存储器芯片;地址建立周期结束后读使能信号线发出读使能信号,接着存储器通过数据信号线把目标数据传输给FSMC, FSMC 把它交给内核。

  • FSMC模式B写时序
    在这里插入图片描述
    写时序类似,区别是它的一个 存储器操作周期 仅由 地址建立周期(ADDSET+1)数据建立周期(DATAST+1) 组成,且在数根据STM32对寻址空间的地址映射,地址0x6000 0000 ~0x9FFF FFFF是映射到外部存储器的,而其中的0x6000 0000 ~0x6FFF FFFF则是分配给NOR FLASH、PSRAM这类可直接寻址的器件。
      当FSMC外设被配置成正常工作,并且外部接了SRAM时,若向0x60000000地址写入数据如0xABCD,FSMC会自动在各信号线上产生相应的电平信号,写入数据。FSMC会控制片选信号NE1选择相应的NOR芯片,然后使用地址线A[25:0]输出0x60000000(实际是访问外设存储器的0x000 000地址),在NWE写使能信号线上发出低电平的写使能信号,而要写入的数据信号0xABCD则从数据线D[15:0]输出,然后数据就被保存到SRAM中了。

iii.FSMC的NORFLASH访问时序与LCD8080时序对比

  • FSMC的NOR FLASH访问时序 与LCD的8080时序对比。
    在这里插入图片描述
    在这里插入图片描述
    对比FSMC NOR/PSRAM中的模式B时序与ILI9341液晶控制器芯片使用的8080时序可发现,这两个时序是十分相似的(除了FSMC的地址线A和8080的D/CX线,可以说是完全一样)
    FSMC NOR/PSRAM的时钟写A[25:0]共有26根,我们使用其中一根地址线与ILI9341芯片8080接口的D/Cx信号线相连接来模拟时序。
    例如:我们可以把FSMC的A[10]地址线(也可以使用A[0] or A[1] or A[2]等其它地址线,这里得根据硬件连接考虑)与ILI9341芯片8080接口的D/CX信号线连接,那么当A[10]为高电平时(即D/CX为高电平),数据线D[15:0]的信号会被ILI9341理解为数值,若A[10]为低电平时(即D/CX为低电平),传输的信号则会被理解为命令。
    即使用A[10]地址线作为D/CX时,A[10]输出低电平表示命令,A[10]输出高电平表示数据。
数据宽度对应问题
  • 思考:什么确定A[10]的高低电平呢?
    A[0] 即 FSMC_A[10] , FSMC_A[10] 根据 数据的宽度HADDR[10] 或 HADDR[11] 确定。
    我们编程是设定 HADDR[x] 的值,而 HADDR[x] 与 FSMC_A[x]/FSMC_A[x+1] 连接,我们设定HADDR[x],与 HADDR[x]相连接的 FSMC_A[x]/FSMC_A[x+1] 就会被设定。
    FSMC_A 与 HAADR 连接 关系如下:
    在这里插入图片描述
    个人理解是:HADDR[0]可以找到最小单位为8位(一个字节)的存储空间单元,HADDR[1]可以确定一个16位(二个字节)的存储空间,2 * 8=16嘛。所以外部存储器地址是8位数据宽度时,我们要确定8位的最小存储空间单元,FSMC_A[0]对应HADDR[0]就可以找到最小存储单元的数据;外部存储器地址是16位数据宽度的时候,我们要确定16位的最小存储空间单元,FSMC_A[0]就对应HADDR[1],这时我们就可以找到最小存储单元的数据。所以外接存储器是16位时,HADDR[x+1]就对应着FSMC_A[x],而且这时FSMC_A[24:0]已经足够表达存储器空间,故省去了FSMC_A[25]。
    在这里插入图片描述
    我们使用的LCD存储器是16位数据宽度的,所以 A[10] 数值 最终由HADDR[11] 确定
    要使 FSMC_ A10地址线为高电平,实质是置内部HADDR地址的第(10+1)位为1即可。要使 FSMC_ A10地址线为低电平,实质是置内部HADDR地址的第(10+1)位为0即可。

LCD使用 FSMC Bank1 第4区,运算如下:
在这里插入图片描述
FSMC Bank1 第4区使用0X6C00 0000 ~ 0X6FFF FFFF内的任意地址,故其基地址是0X6C00 0000。
使 FSMC_A10 地址线为高电平的运算:0X6C00 0000 |= (1<<(10+1)) = 0X6C00 8000
使 FSMC_A10 地址线为低电平的运算:0X6C00 0000 &= ~(1<<(10+1)) = 0X6C00 0000

iv. FSMC传输数据

FSMC定义为16位数据宽度时,我们定义一个指针指向0X6C00 8000地址如#define LCD_DATA_ADDR ((u32*)(0x6C008000)).
向该地址指针赋值如*LCD_DATA_ADDR = 0x1234;,这时这时FSMC_A[24:0]对应FSMC_A[24]、FSMC_A[23]、FSMC_A[10]地址线就会置1,选择外接存储器的对应的地址,然后FSMC就会操作数据线FSMC_D[ 15:0]开始传输输数据,向外接存储器的0x00 8000的地址写入数据内容。

若我们是外接ILI9341的LCD,*LCD_DATA_ADDR = 0x1234;这个语句作用就是把FSMC_A[10]置1,然后FSMC就会操作数据线FSMC_D[ 15:0]开始传输输数据。

二、ALINETK TFTLCD

2.1 ALINETK 2.8寸TFTLCD简介及接口说明

  • 简介
    在这里插入图片描述
    240*320分辨率16位真彩显示(65536色)、自带电阻触摸屏、自带背光电路。
      液晶触摸面板由液晶屏和触摸屏组成,屏幕表面的灰色线框即为电阻触摸屏的信号线,触摸屏的下方即为液晶面板,在它的内部包含了一个型号为IL9341的液晶控制器芯片(由于集成度高,所以图中无法看见),该液晶控制器使用8080接口与单片机通讯,图中液晶面板引出的FPC信号线即8080接口(RGB接口已在内部直接与ILI9341相连),且控制器中包含有显存,单片机把要显示的数据通过引出的8080接口发送到液晶控制器,这些数据会被存储到它内部的显存中,然后液晶控制器不断把显存的内容刷新到液晶面板,显示内容。
      PCB底板,它主要包含了一个电阻触摸屏的控制器XPT2046,电阻触摸屏控制器实质上是一个ADC芯片,通过检测电压值来计算触摸坐标。PCB底板与液晶触摸面板通过FPC排线座连接,然后引出到排针,方便与实验板的排母连接。

  • 引脚接口
    在这里插入图片描述

BL_CTR:背光控制信号,连接 PB0
LCD_CS:LCD片选信号,连接FSMC_NE4片选引脚 PG12
LCD_RS:命令/数据标志(0:命令,1:数据),连接FSMC_A10地址信号引脚 PG0
LCD_WR:LCD写信号,连接FSMC_NWE写使能引脚 PD5
LCD_RD:LCD读信号,连接FSMC_NOE读使能引脚 PD4
DB[17:1]:16位双向数据线。连接FSMC对应的数据线
LCD_RST;硬复位LCD信号,与开发板的复位共用。

T_MISO/T_MOSI/T_PEN/T_CS/T_CLK:触摸屏接口信号

注意:DB1~ DB8 ,DB10 ~ DB17,共16引脚,总是按顺序连接stm32FSMC的D0~D15
注意:如果我们只是要点亮LCD,不使用触摸屏可不接相关引脚(29-34)
  • LCD使用流程图如下:
    在这里插入图片描述

2.2 编程代码获取LCD的ILIID号

思路:

  1. 首先初始化使用到的GPIO口。
  2. 初始化FSMC,设置正确的时序,可以先参考根据SRAM时序初始化FSMC的ILI9341时序结构体编写部分
  3. 编写stm32向ILI9341读取数据操作,stm32向ILI9341发送命令操作,stm32向ILI9341发送数据操作
  4. 读取LCD的ID号

代码:

ili9341_lcd.h

#ifndef ILI9341_LCD_H
#define ILI9341_LCD_H
#include "stm32f10x.h"
#include "Tool.h"


#define FSMC_Bank1_NORSRAMx  FSMC_Bank1_NORSRAM4

/* LCD 背光控制端口 PB0 */
#define	LCD_LED PBout(0) 		//LCD背光  PB0 	  
#define ILI9341_BL_CLK			RCC_APB2Periph_GPIOB
#define ILI9341_BL_PORT			GPIOB
#define ILI9341_BL_PIN			GPIO_Pin_0

/* LCD 片选信号控制端口 PG12 FSMC NE4 */
#define ILI9341_CS_CLK			RCC_APB2Periph_GPIOG
#define ILI9341_CS_PORT			GPIOG
#define ILI9341_CS_PIN			GPIO_Pin_12

/* LCD 读操作控制端口  PD4 FSMC NOE */
#define ILI9341_RD_CLK			RCC_APB2Periph_GPIOD
#define ILI9341_RD_PORT			GPIOD
#define ILI9341_RD_PIN			GPIO_Pin_4

/* LCD 写操作控制端口  PD5 FSMC NWE */
#define ILI9341_WR_CLK			RCC_APB2Periph_GPIOD
#define ILI9341_WR_PORT			GPIOD
#define ILI9341_WR_PIN			GPIO_Pin_5

/* LCD 数据/命令控制端口  PG0 FSMC D/CX */
#define ILI9341_DC_CLK			RCC_APB2Periph_GPIOG
#define ILI9341_DC_PORT			GPIOG
#define ILI9341_DC_PIN			GPIO_Pin_0


#define		FSMC_Addr_ILI9341_CMD		( ( vu16 * ) 0x6C000000 )//FSMC Bank1区4 NORSRAM 用于LCD命令操作的地址
#define		FSMC_Addr_ILI9341_DATA	( ( vu16 * ) 0x6C000800 )//FSMC Bank1区4 NORSRAM 用于LCD数据操作的地址



void ILI9341_Init(void);
void ILI9341_Write_Cmd(u16 usCmd);
u16 ILI9341_Read_Data(void);

#endif



ili9341_lcd.c

#include "ili9341_lcd.h"

void ILI9341_GPIO_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE|ILI9341_CS_CLK|ILI9341_DC_CLK|ILI9341_WR_CLK|ILI9341_RD_CLK,ENABLE);//使能LCD背光PB、LCD片选PG,命令\数据信号PG,读操作PD,写操作PD,传输数据并行管脚时钟PB、PE时钟

	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);
	
	//片选信号NE4,复用推挽输出 
	GPIO_InitStructure.GPIO_Pin = ILI9341_CS_PIN;	 //	//PORTD复用推挽输出  
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 		 //复用推挽输出   
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(ILI9341_CS_PORT, &GPIO_InitStructure); 
	
	//地址线A10端口,复用推挽输出 
	GPIO_InitStructure.GPIO_Pin = ILI9341_DC_PIN;	 //	//PORTD复用推挽输出  
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 		 //复用推挽输出   
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(ILI9341_DC_PORT, &GPIO_InitStructure); 
	
	//写使能NWE端口,复用推挽输出 
	GPIO_InitStructure.GPIO_Pin = ILI9341_WR_PIN;	 //	//PORTD复用推挽输出  
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 		 //复用推挽输出   
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(ILI9341_WR_PORT, &GPIO_InitStructure); 
	
	//读使能端口,复用推挽输出 
	GPIO_InitStructure.GPIO_Pin = ILI9341_RD_PIN;	 //	//PORTD复用推挽输出  
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 		 //复用推挽输出   
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(ILI9341_RD_PORT, &GPIO_InitStructure); 
	
 	//PORTD复用推挽输出  
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_14|GPIO_Pin_15;				 //	//PORTD复用推挽输出  
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOD, &GPIO_InitStructure); 
  	 
	//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;				 //	//PORTD复用推挽输出  
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 		 //复用推挽输出   
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOE, &GPIO_InitStructure);    	    	 											 

}

void ILI9341_FSMC_Init(void)
{
	
	FSMC_NORSRAMInitTypeDef  FSMC_NORSRAMInitStructure;//初始化结构体
  FSMC_NORSRAMTimingInitTypeDef  readWriteTiming; //写时序结构体
	FSMC_NORSRAMTimingInitTypeDef  writeTiming;//读时序结构体
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC,ENABLE);	//使能FSMC时钟
	
	readWriteTiming.FSMC_AddressSetupTime = 0x00;	 
	readWriteTiming.FSMC_DataSetupTime = 0x0f;		 
	readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_B;
  readWriteTiming.FSMC_AddressHoldTime = 0x00;	 //地址保持时间(ADDHLD)模式A未用到	
  readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
  readWriteTiming.FSMC_CLKDivision = 0x00;
  readWriteTiming.FSMC_DataLatency = 0x00;

	writeTiming.FSMC_AddressSetupTime = 0x01;	 
	writeTiming.FSMC_DataSetupTime = 0x02;		 
	writeTiming.FSMC_AccessMode = FSMC_AccessMode_B;	 //模式B
  writeTiming.FSMC_AddressHoldTime = 0x00;	 //地址保持时间A		
  writeTiming.FSMC_BusTurnAroundDuration = 0x00;
  writeTiming.FSMC_CLKDivision = 0x00;
  writeTiming.FSMC_DataLatency = 0x00;

  FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM4;//  这里我们使用NE4 ,也就对应BTCR[6],[7]。
  FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable; // 不复用数据地址
  FSMC_NORSRAMInitStructure.FSMC_MemoryType =FSMC_MemoryType_NOR;// FSMC_MemoryType_SRAM;  //SRAM   
  FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;//存储器数据宽度为16bit 
	FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;	//  存储器写使能
  FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Enable; // 读写使用不同的时序  
	FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &readWriteTiming; //读写时序
  FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &writeTiming;  //写时序

 FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode =FSMC_BurstAccessMode_Disable;// FSMC_BurstAccessMode_Disable; 
  FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
	FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait=FSMC_AsynchronousWait_Disable; 
  FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;   
  FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;  
  FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;   
  FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable; 

 	FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);  //初始化FSMC配置
 	FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4, ENABLE);  // 使能BANK1 
}

void ILI9341_Init(void)
{
	ILI9341_FSMC_Init();
	ILI9341_GPIO_Init();
	LCD_LED = 1;
}

void ILI9341_Write_Cmd(u16 usCmd)
{
	*(FSMC_Addr_ILI9341_CMD)=usCmd;//写入要写的命令
}

u16 ILI9341_Read_Data(void)
{
	return *(FSMC_Addr_ILI9341_DATA);
}

main.c

#include "stm32f10x.h"
#include "bsp_systick.h"
#include "usart.h"
#include "led.h"
#include "ili9341_lcd.h"


int main(void)
{
	usart1_Init(9600);
	LED_Init();
	ILI9341_Init();
	
	printf("LCD实验");
	
	
	
	while(1)
	{
		ILI9341_Write_Cmd(0XD3);
		printf("第一次获取数据是:0x%x",ILI9341_Read_Data());
		printf("第二次获取数据是:0x%x",ILI9341_Read_Data());
		printf("第三次获取数据是:0x%x",ILI9341_Read_Data());
		printf("第四次获取数据是:0x%x",ILI9341_Read_Data());
		delay_ms(1000);
	}

}

结果现象:

在第3、第4次获取93、41连个16进制值,可以通过这个现象知道,向LCDILI9341写入指令,读取数据时序没有错误。
在这里插入图片描述
在这里插入图片描述

遇到的一些问题 :

  • 获取数据都是0x0的问题,检查发现是GPIO口没配置对。
  • 定义指针问题
    下面指针是32位,还是16位问题。
    在这里插入图片描述
    指针的类型,是根据指针指向数据的类型来确定的。FSMC采样了16位数据宽度,所以指针类型应该是u16,为了防止优化,加上关键字volatile,就是vu16。
  • 第二次数据一直都是0x41问题
    粗心,没有写对数据地址,不是0x6C008000,而是0x6C000800
    在这里插入图片描述

总结

驱动TFTLCD,无非就是控制TFTLCD的晶体薄膜管,我们不可能使用STM32的SRAM存储控制TFTLCD的晶体薄膜管的信息,所以我们使用了带显存GRAM的ILI9341控制器来辅助控制TFTLCD,我们使用STM32的FSMC产生8080时序来控制操作ILI9341就可以实现驱动TFTLCD。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值