人生绕不开一台LCD1602,虽然它比较丑,接的线也比较多。#非标协议【上】

前言

  本篇博文介绍的是用51单片机的非标准写协议【上】(LCD1602),包含初识LCD1602,LCD在一个位置显示一个字母的逻辑,LCD1602写时序分析,编程实现LCD1602显示字符C,编程实现LCD1602显示一行。看到这篇博文的朋友,可以先赞再看吗?

预备知识

  一、基本电路标识识别和接线,例如VCC,GND。
  二、数电时序图的阅读,高低电平的识别。
  三、C变量
  四、基本输入输出
  五、流程控制
  六、函数

  七、指针
  八,字符串

  如果以上知识不清楚,请自行学习后再来浏览。如果我有没例出的,请在评论区写一下。谢谢啦!

1.初识LCD1602

1.1概述

  LCD1602(Liquid Crystal Display)是一种工业字符型液晶,能够同时显示 16×02 即 32 字符(16列两行)

在这里插入图片描述

1.2引脚说明

编号符号引脚说明编号符号引脚说明
1VSS电源地9D2数据
2VDD电源正极10D3数据
3VL液晶显示偏压11D4数据
4RS数据/命令选择12D5数据
5R/W读/写选择13D6数据
6E使能信号14D7数据
7D0数据15BLA背光源正极
8D1数据16BLK背光源负极

  第 1 脚: VSS 为电源地
  第 2 脚: VDD 接 5V 正电源
  第 3 脚: VL 为液晶显示器对比度调整端,接正电源时对比度最弱,接地时对比度最高,对比度过高时会产生“鬼影”, 使用时可以通过一个 10K 的电位器调整对比度。(注:电位器可用50K的,接线方式为左右两只引脚接VCC,GND,中间引脚接VL
  第 4 脚: RS 为寄存器选择,高电平时选择数据寄存器、 低电平时选择指令寄存器。
  第 5 脚: R/W 为读写信号线,高电平时进行读操作,低电平时进行写操作。当 RS 和 R/W 共同为低电平时可以写入指令或者显示地址,当 RS 为低电平 R/W 为高电平时可以读忙信号,当 RS 为高电平 R/W 为低电平时可以写入数据。
  第 6 脚: E 端为使能端,当 E 端由高电平跳变成低电平时,液晶模块执行命令。
  第 7~14 脚: D0~D7 为 8 位双向数据线。
  **第 15 脚:**背光源正极。
  **第 16 脚:**背光源负极。

1.3和单片机接线说明

  • 电源

  VSS -- GND
  VDD -- 5V

  • 对比度

  VL -- GND

  • 控制线

  RS -- P1.0
  RW -- P1.1
  E -- P1.4

  • 背光灯

  A -- 5V
  K -- GND

  • 数据

  D0到D7 -- P0.1到P0.7

1.4LCD1602 的指令说明

  LCD1602 液晶模块内部的控制器共有 11 条控制指令,如表 2 所示:

序号指令RSR/WD7D6D5D4D3D2D1D0
1清显示0000000001
2光标返回000000001*
3置输入模式00000001I/DS
4显示开/关控制0000001DCB
5光标或字符移位000001S/CR/L**
6置功能00001DLNF**
7置字符发生存贮器地址0001字符发生存贮器地址(自定义字符)
8置数据存贮器地址001显示数据存贮器地址(在哪里显示)
9读忙标志或地址01BF计算器地址
10写数到 CGRAM 或 DDRAM10要写的数据内容(显示什么)
11从 CGRAM 或 DDRAM 读数11读出的数据内容

  1602 液晶模块的读写操作,屏幕和光标的操作都是通过指令编程来实现的。(说明: 1 为高电平、 0 为低电平)

  **指令 1:**清显示,指令码 01H,光标复位到地址 00H 位置。
  **指令 2:**光标复位,光标返回到地址 00H。
  **指令 3:**光标和显示模式设置。 I/D:光标移动方向,高电平右移,低电平左移。 实际上就是控制从左到右写入还是从右至左的写入顺序。 S:屏幕上所有文字是否左移或者右移。高电平表示有效,低电平则无效。 S=1 当写一个字符,整屏显示左移(ID=1)或者右移(I/D=0),以得到光标不移动而屏幕移动的效果。 S=0 当写一个字符,整屏显示不移动。
  **指令 4:**显示开关控制。 D:控制整体显示的开与关,高电平表示开显示,低电平表示关显示. C:控制光标的开与关,高电平表示有光标,低电平表示无光标. B:控制光标是否闪烁, 高电平闪烁,低电平不闪烁。
  **指令 5:**光标或显示移位 S/C:高电平时移动显示的文字,低电平时移动光标。 R/L:文字或者光标移动方向, R 表示右移, L 表示左移。
  **指令 6:**功能设置命令 DL:高电平时为 8 位总线,低电平时为 4 位总线。 N:低电平时为单行显示,高电平时双行显示。 F:低电平时显示 5×8 的点阵字符,高电平时显示 5×10的点阵字符。
  **指令 7:**字符发生器 RAM 地址设置。
  指令 8: DDRAM 地址设置。
  **指令 9:**读忙信号和光标地址。 BF:为忙标志位,高电平表示忙,此时模块不能接收命令或者数据,如果为低电平表示不忙。
  **指令 10:**写数据。
  **指令 11:**读数据。

2.LCD在一个位置显示一个字母的逻辑

2.1在哪里显示

在这里插入图片描述

  例如第二行第一个字符的地址是 40H,那么是否直接写入 40H 就可以将光标定位在第二行第一个字符的位置呢?这样不行,因为写入显示地址时要求最高位 D7 恒定为高电平 1 所以实际写入的数据应该是

  **01000000B(40H)+10000000B(80H)=11000000B(C0H)。**

2.2显示的是什么

  1602 液晶模块内部的字符发生存储器(CGROM)已经存储了 160 个不同的点阵字符图形, 如表 3 所示,这些字符有:阿拉伯数字英文字母的大小写常用的符号,和日文假名等,每一个字符都有一个固定的代码,比如大写的英文字符“A”的代码是 01000001B(41H), 显示时模块把地址 41H 中的点阵字符图形显示出来,我们就能看到字母“A”

在这里插入图片描述

  我们可以发现上表中字符“a”的高位为0110,低位为0001,高位低位组合一下,便知道其16进制数为61十进制数为97。刚好是字符“a”ASSII码。所以1602字符码是ASSII码,那么显示一个字符就可以在代码中直接定义一个字符。

2.4如何解决D0到D7既要传输字符位置信息又要传输字符数据信息

  可以通过第 4 脚: RS 寄存器来解决,当高电平时选择数据寄存器、 低电平时选择指令寄存器。也就是说RS=1,就是传输字符数据,RS=0时就是传送字符位置信息。

3.LCD1602写时序分析

3.1写时序图与写时序参数

  • 写时序图

在这里插入图片描述

  • 时序参数

在这里插入图片描述

3.2写时序图分析

在这里插入图片描述

4.编程实现LCD1602显示字符C

4.1编程实现LCD1602显示字符C的核心思路

  • 配置LCD1602数据引脚D0D7
  • 配置LCD1602控制引脚RSRWEN
  • 根据写时序图配置LCD1602指令和写数据函数
  • 根据读时序图配置LCD1602检测信号函数
  • 根据LCD1602手册一般初始化(复位)过程建立LCD609初始化函数
  • 主函数整合

4.2配置LCD1602数据引脚D0到D7

  • 使用51单片机P0口来传输LCD1602的字符地址位置

  • 代码如下

//数据线定义
//D0到D7 -- P0.1到P0.7
#define dataBuffer P0

4.3配置LCD1602控制引脚RS、RW、EN

  • 使用51单片机的P1.0口接RSP1.1口接RWP1.4口接EN
  • 代码如下
//控制线定义
/*
RS--P1.0
RW--P1.1
EN--P1.4
*/
sbit RS = P1^0;
sbit RW = P1^1;
sbit EN = P1^4;

4.4根据写时序图配置LCD1602写指令和写数据函数

  • 写指令和写数据函数的时序都是一样的,只有RS的值不一样,RS=0写指令RS=1是写数据
  • 时序图代码注解

在这里插入图片描述

  • 写指令函数
void writeCmd(char cmd)
{       
	checkBusy();       //检测忙信号
	RS = 0;            //RS等于零,是写指令操作
	RW = 0;            //RW在手册中写的时序图是可以全程低电平
	EN = 0;            //根据手册中的写时序图,EN开始为低电平
	_nop_();           //执行空函数等待EN拉高
	dataBuffer = cmd;  //根据手册中的写时序图,数据在EN还在低电平时就建立数据
	EN = 1;            //根据手册中的写时序图,EN被拉高
	_nop_();           //执行空函数等待tpw和tF高电平过程
	_nop_();
	EN = 0;            //根据手册中的写时序图,EN被拉低
	_nop_();           //根据手册中的写时序图,让EN保持一段时间的低电平(这里一定不要忘了延时)
}
  • 写数据函数
void weiteData(char cData)
{
	checkBusy();       //检测忙信号
	RS = 1;            //RS等于1,是写数据操作
	RW = 0;            //RW在手册中写的时序图是可以全程低电平
	EN = 0;            //根据手册中的写时序图,EN开始为低电平
	_nop_();           //执行空函数等待EN拉高
	dataBuffer = cData;  //根据手册中的写时序图,数据在EN还在低电平时就建立数据
	EN = 1;            //根据手册中的写时序图,EN被拉高
	_nop_();           //执行空函数等待tpw和tF高电平过程
	_nop_();
	EN = 0;            //根据手册中的写时序图,EN被拉低
	_nop_();           //根据手册中的写时序图,让EN保持一段时间的低电平
}

4.5根据读时序图配置LCD1602检测忙信号函数

  • 检测忙函数可以根据LCD1602手册中指令说明指令9进行配置请看1.4,此时会发现RW=1,那么就会用到读时序图
  • 因为要检测LCD1602数据位第8位的值是否为1来确定是否,所以会用到while循环来一直检测。也会用到十六进制的0x80来判断,因为0x80==(1000 0000)2。
  • 时序图代码解释

在这里插入图片描述

  • LCD1602检测忙信号函数
void checkBusy()
{
  char tmp   = 0x80;
	dataBuffer = 0x80;
	while(tmp & 0x80)    //只要dataBuffer中的第8位为1就为忙,因为BF:为忙标志位,高电平表示忙,此时模块不能接收命令或者数据,如果为低电平表示不忙。
	{
		RS = 0;            //根据手册中指令表可知RS=0,RW=1
		RW = 1;            //RW=1为读的操作,所以需要配置读的时序图
		EN = 0;            //根据手册中的写时序图,EN开始为低电平
		_nop_();           //执行空函数等待EN拉高
		EN = 1;            //根据手册中的读时序图,EN被拉高
		_nop_();           //执行空函数等待tpw和tF高电平过程
		_nop_();
		tmp = dataBuffer;  //读取dataBuffer中的忙信号
		EN = 0;            //根据手册中的写时序图,EN被拉低
		_nop_();           //根据手册中的写时序图,让EN保持一段时间的低电平
	}
}

4.6根据LCD1602手册一般初始化(复位)过程建立LCD609初始化函数

  • 手册相关内容

(1) 延时 15ms
(2) 写指令 38H(不检测忙信号)
(3) 延时 5ms
(4) 以后每次写指令,读/写数据操作均需要检测忙信号
(5) 写指令 38H:显示模式设置
(6) 写指令 08H:显示关闭
(7) 写指令 01H:显示清屏
(8) 写指令 06H:显示光标移动设置
(9) 写指令 0CH:显示开及光标设置

  • LCD609初始化函数
void initLCD1602()
{
//(1) 延时 15ms
	Delay15ms();
//(2) 写指令 38H(不检测忙信号)
	writeCmd(0x38);
//(3) 延时 5ms
	Delay5ms();
//(4) 以后每次写指令,读/写数据操作均需要检测忙信号
//(5) 写指令 38H:显示模式设置
	writeCmd(0x38);
//(6) 写指令 08H:显示关闭
	writeCmd(0x08);
//(7) 写指令 01H:显示清屏
	writeCmd(0x01);
//(8) 写指令 06H:显示光标移动设置
	writeCmd(0x06);
//(9) 写指令 0CH:显示开及光标设置
	writeCmd(0x0C);
}

4.7主函数整合

  • 主函数代码
void main()
{
	char address = 0x80 + 0x05;  //字符显示位置
	char cData   = 'C';          //显示的字符数据
	
	initLCD1602();               //初始化LCD1602
	writeCmd(address);           //写字符位置
	weiteData(cData);            //写字符数据
	
}

4.8完整程序代码

#include "reg52.h"
#include "intrins.h"

//数据线定义
//D0到D7 -- P0.1到P0.7
#define dataBuffer P0

//控制线定义
/*
RS--P1.0
RW--P1.1
EN--P1.4
*/
sbit RS = P1^0;
sbit RW = P1^1;
sbit EN = P1^4;

void Delay15ms()		//@11.0592MHz
{
	unsigned char i, j;

	i = 27;
	j = 226;
	do
	{
		while (--j);
	} while (--i);
}

void Delay5ms()		//@11.0592MHz
{
	unsigned char i, j;

	i = 9;
	j = 244;
	do
	{
		while (--j);
	} while (--i);
}

void checkBusy()
{
  char tmp   = 0x80;
	dataBuffer = 0x80;
	while(tmp & 0x80)    //只要dataBuffer中的第8位为1就为忙,因为BF:为忙标志位,高电平表示忙,此时模块不能接收命令或者数据,如果为低电平表示不忙。
	{
		RS = 0;            //根据手册中指令表可知RS=0,RW=1
		RW = 1;            //RW=1为读的操作,所以需要配置读的时序图
		EN = 0;            //根据手册中的写时序图,EN开始为低电平
		_nop_();           //执行空函数等待EN拉高
		EN = 1;            //根据手册中的读时序图,EN被拉高
		_nop_();           //执行空函数等待tpw和tF高电平过程
		_nop_();
		tmp = dataBuffer;  //读取dataBuffer中的忙信号
		EN = 0;            //根据手册中的写时序图,EN被拉低
		_nop_();           //根据手册中的写时序图,让EN保持一段时间的低电平
	}
}

void writeCmd(char cmd)
{       
	checkBusy();       //检测忙信号
	RS = 0;            //RS等于零,是写指令操作
	RW = 0;            //RW在手册中写的时序图是可以全程低电平
	EN = 0;            //根据手册中的写时序图,EN开始为低电平
	_nop_();           //执行空函数等待EN拉高
	dataBuffer = cmd;  //根据手册中的写时序图,数据在EN还在低电平时就建立数据
	EN = 1;            //根据手册中的写时序图,EN被拉高
	_nop_();           //执行空函数等待tpw和tF高电平过程
	_nop_();
	EN = 0;            //根据手册中的写时序图,EN被拉低
	_nop_();           //根据手册中的写时序图,让EN保持一段时间的低电平
}

void weiteData(char cData)
{
	checkBusy();       //检测忙信号
	RS = 1;            //RS等于1,是写数据操作
	RW = 0;            //RW在手册中写的时序图是可以全程低电平
	EN = 0;            //根据手册中的写时序图,EN开始为低电平
	_nop_();           //执行空函数等待EN拉高
	dataBuffer = cData;  //根据手册中的写时序图,数据在EN还在低电平时就建立数据
	EN = 1;            //根据手册中的写时序图,EN被拉高
	_nop_();           //执行空函数等待tpw和tF高电平过程
	_nop_();
	EN = 0;            //根据手册中的写时序图,EN被拉低
	_nop_();           //根据手册中的写时序图,让EN保持一段时间的低电平
}

void initLCD1602()
{
//(1) 延时 15ms
	Delay15ms();
//(2) 写指令 38H(不检测忙信号)
	writeCmd(0x38);
//(3) 延时 5ms
	Delay5ms();
//(4) 以后每次写指令,读/写数据操作均需要检测忙信号
//(5) 写指令 38H:显示模式设置
	writeCmd(0x38);
//(6) 写指令 08H:显示关闭
	writeCmd(0x08);
//(7) 写指令 01H:显示清屏
	writeCmd(0x01);
//(8) 写指令 06H:显示光标移动设置
	writeCmd(0x06);
//(9) 写指令 0CH:显示开及光标设置
	writeCmd(0x0C);
}

void main()
{
	char address = 0x80 + 0x05;  //字符显示位置
	char cData   = 'C';          //显示的字符数据
	
	initLCD1602();               //初始化LCD1602
	writeCmd(address);           //写字符位置
	weiteData(cData);            //写字符数据
	
}

5.编程实现LCD1602显示一行

5.1编程实现LCD1602显示一行核心思路

  • 定义一个空类型名为LCD1602ShowARow的函数,形参为字符型的行字符型的列字符型的指针变量str
  • 运用C语言中的选择语句switch来选择哪行显示。
  • 整体算法见代码

5.2定义一个空类型名为LCD1602ShowARow的函数,形参为字符型的行,字符型的列,字符型的指针变量str。

void LCD1602ShowARow(char line,char column,char *str)

5.3运用C语言中的选择语句switch来选择哪行显示。

switch(line)
{
    case 1:
        break;
    case 2:
        break;
}

5.4整体算法见代码

void LCD1602ShowARow(char line,char column,char *str)
{
	switch(line) //选择哪行显示
	{
		case 1:                                        //第一行显示
				writeCmd(0x80 + column);                   //选择哪个位置显示,因为位置地址的最高位必须为1,所以0x80+列数
				while(*str)                                //判断字符串指针变量不为空执行循环,不用判断字符串结束标志
				{
					weiteData(*str);                         //发送字符串单个字符
					str++;                                   //字符串指针变量偏移
				}
				break;
		case 2:                                        //第二行显示
				writeCmd(0x80 + 0x40 + column);            //选择哪个位置显示,因为位置地址的最高位必须为1,所以0x80+列数,又因为第二行的列从0x40开始
				while(*str)                                //所以是从0x80 + 0x40 + 列数(特别注意)
				{                     
					weiteData(*str);
					str++;
				}
				break;
	}
}

5.5完整程序代码

#include "reg52.h"
#include "intrins.h"

//数据线定义
//D0到D7 -- P0.1到P0.7
#define dataBuffer P0

//控制线定义
/*
RS--P1.0
RW--P1.1
EN--P1.4
*/
sbit RS = P1^0;
sbit RW = P1^1;
sbit EN = P1^4;

void Delay15ms()		//@11.0592MHz
{
	unsigned char i, j;

	i = 27;
	j = 226;
	do
	{
		while (--j);
	} while (--i);
}

void Delay5ms()		//@11.0592MHz
{
	unsigned char i, j;

	i = 9;
	j = 244;
	do
	{
		while (--j);
	} while (--i);
}

void checkBusy()
{
  char tmp   = 0x80;
	dataBuffer = 0x80;
	while(tmp & 0x80)    //只要dataBuffer中的第8位为1就为忙,因为BF:为忙标志位,高电平表示忙,此时模块不能接收命令或者数据,如果为低电平表示不忙。
	{
		RS = 0;            //根据手册中指令表可知RS=0,RW=1
		RW = 1;            //RW=1为读的操作,所以需要配置读的时序图
		EN = 0;            //根据手册中的写时序图,EN开始为低电平
		_nop_();           //执行空函数等待EN拉高
		EN = 1;            //根据手册中的读时序图,EN被拉高
		_nop_();           //执行空函数等待tpw和tF高电平过程
		_nop_();
		tmp = dataBuffer;  //读取dataBuffer中的忙信号
		EN = 0;            //根据手册中的写时序图,EN被拉低
		_nop_();           //根据手册中的写时序图,让EN保持一段时间的低电平
	}
}

void writeCmd(char cmd)
{       
	checkBusy();       //检测忙信号
	RS = 0;            //RS等于零,是写指令操作
	RW = 0;            //RW在手册中写的时序图是可以全程低电平
	EN = 0;            //根据手册中的写时序图,EN开始为低电平
	_nop_();           //执行空函数等待EN拉高
	dataBuffer = cmd;  //根据手册中的写时序图,数据在EN还在低电平时就建立数据
	EN = 1;            //根据手册中的写时序图,EN被拉高
	_nop_();           //执行空函数等待tpw和tF高电平过程
	_nop_();
	EN = 0;            //根据手册中的写时序图,EN被拉低
	_nop_();           //根据手册中的写时序图,让EN保持一段时间的低电平
}

void weiteData(char cData)
{
	checkBusy();       //检测忙信号
	RS = 1;            //RS等于1,是写数据操作
	RW = 0;            //RW在手册中写的时序图是可以全程低电平
	EN = 0;            //根据手册中的写时序图,EN开始为低电平
	_nop_();           //执行空函数等待EN拉高
	dataBuffer = cData;  //根据手册中的写时序图,数据在EN还在低电平时就建立数据
	EN = 1;            //根据手册中的写时序图,EN被拉高
	_nop_();           //执行空函数等待tpw和tF高电平过程
	_nop_();
	EN = 0;            //根据手册中的写时序图,EN被拉低
	_nop_();           //根据手册中的写时序图,让EN保持一段时间的低电平
}

void initLCD1602()
{
//(1) 延时 15ms
	Delay15ms();
//(2) 写指令 38H(不检测忙信号)
	writeCmd(0x38);
//(3) 延时 5ms
	Delay5ms();
//(4) 以后每次写指令,读/写数据操作均需要检测忙信号
//(5) 写指令 38H:显示模式设置
	writeCmd(0x38);
//(6) 写指令 08H:显示关闭
	writeCmd(0x08);
//(7) 写指令 01H:显示清屏
	writeCmd(0x01);
//(8) 写指令 06H:显示光标移动设置
	writeCmd(0x06);
//(9) 写指令 0CH:显示开及光标设置
	writeCmd(0x0C);
}

void LCD1602ShowARow(char line,char column,char *str)
{
	switch(line) //选择哪行显示
	{
		case 1:                                        //第一行显示
				writeCmd(0x80 + column);                   //选择哪个位置显示,因为位置地址的最高位必须为1,所以0x80+列数
				while(*str)                                //判断字符串指针变量不为空执行循环,不用判断字符串结束标志
				{
					weiteData(*str);                         //发送字符串单个字符
					str++;                                   //字符串指针变量偏移
				}
				break;
		case 2:                                        //第二行显示
				writeCmd(0x80 + 0x40 + column);            //选择哪个位置显示,因为位置地址的最高位必须为1,所以0x80+列数,又因为第二行的列从0x40开始
				while(*str)                                //所以是从0x80 + 0x40 + 列数(特别注意)
				{                     
					weiteData(*str);
					str++;
				}
				break;
	}
}

void main()
{

	initLCD1602();               //初始化LCD1602
	LCD1602ShowARow(1,6,"MaGe"); 
	LCD1602ShowARow(2,2,"You Handsome");
	
}

5.6 LCD1602接收字符串时不必担心光标偏移问题,LCD1602会自动偏移。

结束语

  很高兴您能看到这里,点个赞再走呗。谢谢您啦!!!

  • 25
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值