LV16-26-LCD-03-ILI9341驱动LCD
本文主要是STM32开发——ILI9341驱动LCD的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows | windows11 |
Ubuntu | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
SecureCRT | Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 |
开发板 | 正点原子 i.MX6ULL Linux阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) |
linux内核 | linux-4.15(NXP官方提供) |
STM32开发板 | 正点原子战舰V3(STM32F103ZET6) |
点击查看本文参考资料
- 通用
分类 | 网址 | 说明 |
官方网站 | https://www.arm.com/ | ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档 |
https://www.st.com/content/st_com/zh.html | ST官方网站,在这里我们可以找到STM32的相关文档 | |
https://www.stmcu.com.cn/ | 意法半导体ST中文官方网站,在这里我们可以找到STM32的相关中文参考文档 | |
http://elm-chan.org/fsw/ff/00index_e.html | FatFs文件系统官网 | |
教程书籍 | 《ARM Cortex-M3权威指南》 | ARM公司专家Joseph Yiu(姚文祥)的力作,中文翻译是NXP的宋岩 |
《ARM Cortex-M0权威指南》 | ||
《ARM Cortex-M3与Cortex-M4权威指南》 | ||
开发论坛 | http://47.111.11.73/forum.php | 开源电子网,正点原子的资料下载及问题讨论论坛 |
https://www.firebbs.cn/forum.php | 国内Kinetis开发板-野火/秉火(刘火良)主持的论坛,现也做STM32和i.MX RT | |
https://www.amobbs.com/index.php | 阿莫(莫进明)主持的论坛,号称国内最早最火的电子论坛,以交流Atmel AVR系列单片机起家,现已拓展到嵌入式全平台,其STM32系列帖子有70W+。 | |
http://download.100ask.net/index.html | 韦东山嵌入式资料中心,有些STM32和linux的相关资料也可以来这里找。 | |
博客参考 | http://www.openedv.com/ | 开源网-原子哥个人博客 |
http://blog.chinaaet.com/jihceng0622 | 博主是原Freescale现NXP的现场应用工程师 | |
cortex-m-resources | 这其实并不算是一个博客,这是ARM公司专家Joseph Yiu收集整理的所有对开发者有用的官方Cortex-M资料链接(也包含极少数外部资源链接) |
STM32 | STM32 HAL库开发实战指南——基于F103系列开发板 | 野火STM32开发教程在线文档 |
STM32库开发实战指南——基于野火霸道开发板 | 野火STM32开发教程在线文档 |
- SD卡
SD Association | 提供了SD存储卡和SDIO卡系统规范 |
点击查看相关文件下载
STM32F103xx英文数据手册 | STM32F103xC/D/E系列的英文数据手册 |
STM32F103xx中文数据手册 | STM32F103xC/D/E系列的中文数据手册 |
STM32F10xxx英文参考手册(RM0008) | STM32F10xxx系列的英文参考手册 |
STM32F10xxx中文参考手册(RM0008) | STM32F10xxx系列的中文参考手册 |
Arm Cortex-M3 处理器技术参考手册-英文版 | Cortex-M3技术参考手册-英文版 |
STM32F10xxx Cortex-M3编程手册-英文版(PM0056) | STM32F10xxx/20xxx/21xxx/L1xxxx系列Cortex-M3编程手册-英文版 |
SD卡相关资料——最新版本 | 有关SD卡的一些资料可以从这里下载 |
SD卡相关资料——历史版本 | 有关SD卡的一些历史版本资料可以从这里下载,比如后边看的SD卡2.0协议 |
SD 2.0 协议标准完整版 | 这是一篇关于SD卡2.0协议的中文文档,还是比较有参考价值的,可以一看 |
一、相关资料
这一部分主要看一下我们有哪些资料可以看,注意这里的资料都是放在本地的笔记对应的文件夹中:
- ATK-MD0280模块用户手册:正点原子ATK-MD0280模块用户手册.pdf
- ATK-MD0280模块裸屏图:00ATK-MD0280裸屏尺寸图(钢化玻璃TP).pdf
- ILI9341芯片手册:01ILI9341_DS.pdf
- 触摸驱动芯片手册:04XPT2046.pdf
二、2.8寸LCD模块
我使用的是正点原子的2.8寸LCD模块,后边也是以这个模块进行学习。
1. 屏幕简介
ATK-MD0280-V2.4(V2.4 是版本号,下面均以 ATK-MD0280 表示该产品)是正点原子推出的一款高性能 2.8 寸电阻触摸屏模块。该模块屏幕分辨率为 320*240, 16 位真彩显示,采用 ILI9341/ST7789 驱动,该芯片直接自带 GRAM,无需外加驱动器,因而任何单片机,都可以轻易驱动。 ATK-MD0280 模块采用电阻触摸屏, 精度高,抗干扰能力强,稳定性好,但不支持多点触摸。
在它的内部包含了一个型号为ILI9341的液晶控制器芯片(由于集成度高,所以图中无法看见),该液晶控制器使用8080接口与单片机通讯,图中液晶面板引出的FPC信号线即8080接口(RGB接口已在内部直接与ILI9341相连),且控制器中包含有显存,单片机把要显示的数据通过引出的8080接口发送到液晶控制器,这些数据会被存储到它内部的显存中,然后液晶控制器不断把显存的内容刷新到液晶面板,显示内容。
2. 模块参数
项目 | 说明 |
---|---|
接口类型 | LCD: Intel8080-16 位并口 触摸屏: SPI |
颜色格式 | RGB565 |
颜色深度 | 16 位 |
LCD 驱动器芯片 | ILI9341/ST7789 |
电阻触摸屏触摸芯片 | XPT2046 / HR2046 |
LCD 分辨率 | 320*240 |
触摸屏类型 | 电阻触摸 |
触摸点数 | 单点触摸 |
屏幕尺寸 | 2.8’ |
工作温度 | -20℃~70℃ |
存储温度 | -30℃~80℃ |
外形尺寸 | 51mm*82.6mm |
3. 电气特性
项目 | 说明 |
---|---|
电源电压 | 背光: 5V 其他: 3.3V |
IO 口电平 | 3.3V |
功耗 | 30~113mA |
(1)3.3V 系统,可以直接接本模块(供电必须 5V&3.3V 双供电),如果是 5V 系统,建议串接 100R左右电阻,做限流处理。
(2)30mA 对应背光关闭时的功耗, 113mA 对应背光最亮时的功耗,此数据是模块的总功耗(包括3.3V 和 5V 电源部分),实际应用中功耗会由于电源电压的波动而略微变化。
4. 模块特点
(1)320× 240 的分辨率;
(2)自带驱动,无需外加驱动器,单片机直接使用;
(3)板载背光电路,只需要 3.3V&5V 供电即可,无需外加高压;
(4)接口简单(LCD 采用 16 位 8080 并口,触摸屏采用 SPI 接口),使用方便;
(5)PCB 尺寸为 51mm*82.6mm,并带有安装孔位,安装方便;
(6)16 位真彩显示(65536色)。
【注意】模块是3.3V供电的,不支持5V电压的MCU,如果是5V MCU,必须在信号线串接120R电阻使用。
5. 模块原理图
这个原理图是来自于模块自带的资料。
【注意】DB1DB8,DB10DB17,总是按顺序连接MCU的FSMC_D0~D15
序号 | 名称 | 说明 |
---|---|---|
1 | CS | LCD 片选信号(低电平有效) |
2 | RS | 命令/数据控制信号(0,命令; 1,数据;) |
3 | WR | 写使能信号(低电平有效) |
4 | RD | 读使能信号(低电平有效) |
5 | RST | 复位信号(低电平有效) |
6~21 | D0~D15 | 双向数据总线 |
22,26,27 | GND | 地线 |
23 | BL_CTR | 背光控制引脚(高电平点亮背光,低电平关闭) |
24,25 | VCC3.3 | 主电源供电引脚(3.3V) |
28 | VCC5 | 背光供电引脚(5V) |
29 | MISO | NC,电容触摸屏未用到 |
30 | MOSI | 电容触摸屏 IIC_SDA 信号(CT_SDA) |
31 | PEN | 电容触摸屏中断信号(CT_INT) |
32 | BUSY | NC,电容触摸屏未用到 |
33 | CS | 电容触摸屏复位信号(CT_RST) |
34 | CLK | 电容触摸屏 IIC_SCL 信号(CT_SCL) |
6. 与STM32接线
TFTLCD 模块采用 2*17 的 2.54 公排针与外部连接(即接开发板的接口),与STM32引脚接口定义如图所示 :
7. 裸屏引脚
我们来看一下00ATK-MD0280裸屏尺寸图(钢化玻璃TP).pdf中的LCD裸屏引脚图:
这是裸屏的引脚排布,裸屏是接了一个ILI9341液晶控制器,我们通过控制ILI9341控制器来驱动LCD屏幕。
三、ILI9341驱动
1. 简介
ILI9341是奕力科技股份有限公司 (ilitek.com)的一款小尺寸驱动IC,我在官网没看到芯片手册,但是有些查datasheet的地方是可以查到的,这里就不写链接了。我在本地保存了一份:01ILI9341_DS.pdf
它是一个支持分辨率为240x320点阵的a-TFT LCD 的262144(26万色)色单片驱动器。这个单片驱动器包含了一个720通道的源极驱动器(source driver),一个320通道的栅极驱动器(gate driver),自带一个172800字节(320x240x18/8)的GRAM用于显示240x320分辨率的图片数据。ILI9341提供8位/9位/16位/18位的并行MCU数据总线,6位/16位/18位RGB接口数据总线以及3或4线SPI接口(serial peripheral interface),工作于1.65V-3.3V。
2. 内部框图
我们可以查看01ILI9341_DS.pdf的3. Block Diagram :
2.1 ① 控制和信号引脚
①为控制引脚和信号引脚,支持DBI B类接口( Intel 8080接口)、 DPI接口、 MIPI DSI接口输入。我们可以根据其不同状态设置可以使芯片工作在不同的模式,如每个像素点的位数是6、16还是18位;可配置使用SPI接口、8080接口还是RGB接口与MCU进行通讯。
MCU可以通过SPI、8080接口或RGB接口与ILI9341进行通讯,从而访问它的控制寄存器(CR)、地址计数器(AC)、及GRAM。经过索引寄存器( IR)、 控制寄存器( CR)、地址寄存器( AC)、读数据寄存器( RDR)、写数据寄存器( WDR)到GRAM。最后, GRAM把显示内容传输到LCD屏幕的显示。
控制引脚IM[2:0]用于设置控制器的接口模式,如下表所示(01ILI9341_DS.pdf的6. Block Function Description )。 我使用的这个显示屏支持的接口为16 Bit的MIPI-DBIB类,也就是8080接口,使用16根数据线,支持RGB565格式,因此IM[2:0]值为001,该值由屏幕供应商生产时硬件设置,此时用到的数据引脚为DB[15:0]。
2.2 ② GRAM
该芯片最主核心部分是位于中间的GRAM(Graphics RAM),它就是显存。GRAM中每个存储单元都对应着液晶面板的一个像素点。它右侧的各种模块共同作用把GRAM存储单元的数据转化成液晶面板的控制信号,使像素点呈现特定的颜色,而像素点组合起来则成为一幅完整的图像。
2.3 ③ LED控制器
在GRAM的左侧还有一个 ③ LED控制器(LED Controller)。LCD为非发光性的显示装置,它需要借助背光源才能达到显示功能,LED控制器就是用来控制液晶屏中的LED背光源。
3. 与STM32引脚对应关系
这些引出的信号线即8080通讯接口,带 X 的表示低电平有效,STM32通过该接口与ILI9341芯片进行通讯,实现对液晶屏的控制。通讯的内容主要包括命令和显存数据,显存数据即各个像素点的RGB565内容;命令是指对ILI9341的控制指令,MCU可通过8080接口发送命令编码控制ILI9341的工作方式,例如复位指令、设置光标指令、睡眠模式指令等等,具体的指令在01ILI9341_DS.pdf 数据手册均有详细说明。
我们这一节笔记用到的信号线如下表(注意我使用的模块只引出了DB[15:0]使用):
信号线 | ILI9341对应的信号线 | 说明 |
---|---|---|
LCD_DB[17:1] | D[15:0] | 16位双向数据线 |
LCD_RD | RDX | LCD读数据信号,低电平有效 |
LCD_RS | D/CX | 数据/命令信号,高电平时,D[15:0]表示的是数据(RGB像素数据或命令数据),低电平时D[15:0]表示控制命令 |
LCD_RST | RESX | 硬复位信号,低电平有效 |
LCD_WR | WRX | LCD写数据信号,低电平有效 |
LCD_CS | CSX | LCD片选信号,低电平有效 |
LCD_BL | - | LCD的背光信号,低电平点亮 |
模块对外接口采用16位并口,颜色深度为16位,格式为RGB565,关系如下图:
4. 向ILI9341写的时序
我们可以看01ILI9341_DS.pdf 的7.1.3. Write Cycle Sequence 一节:
(1)写命令时序由片选信号CSX拉低开始;
(2)对数据/命令选择信号线D/CX也置低电平,这表示写入的是命令地址(可理解为命令编码,如软件复位命令:0x01);
(3)以写信号WRX为低,读信号RDX为高,表示数据传输方向为写入,在WR的上升沿,使D[17:0]上的数据写入到ILI9341里面。同时,在数据线D[17:0](或D[15:0])输出命令地址。
(4)在第二个传输阶段传送的是命令的参数,所以D/CX要置高电平,表示写入的是命令数据,命令数据是某些指令带有的参数,如复位指令编码为0x01,它后面可以带一个参数,该参数表示多少秒后复位(实际的复位命令不含参数,此处只是为了先了解一下指令编码与参数的区别)。
我们看一个更详细的图:
首先片选信号CS拉低,复位信号RES保持为高。 数据/命令切换信号D/C拉低,写信号引脚拉低,此时DB[15:0]发送的就是指令(黄色部分) ;数据/命令切换信号D/C拉高,写信号引脚拉低,此时DB[15:0]发送的就是一个像素点的颜色数据( 红、绿、蓝部分) ,其中低5位为蓝色数据、中6位为绿色数据、高5位为红色数据。
5. 从ILI9341读的时序
这里我们一般用的不多,这里简单提一下吧,我们可以看01ILI9341_DS.pdf 的7.1.4. Read Cycle Sequence 一节:
先根据要读取的数据的类型,设置RS为高(数据)/低(命令),然后拉低片选,选中ILI9341,接着我们根据是读数据,置RD为低,然后读数据,在RD的上升沿, 读取数据线上的数据(D[15:0])。
6. 命令与寄存器
这一部分我们可以查看01ILI9341_DS.pdf 的8. Command一节,这个后边开始学习驱动LCD的代码的时候结合来看会更加容易理解。这里简单学习几个吧。学到这里,其实觉得挺奇怪的,在芯片手册中,没有看到寄存器的相关概念,也可能是我没有仔细看吧,手册中都是一些命令,但是这些命令控制的应该都是寄存器才对,所以后边这些的话,就可以看作是命令去控制的寄存器。
6.1 命令格式说明
ILI9341所有的指令都是 8 位的(高 8 位无效),且参数除了读写GRAM的时候是 16 位,其他操作参数,都是 8 位的。
6.2Read ID4 (D3h)
该指令为读ID4指令,用于读取LCD控制器的ID 。因此,同一个代码,可以根据ID的不同,执行不同的LCD驱动初始化,以兼容不同的LCD屏幕。
6.3 Memory Access Control (36h)
该指令为存储访问控制指令,可以控制ILI9341存储器的读写方向,简单的说,就是在连续写GRAM的时候,可以控制GRAM指针的增长方向,从而控制显示方式(读GRAM也是一样)。
6.4 . Column Address Set (2Ah)
该指令是列地址设置指令,在从左到右,从上到下的扫描方式(默认)下面,该指令用于设置横坐标(x坐标):
在默认扫描方式时,该指令用于设置x坐标,该指令带有4个参数,实际上是2个坐标值:SC和EC,即列地址的起始值和结束值,SC必须小于等于EC,且0≤SC/EC≤239。一般在设置x坐标的时候,我们只需要带2个参数即可,也就是设置SC即可,因为如果EC没有变化,我们只需要设置一次即可(在初始化ILI9341的时候设置),从而提高速度。
6.5 Page Address Set (2Bh)
该指令是页地址设置指令,在从左到右,从上到下的扫描方式(默认)下面,该指令用于设置纵坐标(y坐标):
在默认扫描方式时,该指令用于设置y坐标,该指令带有4个参数,实际上是2个坐标值:SP和EP,即页地址的起始值和结束值,SP必须小于等于EP,且0≤SP/EP≤319。一般在设置y坐标的时候,我们只需要带2个参数即可,也就是设置SP即可,因为如果EP没有变化,我们只需要设置一次即可(在初始化ILI9341的时候设置),从而提高速度。
6.6 Memory Write (2Ch)
该指令是写GRAM指令,在发送该指令之后,我们便可以往LCD的GRAM里面写入颜色数据了,该指令支持连续写 (地址自动递增):
在收到指令0X2C之后,数据有效位宽变为16位,我们可以连续写入LCD GRAM值,而GRAM的地址将根据MY/MX/MV设置的扫描方向进行自增。例如:假设设置的是从左到右,从上到下的扫描方式,那么设置好起始坐标(通过SC,SP设置)后,每写入一个颜色值,GRAM地址将会自动自增1(SC++),如果碰到EC,则回到SC,同时SP++,一直到坐标:EC,EP结束,其间无需再次设置的坐标,从而大大提高写入速度。
6.7 Memory Read (2Eh)
该指令是读GRAM指令,用于读取ILI9341的显存(GRAM),同0X2C指令,该指令支持连续读 (地址自动递增):
ILI9341在收到该指令后,第一次输出的是dummy数据(无效),第二次开始,读取到的才是有效的GRAM数据(从坐标:SC,SP开始),输出规律为:每个颜色分量占8个位,一次输出2个颜色分量。比如:第一次输出是R1G1,随后的规律为:B1R2→G2B2→R3G3→B3R4→G4B4→R5G5… 以此类推
7. 内存显示地址映射
这一部分可以参考01ILI9341_DS.pdf 的9.2. Memory to Display Address Mapping 一节,对后边理解扫描方向会有好处。其实这一部分笔记有一些是从网上看的,有一些概念看的我挺不明所以的,后来来起来按照我自己的理解,这里GRAM是一个虚拟内存,缓存我们要显示的数据,我们通过MCU向GRAM写入数据,然后LCD屏幕会有一个类似“电子枪”的东西,从GRAM中取出像素点的数据,然后按照特定的扫描方式,点亮LCD面板的真实像素点,这些真实的像素点,按照我自己的理解,我觉得可以理解成LCD的物理内存。
7.1 GRAM与LCD面板
这里的模式为Normal Display ON or Partial Mode ON, Vertical Scroll Mode OFF,也就是说下图只讨论“正常显示”,不讨论“垂直滚动显示”模式。
可以看到GRAM内存被两个指针访问,行指针和列指针,行指针范围从0000h到0013Fh(换算成十进制就是0到319,一共320行),列指针范围为0000h到00EFh(换算成十进制就是0到239,一共240列)。也就是说,GRAM和LCD像素面板的对应关系是一种竖屏(240*320)的对应的关系。至于如何让GRAM数据显示到LCD屏上,不需要我们去考虑,只要知道这种对应关系就可以了。我们要是想要在最左上角显示一个点,可以将将点数据存储在GRAM(列指针,页指针)=(0,0)处。
当实际将GRAM中的数据刷新到LCD屏进行显示的时候,ILI9341有8种显示方式:左上角→右下角(竖屏)、左下角→右上角(竖屏)、右上角→左下角(竖屏)、右下角→左上角(竖屏)、左上角→右下角(横屏)、左下角→右上角(横屏)、右上角-→左下角(横屏)、右下角→左上角(横屏),既然GRAM与LCD像素面板的点是固定对应的,那么不同的显示模式又是怎么实现的呢?
按照我自己的理解,GRAM与LCD面板的像素点的地址是一一对应的,这个对应关系是无法被改变的,前边我们知道,要想显示一幅图像,有一个行同步信号,比如就从左上角开始显示,当显示完一行的时候,会自动进行下一行的显示,显示完一帧图像的时候,会产生一个帧同步信号,进行下一帧的显示,此时就显示到了右下角。当然这里的扫描针对的是LCD显示屏,而非GRAM,可以想象一下,我们有一个写在GRAM(0,0)位置的像素点,现在我们要开始显示,我们在LCD屏的右下角开始显示,那么读取的这一个像素的数据就会被显示到LCD屏的右下角。
7.2 MCU对GRAM的写/读方向
从这张图可以看出,我们通过MCU向GRAM写入数据的时候,数据按照上面所示的顺序写入,B为起始点,E为结束点,按照从左到右,从上到下的顺序进行写入,写入完一行数据后,切换到下一行进行显示。
接下来就是将GRAM中的数据刷新到到LCD的像素面板(也可以称之为物理内存吧,单证目前我是这么认为的)进行显示,在物理内存中写入数据的计数器由“Memory Data Access Control(36h)”命令控制,B5、B6和B7位如下所述:
从图中可以看出,B5位对应的是 MADCTL (Memory Access Control) 寄存器中的D5,表示交换行列,B6对应的是D6,表示列地址顺序,B7对应的是D7,表示的是行地址顺序。多以这里的B[7:5]就对应MADCTL 的D[7:5],这三位共同决定了LCD的八种扫描方式。图中的四个角有四个说明,它们都是在不关心行列交换的情况下,另外两位取不同值的时候整个LCD显示的时候扫描的起始点。比如,B6=0,B7=1,这个时候LCD开始显示的时候,起始点就是左下角的(0,319),对于虚拟地址来说就会显示GRAM中(0, 0)这个位置的数据,当然,这个值是针对竖屏240*320来讲的。
详细的GRAM中的虚拟地址到LCD中的物理地址的转换关系如下表:
于是我们从GRAM到LCD就有8种扫描模式啦。
7.2 8种显示方向
一些说明:
(1)MV、MX、MY的控制位在命令 0x36 对应的寄存器中
(2)Image in the Memory(MPU)中描述的是在MCU的240(宽)* 320(高)* 16bit(RGB565显示方式)中显示了一个图像“F”。
(3)Image In the Driver(Frame Memory)描述的是经过虚拟地址到物理地址转换器后,实际传输到GRAM中的数据显示阵列。
(4)B、E分别描述了MCU传输的数据流的起始位置和终止位置,下边的图都是来自于01ILI9341_DS.pdf 的9.3. MCU to memory write/read direction一节。这里需要注意,从GRAM读取像素点的数据,这个顺序按照从左到右,从上到下的顺序进行,然后送到LCD显示的时候,根据MY、MX和MV三个位的不同值,从指定的位置开始,到这个位置的对角结束,按照先行后列的方式进行扫描显示。
其实不论哪种显示方向,其实并没有改变GRAM物理内存与LCD显示屏的对应关系,也即是说GRAM物理内存与显示屏之间的对应关系、内存到LCD的扫描方式,是固定不变的。那么这种显示方向是怎么出现的?
实际上,显示方向说的是MCU的显示缓存MPU(或者MCU读写GRAM的数据流)与LCD显示屏的对应关系。由于GRAM地址与LCD显示屏像素点的物理地址的对应关系是不会改变的。
用户在写程序的过程中,LCD显示操作是更改MPU的内容,至于MPU到GRAM的传输是驱动程序完成的,就是前边学习的8080时序。也就是说,用户控制显示的内容,接触的是MPU(其实就是GRAM啦),而更改显示方向需要配置ILI9341的寄存器。其实,ILI9341的扫描方向的功能也可以没有,但是这个时候要想改变显示的方向之类的,需要用户自己软件进行转换,直接在GRAM中进行转换,然后刷新到LCD屏幕,其实ILI9341配置的那3位就相当于帮我们做了一个转换关系表,这样就不用我们自己去配置了。
7.2.1 Normal
这是正常的模式,以(x,y)表示(列地址,行地址),那么MCU向(0,0)写入数据,经过虚拟地址到物理地址的转换,实际写入到GRAM的地址是(0,0),对应LCD的左上角。MCU向(239,319)写入数据,经过虚拟地址到物理地址的转换,实际写入到GRAM的地址是(239,319),对应LCD的右下角。
B[7:5] | D[7:5] | MY | MX | MV | D[7:5]十进制 |
---|---|---|---|---|---|
000 | 000 | 0 | 0 | 0 | 0 |
7.2.2 Y-Mirror
这是Y镜像,也就是行地址翻转的情况,以(x,y)表示(列地址,行地址),那么MCU向(0,0)写入数据,经过虚拟地址到物理地址的转换,实际写入到GRAM的地址是(0,319),对应LCD的左下角:
MCU向(239,319)写入数据,经过虚拟地址到物理地址的转换,实际写入到GRAM的地址是(239,0),对应LCD的右上角。最终的效果就是LCD的显示实现了Y方向上的翻转。
B[7:5] | D[7:5] | MY | MX | MV | D[7:5]十进制 |
---|---|---|---|---|---|
100 | 100 | 1 | 0 | 0 | 4 |
7.2.3 X-Mirror
B[7:5] | D[7:5] | MY | MX | MV | D[7:5]十进制 |
---|---|---|---|---|---|
010 | 010 | 0 | 1 | 0 | 2 |
7.2.4 X/Y-Mirror
B[7:5] | D[7:5] | MY | MX | MV | D[7:5]十进制 |
---|---|---|---|---|---|
110 | 110 | 1 | 1 | 0 | 6 |
7.2.5 X-Y Exchange
这是XY交换,也就是行列地址交换的情况,以(x,y)表示(列地址,行地址),MCU向(0,0)写入数据,经过虚拟地址到物理地址的转换,实际写入到GRAM的地址是(0,0),对应LCD的左上角。MCU向(239,319)写入数据,经过虚拟地址到物理地址的转换,实际写入到GRAM的地址是(319,239),对应LCD的右下角。经过这样的变换,LCD就从原来的竖屏变成了横屏显示。最终的效果就是LCD的显示实现了行列的交换。
B[7:5] | D[7:5] | MY | MX | MV | D[7:5]十进制 |
---|---|---|---|---|---|
001 | 001 | 0 | 0 | 1 | 1 |
7.2.6 X-Y Exchange Y-Mirror
B[7:5] | D[7:5] | MY | MX | MV | D[7:5]十进制 |
---|---|---|---|---|---|
101 | 101 | 1 | 0 | 1 | 5 |
7.2.7 XY Exchange X-Mirror
B[7:5] | D[7:5] | MY | MX | MV | D[7:5]十进制 |
---|---|---|---|---|---|
011 | 011 | 0 | 1 | 1 | 3 |
7.2.8 XY Exchange XY-Mirror
B[7:5] | D[7:5] | MY | MX | MV | D[7:5]十进制 |
---|---|---|---|---|---|
111 | 111 | 1 | 1 | 1 | 7 |
7.2.9 八种显示方向对应关系
B[7:5] | D[7:5] | MY | MX | MV | D[7:5]十进制 | 模式说明 | 图片对应 |
---|---|---|---|---|---|---|---|
000 | 000 | 0 | 0 | 0 | 0 | Normal | |
001 | 001 | 0 | 0 | 1 | 1 | X-Y Exchange | |
010 | 010 | 0 | 1 | 0 | 2 | X-Mirror | |
011 | 011 | 0 | 1 | 1 | 3 | XY Exchange X-Mirror | |
100 | 100 | 1 | 0 | 0 | 4 | Y-Mirror | |
101 | 101 | 1 | 0 | 1 | 5 | X-Y Exchange Y-Mirror | |
110 | 110 | 1 | 1 | 0 | 6 | X/Y-Mirror | |
111 | 111 | 1 | 1 | 1 | 7 | XY Exchange XY-Mirror |
四、FSMC模拟8080时序
1. FSMC?
ILI9341的8080通讯接口时序可以由STM32使用普通I/O接口进行模拟,但这样效率太低,STM32提供了一种特别的控制方法——使用FSMC接口实现8080时序。
我们前边学习SRAM的时候了解到STM32的FSMC外设可以用于控制扩展的外部存储器,而MCU对液晶屏的操作实际上就是把显示数据写入到显存中,与控制存储器非常类似,且8080接口的通讯时序完全可以使用FSMC外设产生,因而非常适合使用FSMC控制液晶屏。
2. NOR闪存,非复用接口
控制LCD时,适合使用FSMC的NOR\PSRAM模式,它与前面使用FSMC控制SRAM的稍有不同,控制SRAM时使用的是模式A,而控制LCD时使用的是与NOR FLASH一样的模式B,所以我们重点分析前边框图中NOR FLASH控制信号线部分。我们可以看[STM32中文参考手册](https://www.stmcu.com.cn/Designresource/detail/localization_document /710001)的19.5.1 外部存储器接口信号:
FSMC信号名称 | 信号方向 | 功能 |
---|---|---|
CLK | 输出 | 时钟信号( 用于同步访问的外部存储器) |
A[25:16] | 输出 | 地址总线 |
AD[15:0] | 输入/输出 | 16位复用的,双向地址/数据总线 |
NEx | 输出 | Bank1区域内片选信号, x=1~4,每个区域大小为64MB |
NOE | 输出 | 读使能信号 |
NWE | 输出 | 写使能信号 |
NWAIT | 输入 | NOR闪存要求FSMC等待的信号 |
NOR闪存存储器是按16位的字寻址,最大容量达64M字节(26条地址线)。在控制LCD时,使用的是类似异步、地址与数据线独立的NOR FLASH控制方式,所以实际上CLK、NWAIT、NADV引脚并没有使用到。
3. ILI9341与FSMC怎么连接?
首先我们回顾下外部 SRAM的连接,外部 SRAM 的控制一般有:地址线(如 A0A18)、数据线(如 D0D15)、写信号(WE)、读信号(OE)、片选信号(CS),如果 SRAM 支持字节控制,那么还有 UB/LB 信号。
而 TFTLCD的信号我们在前边有介绍,包括: D/C、 D0 ~ D15、 WR、 RD、 CS、 RST 和 BL 等,其中真正在操作 LCD 的时候需要用到的就只有: D/C、D0 ~ D15、 WR、 RD 和 CS。其操作时序和 SRAM的控制完全类似,唯一不同就是 TFTLCD 有 RS 信号,但是没有地址信号。
TFTLCD 通过 D/C 信号来决定传送的数据是数据还是命令,本质上可以理解为一个地址信号, 比如我们把 D/C 接在 FSMC_A0 上面,那么当 FSMC 控制器写地址 0 的时候,会使得 FSMC_A0 变为 0,对 TFTLCD 来说,就是写命令。而 FSMC 写地址 1 的时候, FSMC_A0 将会变为 1,对 TFTLCD 来说,就是写数据了。这样,就把数据和命令区分开了,他们其实就是对应 SRAM 操作的两个连续地址。当然 D/C 也可以接在其他地址线上,战舰 STM32 开发板是把 D/C 连接在 FSMC_A10 上面的。 所以两者的对应关系可以如下表(带X的表示低电平有效):
FSMC信号 | 功能 | 8080信号 | 功能 | |
---|---|---|---|---|
NEx | Bank1片选信号 | = | CSX | 控制器片选 |
NOE | 读使能 | = | RXD | 读信号引脚 |
NEW | 写使能 | = | WRX | 写信号引脚 |
D[15:0] | 数据总线 | = | DB[15:0] | 数据总线 |
A[25:0] | 地址总线 | ≈ | D/CX | 数据/命令切换信号 |
4. FSMC NOR/PSRAM模式B
我们重点来看一下模式B的写时序(读时序一般不怎么用,因为我们主要是向LCD写数据来进行显示用的,而且我们看一下读时序就知道其实对应关系是一样的,所以我们这里只分析写时序就可以啦,读时序是一样的)与ILI9341的写时序的对应关系:
我们可以看到除了D/CX信号外,剩下的几个信号的时序就是一致的,看到这个图,我们就该明白为什么FSMC可以拿来模拟8080时序啦。但是还有一个问题,那就是D/CX这个信号怎么办呢?要是FSMC也可以产生高低电平来使控制D/CX那不就好了吗?
当我们使用FSMC访问特定地址时, FSMC 会产生相应的模拟 8080 时序,控制地址线输出要访问的内存地址,使用数据信号线接收或发送数据,其它片选信号 NE、读使能信号 NOE、写使能信号NWE 辅助产生完整的时序,而由于控制液晶屏的硬件连接中,使用如图下中的连接来模拟 8080 时序,所以 FSMC 产生的这些信号会被 ILI9341 接收,并且使用其中一根 FSMC_Ax 地址控制命令/数据选择引脚 RS(即 D/CX),因此,我们要是知道 STM32 访问什么地址时,对应的 FSMC_Ax 引脚会输出高电平表示传输的是数据,访问什么地址时,对应的 FSMC_Ax 引脚会输出低电平表示传输的是命令。若理解了计算过程,以后就可以根据自己制作的硬件电路来计算访问地址了。
根据STM32对寻址空间的地址映射,地址0x6000 0000 ~0x9FFF FFFF是映射到外部存储器的,而其中的0x6000 0000 ~0x6FFF FFFF则是分配给NOR FLASH、PSRAM这类可直接寻址的器件。当FSMC外设被配置成正常工作,并且外部接了NOR FLASH时,若向0x60000000地址写入数据如0xABCD,FSMC会自动在各信号线上产生相应的电平信号,写入数据。FSMC会控制片选信号NE1选择相应的NOR 芯片,然后使用地址线A[25:0]输出0x60000000,在NWE写使能信号线上发出低电平的写使能信号,而要写入的数据信号0xABCD则从数据线D[15:0]输出,然后数据就被保存到NOR FLASH中了。
我们假设,现在将D/CX接在FSMC_A0上,由于FSMC会自动产生地址信号,当使用FSMC向0x6xxx xxx1、0x6xxx xxx3、0x6xxx xxx5…这些奇数地址写入数据时,地址最低位的值均为1,所以它会控制地址线A0(D/CX)输出高电平,那么这时通过数据线传输的信号会被理解为数值;若向0x6xxx xxx0 、0x6xxx xxx2、0x6xxx xxx4…这些偶数地址写入数据时,地址最低位的值均为0,所以它会控制地址线A0(D/CX)输出低电平,因此这时通过数据线传输的信号会被理解为命令,如下表:
地址 | 地址的二进制值(低四位) | A0(D/CX)的电平 | 控制ILI9341时的意义 |
---|---|---|---|
0x6xxx xxx1 | 0001 | 1 高电平 | D 数值 |
0x6xxx xxx3 | 0011 | 1 高电平 | D 数值 |
0x6xxx xxx5 | 0101 | 1 高电平 | D 数值 |
0x6xxx xxx0 | 0000 | 0 低电平 | C 命令 |
0x6xxx xxx2 | 0010 | 0 低电平 | C 命令 |
0x6xxx xxx4 | 0100 | 0 低电平 | C 命令 |
有了这个基础,我们只要配置好FSMC外设,然后在代码中利用指针变量,向不同的地址单元写入数据,就能够由FSMC模拟出的8080接口向ILI9341写入控制命令或GRAM的数据了。
注意:在实际控制时,以上地址计算方式还不完整,还需要注意HADDR内部地址与FSMC地址信号线的转换,关于这部分内容在代码讲解时再详细举例说明.
5. FSMC NOR/PSRAM模式A
后边在实际实现LCD驱动的时候,发现,模式A好像也可以使用:
- 读时序
- 写时序
对比过后,模式A好像也能用,反正不管用哪种模式,主要还是ADDSET和DATAST的确定。
6. 地址计算
我们上边知道可以吧D/CX接在FSMC_A[25:0]任意一个引脚上,接在A0的情况上边已经分析了,那要是不使用A0,我们怎么计算ILI9341的命令和数据所对应的FSMC需要发送的地址呢?
(1)我们根据开发板的原理图,我们假设使用的是 FSMC_NE1 作为 8080_CS 片选信号,所以首先可以确认地址范围,当访问0X6000 0000 ~ 0X63FF FFFF 地址时, FSMC 均会对外产生片选有效的访问时序;
(2)我们假设使用 FSMC_A16 地址线作为命令/数据选择线 D/C 信号,所以在以上地址范围内,再选择出使得 FSMC_A16 输出高电平的地址,即可控制表示数据,选择出使得 FSMC_A16 输出低电平的地址,即可控制表示命令。
(3)计算高低电平对应的地址:
要使 FSMC_A16 地址线为高电平,实质是输出地址信号的第 16 位为 1 即可,使用 0X60000000~0X63FF FFFF 内的任意地址,作如下运算:
1 | 设置地址的第 16 位为 1: 0X6000 0000 |= (1<<16) = 0x6001 0000 |
要使 FSMC_A16 地址线为低电平,实质是输出地址信号的第 16 位为 0 即可,使用 0X60000000~0X63FF FFFF 内的任意地址,作如下运算
1 | 设置地址的第 16 位为 0: 0X6000 0000 &= ~ (1<<16) = 0x6000 0000 |
(4)但是,以上方法计算的地址还不完全正确,STM32 内部访问地址时使用的是内部 HADDR 总线,它是需要转换到外部存储器的内部 AHB 地址线,它是字节地址 (8 位),而存储器访问不都是按字节访问,因此接到存储器的地址线依存储器的数据宽度有所不同。
而我们使用的是 16 位的数据访问方式,所以 HADDR 与 FSMC_A 的地址线连接关系会左移一位,如 HADDR[1] 与 FSMC_A[0] 对应、 HADDR[2] 与 FSMC_A[1] 对应。因此,当 FSMC_A0 地址线为 1 时,实际上内部地址的第 1 位为 1, FSMC_A1 地址线为 1 时,实际上内部地址的第 2 位为1。同样地,当希望 FSMC_A16 地址输出高电平或低电平时,需要重新调整计算公式:
要使 FSMC_A16 地址线为高电平,实质是访问内部 HADDR 地址的第 (16+1) 位为 1 即可,使用 0X6000 0000~0X63FF FFFF 内的任意地址,作如下运算:
1 | 使 FSMC_A16 地址线为高电平: 0X6000 0000 |= (1 << (16+1)) = 0x6002 0000 |
要使 FSMC_A16 地址线为低电平,实质是访问内部 HADDR 地址的第 (16+1) 位为 0 即可,使用 0X6000 0000~0X63FF FFFF 内的任意地址,作如下运算:
1 | 使 FSMC_A16 地址线为低电平: 0X6000 0000 &= ~ (1<<(16+1)) = 0x6000 0000 |
根据最终的计算结果,总结如下:当 STM32 访问内部的 0x6002 0000 地址时, FSMC 自动输出时序,且使得与液晶屏的数据/命令选择线 RS(即 D/CX) 相连的 FSMC_A16 输出高电平,使得液晶屏会把传输过程理解为数据传输;类似地,当 STM32 访问内部的 0X6000 0000 地址时, FSMC自动输出时序,且使得与液晶屏的数据/命令选择线 RS(即 D/CX) 相连的 FSMC_A16 输出低电平,使得液晶屏会把传输过程理解为命令传输。
(5)我们可以将上述结果封装成一个宏,方便使用:
1 | /**************************************************************** |
【注意】其实吧,通过这种方式计算得来的地址,并不是所有的地址都能用,不能用的时候注意换一个试一下,我后边写代码的时候用A10测了一下,最低的4个位不能是奇数,至少我学习这个的时候看到是这样的规律随后没有深究,可能是存在一个对齐问题吧,后边有坑了再补充。
6. 读写IL9341
FSMC连接好外部的存储器并初始化后,就可以直接通过访问地址来读写数据。FSMC访问存储器的方式与I2C EEPROM、SPI FLASH的不一样,后两种方式都需要控制I2C或SPI总线给存储器发送地址,然后获取数据;在程序里,这个地址和数据都需要分开使用不同的变量存储,并且访问时还需要使用代码控制发送读写命令。而使用FSMC外接存储器时,其存储单元是映射到STM32的内部寻址空间的;在程序里,定义一个指向这些地址的指针,然后就可以通过指针直接修改该存储单元的内容,FSMC外设会自动完成数据访问过程,读写命令之类的操作不需要程序控制。
总的来说就是,我们确定了数据的读写地址之后,我们直接向这个地址写入数据,就可以发送或者接收16bit数据啦控制命令的地址也是一样,这就是”SRAM”地址空间映射到内核的好处,我们可以直接进行访问。
6.1 写入命令
1 | /** |
6.2 写入数据
1 | /** |
6.3 读取数据
1 | /** |