LCD的驱动为ST7789V,采用SPI协议单工进行通讯。正常SPI通讯拥有四根线片选先线、时钟线、输入线、输出线四根线配合进行输出,而LCD屏只需要写入数据显示即可,因此该显示屏引脚只有SPI通讯的时钟线和输入线。
一、硬件介绍
1.1 1.3寸SPI通讯TFT LCD显示屏
拥有GND、VCC、SCL、SDA、RES、DC、BLK四个引脚,分别为负极、正极(3.3V)、SCL(时钟信号线)、SDA(数据线)、RES(复位线)、DC(寄存器\数据写入选择线)、BLK(背光开启)。
驱动为ST7789V,最多能够驱动240*320分辨率的显示屏,而实际显示屏的分辨率只有240*240的分辨率。
1.2 单片机为STM32F103C6T6
单片机与LCD屏引脚的连接分别为:
BLK -> PB5
DC -> PB6
RES -> PB7
SDA -> PB8
SCL -> PB9
引脚设置均为推挽输出、上拉电阻;PB8与PB9的初始电压设置为高,其余三个引脚初始电压设置为低。
二、程序编写
2.1 GPIO模拟SPI输出
该模块时钟线和数据线初始状态均为高电平,时钟线由低电平变化为高电平的时刻,对信号线进行采样,传输时,高位在先,低位在后。
void LCD_Transport(char data)
{
uint8_t i;
// pull down the clock time
for(i = 0; i < 8; i++)
{
LCD_CLK_Low();
if(data&0x80) // OUTPUT Volt
LCD_SDA_High();
else
LCD_SDA_Low();
LCD_CLK_High(); //RESET
data<<=1;
}
}
2.2 写数据和写命令
写数据分成两个函数,一个是8位的发送函数,用来传输写入寄存器的指令,或者8位的数据;另一是16位的发送函数,一般用来传输颜色,颜色为16位;写命令之前,必须先发送寄存器的地址,当DC引脚为高电平时,LCD屏只接收数据;当DC引脚为低电平时,LCD屏只接收寄存器地址。
void LCD_Write_Data8(char data)
{
LCD_DC_High();
LCD_Transport(data);
}
void LCD_Write_Data(int data)
{
// pull down CS,choose this machine
// DC -> high transport data
LCD_DC_High();
LCD_Transport(data>>8);
LCD_Transport(data);
// SPI hasn't reply mechanism
}
void LCD_Write_REG(char REG)
{
// DC -> REG choose REG
LCD_DC_Low();
LCD_Transport(REG);
}
2.3初始化函数
初始化函数为重启、开启背光、配置寄存器等一些列操作。其中该型号显示屏能够配置12位、16位、18位的RGB等,显示屏分辨率为240*240,也就是240*240个像素点,而每个像素点都可以由12个、16个、18个小灯组成,每个像素点通过不同的RGB搭配方式可以显示出不同的颜色。一般24位以上的RGB显示才能称为真彩显示,因此该显示屏的显示效果比不上现在的手机屏的显示效果。
void LCD_Init()
{
// reset at first
LCD_RST_Low();
Delay_ms(20);
LCD_RST_High();
Delay_ms(20);
LCD_BLK_High(); // open background
// set scan mode in screen memory and data form
LCD_Write_REG(0x36);
LCD_Write_Data8(0x00);
// set RGB form color deep 16 bit R:5 G:6 B:5; RGB: 444 RGB: 666
LCD_Write_REG(0x3A);
LCD_Write_Data8(0x05);
// porch set
LCD_Write_REG(0xB2);
LCD_Write_Data8(0x0C);
LCD_Write_Data8(0x0C);
LCD_Write_Data8(0x00);
LCD_Write_Data8(0x33);
LCD_Write_Data8(0x33);
// Gate volt control default setting VGL=-8.87 VGH=14.06
LCD_Write_REG(0xB7);
LCD_Write_Data8(0x35);
// Vcom volt 0.9v default setting
LCD_Write_REG(0xBB);
LCD_Write_Data8(0x19);
// LCM control
LCD_Write_REG(0xC0);
LCD_Write_Data8(0x2C);
// VDV and VRH Command Enable
LCD_Write_REG(0xC2);
LCD_Write_Data8(0x01);
// VRH Set
LCD_Write_REG(0xC3);
LCD_Write_Data8(0x12);
// VDV Set
LCD_Write_REG(0xC4);
LCD_Write_Data8(0x20);
//Frame Rate Control in Normal Mode 60 Hz 00: 119Hz
LCD_Write_REG(0xC6);
LCD_Write_Data8(0x0F);
// Power Control 1 default value
LCD_Write_REG(0xD0);
LCD_Write_Data8(0xA4);
LCD_Write_Data8(0xA1);
//Positive Voltage Gamma Control
LCD_Write_REG(0xE0);
LCD_Write_Data8(0xD0);
LCD_Write_Data8(0x04);
LCD_Write_Data8(0x0D);
LCD_Write_Data8(0x11);
LCD_Write_Data8(0x13);
LCD_Write_Data8(0x2B);
LCD_Write_Data8(0x3F);
LCD_Write_Data8(0x54);
LCD_Write_Data8(0x4C);
LCD_Write_Data8(0x18);
LCD_Write_Data8(0x0D);
LCD_Write_Data8(0x0B);
LCD_Write_Data8(0x1F);
LCD_Write_Data8(0x23);
//Negative Voltage Gamma Control
LCD_Write_REG(0xE1);
LCD_Write_Data8(0xD0);
LCD_Write_Data8(0x04);
LCD_Write_Data8(0x0C);
LCD_Write_Data8(0x11);
LCD_Write_Data8(0x13);
LCD_Write_Data8(0x2C);
LCD_Write_Data8(0x3F);
LCD_Write_Data8(0x44);
LCD_Write_Data8(0x51);
LCD_Write_Data8(0x2F);
LCD_Write_Data8(0x1F);
LCD_Write_Data8(0x1F);
LCD_Write_Data8(0x20);
LCD_Write_Data8(0x23);
// Display Inversion On
LCD_Write_REG(0x21);
// turn off Sleep mode
LCD_Write_REG(0x11);
Delay_ms(20);
// Display On
LCD_Write_REG(0x29);
}
2.4 设置操作区域函数
LCD屏的显示操作就是先指定需要填充颜色的像素区域或像素点,分别在0x0a、0x2b、0x2c中操作即可。
void address_set(uint16_t x1,uint16_t y1,uint16_t x2, uint16_t y2)
{
if(x2>wide-1|| y2>length-1)return;
else
{
// choose x axle register
LCD_Write_REG(0x2A);
// write 16 bit
LCD_Write_Data8(x1>>8);
LCD_Write_Data8(x1);
LCD_Write_Data8(x2>>8);
LCD_Write_Data8(x2);
// choose y axle register
LCD_Write_REG(0x2B);
LCD_Write_Data8(y1>>8);
LCD_Write_Data8(y1);
LCD_Write_Data8(y2>>8);
LCD_Write_Data8(y2);
// reset to start colum / page position
LCD_Write_REG(0x2C);
}
}
2.5 像素点填充
以清屏函数为例,先指定全屏区域作为填充区域,后在每个像素点中逐个填入颜色。
void LCD_SPI_Clear(uint16_t color)
{
int i,j;
address_set(0,0, wide - 1, length - 1); // choose total screen
for(i = 0; i < wide; i++)
{
for(j = 0; j < length; j++)
{
LCD_Write_Data(color);
}
}
2.4 字符和数字的显示
每个字符占用的像素点为8*16个,因此需要配置8*16个像素点中的每个像素的颜色来显示字符串。字符在文件的存储形式为16个字节的数据,每个字节的8位代表8个像素点的像素值,0代表背景色,1代表显示颜色,这就比较简单,也就是通过二维数组来存储字符串,若要存储图片,那么就还需要存储每个像素点具体像素值,这就需要三维的数组,而数字显示则是将实际的数字进行加减乘除运算得到对应的字符串并显示。
void Draw_one_charater(uint8_t x, uint8_t y,char a, uint16_t color)
{
uint8_t temp;
// the show table only have value after space
a = a - ' ';
address_set(x,y,x + 7,y + 15);
for(uint8_t i = 0; i < 16; i++)
{
temp = asc2_1608[a*16 + i];
for(uint8_t j = 0; j < 8; j++) // write horizontal at first
{
if(temp&0x01)
{
LCD_Write_Data(color);
}
else
{
LCD_Write_Data(BLACK);
}
temp>>=1;
}
}
}
void Show_String(uint8_t x, uint8_t y, char *string,uint8_t String_long)
{
// character occupy 8*16 position 0 or 1 reprensent pixel show black or wihte
if(y+16>length)
{
return;
}
else
{
if(x + 8 * String_long<= wide) // normal Display
{
for(uint8_t i; i < String_long;i++)
{
Draw_one_charater(x + i *8, y,string[i], WHITE);
}
}
else
{
return;
}
}
}
void Show_number(uint8_t x, uint8_t y, uint32_t number,uint8_t number_bit_in_ten)
{
uint32_t temp;
char Display_Number;
// The MAX Display 10 bits in LCD uint32_t is 10 bit
uint8_t memory_nuber[10];
// ASCII: 0 -> 48 9 -> 57
temp = number;
if(x + number_bit_in_ten * 8 > wide)return;
else
{
for(uint8_t i = 0; i < number_bit_in_ten; i++)
{
memory_nuber[i] = temp % 10;
temp = temp / 10;
}
for(uint8_t i = 0; i < number_bit_in_ten; i++)
{
Display_Number = memory_nuber[number_bit_in_ten - i - 1] + 48;
Draw_one_charater(x + i * 8, y,Display_Number, YELLOW);
}
}
}
三、主函数和显示效果
主函数只需要添加初始化和清屏函数即可。
LCD_Init();
LCD_SPI_Clear(BLACK);
LCD_Draw_Horizontal_Line(16,8,220);
LCD_Draw_Horizontal_Line(16,16,220);
LCD_Draw_Horizontal_Line(16,24,220);
LCD_Draw_Horizontal_Line(16,32,220);
LCD_Draw_Vetical_Line(16,8,32);
LCD_Draw_Vetical_Line(116,8,32);
LCD_Draw_Vetical_Line(220,8,32);
Show_String(72, 48, "char:",5);
Show_number(112,48,sizeof(char),1);
Show_String(72, 64, "short int:",10);
Show_number(152,64,sizeof(short int),1);
Show_String(72, 80, "int:",4);
Show_number(104,80,sizeof(int),1);
显示效果:
之前一直错误的以为int在电脑中占用4字节,在单片机中只占用1个字节,现在事实证明,和电脑中的一样;char-> 1bit short int -> 2bit int -> 4bit