在上一章中,小编实现了多功能时钟的测距功能。在这一章中,小编本来打算介绍人机交互界面的设计。但是,后来还是想了一下,先将LCD库函数建立起来,因为真正的技术重点在这里。至于UI的设计,咱们将在下一章介绍。但至于UI设计的美观程度,我只能尽力而为,毕竟咱们的LCD12864液晶分别率只有128*64,而且是单色的,先天不足。
1.模块介绍

LCD12864液晶显示屏
液晶显示器种类很多,本人目前用过LCD1602、LCD12864、Nokia5110等,还有些比如TFT彩屏,OLED都没有用过。而此块LCD12864是一款基于ST7567的128*64的1.7英寸的图形点阵式液晶。而LCD12864液晶有的可以并口传输数据,有的可以串口传输数据,而我们这块LCD12864是以串行口的方式传输数据,并且采用的是SPI协议,后面我会重点讲一下这个协议。
这里,介绍一下LCD12864库函数的编写。当然,这里很多都需要查阅数据手册的(注:数据手册真的很重要,不懂就查,这个就相当于课本,前提是你基础也要扎实一些,这样看起手册不那么费劲)。

LCD12864液晶屏引脚
首先,介绍一下液晶的主要控制引脚:BL为背光源,打开之后,液晶就变得更亮了;RESET为复位,即液晶恢复到初始状态;A0为数据或命令选择,若为1,写数据,若为0,写命令;CSB:片选信号,低电平时允许写操作;SCL为时钟线,SDA为数据线。这样的控制就是基于SPI协议进行数据的同步串行传输,在移位脉冲下,数据按位传输,高位在前,低位在后,为全双工通信,数据传输速度总体来说比I2C总线要快。
其次,要写LCD12864的驱动函数,要查看芯片的时序图,如下图。

SPI时序图
每次在传输数据前,需要将CS端拉低,设置A0的电平,指定操作是写指令还是写数据,然后SDA一次取字节数据的高位到低位进行发送,每一次发送需要一个SCL的上升沿,在发送完1字节数据后,CS端需要拉高,这样1字节的数据就发送完毕了。
这里,LCD12864液晶的指令表这里不再阐述,自己上网查询(小编直接就拿现成的指令用的)。
最后,关于LCD12864液晶的扫描方式,这里重点说一下。LCD12864从上到下分为8页,也就是第1行~第8行为第0页,往下类推,到第57行~第64行为第7页。而LCD12864从左到右分为128列,即第0列到第127列。我在写LCD的时候,需要先写页地址,然后在写列地址,最后写数据。而且这里的列地址要分两次写,先将列地址高4位与0x10进行或运算写入,再将列地址低4位写入。
关于写入字符和汉字等,都需要实现建立字模数据,这里,大家可以用'字模提取V2.2'软件,如果不会的,可以上网查阅相关的资料。至此,就可以编写相关驱动和控制代码了。
2.软件编程
(1)编写相关控制引脚的GPIO
/*lcd的GPIO配置*/
void lcd_gpio_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
}
将LCD12864控制引脚设置成推挽输出模式,开启相关的RCC时钟。这里,有一点别忘了,由于stm32的PB3,PB4的初始默认功能为JTAG的相关引脚,所以需要开启复用功能,才能作为普通的I/O口使用。
(2)编写 写操作函数:包括写数据函数和写指令函数
/*lcd写数据函数*/
void lcd_write_data(u8 data)
{
u8 i;
LCD_CS_H;
LCD_SCL_H;
LCD_A0_H;
LCD_CS_L;
for(i=0; i<8; i++)
{
LCD_SCL_L;
if(data&0x80)
{
LCD_SDA_H;
}
else
{
LCD_SDA_L;
}
LCD_SCL_H;
data <<= 1;
}
LCD_CS_H;
}
/*lcd写指令函数*/
void lcd_write_cmd(u8 cmd)
{
u8 i;
LCD_CS_H;
LCD_SCL_H;
LCD_A0_L;
LCD_CS_L;
for(i=0; i<8; i++)
{
LCD_SCL_L;
if(cmd&0x80)
{
LCD_SDA_H;
}
else
{
LCD_SDA_L;
}
LCD_SCL_H;
cmd <<= 1;
}
LCD_CS_H;
}
写数据函数和写指令函数就根据时序图进行编写,并且注意如何写字节数据的某位以及移位运算符的使用方法,这里不需要做任何的延时。
(3)编写LCD初始化函数
/*lcd复位函数*/
void HDReset(void)
{
LCD_RST_L;
lcd_delay(2);
LCD_RST_H;
lcd_delay(4);
}
/*lcd延时函数*/
void lcd_delay(u16 value)
{
u16 i,j;
for(i=0;i } /*lcd初始化函数*/ void lcd_Init(void) { lcd_gpio_init(); lcd_delay(10); HDReset(); lcd_delay(100); lcd_write_cmd(0xe2); lcd_write_cmd(0xa2); lcd_write_cmd(0xa0); lcd_write_cmd(0xc8); lcd_write_cmd(0xa4); lcd_write_cmd(0xa6); lcd_write_cmd(0x25); lcd_write_cmd(0x81); lcd_write_cmd(0x1a); lcd_write_cmd(0x2f); lcd_write_cmd(0x40); lcd_write_cmd(0xaf); LCD_BK_ON; } (4)编写清屏函数 void lcd_clearscreen(void) { u8 i,j; for(i=0; i<8; i++) { lcd_write_cmd(0xb0+i); //写页地址 for(j=0; j<128; j++) { lcd_write_cmd(0x10+((j&0xf0)>>4)); //写列地址 lcd_write_cmd(0x00+(j&0x0f)); lcd_write_data(0x00); //写数据 } } } (5)建立字模数据库 这里,我们先建立font.h头文件,然后通过字模软件取模,将数据存放在里面。这里,重点讲一下汉字如何存放。首先,我们先定义一个方便对汉字进行查找的结构体。 /*定义新的数据结构,用以方便地对汉字进行索引*/ typedef struct { u8 index[2];//定义汉字索引 u8 charmode[32];//定于汉字字模 }CHAR; 然后,定义一个存放汉字字模数据的数组。 CHAR const str[] = { /*-- 文字: 时 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ {'时',0x00,0xFC,0x84,0x84,0x84,0xFC,0x00,0x10,0x10,0x10,0x10,0x10,0xFF,0x10,0x10,0x00, 0x00,0x3F,0x10,0x10,0x10,0x3F,0x00,0x00,0x01,0x06,0x40,0x80,0x7F,0x00,0x00,0x00}, //后面省略 } 编写写字符串函数思路:首先,取字符串当前字符,并判断是不是结束字符,如果不是,通过此字符与字符串数组里的汉字索引进行对比,即查找字库。如果找到相等的,则在LCD刷新该汉字索引后的字模数据,否则不刷新。最后,取下一个字符,依次类推。 (6)编写写数字函数、写字符函数、写汉字字符串函数 /*lcd显示数字函数*/ void lcd_display_num_m(u8 page, u8 column, u8 num) { u8 i,j,column_H,column_L; for(i=0; i<2; i++) { lcd_write_cmd(0xb0+page+i); for(j=0; j<8; j++) { column_H = 0x10|((column+j)>>4)&0x0f; column_L = (column+j)&0x0f; lcd_write_cmd(column_H); lcd_write_cmd(column_L); lcd_write_data(num_m[num][j+i*8]); } } } /*lcd显示字符函数*/ void lcd_display_letter_m(u8 page, u8 column, u8 letter) { u8 i,j,column_H,column_L; for(i=0; i<2; i++) { lcd_write_cmd(0xb0+page+i); for(j=0; j<8; j++) { column_H = 0x10|((column+j)>>4)&0x0f; column_L = (column+j)&0x0f; lcd_write_cmd(column_H); lcd_write_cmd(column_L); lcd_write_data(letter_m[letter-65][j+i*8]); } } } /*lcd显示字符串函数*/ void lcd_display_string(u8 page, u8 column, u8 *string) { u8 i,j,wordnum,column_H,column_L; while(*string!='
