文章目录
LCD – TFTLCD 原理与配置
TFTLCD(薄膜晶体管液晶显示器)
简介
Thin Film Transistor - Liquid Crystal Display
TFTLCD计技术是微电子技术与液晶显示器技术巧妙结合的一种技术。
TFT-LCD屏可视为两片玻璃基板中间夹着一层液晶,上层的玻璃基板贴合彩色滤波片,下层玻璃有晶体管镶嵌。当有电流通过晶体管产生电场变化,造成液晶分子偏转,借以改变光线的偏极性,再利用偏光片决定像素的明暗状态。此外,上层玻璃因与彩色滤光片贴合,形成每个像素各包含红蓝绿三颜色,像素便构成了皮肤上的图像画面。
每一个像素上都设置有一个薄膜晶体管--->大大提高了图像质量
特点
亮度高
对比度高
层次感强
颜色鲜艳
原理
接口说明:
(DB1DB8,DB10DB17,总是按顺序连接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_MOST/T_PEN/T_CS/T_CLK,触摸屏接口信号
驱动
8080并口读/写过程为:
先根据要写入/读取的数据的类型,设置RS为高(数据)/低(命令),然后拉低片选,选中ILI9341,接着根据是读还是写数据置RD/WD 为低
1.读数据:在RD的上升沿,读取数据线上的数据(D[15:0])
2.写数据:在WR的上升沿,使数据写入到ILI9341里面
驱动流程
硬复位
LCD_RST=0;
delay_ms(100);
LCD_RST=1;
初始化序列
不同厂家出厂的LCD的初始化序列是不同的,由厂家提供。
设置坐标
写GRAM指令
写入颜色数据(0X2C)
LCD显示
读GRAM指令
读出颜色数据(0X2E)
单片机处理
RGB565格式
ILI9341指令格式说明
几条重要指令
0XD3
用来读取LCD控制器的ID
代码相同,ID不同——>执行不同的LCD驱动初始化——>兼容不同的LCD屏幕
0X36
存储访问控制指令,可以控制ILI9341存储器的读写方向
就是在连续写GRAM的时候,可以控制GRAM指针的增长方向,从而控制显示方式(读GRAM也是一样)
0X2A
列地址设置指令,默认从左向右,从上到下的扫描方式,用于设置横坐标
0<=SC<=EC<=239
0X2B
页(理解是行)地址设置指令,从左到右,从上到下的扫描方式下面,该指令用于设置纵坐标。
0<=SP<=EP<=319
0X2C
写GRAM指令,在发送该指令后,可往LCD的GRAM里面写入颜色数据,支持连续写(地址自动递增)
0X2E
读GRAM指令,用于读取ILI9341的显存,同0X2C指令,该指令支持连续读(地址自动递增)
ILI9341收到指令后,第一次输出dummy数据(无效),第二次开始,读取到的才是有效的GRAM数据,输出规律为每个颜色分量占8个位,一次输出2个颜色分量。比如:第一次输出是R1G1,随后的规律为B1R1->G2B2->R3G3->~
FSMC简介
FSMC,灵活的静态存储控制器,能够与同步或异步存储器和16位IPC存储器卡连接。
FSMC驱动LCD
FSMC驱动外部SRAM时,外部SRAM的控制一般有:地址线(如A0A25)、数据线(如D0D15)、写信号(WE,即WR)、读信号(OE,即RD)、片选信号(CS),如果SRAM支持字节控制,那么还有UBLB信号。
而TFTL CD的信号我们在前面介绍过,包括: RS、D0D15、WR、RD、CS.RST和BL等,其中真正在操作LCD的时候需要用到的就只有:RS、DOD15、WR.RD和CS.其操作时序和SRAM的控制完全类似,唯一不同就是TFTLCD有RS信号,但是没有地址信号。
TFTLCD通过RS信号来决定传送的数据是数据还是命令,本质上可以理解为一个地址信号,比如我们把RS接在AO上面,那么当FSMC控制器写地址0的时候,会使得AO变为0,对TFTLCD来说,就是写命令。而FSMC写地址1的时候, A0将会变为1,对TFTLCD来说,就是写数据。这样把数据和命令区分开,对应SRAM操作的两个连续地址。当然RS也可以接在其他地址线上,战舰V3和精英板开发板都是把RS连接在A10上面,而探索者STM32F4把RS接在A6上面。
因此,可以把TFTLCD当成一个SRAM来用,只不过这个SRAM有2个地址,这就是FSMC可以驱动LCD的原理。
NOR PSRAM外设接口
STM32的FSMC将外部存储器划分为固定大小为256M字节的四个存储块
存储块1
Bank1(存储块1)用于驱动NOR FLASH/SRAM/PSRAM,被分为四个区,每区管理54M字节空间,每个区都有独立的寄存器对所连接的存储器进行配置。Bank1 的256M字节空间由28根地址线[HADDR【27:0】]寻址。
HADDR是内部AHB地址总线,其中HADDR【25:0】来自外部存储器地址FSMC_A[25:0],HADDR[26:27]对四个区进行寻址。
当Bank1接的是16位宽度存储器的时候: HADDR[25:1——>FSMC_A[24:0]
当Bank1接的是8位宽度存储器的时候: HADDR[25:0]——>FSMC_A[25:0]
不论外部接8位/16位宽设备,FSMC_A[0]永远接在外部设备地址A[0]
这里使用模式A来驱动LCD
寄存器
对于NORELASH/PSRAM控制器(存储块1),通过ESMC BCRx、FSMC_BTRx和FSMC_ BWTRx寄存器设置(其中x=1~4,对应4个区)。通过这3个寄存器,可E设置FSMC访问外部存储器的时序参数,拓宽了可选用的外部存储器的速度范围。
EXTMOD:扩展模式使能位,控制是否允许读写不同的时序,需设置为1
WREN:写使能位。向TFTLCD写数据,故该位必须设置为1
MWID[1:0]:存储器数据总线宽度。00,表示8位数据模式;01表示16位数据模式;10和11保留。TFTLCD是16位数据线,所以设置WMID[1:0]=01."MTYP[1:0]:存储器类型。00表示SRAM、ROM; 01表示PSRAM:10表示NORFLASH;11保留。LCD当成SRAM,需要设置MTYP[1:0]=00.
MBKEN:存储块使能位。设置为1
ACCMOD[1:0]:访问模式。00:模式A;01:模式B:10:模式C;11:模式D.
DATAST[7:0]:数据保持时间,等于: DATAST(+1)个HCLK时钟周期,DATAST最大为255。对ILI9341来说,其实就是RD低电平持续时间,最大为355ns,对STM32F1,一个HCLK=13.8ns (1/72M),设置为15;对STM32F4,一个HCLK=6ns(1/168M),设置为60。
ADDSET[3:0]:地址建立时间。表示:ADDSET (+1)个HCLK周期, ADDSET最大为15。对ILI9341来说,这里相当于RD高电平持续时间,为90ns,STM32F1的FSMC性能存在问题,即便设置为0,RD也有190ns的高电平,我们这里设置为1。而对STM32F4,则设置为15。
ACCMOD[1:0]:访问模式。00:模式A;01:模式B:10:模式C;11:模式D,
DATAST[7:0]:数据保持时间,等于:DATAST(+1)个HCLK时钟周期, DATAST最大为255。对I LI9341来说,其实就是WR低电平持续时间,为15ns,不过ILI9320等则需要50ns。考虑兼容性,对STM32F1,一个HCLK=13.8ns (1/72M),设置为3;对STM32F4,一个HCLK=6ns(1/168M),设置为9。
ADDSET[3:0]:地址建立时间。表示: ADDSET+1个HCLK周期, ADDSET最大为15。对ILI9341相当于WR高电平持续时间,为15ns。兼容ILI9320,对STM32F1设置为1。STM32F4,设置为8。
寄存器组合
ST官方库提供的寄存器定义,没有定义单独的寄存器,而是将他们进行组合。
FSMC BCR×和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
F.SMC BWTRX则组合成BWTR[7]:
BWTR[O]对应FSMC_BWTR1,BWTR[2]对应FSMC_BWTR2
BWTRI4]对应FSMC BWTR3, BWTR[6]对应FSMC BWTR4
LCD实验源码
硬件连接(STM32F4开发板)
LCD_BL(背光控刷)对应 PBO;
LCD_CS对应PGl2即 FSMC_NE4;
LCD_RS对应 PG0FSMC_Al0;
LCD_wR对应PDS 即 FSMC_NWE;
LCD_RD对应PD4即 FSMC_NOE;
LCD_D[15:0]则直接连接在FSMC_Dl5~FSMC_D0;
LCD&lcddev结构体
LCD结构体
typedef struct
{
vu16 LCD_ REG;
vu16 LCD RAM;
}LCD_TypeDef;
//使用NOR/SRAM的 Bank1.sector4,地址位HADDR[27,26]=11A10作为数据命令区分线
//注意设置时STM32内部会右移一位对齐!
#define LCD_BASE ((u32)(0x6co0o000|Ox0000007FE))
#define LCD ((LCD_ TypeDef*)LCD_ BASE)
LCD_BASE,根据外部电路的连接来确定,如Bank1.sector4就是从地址OX6C000000开始,而OX000007FE,则是A10的偏移量。以A10为例,7FE换成二进制为:111 11111110,而16位数据时,地址右移一位对齐,对应到地址引脚,就是: A10:A0=011 11111111,此时A10是0,但是如果16位地址再加1(对应到8位地址是加2,即7FE+0X02),那么:A10:A0=100 0000 0000,此时A10就是1了,即实现了对RS的0和1的控制。
将((LCD_ TypeDef)LCD_ BASE)这个地址强制转换为LCD_TypeDef结构体地址,得到LCD->LCD_REG的地址OX6CO0,07FE,对应A10的状态为O(即RS=0),而LCD->LCD_RAM的地址就是0X6C00,0800(结构体地址自增),对应A10的状态为1(即RS=1),从而实现对RS的控制。*
lcddev结构体
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坐标指令
}_Icd_dev;
//LCD参数
extern _lcd_dev lcddev;//管理LCD重要参数
该结构体参数的赋值,基本都是在LCD_Display_Dir函数完成
底层接口函数
总括:
void LCD WR REG(u16 regval) //写寄存器值函数
void LCD WR DATA(u16 data) //写数据函数
u16 LCD RD DATA(void) //读数据函数
void LCD_WriteReg(u16 LCD_Reg,u16 LCD_RegValue) //写寄存器内容函数
u16 LCD_ReadReg(u16 LCD_Reg) //读寄存器内容函数
void LCD WriteRAM Prepare(void) //开始写GRAM函数
void LCD_WriteRAM(u16 RGB_Code) //写GRAM函数
void LCD_WR_REG(vu16 regval)
{
regval=regval; //使用-O2优化时,必须插入延时
LCD->LCD_REG=regval;//写入寄存器序号
}//写寄存器值
void LcD_WR_DATA(vul6 data)
data=data;
//使用-o2优化的时候,必须插入的延时
LCD->LCD_RAM=data ;
)
//读LcD数据
ul6 LCD_RD_DATA (void)
vul6 ram;
//防止被优化
ram=LCD->LCD_RAM;
return ram;
//LCD_Reg:寄存器地址
//LCD Regvalue:要写入的数据
void LcD_WriteReg (ul6 LCD_Reg,ul6 LCD_Regvalue)
LCD->LCD_REG= LCD_Reg;//写入要写的寄存器序号LCD->LCD_RAM=LCD_Regvalue; //写入数据
//读寄存器
//LCD Reg:寄存器地址//返回值:读到的数据
ul6 LCD_ReadReg (ul6 LCD_Reg)
L.CD_WR_REG(LCD_Reg) ;//写入要读的寄存器序号delay_us(5) ;
return LCD_RD_DATA ( );//返回读到的值
}
void LCD_writeRAM_Prepare (void)
{
LCD->LCD_REG=lcddev.wramcmd;
}
//RGB_code:颜色值
void LcD_writeRAM (ul6 RGB_code){
LCD->LCD_RAM=RGB_code; //写十六位GRAM
初始化函数
//LCD初始化
void LCD_Init(void)
{
初始化GPIO;
初始化FSMC;
读取LCD D;
printf( "LCD ID:%Kx\rn", lcddev.id);//打印LCD ID,用到了串口1
//初始化串口1,否则黑屏
根据不同的ID执行LCD初始化代码;
LCD Display Dir(0); //默认为竖屏
LCD LED=1; //点亮背光
LCD Clear (WHITE); //清屏
坐标设置函数
//设置光标位置
//Xpos:横坐标
//Ypos:纵坐标
void LCD SetCursor (ul6 Xpos,u16 Ypos)
{
if(lcddev.idoX9341 I|lcddev.id 0X5310)
{
LCD_WR_REG(lcddev.setxcmd);
LCD_WR DATA (Xpos>>8);
LCD_WR DATA (Xpos&OXFF);
LCD_WR_REG(1eddev.setycmd);
LCD_WR_DATA(Ypos>>8);
LCD_WR_ DATA(Ypos&OXFF);
}else if(lcddev. idXXX){}
//根据不同的LCD型号,执行不同的代码
}
画点函数
//画点
//x, y:坐标
//POINT _COLOR:此点的颜色
void LCD_DrawPoint (u16 x, ul6 y)
{
LCD SetCursor (x,y); //设置光标位置
LCD_WriteRAM_Prepare(); //开始写入GRAM
LCD->LCD_ RAM=POINT_COLOR;//非Mini板的操作方式
读点函数
ul6 LCD_ReadPoint (ul6 x,ul6 y){
ul6 r=0, g=o,b=0 ;
if(x>=lcddev.width ||y>=lcddev.height)return 0;//超过了范围,直接返回LcD_setcursor(x,y);
if(lcddev.id==0X9341||lcddev.id==OX6804 ||lcddev.id==OX5310||leddev.id==OX1963)LED_WR_REG(0X2E);//341/684/3510/1963发送读cour指令
else if (lcddev.id==0X5510)LCD_wR_REG (OX2Eo0); //5510发送读GRAw指令
else LCD_WR_REG(0X22);//其他Ic发送读GRAv指令
if(lcddev.id==0X9320)opt_delay (2);//FOR 9320,延时2usr=LCD_RD_DATA();-l / dummy Read
if ( lcddev.id==0X1963) return r;//1963直接读就可以
opt_delay (2);
r=LCD_RD_DATA( ) ;//实际坐标颜色
if (lcddev.id==0X9341||lcddev.id==OX5310||lcddev.id==OX5510)//9341/NT35310/NT35510要分2次读出
{
opt_delay (2);
b=LCD_RD_DATA();
g=r&0XFF;//对于9341/5310/5510,第一次读取的是Rc的值,R在前,c在后,各占8位
g<<=8 ;
if(lcddev.id==O0X9325||lcddev. id=O0X4535||lcddev .id==0X4531 ||lcddev .id==O0XB505]||lcddev.id==0XC505)return r;//这几种rc直接返回颜色值
else if(lcddev.id=0X9341||lcddev.id==0X5310||lcddev.id==0X5510)return ((r>>l)<<I1)|(g>>10)<<5)|(b>>11));//ILI3L1/NT35310/NT35510需要公式转
else return LCD_BGR2RGB(r) ;//其他ic
字符显示函数
void LcD_ShowChar(ul6 x,ul6 y,u8 num, u8 size,u8 mode)
{
u8 temp,tl,t;
ul6 yO=y;
u8 csize=(size/8+((size卺8)?1:0))*(size/2);.//得到字体一个字符对应点阵集所占的字
num=num-' ';//得到偏移后的值(AscII字库是从空格开始取模,所以-·'就是对应字符的字库)
for (t=0;t<csize;t++)
{
if(size==l2)temp=asc2_1206[num][t];//调用1206字体
else if(size==l6)temp=asc2_1608 [num][t];//调用1608字体
else if(size==24)temp=asc2_2412[num][t]; //调用2412字体
else return;
//没有的字库
for (tl=0;tl<8;tl++)
{
if (temp&0x80)LCD_Fast_DrawPoint (x,y,POINT_COLOR) ;
else if (mode==0)LCD_Fast_DrawPoint (x,y,BACK_COLOR);
temp<<=l;
y++;
if(y>=lcddev.height) return;//超区域
if( (y-y0 )==size)
{
y=y0;x++;
if(>=lcddev.width) return;//超区域
break;
}
}
}
}