STM32液晶显示
液晶控制的基本原理,FSMC模拟8080时序实现与液晶控制器ili9341通信从而控制液晶屏,液晶驱动初始化、常用指令。
✅作者简介:嵌入式入坑者,与大家一起加油,希望文章能够帮助各位!!!!
📃个人主页:@rivencode的个人主页
🔥系列专栏:玩转STM32
💬保持学习、保持热爱、认真分享、一起进步!!
目录
一.显示器简介
主要要掌握基本的液晶显示原理,理解像素、分辨率、色彩深度的概念不然学的糊里糊涂的。
显示器属于计算机的 I/O 设备,即输入输出设备,像鼠标,键盘是输入设备,像打印机,显示屏等就是输出设备。它是一种将特定电子信息输出到屏幕上再反射到人眼的显示工具。常见的有 CRT 显示器、液晶显示器、LED 点阵显示器及OLED 显示器。
液晶显示器,简称 LCD(Liquid Crystal Display),相对于上一代 CRT 显示器(阴极射线管显示器),LCD 显示器具有功耗低、体积小、承载的信息量大及不伤眼的优点,因而它成为了现在的主流电子显示设备,其中包括电视、电脑显示器、手机屏幕及各种嵌入式设备的显示器。
液晶是一种介于固体和液体之间的特殊物质,它是一种有机化合物,常态下呈液态,但是它的分子排列却和固体晶体一样非常规则,因此取名液晶。如果给液晶施加电场,会改变它的分子排列,从而改变光线的传播方向,配合偏振光片,它就具有控制光线透过率的作用,再配合彩色滤光片,改变加给液晶电压大小,就能改变某一颜色透光量的多少,各种颜色分量组合在一起就能拼凑成五颜六色的光
利用这种原理,做出可控红、绿、蓝光输出强度的显示结构,把三种显示结构组成一个显示单位,通过控制红绿蓝的强度,可以使该单位混合输出不同的色彩,这样的一个显示单位被称为像素
。
液晶显示屏的缺点
1.液晶本身是不发光的,所以需要有一个背光灯(纯白光)提供光源,光线经一系列处理过程才到输出,所以输出的光线强度是要比光源的强度低很多的,比较浪费能源(像手机在室外看不清内容一样)。
2.而且这些处理过程会导致显示方向比较窄,也就是它的视角较小,从侧面看屏幕会看不清它的显示内容。另外,输出的色彩变换时,液晶分子转动也需要消耗一定的时间(液晶分子转动毕竟是一种机械运动),导致屏幕的响应速度低。
1.显示器的基本参数
- 像素
像素是组成图像的最基本单元要素,显示器的像素指它成像最小的点,即前面讲解液晶原理中提到的一个显示单元,显示屏就是由一个一个像素点组成每个像素点都可以输出各种颜色,当所以像素点有规律的输出就能输出一幅图像。 - 分辨率
一些嵌入式设备的显示器常常以“行像素值 x 列像素值”表示屏幕的分辨率。如分辨率 800x480 表示该显示器的每一行有 800 个像素点,每一列有 480 个像素点,也可理解为有 800 列,480 行,知道分辨率就知道屏幕一共有多少个像素点。 - 色彩深度
色彩深度在计算机图形学领域中表示在位图或者视频帧缓冲区中储存1像素的颜色所用的位数,它也称为位/像素(bpp)。色彩深度越高,可用的颜色就越多,常见的有16位RBG565 与24位 RBG888
- 点距
不是分辨率越高(像素点越多),屏幕的画质就越好,屏幕的画质还是主要取决于点距的大小。
点距:指两个相邻像素点之间 的距离,它会影响画质的细腻度及观看距离,相同尺寸的屏幕,若分辨率越高,则点距越小,画质越细腻。如现在有些手机的屏幕分辨率比电脑显示器的还大,这是手机屏幕点距小的原因;
2.液晶控制原理
这个完整的显示屏由液晶显示面板、电容触摸面板以及 PCB 底板构成。图中的触摸面板带有触摸控制芯片,该芯片处理触摸信号并通过引出的信号线与外部器件通讯。
根据实际需要,PCB 底板上可能会带有“液晶控制器芯片”,图中右侧的液晶屏 PCB 上带有 RA8875 液晶控制器。因为控制液晶面板需要比较多的资源,所以大部分低级微控制器都不能直接控制液晶面板,需要额外配套一个专用液晶控制器来处理显示过程,外部微控制器只要把它希望显示的数据直接交给液晶控制器即可。
STM32F429 系列的芯片不需要额外的液晶控制器,也就是说它把专用液晶控制器的功能集成到 STM32F429 芯片内部了,它节约了额外的控制器成本。而 STM32F1 系列的芯片由于没有集成液晶控制器到芯片内部,所以它只能驱动自带控制器的屏幕。
- 显存
液晶屏中的每个像素点都是数据,在实际应用中需要把每个像素点的数据缓存起来,再传输给液晶屏,一般会使用 SRAM 或 SDRAM 性质的存储器,而这些专门用于存储显示数据的存储器,则被称为显存。
显存一般至少要能存储液晶屏的一帧(一幅图像)显示数据。
如分辨率为 800x480 的 液 晶 屏(显示一个屏幕的图像需要800*480个像素点) , 使 用 RGB888 格 式 显 示 :一个像素点占24位也就是3个字节,则它 的 一 帧 显 示 数 据 大 小 为 :3x800x480=1152000 字 节
; 若 使 用 RGB565 格 式 显 示 , 一 帧 显 示 数 据 大 小 为 :2x800x480=768000 字节。
一般来说,外置的液晶控制器会自带显存,而像 STM32F429 等集成液晶控制器的芯片可使用内部 SRAM 或外扩 SDRAM 用于显存空间。
液晶面板的控制信号
使用下图信号线,液晶面板通过这些信号线与液晶控制器通讯,使用这种
通讯信号的被称为 RGB 接口(RGB Interface)。
1)RGB 信号线
RGB 信号线各有 8 根,分别用于表示液晶屏一个像素点的红、绿、蓝颜色分量。使用红绿蓝颜色分量来表示颜色是一种通用的做法,如果是 RGB565 表示红绿蓝的数据线数分别为 5、6、5 根,一共为 16 个数据位,可表示 2的
16次方种颜色;
2) 同步时钟信号 CLK
液晶屏与外部使用同步通讯方式,以 CLK 信号作为同步时钟,在同步时钟的驱动下,每个时钟传输一个像素点数据。
3) 水平同步信号 HSYNC
水平同步信号 HSYNC(Horizontal Sync)用于表示液晶屏一行像素数据的传输结束,每传输完成液晶屏的一行像素数据时,HSYNC 会发生电平跳变,如分辨率为 800x480 的显示屏(800 列,480 行),传输一帧的图像 HSYNC 的电平会跳变 480 次。
4) 垂直同步信号 VSYNC
垂直同步信号 VSYNC(Vertical Sync)用于表示液晶屏一帧像素数据的传输结束,每传输完成一帧像素数据时,VSYNC 会发生电平跳变。其中“帧”是图像的单位,一幅图像称为一帧,在液晶屏中,一帧指一个完整屏液晶像素点。人们常常用“帧/秒”来表示液晶屏的刷新特性,即液晶屏每秒可以显示多少帧图像,如液晶屏以 60 帧/秒的速率运行时(一秒钟屏幕刷新60次),VSYNC 每秒钟电平会跳变 60 次。
5) 数据使能信号 DE
数据使能信号 DE(Data Enable)用于表示数据的有效性,当 DE 信号线为高电平时,RGB 信号线表示的数据有效。
3.ILI9341液晶控制器简介(重点)
ILI9341是一款262,144色单芯片SOC驱动器,用于a-TFT液晶显示器,分辨率为240RGBx320点,包括一个 720 通道源驱动器、一个 320 通道栅极驱动器、172,800 字节 GRAM 图形显示240RGBx320点的数据
,以及供电电路。ILI9341 支持并行 8/9/16/18 位数据总线 MCU 接口、6/16/18 位数据总线 RGB 接口和3/4 线串行外设接口 (SPI)。
显存(GRAM)中每个存储单元都对应着液晶面板的一个像素点。它右侧的各种模块共同作用把 GRAM 存储单元的数据转化成液晶面板的控制信号,使像素点呈现特定的颜色,而像素点组合起来则成为一幅完整的图像。
框图的左上角为 ILI9341 的主要控制信号线和配置引脚,根据其不同状态设置可以使芯片工作在不同的模式,如每个像素点的位数是 6、16 还是 18 位这里一般选择16位(2个字节一个像素点)RGB565的格式;
可配置使用 SPI 接口、8080 接口还是 RGB 接口与 MCU (stm32)进行通讯。MCU 通过 SPI、8080 接口或 RGB 接口与ILI9341 进行通讯,从而访问它的控制寄存器(CR)、地址计数器(AC)、及 GRAM。
液晶屏的信号线及 8080 时序
接到stm32对应的具有FSMC外设功能的引脚通过FSMC模拟8080时序与液晶控制器ILI9341通信、
这些信号线即 8080 通讯接口,带 X 的表示低电平有效,STM32 通过该接口与 ILI9341芯片进行通讯,实现对液晶屏的控制。
通讯的内容主要包括命令和显存数据,显存数据即各个像素点的 RGB565 内容;命令是指对 ILI9341 的控制指令,MCU 可通过 8080 接口发送命令编码控制 ILI9341 的工作方式,例如复位指令、设置光标指令、睡眠模式指令等等,发送命令与数据都是使用数据线D[15:0]所以我们需要一个根信号线D/CX(高低电平)来区分我们发送给ILI9341 液晶控制器的是命令还是数据
写时序图
STM32向ILI9341液晶控制器写入数据或命令
由图可知,写命令时序由片选信号 CSX 拉低开始
第一阶段:主机(stm32)向ILI9341液晶控制器发送(发送像素点的命令0x2Ch)所以 D/CX 置为低电平表示写入的是命令地址(比如填充像素点的指令:0x2Ch),以写信号WRX 为低,读信号 RDX 为高表示数据传输方向为写入,同时,在数据线 D [17:0] ( 或D[15:0] 一般是16根数据线一个像素点16位表示)输出命令地址ILI9341液晶控制器接收到0x2Ch指令后,然后准备开始接收像素点的数据。
第二阶段:在第二个传输阶段传送的是命令的参数(像素点数据),所以 D/CX 要置高电平,表示写入的是像素点数据,此时像素点的数据就被保存在ILI9341的SRAM显存中
读时序图
由图可知,写命令时序由片选信号 CSX 拉低开始
第一阶段:主机(stm32)向ILI9341液晶控制器发送(读数据的命令0x2Eh)所以 D/CX 置为低电平表示写入的是命令地址,以写信号WRX 为低,读信号 RDX 为高表示数据传输方向为写入,同时,在数据线 D [17:0] ( 或D[15:0] 一般是16根数据线一个像素点16位表示)输出命令地址ILI9341液晶控制器接收到0x2Eh指令后,然后准备开始返回数据给主机。
第二阶段:ILI9341液晶控制器返回数据给主机,读使能(RDX为低电平), D/CX 置为高电平代表读取的数据。
二.FSMC模拟8080时序
对STM32FSMC外设不熟悉的请先一定看,《STM32FSMC扩展SRAM》
因为8080时序与FSMC控制存储器(SRAM,NorFALSH等)的时序很相似,所以我们只需将ILI9341液晶控制器当成一个存储器这里我们把它当做一个NorFALSH存储器,所以我们STM32就可以通过FSMC外设与ILI9341液晶控制器通信,向他发送命令或者数据
前面带N的代表低电平有效
2.FSMC 的地址映射
从FSMC的角度看,可以把外部存储器划分为固定大小为256M字节的四个存储块。
● 存储块1用于访问最多4个NOR闪存或PSRAM存储设备。这个存储区被划分为4个NOR/PSRAM区并有4个专用的片选。
● 存储块2和3用于访问NAND闪存设备,每个存储块连接一个NAND闪存。
● 存储块4用于访问PC卡设备
每一个存储块上的存储器类型是由用户在配置寄存器中定义的。
使用 FSMC 外接存储器时,其存储单元是映射到 STM32 的内部寻址空间的;在程序里,定义一个指向这些地址的指针,然后就可以通过指针直接修改该存储单元的内容,FSMC 外设会自动完成数据访问过程,读写命令之类的操作不需要程序控制。FSMC的地址映射见图 FSMC 的地址映射。
FSMC 把整个 External RAM 存储区域分成了 4 个 Bank 区域,并分配了地址范围及适用的存储器类型,如 NOR 及 SRAM 存储器只能使用 Bank1 的地址。
1.FSMC控制异步NORFLASH时序
读时序:该图表示一个存储器操作周期由地址建立周期(ADDSET)、数据建立周期(DATAST)以及 2 个 HCLK 周期组成。在地址建立周期中,地址线发出要访问的地址,数据掩码信号线指示出要读取地址的高、低字节部分,片选信号使能存储器芯片(ILI9341液晶控制器);地址建立周期结束后读使能信号线发出读使能信号,接着存储器(ILI9341液晶控制器);通过数据信号线把目标数据传输给 FSMC,FSMC 把它交给内核。
写时序:它的一个存储器操作周期仅由地址建立周期(ADDSET)和数据建立周期(DATAST)组成,且在数据建立周期期间写使能信号线发出写信号,接着 FSMC把数据通过数据线传输到存储器(ILI9341液晶控制器)中。
FSMC读时序与ILI93141读时序对比
FSMC写时序与ILI93141写时序也一样,只有信号线D/CX对不上其他信号线的时序都一模一样,所以我们只需要选取一根FSMC的地址线连接到D/CX信号线,我们就可以通过访问相应不同的地址来控制的这根地址线的高低电平从而控制D/CX信号的高低电平去控制数据线传输的是命令还是地址
当 FSMC 外设被配置成正常工作,并且外部接了 NOR FLASH 时,我们这里选择的是片选1即bank1的区1即访问的地址范围是:0x6000 0000~0x63FF FFFF。
若向 0x60000000 地址写入数据如 0xABCD,FSMC 会自动在各信号线上产生相应的电平信号,写入数据。FSMC会自动控制片选信号 NE1 选择相应的 NOR 芯片,然后使用地址线 A[25:0]输出0x60000000,在 NWE 写使能信号线上发出低电平的写使能信号,而要写入的数据信号0xABCD 则从数据线 D[15:0]输出,然后数据就被保存到 NOR FLASH 中了。
如何选择存储块区域
HADDR[25:0]包含外部存储器地址。HADDR是字节地址,而存储器访问不都是按字节访问,因此接到存储器的地址线依存储器的数据宽度有所不同,如下表:
为了模拟出 8080 时序,我们可以把 FSMC 的 A16 地址线(也可以使用其它 A1/A2 等地址线)与 ILI9341 芯片 8080 接口的 D/CX 信号线连接,那么当A16为高电平时(即 D/CX 为高电平),数据线 D[15:0]的信号会被 ILI9341 理解为数值,若 A16为低电平时(即 D/CX 为低电平),传输的信号则会被ILI9341 理解为命令。
所以我们得到
要发送命令:0x6000 0000 &~(1<<17) =0X6000 0000
只要我们我们解引用这个地址,向这个地址的内存单元写入数据,则FSMC外设会自动片选ILI9341,然后自动产生模拟8080时序,由于写入的地址(地址线16为低电平即连接该地址线的D/CX信号线也为低电平)则在写入的数据就被认为是命令
要发送数据:0x6000 0000 | (1<<17)=0X6002 0000
只要我们我们解引用这个地址,向这个地址的内存单元写入数据,则FSMC外设会自动片选ILI9341,然后自动产生模拟8080时序,由于写入的地址(地址线16为高电平即连接该地址线的D/CX信号线也为高电平)则在写入的数据就被认为是数据
要读取数据
其实要发送命令或者数据,不一定是这两个地址,地址只需满足在0x6000 0000 ~ 0x63FF FFFF之间,然后满足地址线16电平为低或高电平
2.配置FSMC模式
详细请参考《STM32FSMC扩展SRAM》
由于我们要使用异步 NOR FLASH 的方式模拟 8080 时序,所以选择 FSMC 为模式 B,在 该 模 式 下 配 置 FSMC 的 控 制 时 序 结 构 体 中 , 实 际 上 只 有 地 址 建 立 时 间FSMC_AddressSetupTime(即 ADDSET 的值)以及数据建立时间 FSMC_DataSetupTime(即 DATAST 的值)成员的配置值是有效的,其它异步 NOR FLASH 没使用到的成员值全
配置为 0 即可。而且,这些成员值使用的单位为:1 个 HCLK 的时钟周期,而 HCLK 的时钟频率为 72MHz,对应每个时钟周期为 1/72us=13.8 ns。
ILI9341 时序参数说明及要求可大致得知 ILI9341 的写周期为最小 twc = 66ns,而读周期最小为 trdl+trod=45+20=65ns。(对于读周期表中有参数要一个要求为 trcfm 和 trc 分别为 450ns 及 160ns,但经过测试并不需要遵照它们的指标要求
**结合 ILI9341 的时序要求和 FSMC 的配置图,代码中按照读写时序周期均要求至少66ns 来计算,配置结果为 ADDSET = 1 及 DATST = 4,把时间单位 1/72 微秒(即 1000/72 纳秒)代入
因此读写周期的时间被配置为:
读周期: trc =((ADDSET+1)+(DATST+1)+2) *1000/72 = ((1+1)+(4+1)+2)*1000/72 = 125ns
写周期:twc =((ADDSET+1)+(DATST+1)) 1000/72 = ((1+1)+(4+1))1000/72 = 97ns
其实经过测试配置很多配置都可以成功,所以不需要特别在于配置多少,能跑就行
static void ILI9341_FSMC_Config ( void )
{
FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure;
FSMC_NORSRAMTimingInitTypeDef readWriteTiming;
/*使能FSMC外设时钟*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC,ENABLE);
/*-----------------------SRAM 时序结构体-----------------------*/
//地址建立时间(ADDSET)为1个HCLK 2/72M=28ns
readWriteTiming.FSMC_AddressSetupTime = 0x01; //地址建立时间
//数据保持时间(DATAST)+ 1个HCLK = 5/72M=70ns
readWriteTiming.FSMC_DataSetupTime = 0x04; //数据建立时间
//选择匹配SRAM的模式
readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_B;
/*-----------------------下面成员未用到-----------------------*/
//地址保持时间(ADDHLD)模式A未用到
readWriteTiming.FSMC_AddressHoldTime = 0x00;
//设置总线转换周期,仅用于复用模式的NOR操作
readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
//设置时钟分频,仅用于同步类型的存储器
readWriteTiming.FSMC_CLKDivision = 0x00;
//数据保持时间,仅用于同步型的NOR
readWriteTiming.FSMC_DataLatency = 0x00;
/*-----------------------NORFLASH 初始化结构体-----------------------*/
// 选择FSMC映射的存储区域: Bank1 sram3
FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM1;
//设置要控制的存储器类型:NORFLASH类型
FSMC_NORSRAMInitStructure.FSMC_MemoryType =FSMC_MemoryType_NOR;
//存储器数据宽度:16位
FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
//存储器写使能
FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
// 不使用扩展模式,读写使用相同的时序
FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
//读写时序配置
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &readWriteTiming;
//读写同样时序,使用扩展模式时这个配置才有效
FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &readWriteTiming;
/*-----------------------下面成员未用到-----------------------*/
//设置地址总线与数据总线是否复用,仅用于NOR
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
//设置是否使用突发访问模式,仅用于同步类型的存储器
FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode =FSMC_BurstAccessMode_Disable;
//设置是否使能等待信号,仅用于同步类型的存储器
FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait=FSMC_AsynchronousWait_Disable;
//设置等待信号的有效极性,仅用于同步类型的存储器
FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
//设置是否支持把非对齐的突发操作,仅用于同步类型的存储器
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_NORSRAM1, ENABLE); // 使能BANK
}
三.液晶显示(重点)
1.液晶驱动的初始化
LCD驱动流程
初始化GPIO:
除了背光引脚BK、和复位引脚RST设置成普通推挽输出,其他引脚全部设置为复用推挽输出全部交给FSMC外设来控制与液晶控制器芯片通信。
- 复位液晶屏
- 初始化液晶屏(厂家自带代码)
static void ILI9341_REG_Config ( void )
{
lcdid = ILI9341_ReadID();
if(lcdid == LCDID_ILI9341)
{
/* Power control B (CFh) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xCF );
ILI9341_Write_Data ( 0x00 );
ILI9341_Write_Data ( 0x81 );
ILI9341_Write_Data ( 0x30 );
/* Power on sequence control (EDh) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xED );
ILI9341_Write_Data ( 0x64 );
ILI9341_Write_Data ( 0x03 );
ILI9341_Write_Data ( 0x12 );
ILI9341_Write_Data ( 0x81 );
/* Driver timing control A (E8h) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xE8 );
ILI9341_Write_Data ( 0x85 );
ILI9341_Write_Data ( 0x10 );
ILI9341_Write_Data ( 0x78 );
/* Power control A (CBh) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xCB );
ILI9341_Write_Data ( 0x39 );
ILI9341_Write_Data ( 0x2C );
ILI9341_Write_Data ( 0x00 );
ILI9341_Write_Data ( 0x34 );
//ILI9341_Write_Data ( 0x02 );
ILI9341_Write_Data ( 0x06 ); //原来是0x02改为0x06可防止液晶显示白屏时有条纹的情况
/* Pump ratio control (F7h) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xF7 );
ILI9341_Write_Data ( 0x20 );
/* Driver timing control B */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xEA );
ILI9341_Write_Data ( 0x00 );
ILI9341_Write_Data ( 0x00 );
/* Frame Rate Control (In Normal Mode/Full Colors) (B1h) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xB1 );
ILI9341_Write_Data ( 0x00 );
ILI9341_Write_Data ( 0x1B );
/* Display Function Control (B6h) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xB6 );
ILI9341_Write_Data ( 0x0A );
ILI9341_Write_Data ( 0xA2 );
/* Power Control 1 (C0h) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xC0 );
ILI9341_Write_Data ( 0x35 );
/* Power Control 2 (C1h) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xC1 );
ILI9341_Write_Data ( 0x11 );
/* VCOM Control 1 (C5h) */
ILI9341_Write_Cmd ( 0xC5 );
ILI9341_Write_Data ( 0x45 );
ILI9341_Write_Data ( 0x45 );
/* VCOM Control 2 (C7h) */
ILI9341_Write_Cmd ( 0xC7 );
ILI9341_Write_Data ( 0xA2 );
/* Enable 3G (F2h) */
ILI9341_Write_Cmd ( 0xF2 );
ILI9341_Write_Data ( 0x00 );
/* Gamma Set (26h) */
ILI9341_Write_Cmd ( 0x26 );
ILI9341_Write_Data ( 0x01 );
DEBUG_DELAY ();
/* Positive Gamma Correction */
ILI9341_Write_Cmd ( 0xE0 ); //Set Gamma
ILI9341_Write_Data ( 0x0F );
ILI9341_Write_Data ( 0x26 );
ILI9341_Write_Data ( 0x24 );
ILI9341_Write_Data ( 0x0B );
ILI9341_Write_Data ( 0x0E );
ILI9341_Write_Data ( 0x09 );
ILI9341_Write_Data ( 0x54 );
ILI9341_Write_Data ( 0xA8 );
ILI9341_Write_Data ( 0x46 );
ILI9341_Write_Data ( 0x0C );
ILI9341_Write_Data ( 0x17 );
ILI9341_Write_Data ( 0x09 );
ILI9341_Write_Data ( 0x0F );
ILI9341_Write_Data ( 0x07 );
ILI9341_Write_Data ( 0x00 );
/* Negative Gamma Correction (E1h) */
ILI9341_Write_Cmd ( 0XE1 ); //Set Gamma
ILI9341_Write_Data ( 0x00 );
ILI9341_Write_Data ( 0x19 );
ILI9341_Write_Data ( 0x1B );
ILI9341_Write_Data ( 0x04 );
ILI9341_Write_Data ( 0x10 );
ILI9341_Write_Data ( 0x07 );
ILI9341_Write_Data ( 0x2A );
ILI9341_Write_Data ( 0x47 );
ILI9341_Write_Data ( 0x39 );
ILI9341_Write_Data ( 0x03 );
ILI9341_Write_Data ( 0x06 );
ILI9341_Write_Data ( 0x06 );
ILI9341_Write_Data ( 0x30 );
ILI9341_Write_Data ( 0x38 );
ILI9341_Write_Data ( 0x0F );
/* memory access control set */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0x36 );
ILI9341_Write_Data ( 0xC8 ); /*竖屏 左上角到 (起点)到右下角 (终点)扫描方式*/
DEBUG_DELAY ();
/* column address control set */
ILI9341_Write_Cmd ( CMD_SetCoordinateX );
ILI9341_Write_Data ( 0x00 );
ILI9341_Write_Data ( 0x00 );
ILI9341_Write_Data ( 0x00 );
ILI9341_Write_Data ( 0xEF );
/* page address control set */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( CMD_SetCoordinateY );
ILI9341_Write_Data ( 0x00 );
ILI9341_Write_Data ( 0x00 );
ILI9341_Write_Data ( 0x01 );
ILI9341_Write_Data ( 0x3F );
/* Pixel Format Set (3Ah) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0x3a );
ILI9341_Write_Data ( 0x55 );
/* Sleep Out (11h) */
ILI9341_Write_Cmd ( 0x11 );
ILI9341_Delay ( 0xAFFf<<2 );
DEBUG_DELAY ();
/* Display ON (29h) */
ILI9341_Write_Cmd ( 0x29 );
}
else if(lcdid == LCDID_ST7789V)
{
/* Power control B (CFh) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xCF );
ILI9341_Write_Data ( 0x00 );
ILI9341_Write_Data ( 0xC1 );
ILI9341_Write_Data ( 0x30 );
/* Power on sequence control (EDh) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xED );
ILI9341_Write_Data ( 0x64 );
ILI9341_Write_Data ( 0x03 );
ILI9341_Write_Data ( 0x12 );
ILI9341_Write_Data ( 0x81 );
/* Driver timing control A (E8h) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xE8 );
ILI9341_Write_Data ( 0x85 );
ILI9341_Write_Data ( 0x10 );
ILI9341_Write_Data ( 0x78 );
/* Power control A (CBh) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xCB );
ILI9341_Write_Data ( 0x39 );
ILI9341_Write_Data ( 0x2C );
ILI9341_Write_Data ( 0x00 );
ILI9341_Write_Data ( 0x34 );
ILI9341_Write_Data ( 0x02 );
/* Pump ratio control (F7h) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xF7 );
ILI9341_Write_Data ( 0x20 );
/* Driver timing control B */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xEA );
ILI9341_Write_Data ( 0x00 );
ILI9341_Write_Data ( 0x00 );
/* Power Control 1 (C0h) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xC0 ); //Power control
ILI9341_Write_Data ( 0x21 ); //VRH[5:0]
/* Power Control 2 (C1h) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xC1 ); //Power control
ILI9341_Write_Data ( 0x11 ); //SAP[2:0];BT[3:0]
/* VCOM Control 1 (C5h) */
ILI9341_Write_Cmd ( 0xC5 );
ILI9341_Write_Data ( 0x2D );
ILI9341_Write_Data ( 0x33 );
/* VCOM Control 2 (C7h) */
// ILI9341_Write_Cmd ( 0xC7 );
// ILI9341_Write_Data ( 0XC0 );
/* memory access control set */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0x36 ); //Memory Access Control
ILI9341_Write_Data ( 0x00 |(6<<5)); /*竖屏 左上角到 (起点)到右下角 (终点)扫描方式*/
DEBUG_DELAY ();
ILI9341_Write_Cmd(0x3A);
ILI9341_Write_Data(0x55);
/* Frame Rate Control (In Normal Mode/Full Colors) (B1h) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xB1 );
ILI9341_Write_Data ( 0x00 );
ILI9341_Write_Data ( 0x17 );
/* Display Function Control (B6h) */
DEBUG_DELAY ();
ILI9341_Write_Cmd ( 0xB6 );
ILI9341_Write_Data ( 0x0A );
ILI9341_Write_Data ( 0xA2 );
ILI9341_Write_Cmd(0xF6);
ILI9341_Write_Data(0x01);
ILI9341_Write_Data(0x30);
/* Enable 3G (F2h) */
ILI9341_Write_Cmd ( 0xF2 );
ILI9341_Write_Data ( 0x00 );
/* Gamma Set (26h) */
ILI9341_Write_Cmd ( 0x26 );
ILI9341_Write_Data ( 0x01 );
DEBUG_DELAY ();
/* Positive Gamma Correction */
ILI9341_Write_Cmd(0xe0); //Positive gamma
ILI9341_Write_Data(0xd0);
ILI9341_Write_Data(0x00);
ILI9341_Write_Data(0x02);
ILI9341_Write_Data(0x07);
ILI9341_Write_Data(0x0b);
ILI9341_Write_Data(0x1a);
ILI9341_Write_Data(0x31);
ILI9341_Write_Data(0x54);
ILI9341_Write_Data(0x40);
ILI9341_Write_Data(0x29);
ILI9341_Write_Data(0x12);
ILI9341_Write_Data(0x12);
ILI9341_Write_Data(0x12);
ILI9341_Write_Data(0x17);
/* Negative Gamma Correction (E1h) */
ILI9341_Write_Cmd(0xe1); //Negative gamma
ILI9341_Write_Data(0xd0);
ILI9341_Write_Data(0x00);
ILI9341_Write_Data(0x02);
ILI9341_Write_Data(0x07);
ILI9341_Write_Data(0x05);
ILI9341_Write_Data(0x25);
ILI9341_Write_Data(0x2d);
ILI9341_Write_Data(0x44);
ILI9341_Write_Data(0x45);
ILI9341_Write_Data(0x1c);
ILI9341_Write_Data(0x18);
ILI9341_Write_Data(0x16);
ILI9341_Write_Data(0x1c);
ILI9341_Write_Data(0x1d);
// /* column address control set */
// ILI9341_Write_Cmd ( CMD_SetCoordinateX );
// ILI9341_Write_Data ( 0x00 );
// ILI9341_Write_Data ( 0x00 );
// ILI9341_Write_Data ( 0x00 );
// ILI9341_Write_Data ( 0xEF );
//
// /* page address control set */
// DEBUG_DELAY ();
// ILI9341_Write_Cmd ( CMD_SetCoordinateY );
// ILI9341_Write_Data ( 0x00 );
// ILI9341_Write_Data ( 0x00 );
// ILI9341_Write_Data ( 0x01 );
// ILI9341_Write_Data ( 0x3F );
/* Sleep Out (11h) */
ILI9341_Write_Cmd ( 0x11 ); //Exit Sleep
ILI9341_Delay ( 0xAFFf<<2 );
DEBUG_DELAY ();
/* Display ON (29h) */
ILI9341_Write_Cmd ( 0x29 ); //Display on
ILI9341_Write_Cmd(0x2c);
}
}
上面提供了两款液晶控制器芯片的初始化代码:ILI9341和ST7789V芯片的初始化,不过两个芯片功能差不多都是控制分辨率为240*320的LCD屏幕,根据自己的液晶控制芯片要选择配套的初始化代码,不然会显示异常,初始化代码里面也有好多我们就学习的指令,后面就一 一展开来讲。
里面有一个读ID的指令:
ST7789V液晶控制芯片:返回ID命令:0x04
ILI9341液晶控制芯片:返回ID命令:0xD3
- 开启液晶屏的背光灯
到此液晶的初始化全部完毕(最后还有一个液晶扫描方式后面再讲)
2.常用液晶命令(画一个实心矩形)
初始化液晶之后,以显示一个矩形为例讲解,讲解如何设置坐标和如何填充像素点。
首先讲一下写入命令或数据,读取数据要注意的事项
向ILI9341写入命令:
向ILI9341写入数据:
从ILI9341读取数据:
不管是写入数据、命令还是读取数据,本质是访问ILI9341芯片(当成一个FSMC扩展的一个存储器)映射的地址,往地址写入数据或命令,就会产生相应的时序将数据或命令发送到LIL9341芯片,读该地址的内存单元同样会产生时序LIL9341将数据通过数据线存储在该地址上的内存单元上。
第一个要注意:访问的变量记得加volatile
详细的volatile关键字的介绍:《register关键字深入解析》
第二个:什么是内联函数
建议编译器(编译器不一定会怎么做)将指定的函数体插入并取代每一处调用该函数的地方(上下文),从而节省了每次调用函数带来的额外时间开支。
使用内联函数是典型的以空间换时间的操作:
编译器会将调用该函数的地方直接插入该函数的函数体,通俗点讲就是将函数中的代码嵌入调用函数的地方,这样就会节省函数的调用带来的额外时间开支(函数栈帧的创建和释放),不过每调用一次函数就会嵌入一段代码所以要更多的空间来存储。
与宏的比较
与宏的功能差不多,但是函数有类型检查,而且更易于调试,而宏相反
想更多了解:调用函数怎么就耗时间啦——>《函数栈帧的形成与释放》
接下来就是画一个矩形
第一步开窗:
1)设置液晶显示窗口的 X 坐标:2Ah
在默认扫描方式时,该指令用于设置x坐标,该指令带有4个参数,实际上是2个坐标值:前面两个参数:SC和后面两个参数:EC,即列地址的起始值和结束值,SC必须小于等于EC,且0≤SC或EC≤239。
一般在设置x坐标的时候,我们只需要带2个参数即可,也就是设置SC起始X坐标即可,因为如果EC没有变化,我们只需要设置一次即可(在初始化ILI9341的时候设置),从而提高速度。
超出范围的数据将被忽略
2)设置液晶显示窗口的 Y 坐标:2Bh
在默认扫描方式时,该指令用于设置y坐标,该指令带有4个参数,实际上是2个坐标值:前面两个参数:SP和后面两个参数:EP,即页地址的起始值和结束值,SP必须小于等于EP,且0≤SP/EP≤319。一般在设置y坐标的时候,我们只需要带2个参数即可,也就是设置SP即可,因为如果EP没有变化,我们只需要设置一次即可(在初始化ILI9341的时候设置),从而提高速度。
超出范围的数据将被忽略
- 开窗函数
3)填充像素点:0x2C
在收到指令0X2C之后,数据有效位宽变为16位(存储一个像素点需要16位:RGB565),我们可以连续写入LCD GRAM值,而GRAM的地址将根据wm 我们后面设置的扫描方向进行自增。例如:假设设置的是从左到右,从上到下的扫描方式,那么设置好起始坐标(通过SC,SP设置)后,每写入一个颜色值,GRAM地址将会自动自增1(SC++),如果碰到EC,则回到SC,同时SP++,一直到坐标:EC,EP结束,其间无需再次设置的坐标,从而大大提高写入速度。
(就是一行一行填充过去,碰到一行结尾自动换行,所以我们填充像素点的时候我们只需要确定要填充的像素点数目,屏幕会自动根据开设的窗口自动填充像素点)
效果展示一下:
4)读取像素点数据:2Eh
列和页寄存器分别重置为起始列 (SC) 和起始页 (SP)。像素从中读取
帧内存位于 (SC、SP)。然后,列寄存器递增,并从帧存储器中读取像素,直到列寄存器等于结束列 (EC) 值。然后,列寄存器重置为 SC,并且页面寄存器为递增。从帧存储器中读取像素,直到页面寄存器等于结束页 (EP) 值或主机处理器发送另一个命令。
(从我们设置坐标(x,y)开始返回像素数据,同样是根据扫描方向一行一行的返回像素数据,到行尾就自动换行)
发送完2Eh命令后,液晶芯片开始返回像素点数据
返回的一个参数数据是16位的
第一个参数返回的是空
第二个参数返回的是(高5位(D11 ~ D15)为第一个像素点的红色数据分量,(D2 ~ D7)为第一个像素点的绿色数据分量,其余为空)
第三个参数返回的是(高5位(D11 ~ D15) 为第一个像素点的蓝色数据分量,
(D3 ~ D7)为第二个像素点的红色数据分量,其余为空)
…后面一直重复返回
所以我们要接收一个像素点颜色数据RGB565必须接收两个参数的数据然后把他们拼凑起来。
读取一个像素点的数据
自己对照着返回的参数,看看代码是如何拼接一个像素点的数据的(16位RGB565),看代码自己运算一下就非常清晰了
读取某一个坐标点的像素数据
5)设置ILI9341的GRAM的扫描方向:0x36
直接看下图解释你会明明白白的
主要是设置:MY、MX、MV D5D7三个位,三个位组合就有07 8种模式
上面那个表对应下面这个图
下面的图是野火是扫描方向,仔细一对比你会发现好像都反了,其实是野火的屏幕装的是倒过来的,所以把屏幕转过180度就全对上了,当时这个问题困扰了好久问客服讨论了很久才知道的
void ILI9341_GramScan ( uint8_t ucOption )
{
//参数检查,只可输入0-7
if(ucOption >7 )
return;
//根据模式更新LCD_SCAN_MODE的值,主要用于触摸屏选择计算参数
LCD_SCAN_MODE = ucOption;
//根据模式更新XY方向的像素宽度
if(ucOption%2 == 0)
{
//0 2 4 6模式下X方向像素宽度为240,Y方向为320
LCD_X_LENGTH = ILI9341_LESS_PIXEL;
LCD_Y_LENGTH = ILI9341_MORE_PIXEL;
}
else
{
//1 3 5 7模式下X方向像素宽度为320,Y方向为240
LCD_X_LENGTH = ILI9341_MORE_PIXEL;
LCD_Y_LENGTH = ILI9341_LESS_PIXEL;
}
//0x36命令参数的高3位可用于设置GRAM扫描方向
ILI9341_Write_Cmd ( 0x36 );
if(lcdid == LCDID_ILI9341)
{
ILI9341_Write_Data ( 0x08 |(ucOption<<5));//根据ucOption的值设置LCD参数,共0-7种模式
}
else if(lcdid == LCDID_ST7789V)
{
ILI9341_Write_Data ( 0x00 |(ucOption<<5));//根据ucOption的值设置LCD参数,共0-7种模式
}
ILI9341_Write_Cmd ( CMD_SetCoordinateX );
ILI9341_Write_Data ( 0x00 ); /* x 起始坐标高8位 */
ILI9341_Write_Data ( 0x00 ); /* x 起始坐标低8位 */
ILI9341_Write_Data ( ((LCD_X_LENGTH-1)>>8)&0xFF ); /* x 结束坐标高8位 */
ILI9341_Write_Data ( (LCD_X_LENGTH-1)&0xFF ); /* x 结束坐标低8位 */
ILI9341_Write_Cmd ( CMD_SetCoordinateY );
ILI9341_Write_Data ( 0x00 ); /* y 起始坐标高8位 */
ILI9341_Write_Data ( 0x00 ); /* y 起始坐标低8位 */
ILI9341_Write_Data ( ((LCD_Y_LENGTH-1)>>8)&0xFF ); /* y 结束坐标高8位 */
ILI9341_Write_Data ( (LCD_Y_LENGTH-1)&0xFF ); /* y 结束坐标低8位 */
/* write gram start */
ILI9341_Write_Cmd ( CMD_SetPixel );
}
上面的程序就是设置MY、MX、MV D5~D7三个位的值,从000 ~ 111 一共八种模式,然后根据模式不同开整屏 (320*240)的窗口,就是把000 ~ 111 z左移5位移到MY、MX、MV三个位上。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)