前言:
为了方便查看博客,特意申请了一个公众号,附上二维码,有兴趣的朋友可以关注,和我一起讨论学习,一起享受技术,一起成长。
1.TFTLCD 简介
TFT-LCD 即薄膜晶体管液晶显示器。TFT-LCD与无源 TN-LCD、 STN-LCD 的简单矩阵不同,它在液晶显示屏的每一个象素上都设置有一个薄膜晶体管( TFT),可有效地克服非选通时的串扰,使显示液晶屏的静态特性与扫描线数无关,因此大大提高了图像质量。 TFT-LCD 也被叫做真彩液晶显示器。ALIENTEK TFTLCD 模块采用 16 位的并方式与外部连接。
2.80 并口有如下一些信号线:
信号线作用
CSTFTLCD 片选信号WR向 TFTLCD 写入数据RD从 TFTLCD 读取数据D[15: 0]16 位双向数据线RST硬复位 TFTLCDRS命令/数据标志( 0,读写命令; 1,读写数据)
注:TFTLCD 模块的 RST 信号线是直接接到 STM32 的复位脚上,并不由软件控制,这样可以省下来一个 IO口。另外我们还需要一个背光控制线来控制 TFTLCD 的背光。所以,我们总共需要的 IO 口数目为 21 个。
3.ILI9341 控制器介绍
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 位的。
4.ILI9341 的重要命令
(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
是页地址设置指令, 在从左到右,从上到下的扫描方(默认)下面,该指令用于设置纵坐标( 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 设置的扫描方向进行自增。
(6)0X2E
该指令是读 GRAM 指令,用于读取 ILI9341 的显存( GRAM),输出情况如表:
该指令用于读取 GRAM,如表 所示,ILI9341在收到该指令后,第一次输出的是 dummy数据,也就是无效的数据,第二次开始,读取到的才是有效的 GRAM 数据(从坐标: SC, SP开始),输出规律为:每个颜色分量占 8 个位,一次输出 2 个颜色分量。
比如:
第一次输出是R1G1,随后的规律为:B1R2G2B2R3G3B3R4G4B4R5G5… 以此类推。如果我们只需要读取一个点的颜色值,那么只需要接收到参数 3 即可,如果要连续读取(利用 GRAM 地址自增),那么就按照上述规律去接收颜色数据。
5.TFTLCD 模块的使用流程
任何 LCD,使用流程都可以简单的用以上流程图表示。其中硬复位和初始化序列,只需要执行一次即可。而画点流程就是:设置坐标 -> 写 GRAM 指令 -> 写入颜色数据,然后在 LCD 上面,我们就可以看到对应的点显示我们写入的颜色了。读点流程为:设置坐标 -> 读 GRAM 指令 -> 读取颜色数据,这样就可以获取到对应点的颜色数据了。
1) 设置 STM32 与 TFTLCD 模块相连接的 IO。
先将我们与 TFTLCD 模块相连的 IO 口进行初始化,以便驱动 LCD。 这里需要根据连接电路以及 TFTLCD 模块的设置来确定。
2) 初始化 TFTLCD 模块。
即上图的初始化序列,这里我们没有硬复位 LCD,因为 MiniSTM32 开发板的 LCD 接口,将 TFTLCD 的 RST 同 STM32 的 RESET 连接在一起了,只要按下开发板的 RESET 键,就会对 LCD 进行硬复位。
初始化序列,就是向 LCD 控制器写入一系列的设置值(比如伽马校准),这些初始化序列一般 LCD 供应商会提供给客户,我们直接使用这些序列即可,不需要深入研究。在初始化之后, LCD 才可以正常使用。
3) 通过函数将字符和数字显示到 TFTLCD 模块上。
这一步则通过上图 左侧的流程,即:设置坐标->写 GRAM 指令->写 GRAM 来实现,但是这个步骤,只是一个点的处理,我们要显示字符/数字,就必须要多次使用这个步骤,从而达到显示字符/数字的目标,所以需要设计一个函数来实现数字/字符的显示,之后调用该函数,就可以实现数字/字符的显示了。
6.实例学习
(1)在硬件上, TFTLCD 模块与 MiniSTM32 开发板的 IO口对应关系如下:
功能IO
LCD_LEDPC10LCD_CSPC9LCD _RSPC8LCD _WRPC7LCD _RDPC6LCD _D[17:1]PB[15:0]
(2)时序
模块的8080并口读/写的过程为:
先根据要写入/读取的数据的类型,设置RS为高(数据)/低(命令),然后拉低片选,选中ILI9341,接着我们根据是读数据,还是要写数据置RD/WR为低。
1.读数据:在RD的上升沿, 读取数据线上的数据(D[15:0]);
2.写数据:在WR的上升沿,使数据写入到ILI9341里面
3.例程分析
typedef struct
{
u16 width
;
u16 height
;
u16 id
;
u8 dir
;
u16 wramcmd
;
u16 setxcmd
;
u16 setycmd
;
}_lcd_dev
;
#define LCD_WR_DATA(data){\
LCD_RS_SET;\
LCD_CS_CLR;\
DATAOUT(data);\
LCD_WR_CLR;\
LCD_WR_SET;\
LCD_CS_SET;\
}
void LCD_WR_DATAX(u16 data
)
{
LCD_RS_SET
;
LCD_CS_CLR
;
DATAOUT(data
);
LCD_WR_CLR
;
LCD_WR_SET
;
LCD_CS_SET
;
}
void LCD_WR_REG(u16 data
)
{
LCD_RS_CLR
;
LCD_CS_CLR
;
DATAOUT(data
);
LCD_WR_CLR
;
LCD_WR_SET
;
LCD_CS_SET
;
}
u16
LCD_RD_DATA(void)
{
u16 t
;
GPIOB
->CRL
=0X88888888;
GPIOB
->CRH
=0X88888888;
GPIOB
->ODR
=0X0000;
LCD_RS_SET
;
LCD_CS_CLR
;
LCD_RD_CLR
;
if(lcddev
.id
==0X8989)delay_us(2);
t
=DATAIN
;
LCD_RD_SET
;
LCD_CS_SET
;
GPIOB
->CRL
=0X33333333;
GPIOB
->CRH
=0X33333333;
GPIOB
->ODR
=0XFFFF;
return t
;
}
void LCD_WriteReg(u16 LCD_Reg
,u16 LCD_RegValue
)
{
LCD_WR_REG(LCD_Reg
);
LCD_WR_DATA(LCD_RegValue
);
}
u16
LCD_ReadReg(u16 LCD_Reg
)
{
LCD_WR_REG(LCD_Reg
);
return LCD_RD_DATA();
}
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
;
LCD_WriteReg(lcddev
.setxcmd
, Xpos
);
LCD_WriteReg(lcddev
.setycmd
, Ypos
);
}
}
void LCD_DrawPoint(u16 x
,u16 y
)
{
LCD_SetCursor(x
,y
);
LCD_WriteRAM_Prepare();
LCD_WR_DATA(POINT_COLOR
);
}
u16
LCD_ReadPoint(u16 x
,u16 y
)
{
u16 r
,g
,b
;
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);
else if(lcddev
.id
==0X5510)LCD_WR_REG(0X2E00);
else LCD_WR_REG(0X22);
GPIOB
->CRL
=0X88888888;
GPIOB
->CRH
=0X88888888;
GPIOB
->ODR
=0XFFFF;
LCD_RS_SET
;
LCD_CS_CLR
;
LCD_RD_CLR
;
opt_delay(2);
r
=DATAIN
;
LCD_RD_SET
;
if(lcddev
.id
==0X1963)
{
LCD_CS_SET
;
GPIOB
->CRL
=0X33333333;
GPIOB
->CRH
=0X33333333;
GPIOB
->ODR
=0XFFFF;
return r
;
}
LCD_RD_CLR
;
opt_delay(2);
r
=DATAIN
;
LCD_RD_SET
;
if(lcddev
.id
==0X9341||lcddev
.id
==0X5310||lcddev
.id
==0X5510)
{
LCD_RD_CLR
;
opt_delay(2);
b
=DATAIN
;
LCD_RD_SET
;
g
=r
&0XFF;
g
<<=8;
}else if(lcddev
.id
==0X6804)
{
LCD_RD_CLR
;
LCD_RD_SET
;
r
=DATAIN
;
}
LCD_CS_SET
;
GPIOB
->CRL
=0X33333333;
GPIOB
->CRH
=0X33333333;
GPIOB
->ODR
=0XFFFF;
if(lcddev
.id
==0X9325||lcddev
.id
==0X4535||lcddev
.id
==0X4531||lcddev
.id
==0X8989||
lcddev
.id
==0XB505)return r
;
else if(lcddev
.id
==0X9341||lcddev
.id
==0X5310||lcddev
.id
==0X5510)
return (((r
>>11)<<11)|((g
>>10)<<5)|(b
>>11));
else return LCD_BGR2RGB(r
);
}
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
-' ';
for(t
=0;t
<csize
;t
++)
{
if(size
==12)temp
=asc2_1206
[num
][t
];
else if(size
==16)temp
=asc2_1608
[num
][t
];
else if(size
==24)temp
=asc2_2412
[num
][t
];
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(x
>=lcddev
.width
)return;
if((y
-y0
)==size
)
{
y
=y0
; x
++;
if(x
>=lcddev
.width
)return;
break;
}
}
}
}
void LCD_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure
;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC
|RCC_APB2Periph_GPIOB
|RCC_APB2Periph_AFIO
, ENABLE
);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable
, ENABLE
);
GPIO_InitStructure
.GPIO_Pin
= GPIO_Pin_10
|GPIO_Pin_9
|GPIO_Pin_8
|GPIO_Pin_7
|GPIO_Pin_6
;
GPIO_InitStructure
.GPIO_Mode
= GPIO_Mode_Out_PP
;
GPIO_InitStructure
.GPIO_Speed
= GPIO_Speed_50MHz
;
GPIO_Init(GPIOC
, &GPIO_InitStructure
);
GPIO_SetBits(GPIOC
,GPIO_Pin_10
|GPIO_Pin_9
|GPIO_Pin_8
|GPIO_Pin_7
|GPIO_Pin_6
);
GPIO_InitStructure
.GPIO_Pin
= GPIO_Pin_All
;
GPIO_Init(GPIOB
, &GPIO_InitStructure
);
GPIO_SetBits(GPIOB
,GPIO_Pin_All
);
delay_ms(50);
LCD_WriteReg(0x0000,0x0001);
delay_ms(50);
lcddev
.id
= LCD_ReadReg(0x0000);
if(lcddev
.id
<0XFF||lcddev
.id
==0XFFFF||lcddev
.id
==0X9300)
{
LCD_WR_REG(0XD3);
LCD_RD_DATA();
LCD_RD_DATA();
lcddev
.id
=LCD_RD_DATA();
lcddev
.id
<<=8;
lcddev
.id
|=LCD_RD_DATA();
if(lcddev
.id
!=0X9341)
{
LCD_WR_REG(0XBF);
LCD_RD_DATA();
LCD_RD_DATA();
LCD_RD_DATA();
lcddev
.id
=LCD_RD_DATA();
lcddev
.id
<<=8;
lcddev
.id
|=LCD_RD_DATA();
if(lcddev
.id
!=0X6804)
{
LCD_WR_REG(0XD4);
LCD_RD_DATA();
LCD_RD_DATA();
lcddev
.id
=LCD_RD_DATA();
lcddev
.id
<<=8;
lcddev
.id
|=LCD_RD_DATA();
if(lcddev
.id
!=0X5310)
{
LCD_WR_REG(0XDA00);
LCD_RD_DATA();
LCD_WR_REG(0XDB00);
lcddev
.id
=LCD_RD_DATA();
lcddev
.id
<<=8;
LCD_WR_REG(0XDC00);
lcddev
.id
|=LCD_RD_DATA();
if(lcddev
.id
==0x8000)lcddev
.id
=0x5510;
if(lcddev
.id
!=0X5510)
{
LCD_WR_REG(0XA1);
lcddev
.id
=LCD_RD_DATA();
lcddev
.id
=LCD_RD_DATA();
lcddev
.id
<<=8;
lcddev
.id
|=LCD_RD_DATA();
if(lcddev
.id
==0X5761)lcddev
.id
=0X1963;
}
}
}
}
}
printf(" LCD ID:%x\r\n",lcddev
.id
);
if(lcddev
.id
==0X9341)
{
LCD_WR_REG(0xCF);
LCD_WR_DATAX(0x00);
LCD_WR_DATAX(0xC1);
LCD_WR_DATAX(0X30);
LCD_WR_REG(0xED);
LCD_WR_DATAX(0x64);
LCD_WR_DATAX(0x03);
LCD_WR_DATAX(0X12);
LCD_WR_DATAX(0X81);
LCD_WR_REG(0xE8);
LCD_WR_DATAX(0x85);
LCD_WR_DATAX(0x10);
LCD_WR_DATAX(0x7A);
LCD_WR_REG(0xCB);
LCD_WR_DATAX(0x39);
LCD_WR_DATAX(0x2C);
LCD_WR_DATAX(0x00);
LCD_WR_DATAX(0x34);
LCD_WR_DATAX(0x02);
LCD_WR_REG(0xF7);
LCD_WR_DATAX(0x20);
LCD_WR_REG(0xEA);
LCD_WR_DATAX(0x00);
LCD_WR_DATAX(0x00);
LCD_WR_REG(0xC0);
LCD_WR_DATAX(0x1B);
LCD_WR_REG(0xC1);
LCD_WR_DATAX(0x01);
LCD_WR_REG(0xC5);
LCD_WR_DATAX(0x30);
LCD_WR_DATAX(0x30);
LCD_WR_REG(0xC7);
LCD_WR_DATAX(0XB7);
LCD_WR_REG(0x36);
LCD_WR_DATAX(0x48);
LCD_WR_REG(0x3A);
LCD_WR_DATAX(0x55);
LCD_WR_REG(0xB1);
LCD_WR_DATAX(0x00);
LCD_WR_DATAX(0x1A);
LCD_WR_REG(0xB6);
LCD_WR_DATAX(0x0A);
LCD_WR_DATAX(0xA2);
LCD_WR_REG(0xF2);
LCD_WR_DATAX(0x00);
LCD_WR_REG(0x26);
LCD_WR_DATAX(0x01);
LCD_WR_REG(0xE0);
LCD_WR_DATAX(0x0F);
LCD_WR_DATAX(0x2A);
LCD_WR_DATAX(0x28);
LCD_WR_DATAX(0x08);
LCD_WR_DATAX(0x0E);
LCD_WR_DATAX(0x08);
LCD_WR_DATAX(0x54);
LCD_WR_DATAX(0XA9);
LCD_WR_DATAX(0x43);
LCD_WR_DATAX(0x0A);
LCD_WR_DATAX(0x0F);
LCD_WR_DATAX(0x00);
LCD_WR_DATAX(0x00);
LCD_WR_DATAX(0x00);
LCD_WR_DATAX(0x00);
LCD_WR_REG(0XE1);
LCD_WR_DATAX(0x00);
LCD_WR_DATAX(0x15);
LCD_WR_DATAX(0x17);
LCD_WR_DATAX(0x07);
LCD_WR_DATAX(0x11);
LCD_WR_DATAX(0x06);
LCD_WR_DATAX(0x2B);
LCD_WR_DATAX(0x56);
LCD_WR_DATAX(0x3C);
LCD_WR_DATAX(0x05);
LCD_WR_DATAX(0x10);
LCD_WR_DATAX(0x0F);
LCD_WR_DATAX(0x3F);
LCD_WR_DATAX(0x3F);
LCD_WR_DATAX(0x0F);
LCD_WR_REG(0x2B);
LCD_WR_DATAX(0x00);
LCD_WR_DATAX(0x00);
LCD_WR_DATAX(0x01);
LCD_WR_DATAX(0x3f);
LCD_WR_REG(0x2A);
LCD_WR_DATAX(0x00);
LCD_WR_DATAX(0x00);
LCD_WR_DATAX(0x00);
LCD_WR_DATAX(0xef);
LCD_WR_REG(0x11);
delay_ms(120);
LCD_WR_REG(0x29);
}
LCD_Display_Dir(0);
LCD_LED
=1;
LCD_Clear(WHITE
);
}
注:此处仅为9341 的初始化
参考:
原子开发手册-----库函数版