学完51单片机再来学AVR,感觉很容易上手,LCD1602在学51的时候学过,所以可以直接修改相关的IO口操作即可。现在顺便也复习一下。1602可以显示两行字符,每行可以显示16个字符,可惜的是不能显示中文,在我调试的时候它帮我不少的忙。
一、主要技术参数:
显示容量: 16X2个字符(两行,每行16个字符)
模块工作电源: 4.5~5.5V
模块工作电流: 2.0mA (5.0V)
模块最佳工作电源: 5.0V
字符尺寸: 2.95X4.35(WXH)mm
二、IO引脚功能
LCD1602A模块引脚功能 |
|||||
编号 |
符号 |
引脚说明 |
编号 |
符号 |
引脚说明 |
1 |
VSS |
电源地(模块供电) |
9 |
D2 |
Data I/O |
2 |
VDD |
电源正极(模块供电) |
10 |
D3 |
Data I/O |
3 |
VL |
接在滑动电阻可以调节对比度 |
11 |
D4 |
Data I/O |
4 |
RS |
数据/命令选择端 (H/L) |
12 |
D5 |
Data I/O |
5 |
R/W |
读/写选择端 (H/L) |
13 |
D6 |
Data I/O |
6 |
E |
使能信号(通知芯片读取数据) |
14 |
D7 |
Data I/O |
7 |
D0 |
Data I/O |
15 |
BLA |
模块背光灯正极 |
8 |
D1 |
Data I/O |
16 |
BLK |
模块背光灯负极 |
三、基本操作时序:
读状态:输入:RS=L、RW=H、E=H 模块输出:状态字=D0~D7
写指令:输入:RS=L、RW=L、D0~D7=指令码、E=H 模块输出:无
读数据:输入:RS=H、RW=H、E=H 模块输出:数据=D0~D7
写数据:输入:RS=H、RW=L、D0~D7=数据、E=H 模块输出:无
状态字说明(因为单片机的速度可能快过1602,所以需要判断当1602是否在忙,或者延时。) |
|||||||
STA7 |
STA6 |
STA5 |
STA4 |
STA3 |
STA2 |
STA1 |
STA0 |
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
STA0-6:当前数据地址指针的数值 |
|||||||
STA7 |
主要用来检查1602模块的是否可以写入或读出操作 |
1:禁止 0:允许 |
|||||
四、指令说明(这指令是用来设置1602显示的方式)
显示模式设置 |
||||||||
指令码 |
功能 |
|||||||
0 |
0 |
1 |
1 |
1 |
0 |
0 |
0 |
设置16X2显示,5X7点阵,8位数据接口 |
显示开/关及光标设置 |
||||||||
指令码 |
功能 |
|||||||
0 |
0 |
0 |
0 |
1 |
D |
C |
B |
D=1 开显示 D=0 关显示 |
0 |
0 |
0 |
0 |
0 |
1 |
N |
S |
N=1 当读或写一个字符后地址指针加1,且光 |
五、时序图
写指令时序,RS拉低表示写指令,R/W拉低表示写操作,给DB0-DB7赋值指令数据并保持>40ns时间,将E拉高表示数据已经准备好保持>150ns让1602读取,将E拉低,并延时>10ns一次写操作完毕。
写数据时序,RS拉高表示写数据,R/W拉低表示写操作,给DB0-DB7赋值指令数据并保持>40ns时间,将E拉高表示数据已经准备好保持>150ns让1602读取,将E拉低,并延时>10ns一次写操作完毕。
六、电路图
由于只是写操作,所以R/W直接接地即可。
PORTA接1602的8位数据引脚
PORTD5接1602的RS引脚 区分数据或命令:H数据,L命令
PORTD4接1602的E引脚 高脉冲使能,使能脉宽最小150ns
七、完整代码。
// 代码功能:驱动LCD1602显示屏
// 平台:DB-51 Ver2.1开发板
// 控制芯片:ATmega16A
// 时钟:片内时钟
// 设计者:L、QQ:1007566569
// 2013-8-10
//*************************************** 包含必备文件 *******************************************************
#ifndef _STRING_H
#include "string.h"
#endif
#ifndef uchar
#define uchar unsigned char
#endif
#ifndef uint
#define uint unsigned int
#endif
//*************************************** 移植必须修改 *******************************************************
// 共占用 PORTA一组IO 、PORTD中第4、5 IO 由于只是写操作,所以R/W直接接地即可。
#define _1602_DATA PORTA // 接1602的8位数据引脚
#define _1602_CMDIO PORTD // 控制线所对应的IO口
#define _1602_CMDIO_RS 5 // 接1602的RS引脚 区分数据或命令:H数据,L命令
#define _1602_CMDIO_E 4 // 接1602的E引脚 高脉冲使能,使能脉宽最小150ns
/**************************************************
设计者:L、QQ:1007566569
函数:自定义函数 void My_SetBit(unsigned char *Date, unsigned char Wei, unsigned char ms)
参数:Date:寄存器地址 Wei:更改指定位 ms:若为1则将指定位置1 若为0则将指定位置0
返回值:无返回值,函数里面会更改寄存器值
例子:My_SetBit((unsigned char*)&PORTB, 2, 1); 将PORTB寄存器中的第2位置1
函数描述:
修改指定寄存器中的指定位,不影响其他位。
**************************************************/
/*** 第二版本 ***/
void My_SetBit(unsigned char *Date, unsigned char Wei, unsigned char ms)
{
(((ms) == (0)) ? (*Date &= ~(0x1<<Wei)) : (*Date |= (0x1<<Wei)));
}
/* 延时函数 */
void delay_50us_1602(unsigned int x)
{
unsigned char y;
for(;x>0;x--)
for(y=19;y>0;y--);
}
/* 写入指定命令 由于是写操作 R/W 可以直接接地保持低电平即可*/
void lcdwrcom_1602(unsigned char cdat)
{
My_SetBit((unsigned char*)&_1602_CMDIO, _1602_CMDIO_E, 0); // E置0,即将改变数据。不允许1602读取数据(IO口状态)
My_SetBit((unsigned char*)&_1602_CMDIO, _1602_CMDIO_RS, 0); // RS拉低 表示是命令
_1602_DATA = cdat; // 将命令送至与1602的8位数据口相连接的IO口
delay_50us_1602(4); // 数据建立时间要 >40ns
My_SetBit((unsigned char*)&_1602_CMDIO, _1602_CMDIO_E, 1); // E使能,命令已经准备好,通知1602读取命令(IO口状态)
delay_50us_1602(8); // E高电平保持时间要 >150ns,让1602有时间读取完整
My_SetBit((unsigned char*)&_1602_CMDIO, _1602_CMDIO_E, 0); // E置0,不允许1602读取数据(IO口状态)可能改变。
delay_50us_1602(2); // 根据时序图,让数据再保持一小段时间 >10ns
}
/* 将指定数据写入显示屏 由于是写操作 R/W 可以直接接地保持低电平即可*/
void lcdwrdata_1602(unsigned char dat)
{
My_SetBit((unsigned char*)&_1602_CMDIO, _1602_CMDIO_E, 0); // E置0,即将改变数据。不允许1602读取数据(IO口状态)
My_SetBit((unsigned char*)&_1602_CMDIO, _1602_CMDIO_RS, 1); // RS拉高 表示是数据
_1602_DATA = dat; // 将命令送至与1602的8位数据口相连接的IO口
delay_50us_1602(4); // 数据建立时间要 >40ns
My_SetBit((unsigned char*)&_1602_CMDIO, _1602_CMDIO_E, 1); // E使能,命令已经准备好,通知1602读取命令(IO口状态)
delay_50us_1602(8); // E高电平保持时间要 >150ns,让1602有时间读取完整
My_SetBit((unsigned char*)&_1602_CMDIO, _1602_CMDIO_E, 0); // E置0,不允许1602读取数据(IO口状态)可能改变。
delay_50us_1602(2); // 根据时序图,让数据再保持一小段时间 >10ns // 根据时序图,让数据保持一小段时间
}
/* 初始化 1602 */
void lcd_init_1602()
{ // 具体含义请参考 1602官方使用手册
lcdwrcom_1602(0x38);//0x38设置显示模式为:16X2 显示,5X7 点阵,8 位数据接口
lcdwrcom_1602(0x0C);//打开显示光标闪烁
lcdwrcom_1602(0x06);//显示光标移动设置
lcdwrcom_1602(0x01);//显示清屏
}
/* 综合函数 API */
/* 参数:Show_1602(第一行显示的数据, 第二行显示的数据, 每个字符显示的时间)*/
void Show_1602(unsigned char Text_1[], unsigned char Text_2[], unsigned int Time)
{
unsigned char n;
unsigned int Tmp;
unsigned char TmpA, TmpC, TmpD;
// 备份IO口原来的配置以备复原,避免影响其他模块代码
TmpA = DDRA;
TmpC = DDRC;
TmpD = DDRD;
// 端口初始化
DDRA = 0xFF;
DDRC = 0xC0;
DDRD = 0x30;
// 1602 初始化
lcd_init_1602();
lcdwrcom_1602(0x80+0x00); // 设置要显示(写入)的位置 0x80-0x8F 第一行的位置
Tmp = strlen(Text_1);
if(Tmp)
{
for(n=0; n<Tmp; n++)
{
lcdwrdata_1602(Text_1[n]); // 一个字节一个字节写入
delay_50us_1602(Time); // 延时指定时间 可以达到一个字符一个字符慢慢依次显示的效果
}
}
lcdwrcom_1602(0x80+0x40); // 设置要显示(写入)的位置 0xC0-0xCF 第二行的位置
Tmp = strlen(Text_2);
if(Tmp)
{
for(n=0; n<Tmp; n++)
{
lcdwrdata_1602(Text_2[n]); // 一个字节一个字节写入
delay_50us_1602(Time); // 延时指定时间 可以达到一个字符一个字符慢慢依次显示的效果
}
}
// 恢复端口原来的设置避免影响其他模块的使用。
DDRA = TmpA;
DDRC = TmpC;
DDRD = TmpD;
}
/* 写完这个函数后,灵光一现!擦,被误导了!霖锋老师和郭天祥老师是不是都写懵
了,搞那么复杂,靠 一个 sprintf 就能搞定。
void Show_1602_Int(unsigned int Shuzi, unsigned int Time)
{
unsigned char Tdate[]="0123456789";
char Text[33] = {0};
unsigned int Tmp;
unsigned int Weishu = 10;
char Textlen = 0;
// 判断位数 ↓
if(Shuzi < 10 ) // 如果是一位数 则不必拆分 直接引用
{
Text[Textlen] = Tdate[Shuzi];
Show_1602_Char(Text, "", Time);
return ;
}
while(1) // 如果是两位数以上 则先判断几位
{
if(Shuzi>=Weishu && Shuzi<Weishu*10)
break;
Weishu*=10;
}
// 判断位数 ↑
// 将每一位拆分翻译 ↓
for(Textlen=0; Weishu; Textlen++)
{
Tmp = Shuzi/Weishu;
Text[Textlen] = Tdate[Tmp];
Shuzi = Shuzi % Weishu;
Weishu /= 10;
}
// 将每一位拆分翻译 ↑
Show_1602_Char(Text, "", Time);
}
*/