TFTLCD实验

1.TFTLCD简介:

我们通过STM32的普通IO口模拟8080总线来控制TFTLCD的显示。TFT-LCD即薄膜晶体管液晶显示器。

TFTLCD实验_第1张图片

TFTLCD模块采用16位的并方式与外部连接,之所以不 采用8位的方式,是因为彩屏的数据量比较大,尤其在显示图片的时候,如果用8位的数据线,就会比16位方式慢一倍以上。我们当然希望速度越快越好,所以我们选择 16 位的接口。
该模块的80并口有如下一些信号线:

CS:TFTLCD片选信号。

WR:向TFTLCD写入数据。

RD:从TFTLCD读取数据。

D[15:0]:16位双向数据线。

RST:硬复位TFTLCD.

RS:命令/数据标志(0,读写命令;1,读写数据)。

模块的8080并口读/写的过程为:先根据要写入/读取的数据的类型,设置DC为高(数据)/低(命令),然后拉低 

片选,选中SSD1306,接着我们根据是读数据,还是根据要写数据置RD/WR为低,然后:

在RD的上升沿,使数据锁存到数据线(D[7:0])上;

在 WR 的上升沿,使数据写入到 SSD1306 里面;
TFTLCD模块的RST信号线是直接接到STM32的复位脚上,并不由软件控制,这样可以省下来一个IO口。另外我们还需要一个背光控制线来控制TFTLCD的背光。我们总共需要的IO口数目为21个。我们标注的DB1~DB8,DB10~DB17,是相对于LCD控制IC标注的,实际上可以把它们等同于D0~D15。

 ALIENTEK 提供 2.8/3.5/4.3/7 寸等不同尺寸的 TFTLCD 模块,其驱动芯片有很多种类型,比如有: ILI9341/ILI9325/RM68042/RM68021/ILI9320/ILI9328/LGDP4531/LGDP4535/SPFD5408/SSD1289/1505/B505/C505/NT35310/NT35510/SSD1963 等(具体的型号,大家可以通过下载本章实验代码,通过串口或者 LCD 显示查看),这里我们仅以 ILI9341 控制器为例进行介绍。

ILI9341 液晶控制器自带显存,其显存总大小为 172800(240*320*18/8),即 18 位模式(26万色)下的显存量。在 16 位模式下, ILI9341 采用 RGB565 格式存储颜色数据,此时 ILI9341的 18 位数据线与 MCU 的 16 位数据线以及 LCD GRAM 的对应关系如图 16.1.4 所示:

从图中可以看出,ILI9341在16模式下面,数据线有用的是:D17~D13和D11~D1,D0和D12没有用到,实际上在我们LCD模块里面,ILI9341的D0和D12压根就没有引出来,这样,ILI9341的D17~D13和D11~D1对应MCU的D15~D0。

这样MCU的16位数据,最低5位代表蓝色,中间6位为绿色,最高位为红色,数值越大,表示该颜色越深。另外,要特别注意ILI9341所代表的指令都是8位的(高8位无效),且参数除了读写GRAM的时候是16位,其他操作参数,都是8位的,这个和ILI9320等驱动器不一样,必须加以注意。

我们介绍一下 ILI9341 的几个重要命令,0XD3, 0X36, 0X2A, 0X2B, 0X2C, 0X2E 等 6 条指令。
首先来看指令: 0XD3,这个是读 ID4 指令,用于读取 LCD 控制器的 ID,该指令如表 16.1.1所示:
TFTLCD实验_第2张图片

从上表看以看出,OXD3指令后面跟了4个参数,最后2个参数,读出来是0X93和0X41,刚好使我们控制器ILI9341的数字部分,从而,通过该指令,即可判别所用的 LCD 驱动器是什么型号,这样,我们的代码,就可以根据控制器的型号去执行对应驱动 IC 的初始化代码,从而兼容不同驱动 IC 的屏,使得一个代码支持多款 LCD。

接下来看指令:0X36,这是存储访问控制指令,可以控制ILI9341存储器的读写方向,简单的说,就是在连续写GRAM的时候,可以控制GRAM指针的增长方向,从而控制显示方式(读GRAM也是一样)。

TFTLCD实验_第3张图片

从上表可以看出,0X36指令后面,紧跟一个参数,这里我们主要关注:MY、MX、MV这三个位,通过这三个位的位置,我们可以控制整个ILI9341的全部扫描方向,

TFTLCD实验_第4张图片

这样,我们在利用ILI9341显示内容的时候,就有很大灵活性了,比如显示 BMP 图片,BMP 解码数据,就是从图片的左下角开始,慢慢显示到右上角,如果设置 LCD 扫描方向为从左到右,从下到上,那么我们只需要设置一次坐标,然后就不停的往 LCD 填充颜色数据即可,这样可以大大提高显示速度。

接下来看指令: 0X2A,这是列地址设置指令, 在从左到右,从上到下的扫描方式(默认)
下面,该指令用于设置横坐标(x 坐标),该指令如表 16.1.4 所示:

TFTLCD实验_第5张图片

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

一般 TFTLCD 模块的使用流程如图:

TFTLCD实验_第6张图片
 

任何 LCD,使用流程都可以简单的用以上流程图表示。其中硬复位和初始化序列,只需要执行一次即可。而画点流程就是:设置坐标\rightarrow写 GRAM 指令\rightarrow写入颜色数据,然后在 LCD 上面,我们就可以看到对应的点显示我们写入的颜色了。读点流程为:设置坐标\rightarrow读 GRAM 指令\rightarrow读取颜色数据,这样就可以获取到对应点的颜色数据了。

1) 设置 STM32 与 TFTLCD 模块相连接的 IO。这一步,先将我们与 TFTLCD 模块相连的 IO 口进行初始化,以便驱动 LCD。 这里需要根据连接电路以及 TFTLCD 模块的设置来确定。
2) 初始化 TFTLCD 模块。即图 16.1.4 的初始化序列,这里我们没有硬复位 LCD,因为 MiniSTM32 开发板的 LCD 接口,将 TFTLCD 的 RST 同 STM32 的 RESET 连接在一起了,只要按下开发板的 RESET 键,就会对 LCD 进行硬复位。初始化序列,就是向 LCD 控制器写入一系列的设置值(比如伽马校准),这些初始化序列一般 LCD 供应商会提供给客户,我们直接使用这些序列即可,不需要深入研究。在初始化之后, LCD 才可以正常使用。
3) 通过函数将字符和数字显示到 TFTLCD 模块上。这一步则通过图 16.1.4 左侧的流程,即:设置坐标\rightarrow写 GRAM 指令\rightarrow写GRAM 来实现,但是这个步骤,只是一个点的处理,我们要显示字符/数字,就必须要多次使用这个步骤,从而达到显示字符/数字的目标,所以需要设计一个函数来实现数字/字符的显示,之后调用该函数,就可以实现数字/字符的显示了。

2.硬件设计

1)指示灯DS0

2)TFTLCD模块

板上的接口比液晶模块的插针要多2个口,液晶模块在这里是靠右插的。多出的2个口是给OLED用的,所以OLCD模块接这里的时候,是靠左插的。

LCD_LED 对应 PC10;
LCD_CS 对应 PC9;
LCD _RS 对应 PC8;
LCD _WR 对应 PC7;
LCD _RD 对应 PC6;
LCD _D[17:1]对应 PB[15:0]
这些线的连接,MiniSTM32开发板的内部已经连接好了,我们只需要将TFTLCD模块插上去就好了。

3.软件设计

首先,我们介绍一下 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 ID(驱动 IC 型号)、LCD 横竖屏状态等,这个结构体虽然占用了 14 个字节的内存,但是却可以让我们的驱动函数支持不同尺寸的 LCD,同时可以实现 LCD 横竖屏切换等重要功能,所以还是利大于弊的。 有了以上了解,下面我们开始介绍 ILI93xx.c 里面的一些重要函数。

第一个是 LCD_WR_DATA 函数,该函数在 lcd.h 里面,通过宏定义的方式申明。该函数通过 80 并口向 LCD 模块写入一个 16 位的数据,使用频率是最高的,这里我们采用了宏定义的方式,以提高速度。其代码如下:

//写数据函数
#define LCD_WR_DATA(data){\
LCD_RS_SET;\
LCD_CS_CLR;\
DATAOUT(data);\
LCD_WR_CLR;\
LCD_WR_SET;\
LCD_CS_SET;\
}
上面函数中的‘\’是 C 语言中的一个转义字符,用来连接上下文,因为宏定义只能是一个串,而当你的串过长(超过一行的时候),就需要换行了,此时就必须通过反斜杠来连接上下文。这里的‘\’正是起这个作用。在上面的函数中,LCD_RS_SET/LCD_CS_CLR/ LCD_WR_CLR/LCD_WR_SET/LCD_CS_SET 等是操作 RS/CS/WR 的宏定义,均是采用 STM32 的快速 IO 控制寄存器实现的,从而提高速度。
第二个是: LCD_WR_DATAX 函数,该函数在 ILI93xx.c 里面定义,功能和 LCD_WR_DATA一模一样,该函数代码如下:
//写数据函数
//可以替代 LCD_WR_DATAX 宏,拿时间换空间.
//data:寄存器值
void LCD_WR_DATAX(u16 data)
{
LCD_RS_SET;
LCD_CS_CLR;
DATAOUT(data);
LCD_WR_CLR;
LCD_WR_SET;
LCD_CS_SET;
}
如果全部用宏定义的 LCD_WR_DATA 函数,那么就会占用非常大的 flash,所以我们这里另外实现一个函数: LCD_WR_DATAX,给 LCD_Init 函数调用,从而大大减少 flash 占用量。

第三个是 LCD_WR_REG 函数,该函数是通过 8080 并口向 LCD 模块写入寄存器命令,因为该函数使用频率不是很高,我们不采用宏定义来做(宏定义占用 FLASH 较多),通过 LCD_RS来标记是写入命令(LCD_RS=0)还是数据(LCD_RS=1)。该函数代码如下:

//写寄存器函数
//data:寄存器值
void LCD_WR_REG(u16 data)
{
LCD_RS_CLR;//写地址
LCD_CS_CLR;
DATAOUT(data);
LCD_WR_CLR;
LCD_WR_SET;
LCD_CS_SET;
}
既然有写寄存器命令函数,那就有读寄存器数据函数。接下来介绍 LCD_RD_DATA 函数,该函数用来读取 LCD 控制器的寄存器数据(非 GRAM 数据),该函数代码如下:
//读 LCD 寄存器数据
//返回值:读到的值
u16 LCD_RD_DATA(void)
{
u16 t;
GPIOB->CRL=0X88888888; //PB0-7 上拉输入
GPIOB->CRH=0X88888888; //PB8-15 上拉输入
GPIOB->ODR=0X0000; //全部输出 0
LCD_RS_SET;
LCD_CS_CLR;
LCD_RD_CLR; //读取数据(读寄存器时,并不需要读 2 次)
if(lcddev.id==0X8989)delay_us(2);//FOR 8989,延时 2us
t=DATAIN;
LCD_RD_SET;
LCD_CS_SET;
GPIOB->CRL=0X33333333; //PB0-7 上拉输出
GPIOB->CRH=0X33333333; //PB8-15 上拉输出
GPIOB->ODR=0XFFFF; //全部输出高
return t;
}
以上 4 个函数,用于实现 LCD 基本的读写操作,接下来,我们介绍 2 个 LCD 寄存器操作的函数, LCD_WriteReg 和 LCD_ReadReg,这两个函数代码如下:
//写寄存器
//LCD_Reg:寄存器编号
//LCD_RegValue:要写入的值
void LCD_WriteReg(u16 LCD_Reg,u16 LCD_RegValue)
{
LCD_WR_REG(LCD_Reg);
LCD_WR_DATA(LCD_RegValue);
}
//读寄存器
//LCD_Reg:寄存器编号
//返回值:读到的值
u16 LCD_ReadReg(u16 LCD_Reg)
{
LCD_WR_REG(LCD_Reg); //写入要读的寄存器号
return LCD_RD_DATA();
}
这两个函数十分简单, LCD_WriteReg 用于向 LCD 指定寄存器写入指定数据,而LCD_ReadReg 则用于读取指定寄存器的数据,这两个函数, 都只带一个参数/返回值,所以,在有多个参数操作(读取/写入)的时候,就不适合用这两个函数了,得另外实现。
第七个要介绍的函数是坐标设置函数,该函数代码如下:
//设置光标位置
//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)。因为不同 LCD 的设置方式不一定完全一样,所以代码里面有好几个判断,对不同的驱动 IC 进行不同的设置。
接下来我们介绍第八个函数:画点函数。该函数实现代码如下:
//画点
//x,y:坐标
//POINT_COLOR:此点的颜色
void LCD_DrawPoint(u16 x,u16 y)
{
LCD_SetCursor(x,y); //设置光标位置
LCD_WriteRAM_Prepare(); //开始写入 GRAM
LCD_WR_DATA(POINT_COLOR);
}
该函数实现比较简单,就是先设置坐标,然后往坐标写颜色。其中 POINT_COLOR 是我们定义的一个全局变量,用于存放画笔颜色,顺带介绍一下另外一个全局变量: BACK_COLOR,该变量代表 LCD 的背景色。 LCD_DrawPoint 函数虽然简单,但是至关重要,其他几乎所有上层函数,都是通过调用这个函数实现的。
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(STM32)