STM32的FSMC

FSMC接口介绍

大容量,且引脚数在 100 脚以上的 STM32F103 芯片都带有 FSMC 接口。
FSMC,即灵活的静态存储控制器flexible static memory controller。

扩展内存

  • STM32 的 FSMC 接口支持包括 SRAM、NAND FLASH、NOR FLASH 和 PSRAM 等存储器。
    在这里插入图片描述

何为PSRAM

  • FSMC就是一个MCU与外部存储器(SRAM,FLASH等)读写数据的一个接口

FSMC内部原理

FSMC 的框图

在这里插入图片描述

  • NE[4-1] 片选,用来区分不同设备
  • NWE为写。
  • NOE为读
    • n低电平有效
    • o output
    • e 使能

驱动SRAM

  • FSMC驱动外部SRAM时,外部SRAM的控制一般有
    • 地址线(如A0-A25)
    • 数据线(如D0-D15)
    • 写信号(WE,即WR)
    • 读信号(OE,即RD)
    • 片选信号(CS)
    • 如果SRAM支持字节控制,那么还有UB/LB信号。

驱动TFTLCD

  • 真正在操作LCD的时候需要用到的就只有:

    • RS、D0~D15、WR、RD和CS。
  • 其操作时序和SRAM的控制完全类似,唯一不同就是TFTLCD有RS信号,但是没有地址信号。(所以将RS当做地址线来用)
    在这里插入图片描述
    在这里插入图片描述
    (该表格为F407的,与103不太一样,仅看个大概意思)

  • TFTLCD通过RS信号来决定传送的数据是数据还是命令,本质上可以理解为一个地址信号

    • 比如我们把*RS接在A0上面
    • 那么当FSMC控制器写地址0的时候,会使得A0变为0,对TFTLCD来说,就是写命令。而FSMC写地址1的时候,A0将会变为1,对TFTLCD来说,就是写数据了
    • 当然RS也可以接在其他地址线上,而探索者STM32F4把RS接在A6上面。
  • 因此,可以把TFTLCD当成一个SRAM来用,只不过这个SRAM有2个地址,这就是FSMC可以驱动LCD的原理。

FSMC内存划分

在这里插入图片描述

FSMC的分块

STM32的 FSMC将外部存储器划分为固定大小为 256M 字节的四个存储块(Bank),FSMC 总共管理 1GB 空间.
如下为FSMC在CPU中的地址映射划分。
在这里插入图片描述
各模块配置的地址范围
在这里插入图片描述

存储块1的介绍

在这里插入图片描述

每个区的地址范围可看下文

存储块1的分区

STM32 的 FSMC 存储块 1(Bank1)被分为 4 个区,每个区管理 64M 字节空间,每个区都有独立的寄存器对所连接的存储器进行配置。(所以它有四个不同的片选,可以接不同的外设)

存储块1的地址

Bank1 的 256M 字节空间由 28 根地址线(HADDR[27:0])寻址。
HADDR 是需要转换到外部设备的内部 AHB 总线地址,一个地址对应八位数据。
其中 HADDR[25:0]来自外部存储器地址FSMC_A[25:0],直接对应地址总线 。
HADDR[27:26]位用于选择四个存储块之一。
在这里插入图片描述

在这里插入图片描述
但[0:25]却需要根据不同位宽讨论。
不同区的地址范围
在这里插入图片描述

不同数据宽度对存储块1寻址的影响

在这里插入图片描述
需要或者可以访问的地址空间大小。
当Bank1接的是16位宽度存储器的时候:HADDR[25:1],FSMC_A[24:0]

  • 当psram地址为0x0001(16)时,对应ARMM地址0x000002(24)
  • 当psram地址为0x0002(16)时,对应ARMM地址0x000004(24)
    当Bank1接的是8位宽度存储器的时候:HADDR[25:0],FSMC_A[25:0]

不论外部接8位/16位宽设备,FSMC_A[0]永远接在外部设备地址A[0]

可以区分一下,FSMC即为外部存储器的地址,是FSMC端的,HADDR是AHB总线端的

CPU发出两个地址,去接受一个16位的数据,它不知道自己发出的两个地址会落到同一个位置。因为被右移了两位。
不论外部存储器的宽度是多少(16位或8位),FSMC_A[0]始终应该连到外部存储器的地址线A[0]

FSMC中地址与外设地址的对应关系

FSMC中的1G空间存储的是外设地址,当我们在存储块中的访问单元序号+1,对应的外设存储单元的访问地址就自加8(如果外设数据存储的数据宽度为8b的话)
在这里插入图片描述

FSMC相关寄存器

SRAM/NOR 闪存片选控制寄存器:FSMC_BCRx

在这里插入图片描述

该寄存器有1-4。

14 EXTMOD 扩展模式使能位

也就是是否允许读写不同的时序
○ 我们本章需要读写不同的时序,故该位需要设置为 1。
○ 当该位设置为0,读写就公用BTRx寄存器。

12 WREN写使能位。

我们需要向 TFTLCD 写数据,故该位必须设置为 1。

5-4 MWID[1:0]:存储器数据总线宽度

00,表示 8 位数据模式;
01 表示 16 位数据模式;
10和 11 保留。
○ 我们的 TFTLCD 是 16 位数据线,所以设置 WMID[1:0]=01。

3-2 MTYP[1:0]:存储器类型。

  • 00 表示 SRAM、ROM;
  • 01 表示 PSRAM;
  • 10 表示 NOR FLASH;11保留。

0 MBKEN:存储块使能位

SRAM/NOR 闪存片选时序寄存器:FSMC_BTRx

BTRx寄存器介绍

该寄存器有1-4。
在这里插入图片描述

如果 FSMC_BCRx 寄存器中设置了 EXTMOD 位,则有两个时序寄存器分别对应读(本寄存器)和写操作(FSMC_BWTRx 寄存器)。

29-28 ACCMOD[1:0]:访问模式。

00 表示访问模式 A;01 表示访问模式 B;
10 表示访问模式 C;11 表示访问模式 D
本章我们用到模式 A,故设置为 00。

15-8 DATAST[7:0]:数据保持时间。

0 为保留设置,其他设置则代表保持时间为: 
DATAST 个HCLK 时钟周期,最大为 255 个 HCLK 周期。
数据保持时间,等于: DATAST(+1)个HCLK时钟周期
DATAST最大为255。
对ILI9341来说,其实就是WR低电平持续时间,为15ns
不过ILI9320等则需要50ns。
考虑兼容性,对STM32F1,一个HCLK=13.8ns (1/72M),设置为3;
对STM32F4,一个HCLK=6ns(1/168M) ,设置为9。 
F4不加1

3-0 ADDSET[3:0]:地址建立时间。

其建立时间为:ADDSET 个 HCLK 周期,最大为 15 个 HCLK周期,每个周期4ns(1/25M)。
表示:ADDSET+1个HCLK周期,ADDSET最大为15。
对ILI9341来说,这里相当于WR高电平持续时间,为15ns。
同样考虑兼容ILI9320,对STM32F1,这里即便设置为1,WR也有100ns的高电平,我们这里设置为1。
而对STM32F4,则设置为8。 
F4不加1

地址建立时间

建立时间(Tsu:setuptime)是指在时钟沿到来之前数据从不稳定到稳定所需的时间
用于从外设,如TFTLCD读取到稳定的RS信号。
这段时间内,RDX和WRX均为无效电平。

数据保持时间

保持时间(Th:holdtime)是指数据稳定后保持的时间,如果保持时间不满足要求那么数据同样也不能被稳定的打入触发器。
NT35510数据锁存的时序图:
在这里插入图片描述
数据锁存可以让NT35510芯片识别稳定的数据。
为了我们识别到稳定可靠的数据,数据保持时间必须大于RDX低电平持续时间,即RDX的上升沿必须出现在地址保持时间之内。
在计算的时候,应该查看驱动外设的读写最小时间,然后除时钟周期即可。
ADDSET最小为250ns/HCLK,DATAST最小为150ns/HCLK。

SRAM/NOR 闪写时序寄存器:FSMC_BWTRx

在这里插入图片描述

  • 该寄存器在本章用作写操作时序控制寄存器

ACCMOD

ACCMOD 设置同 FSMC_BTRx 一模一样,同样是选择模式 A

  • 另外 DATAST 和ADDSET 则对应低电平和高电平持续时间
    ○ 对 ILI9341 来说,这两个时间只需要 15ns 就够了,比读操作快得多。
    ○ 所以我们这里设置 DATAST 为 3,即 4 个 HCLK 周期,时间约为 55ns(因为9320 等控制器,这个时间要求比较长,要 50ns)。
    ○ ADDSET(也存在性能问题)设置为 0,即 1 个 HCLK 周期,实际 WR 高电平时间大于 100ns。

寄存器组合

这里还要给大家做下科普,在 MDK 的寄存器定义里面,并没有定义 FSMC_BCRx、FSMC_BTRx、FSMC_BWTRx 等这个单独的寄存器,而是将他们进行了一些组合。
FSMC_BCRx 和 FSMC_BTRx,组合成 BTCR[8]寄存器组,他们的对应关系如下:
BTCR[0]对应 FSMC_BCR1,BTCR[1]对应 FSMC_BTR1
BTCR[2]对应 FSMC_BCR2,BTCR[3]对应 FSMC_BTR2
BTCR[4]对应 FSMC_BCR3,BTCR[5]对应 FSMC_BTR3
BTCR[6]对应 FSMC_BCR4,BTCR[7]对应 FSMC_BTR4
FSMC_BWTRx 则组合成 BWTR[7],他们的对应关系如下:
BWTR[0]对应 FSMC_BWTR1,BWTR[2]对应 FSMC_BWTR2,
BWTR[4]对应 FSMC_BWTR3,BWTR[6]对应 FSMC_BWTR4,
BWTR[1]、BWTR[3]和 BWTR[5]保留,没有用到。

FSMC的读写时序

模式

FSMC 的 NOR FLASH 控制器支持同步和异步突发两种访问方式。

  • 选用同步突发访问方式时,FSMC 将 HCLK(系统时钟)分频后,发送给外部存储器作为同步时钟信号 FSMC_CLK。此时需要的设置的时间参数有 2 个:
    • 1,HCLK 与 FSMC_CLK 的分频系数(CLKDIV),可以为 2~16 分频;
    • 2,同步突发访问中获得第 1 个数据所需要的等待延迟(DATLAT)。

对于异步突发访问方式,FSMC 主要设置 3 个时间参数:地址建立时间(ADDSET)、数据建立时间(DATAST)和地址保持时间(ADDHLD)。
在这里插入图片描述
模式A与模式1的最大区别在于:模式1中读写速度一样,模式A中读写速度不同。
模式A支持独立的读写时序控制,这个对我们驱动TFTLCD来说非常有用,因为TFTLCD在读的时候,一般比较慢,而在写的时候可以比较快,如果读写用一样的时序,那么只能以读的时序为基准,从而导致写的速度变慢。

FSMC与外设时序列保持一致

FSMC作为CPU与外部SRAM沟通的桥梁,那就得说他们两个人都认识的话——读写时序
对FSMC管理的变量Var进行读写就会引起FSMC产生读取SRAM中数据的时序,引发对SRAM的读写操作。
在这里插入图片描述

FSMC的模式A的读时序

在这里插入图片描述

NOE为读,NWE为写。
n低电平有效
o output
e 使能
nex 片选cs
最后的2HCLK是用于存储器将读取到的数据存入锁存器(即保存数据)用的。

FSMC的模式A的写时序

在这里插入图片描述

FSMC的配置步骤

TFTLCD液晶屏

屏幕介绍

TFTLCD即薄膜晶体管液晶显示器。它与无源TN-LCD、STN-LCD的简单矩阵不同,它在液晶显示屏的每一个象素上都设置有一个薄膜晶体管(TFT),可有效地克服非选通时的串扰,使显示液晶屏的静态特性与扫描线数无关,因此大大提高了图像质量。

TFTLCD具有:亮度好、对比度高、层次感强、颜色鲜艳等特点。是目前最主流的LCD显示器。广泛应用于电视、手机、电脑、平板等各种电子产品。

屏幕尺寸

ALIENTEK提供丰富的TFTLCD模块型号,供大家选择,目前有以下型号可选:
1,ATK-2.8寸 TFTLCD模块
分辨率:240320,驱动IC:ILI9341,电阻触摸屏,16位并口驱动(在用)
2,ATK-3.5寸 TFTLCD模块
分辨率:320
480,驱动IC:NT35310,电阻触摸屏,16位并口驱动
3,ATK-4.3寸 TFTLCD模块
分辨率:480800,驱动IC:NT35510,电容触摸屏,16位并口驱动
4,ATK-7寸 TFTLCD模块(V1版本)
分辨率:480
800,驱动IC:CPLD+SDRAM,电容触摸屏,16位并口驱动
5,ATK-7寸 TFTLCD模块(V2版本)
分辨率:480*800,驱动IC:SSD1963,电容触摸屏,8/9/12/16位并口驱动

2.8寸屏介绍

240*320分辨率
16位真彩显示(65536色)
自带电阻触摸屏
自带背光电路
模块是3.3V供电的,不支持5V电压的MCU,如果是5V MCU,必须在信号线串接120R电阻使用。

屏幕原理图

在这里插入图片描述
在这里插入图片描述
注意:DB1-DB8,DB10-DB17,总是按顺序连接MCU的D0~D15
LCD_CS:LCD片选信号
LCD_WR:LCD写信号
LCD_RD:LCD读信号
DB[17:1]:16位双向数据线。
LCD_RST:硬复位LCD信号
LCD_RS:命令/数据标志 (0:命令,1:数据)
BL_CTR:背光控制信号
T_MISO/T_MOSI/T_PEN/T_CS/T_CLK,触摸屏接口信号
在这里插入图片描述
(该表格为F407的,与103不太一样,仅看个大概意思)

  • TFTLCD通过RS信号来决定传送的数据是数据还是命令,本质上可以理解为一个地址信号
    • 比如我们把*RS接在A0上面
    • 那么当FSMC控制器写地址0的时候,会使得A0变为0,对TFTLCD来说,就是写命令。而FSMC写地址1的时候,A0将会变为1,对TFTLCD来说,就是写数据了
    • 当然RS也可以接在其他地址线上,而探索者STM32F4把RS接在A6上面。

用到的主要的线
在这里插入图片描述

8080并口 驱动 读写

介绍

8080 并行接口的发明者是 INTEL,该总线也被广泛应用于各类液晶显示器。

读写

模块的8080并口读/写的过程为:
在这里插入图片描述

1.读数据:在RD的上升沿, 读取数据线上的数据(D[15:0]);
2.写数据:在WR的上升沿,使数据写入到ILI9341里面

并口写时序
在这里插入图片描述
并口读时序
在这里插入图片描述

读写LCD本质上是对寻址空间的某一个地址进行读写操作:

读:var=(uint32_t)0x6C000080;

写:(uint32_t)0x6C000080=var;

RS选择代码

Bank1.sector4就是从地址0X6C000000开始,而0X000007FE,则是A[10]的偏移量。
以A[10]为例,A[10]相对于Bank1.sector4的地址0X7FE换成二进制为:0b011111111110,而16位数据时,地址右移一位对齐,对应到地址引脚就是:A[10:0]=0b001111111111,此时A[10]是0
如果16位地址加1(对应到8位地址是加2,即0X7FE+0X02),那么:A[10:0]=0b010000000000,此时A[10]就是1了,即实现了对RS的0和1的控制。

//LCD地址结构体
typedefstruct
{
    vu16LCD_REG;
    vu16LCD_RAM;
}LCD_TypeDef;
//使用NOR/SRAM的Bank1.sector4,地址位HADDR[27,26]=11A10作为数据命令区分线
//注意设置时STM32内部会右移一位对其!
#define LCD_BASE((u32)(0x6C000000|0x000007FE))
#define LCD((LCD_TypeDef*)LCD_BASE)

将这个地址强制转换为LCD_TypeDef结构体地址,那么可以得到LCD->LCD_REG的地址就是0X6C00,07FE,对应A[10]的状态为0(即RS=0)
LCD->LCD_RAM的地址就是0X6C00,0800(结构体地址自增),对应A[10]的状态为1(即RS=1),

驱动时序

在这里插入图片描述
ILI9341_DS.pdf,232页
重点时序:
读ID低电平脉宽(trdl)
读ID高电平脉宽(trdh)
读FM低电平脉宽(trdlfm)
读FM高电平脉宽(trdhfm)
写控制低电平脉宽(twrl)
写控制高电平脉宽(twrh)

注意:ID指LCD的ID号
FM指帧缓存,frame memory,即:GRAM

驱动流程

在这里插入图片描述

指令说明

RGB565格式

模块对外接口采用16位并口,颜色深度为16位,格式为RGB565,关系如下图:
在这里插入图片描述

ILI9341指令

ILI9341所有的指令都是8位的(高8位无效),且参数除了读写GRAM的时候是16位,其他操作参数,都是8位的。

ILI9341的指令很多,这里不一一介绍,仅介绍几个重要的指令,他们是:0XD3,0X36,0X2A,0X2B,0X2C,0X2E等6条指令。

0XD3指令

该指令为读ID4指令,用于读取LCD控制器的ID 。因此,同一个代码,可以根据ID的不同,执行不同的LCD驱动初始化,以兼容不同的LCD屏幕。
在这里插入图片描述

0X36指令

该指令为存储访问控制指令,可以控制ILI9341存储器的读写方向,简单的说,就是在连续写GRAM的时候,可以控制GRAM指针的增长方向,从而控制显示方式(读GRAM也是一样)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

0X2A指令

该指令是列地址设置指令,在从左到右,从上到下的扫描方式(默认)下面,该指令用于设置横坐标(x坐标)
在这里插入图片描述
在默认扫描方式时,该指令用于设置x坐标,该指令带有4个参数,实际上是2个坐标值:SC和EC,即列地址的起始值和结束值,SC必须小于等于EC,且0≤SC/EC≤239。一般在设置x坐标的时候,我们只需要带2个参数即可,也就是设置SC即可,因为如果EC没有变化,我们只需要设置一次即可(在初始化ILI9341的时候设置),从而提高速度。

0X2B指令

该指令是页地址设置指令,在从左到右,从上到下的扫描方式(默认)下面,该指令用于设置纵坐标(y坐标)
在这里插入图片描述
在默认扫描方式时,该指令用于设置y坐标,该指令带有4个参数,实际上是2个坐标值:SP和EP,即页地址的起始值和结束值,SP必须小于等于EP,且0≤SP/EP≤319。一般在设置y坐标的时候,我们只需要带2个参数即可,也就是设置SP即可,因为如果EP没有变化,我们只需要设置一次即可(在初始化ILI9341的时候设置),从而提高速度。

0X2C指令

该指令是写GRAM指令,在发送该指令之后,我们便可以往LCD的GRAM里面写入颜色数据了,该指令支持连续写 (地址自动递增)
在这里插入图片描述
在收到指令0X2C之后,数据有效位宽变为16位,我们可以连续写入LCD GRAM值,而GRAM的地址将根据MY/MX/MV设置的扫描方向进行自增。
例如:假设设置的是从左到右,从上到下的扫描方式,那么设置好起始坐标(通过SC,SP设置)后,每写入一个颜色值,GRAM地址将会自动自增1(SC++),如果碰到EC,则回到SC,同时SP++,一直到坐标:EC,EP结束,其间无需再次设置的坐标,从而大大提高写入速度。

0X2E指令

该指令是读GRAM指令,用于读取ILI9341的显存(GRAM),同0X2C指令,该指令支持连续读 (地址自动递增)
在这里插入图片描述
ILI9341在收到该指令后,第一次输出的是dummy数据(无效),第二次开始,读取到的才是有效的GRAM数据(从坐标:SC,SP开始),输出规律为:每个颜色分量占8个位,一次输出2个颜色分量。比如:第一次输出是R1G1,随后的规律为:B1R2G2B2R3G3B3R4G4B4R5G5… 以此类推

F407模式A读写时序与103略有不同

中文手册1203
在这里插入图片描述
在这里插入图片描述

TFTLCD引脚

NBL用不到
A[25-0]用一个
NEx片选
NOE读,NWE写
D数据
根据驱动时序时间设计ADDSET与DATASET

TFTLCD 显示

需要的相关设置步骤如下:

初始化 FSMC

主要是初始化三个寄存器 FSMC_BCRx,FSMC_BTRx,FSMC_BWTRx。
固件库提供了 3 个 FSMC 初始化函数分别为

FSMC_NORSRAMInit()FSMC_NANDInit()FSMC_PCCARDInit()
  • 设置 STM32F4 与 与 TFTLCD 模块相连接的 IO 。
  • 初始化 TFTLCD 模块
    • 探索者 STM32F4 开发板的LCD 接口,将 TFTLCD 的 RST 同 STM32F4 的 RESET 连接在一起了,只要按下开发板的 RESET键,就会对 LCD 进行硬复位。
    • 初始化序列,就是向 LCD 控制器写入一系列的设置值(比如伽马校准),这些初始化序列一般 LCD 供应商会提供给客户,我们直接使用这些序列即可,不需要深入研究。
  • 通过函数将字符和数字显示到 TFTLCD 模块上
    • 设置坐标→写 GRAM 指令→写 GRAM 来实现,但是这个步骤,只是一个点的处理,我们要显示字符/数字,就必须要多次使用这个步骤,从而达到显示字符/数字的目的,所以需要设计一个函数来实现数字/字符的显示,之后调用该函数,就可以实现数字/字符的显示了。

LCD结构体

定义如下 LCD 操作结构体(在 lcd.h 里面定义):

//LCD 操作结构体
typedef struct
{
vu16 LCD_REG;
vu16 LCD_RAM;
} LCD_TypeDef;
//使用 NOR/SRAM 的 Bank1.sector4,地址位 HADDR[27,26]=11 A6 作为数据命令区分线
//注意 16 位数据总线时,STM32 内部地址会右移一位对齐!
#define LCD_BASE ((u32)(0x6C000000 | 0x0000007E))
#define LCD ((LCD_TypeDef *) LCD_BASE)

有了这个定义,当我们要往 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重要参数

介绍一下 lcd.h 里面的另一个重要结构体:

//LCD 重要参数集
typedef struct
{
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 横竖屏切换等重要功能,所以还是利大于弊的。

几个重要函数

写寄存器命令

//写寄存器函数
//regval:寄存器值
void LCD_WR_REG(vu16 regval)
{ regval=regval; //使用-O2 优化的时候,必须插入的延时
LCD->LCD_REG=regval;//写入要写的寄存器序号
}

写LCD数据

//写 LCD 数据
//data:要写入的值
void LCD_WR_DATA(vu16 data)
{ data=data; //使用-O2 优化的时候,必须插入的延时
LCD->LCD_RAM=data;
}

读LCD数据

//读 LCD 数据
//返回值:读到的值
u16 LCD_RD_DATA(void)
{ vu16 ram; //防止被优化
ram=LCD->LCD_RAM;
return ram;
}

写寄存器

//写寄存器
//LCD_Reg:寄存器地址
//LCD_RegValue:要写入的数据
void LCD_WriteReg(vu16 LCD_Reg, vu16 LCD_RegValue)
{ LCD->LCD_REG = LCD_Reg; //写入要写的寄存器序号
LCD->LCD_RAM = LCD_RegValue; //写入数据
}

读寄存器

//读寄存器
//LCD_Reg:寄存器地址
//返回值:读到的数据
u16 LCD_ReadReg(vu16 LCD_Reg)
{ LCD_WR_REG(LCD_Reg); //写入要读的寄存器序号
delay_us(5);
return LCD_RD_DATA(); //返回读到的值
}

写GRAM

//开始写 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
}

坐标设置函数

该函数代码如下:

//设置光标位置
//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);
}else if(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);
}else if(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)。因为 9341/5310/6804/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 的背景色。LCD_DrawPoint 函数虽然简单,但是至关重要,其他几乎所有上层函数,都是通过调用这个函数实现的。

TFTLCD 模块的初始化函数 LCD_Init


//初始化lcd
//该初始化函数可以初始化各种ILI93XX液晶,但是其他函数是基于ILI9320的!!!
//在其他型号的驱动芯片上没有测试! 
void LCD_Init(void)
{ 	
	vu32 i=0;
	
  GPIO_InitTypeDef  GPIO_InitStructure;
	FSMC_NORSRAMInitTypeDef  FSMC_NORSRAMInitStructure;
  FSMC_NORSRAMTimingInitTypeDef  readWriteTiming; 
	FSMC_NORSRAMTimingInitTypeDef  writeTiming;
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOD|RCC_AHB1Periph_GPIOE|RCC_AHB1Periph_GPIOF|RCC_AHB1Periph_GPIOG, ENABLE);//使能PD,PE,PF,PG时钟  
  RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FSMC,ENABLE);//使能FSMC时钟  
	
 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;//PB15 推挽输出,控制背光
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化 //PB15 推挽输出,控制背光
	
  GPIO_InitStructure.GPIO_Pin = (3<<0)|(3<<4)|(7<<8)|(3<<14);//PD0,1,4,5,8,9,10,14,15 AF OUT
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOD, &GPIO_InitStructure);//初始化  
	
  GPIO_InitStructure.GPIO_Pin = (0X1FF<<7);//PE7~15,AF OUT
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化  

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//PF12,FSMC_A6
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化  

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//PF12,FSMC_A6
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOG, &GPIO_InitStructure);//初始化 

  GPIO_PinAFConfig(GPIOD,GPIO_PinSource0,GPIO_AF_FSMC);//PD0,AF12
  GPIO_PinAFConfig(GPIOD,GPIO_PinSource1,GPIO_AF_FSMC);//PD1,AF12
  GPIO_PinAFConfig(GPIOD,GPIO_PinSource4,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOD,GPIO_PinSource5,GPIO_AF_FSMC); 
  GPIO_PinAFConfig(GPIOD,GPIO_PinSource8,GPIO_AF_FSMC); 
  GPIO_PinAFConfig(GPIOD,GPIO_PinSource9,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOD,GPIO_PinSource10,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOD,GPIO_PinSource14,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOD,GPIO_PinSource15,GPIO_AF_FSMC);//PD15,AF12
 
  GPIO_PinAFConfig(GPIOE,GPIO_PinSource7,GPIO_AF_FSMC);//PE7,AF12
  GPIO_PinAFConfig(GPIOE,GPIO_PinSource8,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOE,GPIO_PinSource9,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOE,GPIO_PinSource10,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOE,GPIO_PinSource11,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOE,GPIO_PinSource12,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOE,GPIO_PinSource13,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOE,GPIO_PinSource14,GPIO_AF_FSMC);
  GPIO_PinAFConfig(GPIOE,GPIO_PinSource15,GPIO_AF_FSMC);//PE15,AF12
 
  GPIO_PinAFConfig(GPIOF,GPIO_PinSource12,GPIO_AF_FSMC);//PF12,AF12
  GPIO_PinAFConfig(GPIOG,GPIO_PinSource12,GPIO_AF_FSMC);


  readWriteTiming.FSMC_AddressSetupTime = 0XF;	 //地址建立时间(ADDSET)为16个HCLK 1/168M=6ns*16=96ns	
  readWriteTiming.FSMC_AddressHoldTime = 0x00;	 //地址保持时间(ADDHLD)模式A未用到	
  readWriteTiming.FSMC_DataSetupTime = 60;			//数据保存时间为60个HCLK	=6*60=360ns
  readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
  readWriteTiming.FSMC_CLKDivision = 0x00;
  readWriteTiming.FSMC_DataLatency = 0x00;
  readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A;	 //模式A 
    

	writeTiming.FSMC_AddressSetupTime =9;	      //地址建立时间(ADDSET)为9个HCLK =54ns 
  writeTiming.FSMC_AddressHoldTime = 0x00;	 //地址保持时间(A		
  writeTiming.FSMC_DataSetupTime = 8;		 //数据保存时间为6ns*9个HCLK=54ns
  writeTiming.FSMC_BusTurnAroundDuration = 0x00;
  writeTiming.FSMC_CLKDivision = 0x00;
  writeTiming.FSMC_DataLatency = 0x00;
  writeTiming.FSMC_AccessMode = FSMC_AccessMode_A;	 //模式A 

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

  FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);  //初始化FSMC配置

  FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4, ENABLE);  // 使能BANK1 
		
 	delay_ms(50); // delay 50 ms 
 	LCD_WriteReg(0x0000,0x0001);
	delay_ms(50); // delay 50 ms 
  	lcddev.id = LCD_ReadReg(0x0000);   
   	if(lcddev.id<0XFF||lcddev.id==0XFFFF||lcddev.id==0X9300)//读到ID不正确,新增lcddev.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,尝试看看是不是NT35510
				{
					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)			//也不是NT5510,尝试看看是不是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
					}
				}
			}
 		}  	
	} 
	if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X5510||lcddev.id==0X1963)//如果是这几个IC,则设置WR时序为最快
	{
		//重新配置写时序控制寄存器的时序   	 							    
		FSMC_Bank1E->BWTR[6]&=~(0XF<<0);//地址建立时间(ADDSET)清零 	 
		FSMC_Bank1E->BWTR[6]&=~(0XF<<8);//数据保存时间清零
		FSMC_Bank1E->BWTR[6]|=3<<0;		//地址建立时间(ADDSET)为3个HCLK =18ns  	 
		FSMC_Bank1E->BWTR[6]|=2<<8; 	//数据保存时间(DATAST)为6ns*3个HCLK=18ns
	}else if(lcddev.id==0X6804||lcddev.id==0XC505)	//6804/C505速度上不去,得降低
	{
		//重新配置写时序控制寄存器的时序   	 							    
		FSMC_Bank1E->BWTR[6]&=~(0XF<<0);//地址建立时间(ADDSET)清零 	 
		FSMC_Bank1E->BWTR[6]&=~(0XF<<8);//数据保存时间清零
		FSMC_Bank1E->BWTR[6]|=10<<0;	//地址建立时间(ADDSET)为10个HCLK =60ns  	 
		FSMC_Bank1E->BWTR[6]|=12<<8; 	//数据保存时间(DATAST)为6ns*13个HCLK=78ns
	}
 	printf(" LCD ID:%x\r\n",lcddev.id); //打印LCD ID   
	if(lcddev.id==0X9341)	//9341初始化
	{	 
		LCD_WR_REG(0xCF);  
		LCD_WR_DATA(0x00); 
		LCD_WR_DATA(0xC1); 
		LCD_WR_DATA(0X30); 
		LCD_WR_REG(0xED);  
		LCD_WR_DATA(0x64); 
		LCD_WR_DATA(0x03); 
		LCD_WR_DATA(0X12); 
		LCD_WR_DATA(0X81); 
		LCD_WR_REG(0xE8);  
		LCD_WR_DATA(0x85); 
		LCD_WR_DATA(0x10); 
		LCD_WR_DATA(0x7A); 
		LCD_WR_REG(0xCB);  
		LCD_WR_DATA(0x39); 
		LCD_WR_DATA(0x2C); 
		LCD_WR_DATA(0x00); 
		LCD_WR_DATA(0x34); 
		LCD_WR_DATA(0x02); 
		LCD_WR_REG(0xF7);  
		LCD_WR_DATA(0x20); 
		LCD_WR_REG(0xEA);  
		LCD_WR_DATA(0x00); 
		LCD_WR_DATA(0x00); 
		LCD_WR_REG(0xC0);    //Power control 
		LCD_WR_DATA(0x1B);   //VRH[5:0] 
		LCD_WR_REG(0xC1);    //Power control 
		LCD_WR_DATA(0x01);   //SAP[2:0];BT[3:0] 
		LCD_WR_REG(0xC5);    //VCM control 
		LCD_WR_DATA(0x30); 	 //3F
		LCD_WR_DATA(0x30); 	 //3C
		LCD_WR_REG(0xC7);    //VCM control2 
		LCD_WR_DATA(0XB7); 
		LCD_WR_REG(0x36);    // Memory Access Control 
		LCD_WR_DATA(0x48); 
		LCD_WR_REG(0x3A);   
		LCD_WR_DATA(0x55); 
		LCD_WR_REG(0xB1);   
		LCD_WR_DATA(0x00);   
		LCD_WR_DATA(0x1A); 
		LCD_WR_REG(0xB6);    // Display Function Control 
		LCD_WR_DATA(0x0A); 
		LCD_WR_DATA(0xA2); 
		LCD_WR_REG(0xF2);    // 3Gamma Function Disable 
		LCD_WR_DATA(0x00); 
		LCD_WR_REG(0x26);    //Gamma curve selected 
		LCD_WR_DATA(0x01); 
		LCD_WR_REG(0xE0);    //Set Gamma 
		LCD_WR_DATA(0x0F); 
		LCD_WR_DATA(0x2A); 
		LCD_WR_DATA(0x28); 
		LCD_WR_DATA(0x08); 
		LCD_WR_DATA(0x0E); 
		LCD_WR_DATA(0x08); 
		LCD_WR_DATA(0x54); 
		LCD_WR_DATA(0XA9); 
		LCD_WR_DATA(0x43); 
		LCD_WR_DATA(0x0A); 
		LCD_WR_DATA(0x0F); 
		LCD_WR_DATA(0x00); 
		LCD_WR_DATA(0x00); 
		LCD_WR_DATA(0x00); 
		LCD_WR_DATA(0x00); 		 
		LCD_WR_REG(0XE1);    //Set Gamma 
		LCD_WR_DATA(0x00); 
		LCD_WR_DATA(0x15); 
		LCD_WR_DATA(0x17); 
		LCD_WR_DATA(0x07); 
		LCD_WR_DATA(0x11); 
		LCD_WR_DATA(0x06); 
		LCD_WR_DATA(0x2B); 
		LCD_WR_DATA(0x56); 
		LCD_WR_DATA(0x3C); 
		LCD_WR_DATA(0x05); 
		LCD_WR_DATA(0x10); 
		LCD_WR_DATA(0x0F); 
		LCD_WR_DATA(0x3F); 
		LCD_WR_DATA(0x3F); 
		LCD_WR_DATA(0x0F); 
		LCD_WR_REG(0x2B); 
		LCD_WR_DATA(0x00);
		LCD_WR_DATA(0x00);
		LCD_WR_DATA(0x01);
		LCD_WR_DATA(0x3f);
		LCD_WR_REG(0x2A); 
		LCD_WR_DATA(0x00);
		LCD_WR_DATA(0x00);
		LCD_WR_DATA(0x00);
		LCD_WR_DATA(0xef);	 
		LCD_WR_REG(0x11); //Exit Sleep
		delay_ms(120);
		LCD_WR_REG(0x29); //display on	
	}
		 
	LCD_Display_Dir(0);		//默认为竖屏
	LCD_LED=1;				//点亮背光
	LCD_Clear(WHITE);
}

主函数代码

int main(void)
{ 
 	u8 x=0;
	u8 lcd_id[12];				//存放LCD ID字符串
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
	delay_init(168);      //初始化延时函数
	uart_init(115200);		//初始化串口波特率为115200
	
	LED_Init();					  //初始化LED
 	LCD_Init();           //初始化LCD FSMC接口
	POINT_COLOR=BLACK;      //画笔颜色:红色
	sprintf((char*)lcd_id,"LCD ID:%04X",lcddev.id);//将LCD ID打印到lcd_id数组。				 	
  	while(1) 
	{		 
		switch(x)
		{
			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,16,24,"Explorer STM32F4");	
		LCD_ShowString(30,70,200,16,16,"we will be happiness");
		LCD_ShowString(30,90,200,16,16,"it is ok");
 		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);	
	} 
}

SRAM

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

万码无虫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值