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