TFT显示屏是常见的显示部件。在显示屏中使用的接口中,常见的有I2C、SPI等串口模式,也有8位、16位等并口模式。串口模式因为占用端口少,对于端口紧张的单片机而言,是最好的选择。但串行端口方式驱动显示屏,对于图形、视频处理来说,处理速度上就不如并口了。
本次试验,是我第一次试用16位并口方式驱动ST7789驱动的显示屏。从驱动过程来说,无论串行模式,还是并行模式,其实只是以不同的方式向ST7789的各个寄存器发送指令和数据而已,因此从本质上讲,无论是初始化过程,还是显示数据的过程,只要完成关键数据的传送,处理结果都是一样的。因此在学习过程中,将重点放在如何传输字节数据上。
在贴上代码之前,先讲下接口配置情况。我用的显示屏,使用插接款FPC电缆,计有40个引脚,引脚的定义如下:
引脚编号 | 功能说明 | |
1 | X- | 触摸屏信号线 |
2 | Y- | 触摸屏信号线 |
3 | X+ | 触摸屏信号线 |
4 | Y+ | 触摸屏信号线 |
5 | GND | 地 |
6 | VDDI | I/O口电压(2.8V~3.3V) |
7 | VDD | 模拟电路电源(2.8V~3.3V) |
8 | NC/FMARK | 悬空 |
9 | CSX | 片选信号,低电平有效 |
10 | DCX | 指令/数据选择,L:指令,H:数据 |
11 | WRX | LCD写控制端 |
12 | RDX | LCD读控制端 |
13 | SPI SDI | SPI输入 |
14 | SPI_SDO | SPI输出 |
15 | RESX | 复位信号线 |
16 | GND | 地 |
17 | DB0 | 数据线 |
18 | DB1 | 数据线 |
19 | DB2 | 数据线 |
20 | DB3 | 数据线 |
21 | DB4 | 数据线 |
22 | DB5 | 数据线 |
23 | DB6 | 数据线 |
24 | DB7 | 数据线 |
25 | DB8 | 数据线 |
26 | DB9 | 数据线 |
27 | DB10 | 数据线 |
28 | DB11 | 数据线 |
29 | DB12 | 数据线 |
30 | DB13 | 数据线 |
31 | DB14 | 数据线 |
32 | DB15 | 数据线 |
33 | LED-A | 背光LED+ |
34 | LED-K | 背光LED- |
35 | LED-K | 背光LED- |
36 | LED-K | 背光LED- |
37 | GND | 地 |
38 | IM0 | 模式选择 |
39 | IM1 | 模式选择 |
40 | IM2 | 模式选择 |
为了测试方便,我是用STC32G12K128核心开发板来连接显示屏进行测试。开发板提供了8组Port,每组都是完整的8Bit。我平时特别喜欢用51单片机进行测试的一个理由就是端口被设置为准双向口时,输入输入处理很方便,不需要特别处理。

显示屏本身有一款转接板。转接板本身可以简化显示屏与开发板之间的连接。

在经过了转接板后,上面接口中IM0、IM1、IM2已经被处理为16位的驱动方式。
IM2 IM1 IM0的设置 | 驱动模式 |
000 | 16位驱动,使用DB0 ~ DB15传输指令、数据 |
001 | 8位驱动,使用DB8 ~ DB15传输指令、数据。DB0~DB7接地不用 |
101 | 3线SPI模式 |
110 | 4线SPI模式 |
上面接口模式中,受限于转接板的原因,没有进行16位以外模式的测试。准备以后使用其它转接板重新进行学习和测试。经过转接板以后,和显示屏的实际连接为以下方式。
RS | CS | ||
RD | WR | ||
DB0 | RST | ||
DB2 | DB1 | ||
DB4 | DB3 | ||
DB6 | DB5 | ||
DB8 | DB7 | ||
DB10 | DB9 | ||
DB12 | DB11 | ||
DB14 | DB13 | ||
GND | DB15 | ||
VDD | BL 接3.3V | ||
GND | VDD 接3.3V | ||
+5V | GND | ||
MOSI | MISO | ||
NC | PEN | ||
CLK | CS |
底色为绿色的部分,是实际使用的接口。
//=======================================液晶屏接线===========================================//
//数据总线类型为16位并口
// DB0~DB7 = P0.0~P0.7 低8位数据线
// DB8~DB15 = P2.0~P2.7 高8位数据线
// CS = P3.5 片选信号
// RS = P4.0 数据/命令选择控制信号
// WR = P3.6 写控制信号
// RD = P3.7 读控制信号
// RST = P3.4 复位信号
// LED = P3.3 背光开关控制
#define LCD_DataPortH P2 //高8位数据口
#define LCD_DataPortL P0 //低8位数据口
sbit LCD_RS = P4^0; //数据/命令切换
sbit LCD_WR = P3^6; //写控制
sbit LCD_RD = P3^7; //读控制
sbit LCD_CS = P3^5; //片选
sbit LCD_RESET = P3^4; //复位
sbit LCD_LED = P3^3; //背光控制
//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;
使用杜邦线连接转接板和显示屏。
这里面需要特别说明的是,显示屏的BL端必须要接入电源(需要加限流电阻),否则看不到任何显示。
在处理程序上,关键的几个处理函数说明如下。
1、复位
使用硬件方式处理。
LCD_RESET=1; delay_ms(10); LCD_RESET=0; delay_ms(20); LCD_RESET=1; delay_ms(120);
2、写寄存器函数
这是向ST7789发送指令/数据用的基本函数。
void LCD_WR_REG(u16 REG) {
LCD_RS=0;
LCD_CS=0;
LCD_WR=0;
LCD_DataPortH=REG>>8;
LCD_DataPortL=REG;
LCD_WR=1;
LCD_CS=1;
}
3、写数据函数
void LCD_WR_DATA(u16 DATA) {
LCD_RS=1;
LCD_CS=0;
LCD_WR=0;
LCD_DataPortH=DATA>>8;
LCD_DataPortL=DATA;
LCD_WR=1;
LCD_CS=1;
}
4、写寄存器
//LCD_Reg:寄存器编号
//LCD_RegValue:要写入的值
void LCD_WriteReg(u16 LCD_Reg,u16 LCD_RegValue) {
LCD_WR_REG(LCD_Reg);
LCD_WriteRAM(LCD_RegValue);
}
5、写GRAM
void LCD_WriteRAM_Prepare(void) {
LCD_WR_REG(lcddev.wramcmd);
}
6、LCD写GRAM
//RGB_Code:颜色值
void LCD_WriteRAM(u16 RGB_Code) {
LCD_RS=1;
LCD_CS=0;
LCD_DataPortH=RGB_Code>>8;
LCD_DataPortL=RGB_Code;
LCD_WR=0;
LCD_WR=1;
LCD_CS=1;
}
7、延时i
void opt_delay(u8 i) {
while(i--);
}
8、LCD开启显示
void LCD_DisplayOn(void) {
LCD_WR_REG(0X29); //开启显示
}
9、LCD关闭显示
void LCD_DisplayOff(void) {
LCD_WR_REG(0X28); //关闭显示
}
10、设置光标位置
//Xpos:横坐标
//Ypos:纵坐标
void LCD_SetCursor(u16 Xpos, u16 Ypos) {
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);
}
11、画点
//x,y:坐标
//POINT_COLOR:此点的颜色
void LCD_DrawPoint(u16 x,u16 y) {
LCD_SetCursor(x,y); //设置光标位置
LCD_WriteRAM_Prepare(); //开始写入GRAM
LCD_WriteRAM(POINT_COLOR);
}
12、快速画点
//x,y:坐标
//color:颜色
void LCD_Fast_DrawPoint(u16 x,u16 y,u16 color) {
//设置光标位置
LCD_SetCursor(x,y);
//写入颜色
LCD_WriteReg(lcddev.wramcmd,color);
}
13、选择方向
//dir:方向选择 0-0度旋转,1-180度旋转,2-270度旋转,3-90度旋转
void LCD_Display_Dir(u8 dir) {
//竖屏
if(dir==0||dir==1) {
lcddev.dir=0; //竖屏
lcddev.width=240;
lcddev.height=320;
lcddev.wramcmd=0X2C;
lcddev.setxcmd=0X2A;
lcddev.setycmd=0X2B;
if(dir==0) {
//0:0度旋转
LCD_WR_REG(0x36);
LCD_WR_DATA((0<<3)|(0<<7)|(0<<6)|(0<<5));
} else {
//1:180度旋转
LCD_WR_REG(0x36);
LCD_WR_DATA((0<<3)|(1<<7)|(1<<6)|(0<<5));
}
} else if(dir==2 || dir==3) {
lcddev.dir=1; //横屏
lcddev.width=320;
lcddev.height=240;
lcddev.wramcmd=0X2C;
lcddev.setxcmd=0X2A;
lcddev.setycmd=0X2B;
//2:270度旋转
if(dir==2) {
LCD_WR_REG(0x36);
LCD_WR_DATA((0<<3)|(1<<7)|(0<<6)|(1<<5));
} else {
//3:90度旋转
LCD_WR_REG(0x36);
LCD_WR_DATA((0<<3)|(0<<7)|(1<<6)|(1<<5));
}
}
//设置显示区域
LCD_WR_REG(lcddev.setxcmd);
LCD_WR_DATA(0);LCD_WR_DATA(0);
LCD_WR_DATA((lcddev.width-1)>>8);LCD_WR_DATA((lcddev.width-1)&0XFF);
LCD_WR_REG(lcddev.setycmd);
LCD_WR_DATA(0);LCD_WR_DATA(0);
LCD_WR_DATA((lcddev.height-1)>>8);LCD_WR_DATA((lcddev.height-1)&0XFF);
}
14、设置窗口,并自动设置画点坐标到窗口左上角(sx,sy).
//sx,sy:窗口起始坐标(左上角)
//width,height:窗口宽度和高度,必须大于0!!
//窗体大小:width*height.
void LCD_Set_Window(u16 sx,u16 sy,u16 width,u16 height) {
u16 twidth,theight;
twidth=sx+width-1;
theight=sy+height-1;
LCD_WR_REG(lcddev.setxcmd);
LCD_WR_DATA(sx>>8);
LCD_WR_DATA(sx&0XFF);
LCD_WR_DATA(twidth>>8);
LCD_WR_DATA(twidth&0XFF);
LCD_WR_REG(lcddev.setycmd);
LCD_WR_DATA(sy>>8);
LCD_WR_DATA(sy&0XFF);
LCD_WR_DATA(theight>>8);
LCD_WR_DATA(theight&0XFF);
}
15、清屏函数
//color:要清屏的填充色
void LCD_Clear(u16 color) {
u32 index=0;
u32 totalpoint;
totalpoint=lcddev.width;
totalpoint*=lcddev.height; //得到总点数
LCD_SetCursor(0,0); //设置光标位置
LCD_WriteRAM_Prepare(); //开始写入GRAM
for(index=0;index<totalpoint;index++)
LCD_WriteRAM(color);
}
16、在指定区域内填充指定颜色
//区域大小:(xend-xsta+1)*(yend-ysta+1)
//xsta
//color:要填充的颜色
void LCD_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 color) {
u16 i,j;
u16 xlen=0;
xlen=ex-sx+1;
for(i=sy;i<=ey;i++) {
LCD_SetCursor(sx,i); //设置光标位置
LCD_WriteRAM_Prepare(); //开始写入GRAM
for(j=0;j<xlen;j++)LCD_WriteRAM(color); //设置光标位置
}
}
测试效果如下:
作为对比,看看SPI模式下的写指令/数据函数:
1、写一个字节
void spi_write_byte(u8 d) {
u8 val=0x80;
while(val) {
if(d&val) {
LCD_SDI = 1;
} else {
LCD_SDI = 0;
}
LCD_CLK = 0;
LCD_CLK = 1;
val>>=1;
}
}2、写寄存器
void LCD_WR_REG(u8 Reg) {
LCD_RS=0;
LCD_CS=0;
spi_write_byte(Reg);
LCD_CS=1;
}
3、写数据
void LCD_WR_DATA(u8 Data) {
LCD_RS=1;
LCD_CS=0;
spi_write_byte(Data);
LCD_CS=1;
}4、写16Bit数据
void LCD_WR_DATA_16Bit(u16 Data) {
LCD_CS=0;
LCD_RS=1;
spi_write_byte(Data>>8);
spi_write_byte(Data);
LCD_CS=1;
}5、向寄存器写数据
void LCD_WriteReg(u8 LCD_Reg, u8 LCD_RegValue) {
LCD_WR_REG(LCD_Reg);
LCD_WR_DATA(LCD_RegValue);
}6、写GRAM
void LCD_WriteRAM_Prepare(void) {
LCD_WR_REG(lcddev.wramcmd);
}7、清除屏幕
void LCD_Clear(u16 Color) {
u16 i,j;
LCD_SetWindows(0,0,lcddev.width-1,lcddev.height-1);
LCD_CS=0;
LCD_RS=1;
for(i=0;i<lcddev.width;i++) {
for (j=0;j<lcddev.height;j++) {
spi_write_byte(Color>>8);
spi_write_byte(Color);
}
}
LCD_CS=1;
}8、画点
void LCD_DrawPoint(u16 x,u16 y) {
LCD_SetWindows(x,y,x,y);//设置光标位置
LCD_WR_DATA_16Bit(POINT_COLOR);
}
9、复位
void LCD_Reset(void) {
LCD_RESET=0;
delay_ms(20);
LCD_RESET=1;
delay_ms(20);
}10、设置窗口
void LCD_SetWindows(u16 xStar, u16 yStar,u16 xEnd,u16 yEnd) {
LCD_WR_REG(lcddev.setxcmd);
LCD_WR_DATA((xStar+lcddev.xoffset)>>8);
LCD_WR_DATA(xStar+lcddev.xoffset);
LCD_WR_DATA((xEnd+lcddev.xoffset)>>8);
LCD_WR_DATA(xEnd+lcddev.xoffset);
LCD_WR_REG(lcddev.setycmd);
LCD_WR_DATA((yStar+lcddev.yoffset)>>8);
LCD_WR_DATA(yStar+lcddev.yoffset);
LCD_WR_DATA((yEnd+lcddev.yoffset)>>8);
LCD_WR_DATA(yEnd+lcddev.yoffset);
LCD_WriteRAM_Prepare(); //开始写入GRAM
}
11、初始化
void LCD_Init(void) {
LCD_Reset(); //初始化之前复位
//************* ST7789初始化**********//
LCD_WR_REG(0x36);
LCD_WR_DATA(0x00);
LCD_WR_REG(0x3A);
LCD_WR_DATA(0x05);
LCD_WR_REG(0xB2);
LCD_WR_DATA(0x0C);
LCD_WR_DATA(0x0C);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x33);
LCD_WR_DATA(0x33);
LCD_WR_REG(0xB7);
LCD_WR_DATA(0x35);
LCD_WR_REG(0xBB);
LCD_WR_DATA(0x1A);//
LCD_WR_REG(0xC0);
LCD_WR_DATA(0x2C);
LCD_WR_REG(0xC2);
LCD_WR_DATA(0x01);
LCD_WR_REG(0xC3);
LCD_WR_DATA(0x0B); //
LCD_WR_REG(0xC4);
LCD_WR_DATA(0x20);
LCD_WR_REG(0xC6);
LCD_WR_DATA(0x0F);
LCD_WR_REG(0xD0);
LCD_WR_DATA(0xA4);
LCD_WR_DATA(0xA1);
LCD_WR_REG(0xE0);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x03);
LCD_WR_DATA(0x07);
LCD_WR_DATA(0x08);
LCD_WR_DATA(0x07);
LCD_WR_DATA(0x15);
LCD_WR_DATA(0x2A);
LCD_WR_DATA(0x44);
LCD_WR_DATA(0x42);
LCD_WR_DATA(0x0A);
LCD_WR_DATA(0x17);
LCD_WR_DATA(0x18);
LCD_WR_DATA(0x25);
LCD_WR_DATA(0x27);
LCD_WR_REG(0xE1);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x03);
LCD_WR_DATA(0x08);
LCD_WR_DATA(0x07);
LCD_WR_DATA(0x07);
LCD_WR_DATA(0x23);
LCD_WR_DATA(0x2A);
LCD_WR_DATA(0x43);
LCD_WR_DATA(0x42);
LCD_WR_DATA(0x09);
LCD_WR_DATA(0x18);
LCD_WR_DATA(0x17);
LCD_WR_DATA(0x25);
LCD_WR_DATA(0x27);
LCD_WR_REG(0x21);
LCD_WR_REG(0x11);
delay_ms(120);
LCD_WR_REG(0x29);
//设置LCD属性参数
LCD_direction(USE_HORIZONTAL);//设置LCD显示方向
LCD_BL=1;//点亮背光
}可以看到,只要处理好基本的数据传输用函数,其它处理完全一样。
显示屏处理部分的代码:LCD.zip
我要赚赏金
