亚龙236实验台显示模块上的小长条液晶屏就是今天的主角:1602液晶。我们的任务是编程序驱动它来显示一些英文字母。1602这个名字来源于它可以每行显示16个字母,一共两行。
玩过乐高类的拼装玩具吗?厚厚的一叠说明书,按照说明一步一步拼。搞开发也一样,必须从第一个语句慢慢拼起来,这是急不得的事情。心平气和、头脑清醒是必须的。驱动1602也需要看看1602的说明书—亚龙资料里面有的,在datasheet文件夹里找RT1602.pdf。rt1602pdf文件—来自百度文库
最有用的是 第三页 引脚说明、第四页 读写时序、12页开始的指令解释。
RS:寄存器选择输入端
RS=1:指向数据寄存器
RS=0:指向指令寄存器
RW:读写控制输入端
RW=0:写操作 RW=1:读操作
E:使能信号输入端
读操作时,高电平有效;
写操作时,下降沿有效;
pdf自己看。关键点:
1、E信号频率:最小周期1000ns,意思就是每1us最多操作一次----lcd1602是个老牛车,反应慢的很,为了信号稳定,需要每次延时4us以上。
2、E信号脉宽:最小450ns。意思是 E信号的高电平最少保持0.4us,实际上可能需要1us或更长(数据线过长、没有屏蔽等都会影响传输)。
3、读忙不是必须,如果是竞赛等环境,可以用牺牲效率(延时到肯定不忙)的方法来避免读忙。这样可以少写几行程序。
#define lcd_data P0
sbit lcd_rs=P2^0;
sbit lcd_rw =P2^1;
sbit lcd_e =P2^2;
数据端口接P0,rs接P2.0,rw接P2.1,en接P2.2。
这几句可以看作是—硬件接口层。把硬件操作接口转换成四个符号,操作硬件就是读写这四个符号。
比如:手册第12页第一个指令—清屏
按照手册,我们应该把rs置0,rw置0,DB置00000001(16进制0x01),然后要把DB上的数据写入到寄存器,写入的方法在第4页
时序图上明确了步骤:rs和rw置0,再把e置1,再送DB数据,再把e置0,在e下降沿这一瞬间锁存数据到寄存器。然后可以撤掉DB,撤掉rs、rw。
写成c语句:
lcd_rs= 0; //置0 rs
lcd_rw = 0; //置0 rw
lcd_e = 1; //置1 e
lcd_data = 0x01; //送清屏命令到端口
lcd_e= 0; //置0 e,产生e信号下降沿,写入指令到寄存器
看其它指令,大同小异,就是命令字节不一样,比如显示开关指令
只需要把0x01换成0x0c(00001000)就可以设置指令:开显示、关光标、关闪烁。
那就改一改,写成通用的写指令函数,如下:
void write_command(uchar com)
{
lcd_rs= 0;
lcd_rw = 0;
lcd_e=1;
lcd_data = com;
lcd_e= 0;
}
这样就可以直接传指令码写指令了。
使用方法如下:
write_command(0x01); //清屏指令
方法同写指令,只是rs不一样
void write_data(uchar dat)
{
lcd_rs = 1;
lcd_rw = 0;
lcd_e=1;
lcd_data = dat;
lcd_e= 0;
}
注意,这里面都没有加读忙程序,有时候会显示不完全,可以这样改一改:加延时
void write_data(uchar dat)
{
lcd_rs = 1;
lcd_rw = 0;
lcd_e=1;
lcd_data = dat;
lcd_e= 0;
delayms(1); //延时1ms,确保lcd不忙。
}
这样组合起来,已经可以写指令写数据。
上面的两个函数直接操作端口、端子,可以看作是数据传输层,主管数据传输路径和时序。
下面的工作,就是调用写好的两个函数,写指令、写数据来实现具体显示功能。相当于用户应用层。
看看1602工作流程:
看这个资料北京 同创 1602.pdf第10页初始化流程。
写初始化函数如下:
void LCD_init(void)
{ write_command(0x38); // 8-bits, 2 lines, 7x5 dots
write_command(0x0C); // no cursor, no blink, enable display
write_command(0x06); // auto-increment on
write_command(0x01); // clear screen
}
写显示字符串的函数:
void string(uchar line, uchar col,uchar *s)
{
if (line<2) //如果行数小于二,显示在第一行
write_command(0x80+col); //写指令,定位光标到第一行第col列。
else //否则,应该显示在第二行
write_command(0xc0+col); //写指令,定位光标到第二行第col列。
while(*s>0)
{ write_data(*s++); //写数据,显示
delayms(1); //如果不判忙就要加延时。
}
}
至此,已经可以在指定位置显示英文字库。
完整程序如下:(加了判忙,判忙程序看注释)
/*
* Created: 2020.7.31
* Processor: AT89C51
* Compiler: Keil for 8051
*作者:西峰职专 李枝蔚(修改自proteus例程)
*/
#include
#include
#include
//---------------------------------------------
// 定义硬件连接
#define lcd_data P0
sbit lcd_rs=P2^0;
sbit lcd_rw =P2^1;
sbit lcd_e =P2^2;
// Define new types
typedef unsigned char uchar;
typedef unsigned int uint;
// 函数声明
void check_busy(void);
void write_command(uchar Com);
void write_data(uchar datab);
void LCD_init(void);
void string(uchar line, uchar col,uchar *s);
//------------------------------------
/*******************************************
LCD1602 Driver mapped as IO peripheral
*******************************************/
// 判忙
void check_busy(void)
{ do //do循环,最少执行一次。
{
lcd_data =0x80; //BF置1,假设 忙
lcd_e=0;
lcd_rs=0;
lcd_rw = 1;
lcd_e = 1; //读状态字
_nop_(); //脉冲展宽 ,手册上对lcd_e宽度有要求。
} while(lcd_data & 0x80); //读端口看BF是1吗,是1 则继续循环。
lcd_e = 0;
}
// 写指令
void write_command(uchar com)
{ check_busy();
lcd_rs= 0;
lcd_rw = 0;
lcd_e=1;
lcd_data = com;
lcd_e= 0;
}
// 写数据
void write_data(uchar dat)
{
check_busy();
lcd_rs = 1;
lcd_rw = 0;
lcd_e=1;
lcd_data = dat;
lcd_e= 0;
}
// 初始化
void LCD_init(void)
{ write_command(0x38); // 8-bits, 2 lines, 7x5 dots
write_command(0x0C); // no cursor, no blink, enable display
write_command(0x06); // auto-increment on
write_command(0x01); // clear screen
}
// 在指定位置显示字符串
//line: 显示目的行取值 1或2
//col:显示目的列 取值0-15
//*s :要显示的字符串
void string(uchar line, uchar col,uchar *s)
{
if (line<2)
write_command(0x80+col); //光标定位第一行第col列
else
write_command(0xc0+col);
while(*s>0)
{ write_data(*s++);
}
}
//------------------------------------
void main(void)
{
// Write your code here
LCD_init();
string(1,0,"Have a nice day!");
string(2,1," Hello world!");
while(1)
{
;
}
}
仿真结果:
二次编辑 ----添加无判忙的程序。 取消判忙的优点:rw始终为零,可以直接接地,可以不写判忙程序(少记12行)。缺点:要加延时,仿真表示延时少于600us不能正常显示—Have的H木有了。如图,如果延时太小就直接显示白板。
去判忙加延时方案的程序:
/* Main.c file generated by New Project wizard
*
* Created: 周日 11月 25 2018
* Processor: AT89C51
* Compiler: Keil for 8051
*/
#include
#include
#include
//---------------------------------------------
// Define P3 pins
#define lcd_data P0
sbit lcd_rs=P2^0;
sbit lcd_rw =P2^1;
sbit lcd_e =P2^2;
// Define new types
typedef unsigned char uchar;
typedef unsigned int uint;
// Function Prototypes
void write_command(uchar Com);
void write_data(uchar datab);
void LCD_init(void);
void string(uchar ad ,uchar *s);
void lcd_test(void);
void delay();
//------------------------------------
/*******************************************
LCD1602 Driver mapped as IO peripheral
*******************************************/
// Delay
void delay()
{
uint j=80;
while(--j);
}
// Write a command
void write_command(uchar com)
{ //check_busy();
lcd_e = 0;
lcd_rs= 0;
lcd_rw = 0;
lcd_data = com;
lcd_e= 1;
_nop_();
lcd_e= 0;
delay();
lcd_e= 0;
}
// Write Data
void write_data(uchar dat)
{ //check_busy();
lcd_e= 0;
lcd_rs = 1;
lcd_rw = 0;
lcd_data = dat;
lcd_e= 1;
_nop_();
lcd_e= 0;
delay();
}
// Initialize LCD controller
void LCD_init(void)
{ write_command(0x38); // 8-bits, 2 lines, 7x5 dots
write_command(0x0C); // no cursor, no blink, enable display
write_command(0x06); // auto-increment on
write_command(0x01); // clear screen
}
// Display a string
void string(uchar ad, uchar *s)
{ write_command(ad);
while(*s>0)
{ write_data(*s++);
}
}
//------------------------------------
void main(void)
{
// Write your code here
LCD_init();
string(0x80,"Have a nice day!");
string(0xC0," Proteus VSM");
while(1)
{
delay();
//write_command(0x40);
}
}