锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

TFT-LCD 液晶显示

时间:2022-09-11 04:00:01 100pin矩形连接器

文章目录

  • 前言
  • 一、模块介绍
    • 1. 简介
    • 2. 引脚功能
    • 3. 模块通信和指令集
  • 二、工作流程
  • 三、程序设计
    • 1. 写数据
    • 2. 读数据
    • 3. 初始化
    • 4. 打开/关闭显示器
    • 5. 显示函数
    • 6. 示例
      • 显示展示:


前言

TFT-LCD 由于其厚度薄、画面质量优良,信息的高速、高亮度、高对比度显示已广泛应用于图像显示系统中。虽然TFT-LCD 显示性能好,但是TFT-LCD 驱动信号非常复杂,外围控制单元需要处理的数据量非常大,分辨率为640×480的彩色TFT-LCD 为例,显示一幅彩色图像就需要处理900kbyte对于传统的单片机等处理器来说,数据量是无能为力的。由于图像数据量大,大多数图像在压缩后存储,其中jpeg (joint photographicexperts group)压缩图像的格式应用最广泛。


一、模块介绍

1. 简介

TFT-LCD 即薄膜晶体管液晶显示器,其英文全称:Thin Film Transistor-Liquid CrystalDisplay。TFT-LCD 与无源 TN-LCD、STN-LCD 不同的简单矩阵,它在液晶显示屏的每个象素上都有一个薄膜晶体管(TFT),它能有效克服非选择时的串扰,使显示液晶屏的静态特性与扫描线数无关,从而大大提高了图像质量。TFT-LCD 也叫真彩液晶显示器。

该模块具有以下特点:

  1. 2.4’/2.8’/3.5’/4.3’/7’ 5 屏幕大小可选。
  2. 320×240 的分辨率(3.5分辨率为:320480,4.3’和 7分辨率为800480)。
  3. 16 位真彩显示。
  4. 自带触摸屏,可作为控制输入。

我们以 2.8 寸的 ALIENTEK TFTLCD 例如,模块支持 65K 颜色显示,显示分辨率为 320×240,接口为 16 位的 80 并口,自带触摸屏。
在这里插入图片描述
模块原理图如图所示 18.1.1.2 所示:

2. 引脚功能

TFTLCD 模块采用 2*17 的 2.54 接口定义如图所示:

从图中可以看出:ALIENTEK TFTLCD 模块采用 16 位置并联与外部连接。不使用的原因 8 位置的方式是因为彩屏的数据量比较大,尤其是显示图片的时候。 8 会比较位数据线 16 当然,我们希望速度越快越好,所以我们选择 16 位的接口。

该模块的 80 并口有以下信号线:

  1. CS:TFTLCD 片选信号。
  2. WR:向 TFTLCD 写入数据。
  3. RD:从 TFTLCD 读取数据。
  4. D[15:0]:16 位双向数据线。
  5. RST:硬复位 TFTLCD。
  6. RS:命令/数据标志(0、读写命令;1、读写数据)

需要注意的是,TFTLCD模块的RST直接接到信号线STM32复位脚不受软件控制,可以节省一个 IO口。此外,我们还需要一条背光控制线来控制它TFTLCD 的背光。因此,我们总共需要它 IO口数目为 21 个。这里还需要注意的是,我们标注的DB1DB8,DB10DB17,是相对于 LCD 控制 IC 标注的,实际上大家可以把他们就等同于 D0~D这样理解起来就比较简单了。

3. 模块通信和指令集

ILI9341 液晶控制器有自己的显存,其显存总尺寸为 172800(24032018/8),即 18 显存量在位模式下(26万色)。在 16 位模式下,ILI9341 采用 RGB565 此时,格式存储颜色数据 ILI9341的 18 位数据线与 MCU 的 16 位数据线和 LCD GRAM 如图所示:

从图中可以看出,ILI9341 在 16 在位模式下,数据线有用:D17~D13 和 D11~D1,D0和 D12 没用,其实在我们身上 LCD 模块里面,ILI9341 的 D0 和 D12 根本没有引出,所以,ILI9341 的 D17~D13 和 D11~D1 对应 MCU 的 D15~D0。

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

接下来,让我们介绍一下 ILI9341 几个重要命令:0XD3,0X36,0X2A,0X2B,0X2C,0X2E 等 6 条指令。

  1. 0XD3:这个是读 ID4 读取指令 LCD 控制器的 ID,如图所示:

    从上表可以看出,0XD3 跟着命令 4 最后,参数 2 读出一个参数 0X93 和 0X41正好是我们的控制器 ILI9341 因此,使用的数字部分可以通过该指令来判断 LCD 什么型号的驱动器?这样,我们的代码就可以根据控制器的型号执行相应的驱动器 IC 初始同驱动器兼容的初始代码 IC 一个代码支持多个屏幕 LCD。

  2. 0X36:这是存储访问控制指令,可以控制 ILI9341 简单地说,存储器的读写方向是连续写作 GRAM 可以控制时间 GRAM 指针的增长方向控制显示模式(阅读) GRAM 也一样)。指令如图所示:

    从上表可以看出,0X36 在指令后面,跟随一个参数,这里我们主要关注:MY、MX、MV通过这三个位置的设置,我们可以控制整个位置 ILI9341 如图所示:

    这样,我们就在使用它 ILI9341 显示内容时,有很大的灵活性,如显示 BMP 图片,BMP 解码数据是从图片的左下角慢慢显示到右上角。如果设置 LCD 扫描方向是从左到右,从下到上,所以我们只需要设置一个坐标,然后继续 LCD 填充颜色数据可以大大提高显示速度。

  3. 0X2A:这是列地址设置指令,用于从左到右、从上到下(默认)下设置横坐标(x 坐标),如图所示:

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

  4. 0X2B:与 0X2A 该指令类似于页面地址设置指令。在从左到右、从上到下的扫描(默认)下,该指令用于设置纵坐标(y 坐标)。指令如图所示:

    该指令用于设置默认扫描模式 y 该指令带有坐标 4 实际上是个参数 2 个坐标值:SP 和 EP,即页地址的起始值和结束值,SP 必须小于等于 EP,且 0≤SP/EP≤319。一般在设置y 坐标时,我们只需要带 2 可以设置一个参数,即设置 SP 因为如果 EP 没有变化,我们只需要设置一次(在初始化中 ILI9341 设置时),以提高速度。

  5. 0X2C:该指令是写 GRAM 我们可以通常发送指令 LCD的 GRAM 里面写入颜色数据了,该指令支持连续写,指令描述如图所示:

    从上表可以看出,收到指令0X2C之后,数据的有效位宽变为16位,我们可以连续写入 LCD-GRAM 值,而 GRAM 的地址将根据 MY/MX/MV 设置的扫描方向自增。例如,假设从左到右,从上到下,然后设置起始坐标(通过 SC,SP 设置)后,每次写一个颜色值,GRAM 自动增加地址 1(SC ),如果碰到 EC,则回到 SC,同时 SP ,直到坐标:EC,EP 最后,不需要再设置坐标,从而大大提高了写入速度。

  6. 0X2E:该指令是读 GRAM 读取指令 ILI9341 的显存(GRAM),该指令在 ILI9341 数据手册上的描述是错误的,如图所示:

    该指令用于读取 GRAM,如图所示,ILI9341 收到指令后,第一次输出dummy数据,即无效数据,第二次开始,读取是有效的 GRAM 数据(坐标:SC,SP 开始),输出规则为:每种颜色分量占 8 个位,一次输出 2 个色分量。例如:第一次输出是 R1G1.后续规则如下:B1R2→G2B2→R3G3→B3R4→GB4→R5G5… 以此类推。如果我们只需要读取一个点的颜色值,那么只需要接收到参数 3 即可,如果要连续读取(利用 GRAM地址自增,方法同上),那么就按照上述规律去接收颜色数据。

二、工作流程

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

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

读点流程为:设置坐标→读 GRAM 指令→读取颜色数据,这样就可以获取到对应点的颜色数据了。

以上只是最简单的操作,也是最常用的操作,有了这些操作,一般就可以正常使用 TFTLCD了。接下来我们将该模块(2.8 寸屏模块)用来来显示字符和数字,通过以上介绍,我们可以得出 TFTLCD 显示需要的相关设置步骤如下:

  1. 设置 STM32F1 与 与 TFTLCD 模块相连接的 IO 。

这一步,先将我们与 TFTLCD 模块相连的 IO 口进行初始化,以便驱动 LCD。我们可以使用FSMC。

  1. 初始化 TFTLCD 模块。

即初始化序列,这里我们没有硬复位 LCD,因为精英 STM32F103 的 LCD 接口,将 TFTLCD 的 RST 同 STM32F1 的 RESET 连接在一起了,只要按下开发板的 RESET 键,就会对 LCD 进行硬复位。初始化序列,就是向 LCD 控制器写入一系列的设置值(比如伽马校准),这些初始化序列一般 LCD 供应商会提供给客户,我们直接使用这些序列即可,不需要深入研究。在初始化之后,LCD 才可以正常使用。

  1. 通过函数将字符和数字显示到 TFTLCD 模块上。

这一步则通过图左侧的流程,即:设置坐标→写 GRAM 指令→写 GRAM 来实现,但是这个步骤,只是一个点的处理,我们要显示字符/数字,就必须要多次使用这个步骤,从而达到显示字符/数字的目的,所以需要设计一个函数来实现数字/字符的显示,之后调用该函数,就可以实现数字/字符的显示了。

三、程序设计

1. 写数据

//写寄存器函数
//regval:寄存器值
void LCD_WR_REG(u16 regval)
{ 
           
	LCD->LCD_REG=regval;//写入要写的寄存器序号 
}

//写LCD数据
//data:要写入的值
void LCD_WR_DATA(u16 data)
{ 
        	 
	LCD->LCD_RAM=data;		 
}

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

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

2. 读数据

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

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

//从ILI93xx读出的数据为GBR格式,而我们写入的时候为RGB格式。
//通过该函数转换
//c:GBR格式的颜色值
//返回值:RGB格式的颜色值
u16 LCD_BGR2RGB(u16 c)
{ 
        
	u16  r,g,b,rgb;   
	b=(c>>0)&0x1f;
	g=(c>>5)&0x3f;
	r=(c>>11)&0x1f;	 
	rgb=(b<<11)+(g<<5)+(r<<0);		 
	return(rgb);
} 	

u16 LCD_ReadPoint(u16 x,u16 y)
{ 
        
 	u16 r=0,g=0,b=0;
	if(x>=lcddev.width||y>=lcddev.height)return 0;	//超过了范围,直接返回 
	LCD_SetCursor(x,y);	    
	if(lcddev.id==0X9341||lcddev.id==0X6804||lcddev.id==0X5310||lcddev.id==0X1963)LCD_WR_REG(0X2E);//9341/6804/3510/1963 发送读GRAM指令
	else if(lcddev.id==0X5510)LCD_WR_REG(0X2E00);	//5510 发送读GRAM指令
	else LCD_WR_REG(0X22);      		 			//其他IC发送读GRAM指令
	if(lcddev.id==0X9320)opt_delay(2);				//FOR 9320,延时2us 
 	r=LCD_RD_DATA();								//dummy Read 
	if(lcddev.id==0X1963)return r;					//1963直接读就可以 
	opt_delay(2);	  
 	r=LCD_RD_DATA();  		  						//实际坐标颜色
 	if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X5510)		//9341/NT35310/NT35510要分2次读出
 	{ 
        
		opt_delay(2);	  
		b=LCD_RD_DATA(); 
		g=r&0XFF;		//对于9341/5310/5510,第一次读取的是RG的值,R在前,G在后,各占8位
		g<<=8;
	} 
	if(lcddev.id==0X9325||lcddev.id==0X4535||lcddev.id==0X4531||lcddev.id==0XB505||lcddev.id==0XC505)return r;	//这几种IC直接返回颜色值
	else if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X5510)return (((r>>11)<<11)|((g>>10)<<5)|(b>>11));//ILI9341/NT35310/NT35510需要公式转换一下
	else return LCD_BGR2RGB(r);						//其他IC
}			 		

3. 初始化

//初始化lcd
//该初始化函数可以初始化各种ILI93XX液晶,但是其他函数是基于ILI9320的!!!
//在其他型号的驱动芯片上没有测试! 
void LCD_Init(void)
{ 
         					
 	GPIO_InitTypeDef GPIO_InitStructure;
	FSMC_NORSRAMInitTypeDef  FSMC_NORSRAMInitStructure;
    FSMC_NORSRAMTimingInitTypeDef  readWriteTiming; 
	FSMC_NORSRAMTimingInitTypeDef  writeTiming;
	
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC,ENABLE);	//使能FSMC时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE|RCC_APB2Periph_GPIOG,ENABLE);//使能PORTB,D,E,G以及AFIO复用功能时钟

 
 	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;				 //PB0 推挽输出 背光
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
 	//PORTD复用推挽输出 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_14|GPIO_Pin_15;				 // //PORTD复用推挽输出 
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 		 //复用推挽输出 
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOD, &GPIO_InitStructure); 
  	 
	//PORTE复用推挽输出 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;				 // //PORTD复用推挽输出 
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 		 //复用推挽输出 
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOE, &GPIO_InitStructure);    	    	 											 

   	// //PORTG12复用推挽输出 A0 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_12;	 // //PORTD复用推挽输出 
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 		 //复用推挽输出 
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOG, &GPIO_InitStructure); 

  readWriteTiming.FSMC_AddressSetupTime = 0x01;	 //地址建立时间(ADDSET)为2个HCLK 1/36M=27ns
  readWriteTiming.FSMC_AddressHoldTime = 0x00;	 //地址保持时间(ADDHLD)模式A未用到 
  readWriteTiming.FSMC_DataSetupTime = 0x0f;		 // 数据保存时间为16个HCLK,因为液晶驱动IC的读数据的时候,速度不能太快,尤其对1289这个IC。
  readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
  readWriteTiming.FSMC_CLKDivision = 0x00;
  readWriteTiming.FSMC_DataLatency = 0x00;
  readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A;	 //模式A 
  .......
  .......
  }

4. 开启/关闭显示

//LCD开启显示
void LCD_DisplayOn(void)
{ 
        					   
	if(lcddev.id==0X9341||lcddev.id==0X6804||lcddev.id==0X5310||lcddev.id==0X1963)LCD_WR_REG(0X29);	//开启显示
	else if(lcddev.id==0X5510)LCD_WR_REG(0X2900);	//开启显示
	else LCD_WriteReg(0X07,0x0173); 				 	//开启显示
}	 

//LCD关闭显示
void LCD_DisplayOff(void)
{ 
        	   
	if(lcddev.id==0X9341||lcddev.id==0X6804||lcddev.id==0X5310||lcddev.id==0X1963)LCD_WR_REG(0X28);	//关闭显示
	else if(lcddev.id==0X5510)LCD_WR_REG(0X2800);	//关闭显示
	else LCD_WriteReg(0X07,0x0);//关闭显示 
}   

5. 显示函数

//显示数字,高位为0,还是显示
//x,y:起点坐标
//num:数值(0~999999999); 
//len:长度(即要显示的位数)
//size:字体大小
//mode:
//[7]:0,不填充;1,填充0.
//[6:1]:保留
//[0]:0,非叠加显示;1,叠加显示.
void LCD_ShowxNum(u16 x,u16 y,u32 num,u8 len,u8 size,u8 mode)
{ 
          
	u8 t,temp;
	u8 enshow=0;						   
	for(t=0;t<len;t++)
	{ 
        
		temp=(num/LCD_Pow(10,len-t-1))%10;
		if(enshow==0&&t<(len-1))
		{ 
        
			if(temp==0)
			{ 
        
				if(mode&0X80)LCD_ShowChar(x+(size/2)*t,y,'0',size,mode&0X01);  
				else LCD_ShowChar(x+(size/2)*t,y,' ',size,mode&0X01);  
 				continue;
			}else enshow=1; 
		 	 
		}
	 	LCD_ShowChar(x+(size/2)*t,y,temp+'0',size,mode&0X01); 
	}
} 

//显示字符串
//x,y:起点坐标
//width,height:区域大小 
//size:字体大小
//*p:字符串起始地址 
void LCD_ShowString(u16 x,u16 y,u16 width,u16 height,u8 size,u8 *p)
{ 
                 
	u8 x0=x;
	width+=x;
	height+=y;
    while((*p<='~')&&(*p>=' '))//判断是不是非法字符!
    { 
               
        if(x>=width){ 
        x=x0;y+=size;}
        if(y>=height)break;//退出
        LCD_ShowChar(x,y,*p,size,0);
        x+=size/2;
        p++;
    }  
}

//在指定位置显示一个字符
//x,y:起始坐标
//num:要显示的字符:" "--->"~"
//size:字体大小 12/16/24
//mode:叠加方式(1)还是非叠加方式(0)
void LCD_ShowChar(u16 x,u16 y,u8 num,u8 size,u8 mode)
{ 
          							  
    u8 temp,t1,t;
	u16 y0=y;
	u8 csize=(size/8+((size%8)?1:0))*(size/2);		//得到字体一个字符对应点阵集所占的字节数 
 	num=num-' ';//得到偏移后的值(ASCII字库是从空格开始取模,所以-' '就是对应字符的字库)
	for(t=0;t<csize;t++)
	{ 
           
		if(size==12)temp=asc2_1206[num][t]; 	 	//调用1206字体
		else if(size==16)temp=asc2_1608[num][t];	//调用1608字体
		else if(size==24)temp=asc2_2412[num][t];	//调用2412字体
		else return;								//没有的字库
		for(t1=0;t1<8;t1++)
		{ 
        			    
			if(temp&0x80)LCD_Fast_DrawPoint(x,y,POINT_COLOR);
			else if(mode==0)LCD_Fast_DrawPoint(x,y,BACK_COLOR);
			temp<<=1;
			y++;
			if(y>=lcddev.height)return;		//超区域了
			if((y-y0)==size)
			{ 
        
				y=y0;
				x++;
				if(x>=lcddev.width)return;	//超区域了
				break;
			}
		}  	 
	}  	    	   	 	  
}   

//画矩形 
//(x1,y1),(x2,y2):矩形的对角坐标
void LCD_DrawRectangle(u16 x1, u16 y1, u16 x2, u16 y2)
{ 
        
	LCD_DrawLine(x1,y1,x2,y1);
	LCD_DrawLine(x1,y1,x1,y2);
	LCD_DrawLine(x1,y2,x2,y2);
	LCD_DrawLine(x2,y1,x2,y2);
}
//在指定位置画一个指定大小的圆
//(x,y):中心点
//r :半径
void LCD_Draw_Circle(u16 x0,u16 y0,u8 r)
{ 
        
	int a,b;
	int di;
	a=0;b=r;	  
	di=3-(r<<1);             //判断下个点位置的标志
	while(a<=b)
	{ 
        
		LCD_DrawPoint(x0+a,y0-b);             //5
 		LCD_DrawPoint(x0+b,y0-a);             //0 
		LCD_DrawPoint(x0+b,y0+a);             //4 
		LCD_DrawPoint(x0+a,y0+b);             //6 
		LCD_DrawPoint(x0-a,y0+b);             //1 
 		LCD_DrawPoint(x0-b,y0+a);             
		LCD_DrawPoint(x0-a,y0-b);             //2 
  		LCD_DrawPoint(x0-b<

相关文章