环境

软件

  • uVision V4.02
  • ISIS Professional 7.8

芯片

  • AT89C51
  • LM016L(LCD)

仿真图

实现效果

实现功能有四个

  • 水平滚动显示字符串
  • 带光标显示随机算术式
  • 全码表字符显示
  • CGRAM自定义字符显示

相关代码及资源

https://github.com/duganlx/DSP

操作小记

LCD1602相关函数编写

指令表

指令说明

上述表中,1为高电平0为低电平

  • 指令1:清显示:,光标复位到地址00H位置
  • 指令2:光标返回:,光标返回到地址00H
  • 指令3:光标和显示模式设置
    • I/D:光标移动方向,1:右移,0:左移
    • S:屏幕上所有文字是否左移动或右移:1:有效,0:无效
  • 指令4:显示开关控制
    • D:控制整体显示的开关,1:开显示,0:关显示
    • C:控制光标的开关,1:有光标,0:无光标
    • B:控制光标是否闪烁,1:闪烁,0:不闪烁
  • 指令5:光标或显示移位S/C:1:移动显示的文字,0:移动光标
  • 指令6:功能设置命令
    • DL:1:4位总线,0:8位总线(有些模块反过来,比如这里所用的就是)
    • N:0:单行显示,1:双行显示
    • F:0: 5 7 5*7 57的点阵字符,1: 5 10 5*10 510的点阵字符
  • 指令7:字符发生器RAM地址设置
  • 指令8:DDRAM地址设置
  • 指令9:读忙信号和光标地址
    • BF:1:忙,此时模块不能接收命令或数据,0:不忙
  • 指令10:写数据
  • 指令11:读数据

其他说明

  1. 液晶显示模块是一个慢显示器件,所以在执行每条指令之前一定要确认模块的忙标志为低电平(不忙),否则该指令失效
  2. 当显示字符时要先输入显示字符地址,以下是内部显示地址

    所以若要写入字符到第二行第一个(40H),那么实际上写入的命令数据为C0H0100_0000(40H)+1000_0000(80H)=1100_0000(C0H)

基本操作时序

说明下,RS为寄存器选择,高电平时选择寄存器,低电平时选择指令寄存器RW为读写信号线,高电平时进行读操作低电平时进行写操作EN为使能端,当EN端由高电平跳变成低电平时,液晶模块执行命令L表示低电平,H表示高电平。

  • 读状态
    • 输入:RS=L; RW=H; E=H
    • 输出:D0~7=状态字
  • 写指令
    • 输入:RS=L; RW=L; E=下降沿脉冲; D0~7=指令码
    • 输出:null
  • 读数据
    • 输入:RS=H; RW=H; E=H
    • 输出:D0~7=数据
  • 写数据
    • 输入:RS=H; RW=L; E=下降沿脉冲; D0~7=数据
    • 输出:null
时序图

时序参数

字符代码与字符图形对应关系

使用样例

比如要输出A,查询表可知对应代码为0100_0001,而后显示时模块把地址41H中的点阵字符图形显示出来,我们就可以看到A

头文件

#include<reg51.h>
#include<intrins.h> //使用_nop_();
#include<string.h>

宏定义

// RS为寄存器选择,高电平时选择寄存器,低电平时选择指令寄存器
sbit RS = P2^0;
// RW为读写信号线,高电平时进行读操作,低电平时进行写操作 
sbit RW = P2^1;
// EN为使能端,当EN端由高电平跳变成低电平时,液晶模块执行命令
sbit EN = P2^2;

初始化函数

/** * LCD初始化 * * @return */
void init()
{
	/* 0011_1000 置功能 DL=1 --> 8位总线 N=0 --> 单行显示 F=0 --> 显示5x7的点阵字符 */	
	write_cmd(0x38);
	
	/* 0000_0001 清显示 指令码01H,光标复位到地址00H位置 */ 
	write_cmd(0x01);
	
	/* 0000_0110 置输入模式 I/D=1 --> 光标右移 S=0 --> 屏幕上所有文字左移或右移 */
	write_cmd(0x06);
	
	/* 0000_1100 显示开/关控制 D=1 --> 开显示 C=0 --> 无光标 B=0 --> 光标不闪烁 */
	write_cmd(0x0C);
}

检测BF(busy flag)位状态

/* * 检测BF(busy flag)位状态 * * @return */
void test_BF()
{
	unsigned char LCD_status;
	do{
		P0 = 0xFF; // LCD1602读取状态数据,必须有一个上拉电平
		EN = 0; RS = 0; RW = 1; // RS为0时,P0的数据为命令
		EN=1;// 让RS和RW设置有效
		LCD_status = P0;
		_nop_(); _nop_();
		EN = 0;
	}while(LCD_status&0x80); // 1000_0000 忙碌状态
}

写数据

/* * 写数据(一位一位的写) * * @param data8 八位数据 * @return */
void write_data(unsigned char data8)
{
	test_BF();
	EN = 0; RS = 1; RW = 0;
	P0 = data8;
	EN = 1; _nop_(); EN = 0;
}

写命令

/* * 写命令 * * @param cmd8 八位命令 * @return */
void write_cmd(unsigned char cmd8)
{
	test_BF();
	EN = 0; RS = 0; RW = 0;
	P0 = cmd8;
	EN = 1; _nop_(); EN = 0;
}

写字符串

/** * 写字符串 * * @param r row * @param c column * @param str 字符串 * @return */
void write_str(int r, int c, char *str)
{
	int i=0;	
	unsigned char Addressx[] = {0x80, 0xC0};
	unsigned char StartAdd = (Addressx[r] | c);//按位或

	write_cmd(StartAdd);
	
	for(i = 0; i < 16; i++){
		if(str[i]==0) break;
		write_data(str[i]);
	}
	// 如果不够16位,用空格填充
	for(;i < 16; i++){
		write_data(' '); 	
	}
}

主函数编写

头文件

#include<reg51.h>
#include<intrins.h>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>

宏定义

sbit switch1 = P3^0;
sbit switch2 = P3^1;
sbit switch3 = P3^2;
sbit switch4 = P3^3;

unsigned char *strCode=" I Love You!";

引入外部函数

extern void delay_ms(int ms);
extern void init();
extern void write_cmd(unsigned char cmd8);
extern void write_str(int r, int c, char *str);
extern void write_data(unsigned char data8);

功能1:水平滚动文字

void h_scroll_words()
{
	int i;
	init();
	write_str(0,0, " Example-1 ");
	while(1)
	{
		/* fun1 */
		for(i = 0; i<strlen(strCode); i++)
		{
			write_str(1, 0, strCode + i);
			delay_ms(100);
		}
		
		if(switch1 != 0) break;
	}

}

功能2:随机数相加

void random_words()
{
	int a,b,i;
	unsigned char tempStr[17];

	init();
	write_cmd(0x0F); //0000_1111:显示开/关控制
	write_str(0,0, " Example-2 ");

	while(1)
	{
		//srand(TH0);
		a = rand()%10;
		b = rand()%10;
		sprintf(tempStr, "%d+%d=%d", a, b, a+b);
		write_cmd(0xC0|0x05); //1100_0101
		for(i = 0; i < 11; i++)
		{
			if(tempStr[i]) write_data(tempStr[i]); 
			else write_data(' ');
			delay_ms(150);
		}
		write_str(1, 0, " ");
		delay_ms(150);
		if(switch2 != 0) break;
	}

}

功能3:全码显示

void all_str_code()
{
 	int i,j;
	init();
	write_cmd(0x0F);//0000_1111:显示开/关控制 
	write_str(0,0, " Example-3 "); 	
	while(1)
	{
		write_cmd(0xC0);
		for(i = 0x20; i <= 0xFF; i++)
		{
			if(switch3) return;
			if(i >= 0x80 && i < 0xa0) continue;

			if((++j) == 16)
			{
				write_str(1, 0, " ");
				j = 0;
				//如果命令让它从第二行写,就重置到第二行第一个地址
				write_cmd(0xC0);
			}
			//if(i == 0xFF) i = 0x20;
			write_data(i);
			delay_ms(50);
		}
	
	}

}

功能4:自定义字符显示

void character_str_code()
{
  	int i = 0;
  	unsigned char CC[] = {0x1F,0x11,0x1F,0x11,0x1F,0x11,0x1F,0x00};
  	init();
	write_cmd(0x0F); // 0000_1111:显示开关控制
	write_str(0, 0, " Example-4 ");
	write_cmd(0x40); //0100_0000:置字符发生存贮器地址
	for(i = 0; i < 8; i++)
	{
		// 通过上面write_LCD_CMD(0x40); 把数组写到CGRAM
		write_data(CC[i]);	
	}
	while(1)
	{
		write_cmd(0xC0);
		for(i = 0; i < 16; i++)
		{
			if(switch4) return;
			write_data(0);
			delay_ms(50);
		}
		//当满行显示后清屏
		write_str(1, 0, " ");
		delay_ms(150);
	}
	

}

主函数

void main()
{
	while(1)
	{
		if(switch1 == 0)
		{
			h_scroll_words();
		}
		else if(switch2 == 0)
		{
			random_words();
		}
		else if(switch3 == 0)
		{
			all_str_code();
		}
		else if(switch4 == 0)
		{
			character_str_code();
		}
	}
}