文章框架
前言
好吧,最终我还是决定把LCD和串口通信分开写。
首先祝各位新春快乐,鸡年大吉。上班的事业有成,上学的天天向上。过大年呢,还真没啥心情码字。
借着爆竹声咱扯会儿LCD(液晶显示器,Liquid Crystal Display)。今天要实战的这款俗称为LCD1602,尤其注意这个1602,他说明了这款显示器的显示能力:每行16个字符,共2行,乃字符显示器(仅ASCII)。
参数
显示容量|芯片工作电压|工作电流|模块最佳工作电压
:---:|:---:|:---:|:---:|:---:
16×2 Char|4.5~5.5V|2.0mA(5.0V)|5.0V
引脚
Vss | Vdd | VO | RS | R/W | E | D0~7 | BLA | BLK |
---|---|---|---|---|---|---|---|---|
接地 | 正极 | 对比度调节 | 数据(H)/命令(L)选择端 | 读(H)/写(L)选择端 | 使能(Enable)信号 | 数据口 | 背光电源正极 | 背光电源负极 |
LCD上也有一个单片机,用于控制屏幕显示。我们并不是直接操作那块屏幕,而是与那个单片机交互。
其中D0~7这8个数据口就是用于交互的,为并行传输。
指令
因为我们要和LCD内嵌的单片机交互,所以需要指令。下面所列的东西都是当RS为低电平时发送的(若为高电平,就识别为数据)。
- 数据指针
第一行 | 第二行 |
---|---|
0x80 | 0xC0 (0x80+0x40) |
- 显示相关
模式:
指令 | 功能 |
---|---|
0x38 | 设置16×2显示,5x7点阵,8位数据接口 |
方式:
0 | 0 | 0 | 0 | 1 | D | C | B |
---|---|---|---|---|---|---|---|
||||Display,1:开显示 | Cursor,1:显示光标 | Blink,1:光标闪烁 |
0 | 0 | 0 | 0 | 0 | 1 | N | S |
---|---|---|---|---|---|---|---|
|||||Next,1:读/写一个字符后,指针自动加1 | Shift,1:写字符时,相对字符静止的屏幕移动 |
清屏:
指令 | 功能 |
---|---|
0x01 | 数据指针清0且所有指针清空 |
0x02 | 仅数据指针清0 |
- 操作
指令 | 功能 |
---|---|
0x10 | 光标左移 |
0x14 | 光标右移 |
0x18 | 整体左移 |
0x1c | 整体右移 |
电平
简单说下,逻辑电路中只有高电平和低电平,也就是程序里面的1和0。但是在物理层面上,它需要一个具体的表现,然后整理成标准。
TTL就是本例中要用到的一种电平,单片机和LCD通过引脚传递电信号,从而达到1和0的传递。
TTL中的低电平(0)表现为0V,高电平(1)表现为5V
实例
界内显示
- 电路
在Proteus里,1602就是LM016L
,除了没有背光灯电源外用法一致(VEE
是对比度调节,本例不用)。
RP1
为上拉电阻,用于提高电压。由于这款单片机的P0引脚组的电压低于5V,所以需要上拉至5V,达到TTL的标准。
那个圆形的刻着Volts
字样的东西是电压表,连接两个没被上拉电压的P0引脚,具体数值看后面的演示图。
- 代码
#include <reg52.h>
#define uchar unsigned char
#define uint unsigned int
//coefficient of 1602 display(16 row 2 col. 5*7px per Char)
#define LCD_CLEAR 0x01 //宏定义:清屏
#define DISPLAY_MODE_1602 0x38 //宏定义:1602显示模式
#define DISPLAY_OFF 0x08 //宏定义:关显示
#define DISPLAY_ON_NO_CURSOR 0x0c //宏定义:开显示且无光标
#define DISPLAY_ON_WITH_CURSOR_NO_BLINK 0x0e //宏定义:开显示且有光标但不闪烁
#define DISPLAY_ON_WITH_CURSOR_BLINK 0x0f //宏定义:开显示且有光标且闪烁
#define AUTO_BACK_STEP 0x04 //宏定义:读/写时指针自动减1
#define AUTO_NEXT_STEP 0x06 //宏定义:读/写时指针自动加1
#define AUTO_DISPLAY_MOVE_LEFT 0x07 //宏定义:字符相对静止,整屏左移
#define AUTO_DISPLAY_MOVE_RIGHT 0x05 //宏定义:字符相对静止,整屏右移
#define ALL_MOVE_LEFT 0x18 //宏定义:屏幕左移
#define ALL_MOVE_RIGHT 0x1c //宏定义:屏幕右移
#define CURSOR_MOVE_LEFT 0x10 //宏定义:光标左移
#define CURSOR_MOVE_RIGHT 0x14 //宏定义:光标右移
#define FIRST_ROW 0x80 //宏定义:第一行头地址
#define SECOND_ROW FIRST_ROW+0x40 //宏定义:第二行头地址
uchar code fst[] = "Hello World!"; //第一行要显示的数据数组
uchar code sec[] = ""; //第二行要显示的数据数组
uchar num; //字符计数
sbit enable = P0^5; //使能端
sbit RS = P0^7; //数据/命令切换
sbit RW = P0^6; //读/写切换
sbit anode = P0^0; //连接电压表阳极
sbit cathode = P0^1; //连接电压表阴极
//粗制的延时器,没走一次这个函数大约为1ms,适用于11.0592MHz及附近
void delay(uint z)
{
uint x,y;
for(x=z;x>0;x--)
for(y=110;y>0;y--);
}
//写命令
void writeCmd(uchar cmd)
{
RS = 0; //切换为写命令模式
P2 = cmd;
delay(1); //注意
enable = 1; //执行!
delay(1); //注意
enable = 0; //执行完毕!
}
void writeDat(uchar dat){
RS = 1; //切换为数据模式
P2 = dat;
delay(1); //注意
enable = 1;
delay(1); //注意
enable = 0;
}
//初始化函数
void init(){
anode = 1;
cathode = 0;
RW = 0; //写模式,本例只往LCD写数据
enable = 0;
writeCmd(DISPLAY_MODE_1602); //发送命令:1602模式
writeCmd(DISPLAY_ON_WITH_CURSOR_BLINK); //发送命令:开始显示并闪烁光标
writeCmd(AUTO_NEXT_STEP); //发送命令:数据指针自动加1
writeCmd(LCD_CLEAR); //发送命令:清屏
}
void main(){
init();
writeCmd(FIRST_ROW); //发送命令:开始从第一行写入
for(num=0;num<=12;num++){
writeDat(fst[num]); //发送数据,每次一字节
}
while(1);
}
好,说一下上面代码中标//注意
的地方,全都是delay(1)
。
为了什么呢,不是蛋疼,是因为单片机给LCD传送信号时,数据是要放在数据线上的,要是LCD还没读完单片机给它发的啥就把内容撤走的话,就会造成数据丢失。
就是这个道理,为了传输稳定,所以延时一小会儿。这个延时的数值需要大家自己去试,并不一定所有的情况都延时大约1ms就够的。
- 效果
越界显示
本例用于显示字符数超过16个的情况。
代码改动
uchar code fst[] = "1234567890ABCDEF"; //第一行要显示的数据数组
uchar code sec[] = "1234567890ABCDEFGHIJK"; //第二行要显示的数据数组
- 初始化函数
void init(){
anode = 1;
cathode = 0;
RW = 0;
enable = 0;
writeCmd(DISPLAY_MODE_1602);
writeCmd(DISPLAY_ON_NO_CURSOR); //换成不闪的,虽然跟这个新需求没什么联系,就是给你演示下效果
writeCmd(AUTO_NEXT_STEP);
writeCmd(LCD_CLEAR);
}
- 主函数
void main(){
init();
writeCmd(FIRST_ROW); //发送命令:开始从第一行写入
for(num=0;num<=16;num++){
writeDat(fst[num]);
}
writeCmd(SECOND_ROW); //发送命令:开始从第二行写入
for(num=0;num<=20;num++){
writeDat(sec[num]);
}
while(1){
//向左移动三次,每次间隔500ms
for(num=0;num<=3;num++) {
writeCmd(ALL_MOVE_LEFT); //发送命令:整屏左移
delay(500);
}
delay(1000); //暂停大约1s
//向右移动三次,每次间隔500ms
for(num=0;num<=3;num++) {
writeCmd(ALL_MOVE_RIGHT); //发送命令:整屏右移
delay(500);
}
delay(3000); //暂停大约3s后开始下一轮
}
}
效果
移屏只移3个字符距离,所以并没有把第二行的
K
显示出来。
结语
这次单讲LCD的入门应用,送给不爱看春晚的你。前两天搞定了科二考试,年后准备科三了。《扯单》系列的一周目大概还差两三篇就完结了,下集预告:串口应用。好了,看完文章实践实践后就该打麻将打麻将,该放炮仗放炮仗吧!总之大家吃好玩好。