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模块
分辨率:320480,驱动IC:NT35310,电阻触摸屏,16位并口驱动
3,ATK-4.3寸 TFTLCD模块
分辨率:480800,驱动IC:NT35510,电容触摸屏,16位并口驱动
4,ATK-7寸 TFTLCD模块(V1版本)
分辨率:480800,驱动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并口读/写的过程为:
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,随后的规律为:B1R2G2B2R3G3B3R4G4B4R5G5… 以此类推
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 等的设置同其他屏有些不太一样,所以进行了区别对待。文章来源:https://www.toymoban.com/news/detail-781657.html
画点函数
//画点
//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 函数虽然简单,但是至关重要,其他几乎所有上层函数,都是通过调用这个函数实现的。文章来源地址https://www.toymoban.com/news/detail-781657.html
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
到了这里,关于STM32的FSMC的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!