|
前段时间发了一个LCD1602的驱动(http://www.ourdev.cn/bbs/bbs_content.jsp?bbs_sn=4491097&bbs_page_no=1&search_mode=3&search_text=XIAN1987& bbs_id=9999),可惜没有实物没进行测试,后来感觉很惭愧:没测试的东东都发上去,确实太不地道了。所以周末特地坐了4个小时的地铁去中发买了一个LCD1602,将驱动代码进行实物及Proteus仿真的测试,现在已经全部通过,特上传所有代码和部分图片(手机拍的,不很清晰,包涵):
(原文件名:P100111_10.52.jpg)
(原文件名:P100111_10.53.jpg)
(原文件名:QQ截图未命名1.png)
(原文件名:QQ截图未命名.png)
/**************************************************************************************************************
* 文件名称: hd44780.c
* 功能说明: (控制器为HD44780的)LCD1602字符液晶的底层驱动程序
* 注意事项: 1、由于8051单片机的本身操作速度较慢,所以在产生驱动时序时不需要考虑电平转换间的延时问题,但是,将此
* 程序移植到其他如AVR、PIC或者新型的1T 51等单片机时,必须在认真参考HD44780手册中关于时序的内容,对此
* 程序进行相应延时修改
* 2、为了提高上层函数的灵活性,这里的写入函数都没有检查HD44780的忙状态,请在调用这些函数前进行处理,你
* 可以等待忙状态结束,也可以进行合适长度的软件延时,或者在多任务系统中挂起该任务,把执行时间让给其他
* 任务
* 3、由于LCD_DAT既要用于输入又要用于输出,所以一定要注意IO口方向的适时切换,对于51单片机,虽然没有IO口
* 设定,但是其IO口最为输入时有一个限制,即:在读取之前必须先向IO口写1,请千万注意这一点。
* 4、不要以为仅仅通过修改LCD_BUS_WIDTH就可以完成8位传输与4位传输的切换,对于IO口定义LCD_RS、LCD_RW等的
* 定义肯定也是必须的。并且需要注意以下语句:
#define LCD_R_8b(B) { \
byte tmp=0; \
LCD_DAT|=0xF0; \ -----注意这里,这就是51中读取前必须先写1的地方,但是0xF0是因为LCD_DAT
LCD_R_4b(B); \ -是P1的高四位,所以这个数值应该根据你将LCD的4根数据线接的位置来确
tmp = B; \ -定。最可能的变化是你将LCD的4根数据线接到了P1的低四位,那么这个值
LCD_R_4b(B); \ -就应该是0x0F了。 当然了,如果你使用了8位数据线模式,就不存在这个
tmp += B>>4; \ -问题了。
B = tmp; \
}
* 版本日期: 2010年12月31日
* 作者邮件: xian0901@sina.cn
* 升级记录:
***************************************************************************************************************/
#include "typedef.h"
#include "8052Hal.h"
#include "hd44780.h"
/***************************************************************************************************************
--------------------------------------------连接引脚说明--------------------------------------------------------
*
* LCD与51引脚的连接情况,本应用中使用P1.4--P1.7与1602进行四位的数据传输,从而减少MCU引脚的消耗。
* 请根据你的应用中实际的电路连接情况对此处的定义进行修改
***************************************************************************************************************/
sbit LCD_VE = P1^0; //LCD对比调整电压,接正电源时对比度最弱,接地时对比度最高,可使用此引脚产生PWM波形调节对比度——此功能未实现
sbit LCD_RS = P1^1; //数据、命令选通
sbit LCD_RW = P1^2; //读、写操作选通
sbit LCD_EN = P1^3; //使能控制
#define LCD_DAT P1 //数据端口,使用P1口的高四位进行数据传输
#define LCD_RS_C() LCD_RS = 0 //Register Selected:IR Instruction Register,Can only be Written(AC Address Counter 、BF Busy Flag Can Be Read)
#define LCD_RS_D() LCD_RS = 1 //Register Selected:DR Data Register,Can be both Written into and Read from
#define LCD_RW_R() LCD_RW = 1 //选择读操作
#define LCD_RW_W() LCD_RW = 0 //选择写操作
#define LCD_EN_E() LCD_EN = 1 //Data Exchange is Enable
#define LCD_EN_D() LCD_EN = 0 //Data Exchange is Disable,and internal operation starts on the falling edge
#define LCDDelay() _nop_() //至少保证450ns
#if LCD_BUS_WIDTH == 4 //MCU与LCD1602间使用DB7-DB4这4根数据线进行传输
#define LCD_R_4b(B) LCD_EN_E(); \
LCDDelay(); \
B =LCD_DAT; \
LCD_EN_D(); \
LCDDelay();
#define LCD_R_8b(B) { \
byte tmp=0; \
LCD_DAT|=0xF0; \
LCD_R_4b(B); \
tmp = B; \
LCD_R_4b(B); \
tmp += B>>4; \
B = tmp; \
}
#define LCD_W_4b(B) LCD_DAT =((B&0xF0)|(LCD_DAT&0x0F)); \
LCD_EN_E(); \
LCDDelay(); \
LCD_EN_D(); \
LCDDelay();
#define LCD_W_8b(B) LCD_W_4b(B); \
LCD_W_4b(B<<4);
#elif LCD_BUS_WIDTH == 8 //MCU与LCD1602间使用DB7-DB0这8根数据线进行传输
#define LCD_R_8b(B) LCD_EN_E(); \
LCDDelay(); \
LCD_DAT=0xFF; \
B =LCD_DAT; \
LCD_EN_D(); \
LCDDelay();
#define LCD_W_8b(B) LCD_DAT =B; \
LCD_EN_E(); \
LCDDelay(); \
LCD_EN_D(); \
LCDDelay();
#endif
/**************************************************************************************************************
* 函数名称: LCDBusInit()
* 功能说明: LCD1602数据传输数据线宽度设置和显示屏字符显示行数设置
* 输 入:
* 输 出: ui08 初始化是否成功 0为成功 其他为不成功
* 注意事项:
***************************************************************************************************************/
ui08 LCDBusInit(void)
{
ui08 cmd = 0x00;
if(!((LCD_BUS_WIDTH == 4) ||
(LCD_BUS_WIDTH == 8)))
{
return RET_ERR;
}
if(!((LCD_LINE_NUMS == 2) ||
(LCD_LINE_NUMS == 1)))
{
return RET_ERR;
}
LCD_EN_D();
#if LCD_BUS_WIDTH == 4
LCD_RS_C();
LCD_RW_W();
LCD_W_4b(0x20); //LCD1602上电默认是8-bit数据传输,本条指令用于将其设定为4-bit传输,注意有且仅有这一条指令是半字节的
Delay50us(); //指令0x20需要40us,此时无法使用忙查询指令,必须通过延时等待该指令执行完成
cmd = 0x20 | (((LCD_LINE_NUMS == 2) ? 1 : 0) << 3);
LCD_WR_CMD(cmd);
while(LCD_IsBusy() == 1);
#elif LCD_BUS_WIDTH == 8
cmd = 0x30 | (((LCD_LINE_NUMS == 2) ? 1 : 0) << 3);
LCD_WR_CMD(cmd);
while(LCD_IsBusy() == 1);
#endif
LCD_WR_CMD(0x01); //这里验证初始化是否成功使用了一个技巧,即0x01本身执行需要1.52ms,那么执行完本指令立即检测Busy位应该得到1,否则初始化不成功
if(LCD_IsBusy() == 1) //另外,检测到Busy位为1后,还要LCD1602能够退出Busy状态才是真正表明成功初始化了。
{
while(LCD_IsBusy() == 1);
return RET_OK;
}
else
{
return RET_ERR;
}
}
/**************************************************************************************************************
* 函数名称: LCD_WR_CMD()
* 功能说明: 向LCD1602写入控制指令
* 输 入: byte cmd 指令字,详细指令请参考LCD1062用户手册
* 输 出:
* 注意事项:
***************************************************************************************************************/
void LCD_WR_CMD(byte cmd)
{
LCD_RS_C();
LCD_RW_W();
LCD_W_8b(cmd);
}
/**************************************************************************************************************
* 函数名称: LCD_WR_DAT()
* 功能说明: 向LCD1602的CGRAM或DDRAM当前光标位置写入数据
* 输 入: byte dat 要在当前光标位置写入的数据
* 输 出:
* 注意事项: 具体是操作CGRAM还是DDRAM要看你最近一次写地址指令是写的CGRAM地址还是DDRAM地址
***************************************************************************************************************/
void LCD_WR_DAT(byte dat)
{
LCD_RS_D();
LCD_RW_W();
LCD_W_8b(dat);
}
/**************************************************************************************************************
* 函数名称: LCD_IsBusy()
* 功能说明: 忙检测,忙,即LCD1602正在进行内部操作,在操作完场前无法接受新的指令或数据
* 输 入:
* 输 出: ui08 0:表示LCD1602现在处于空闲状态,可以立即接受指令或数据; 1:表示LCD1602正在进行内部操作
* 注意事项:
***************************************************************************************************************/
ui08 LCD_IsBusy(void)
{
ui08 flg=0;
LCD_RS_C();
LCD_RW_R();
LCD_R_8b(flg);
flg &= 0x80;
if(flg == 0)
{
return 0;
}
else
{
return 1;
}
}
/**************************************************************************************************************
* 函数名称: LCD_GET_AC()
* 功能说明: 获取LCD1602的地址计数器数值,即光标的当前位置,
* 输 入:
* 输 出: ui08 地址计数器的数值
* 注意事项:
***************************************************************************************************************/
ui08 LCD_GET_AC(void)
{
ui08 val=0;
LCD_RS_C();
LCD_RW_R();
LCD_R_8b(val);
val &= 0x7F;
return val;
}
/**************************************************************************************************************
* 函数名称: LCD_RD_DAT()
* 功能说明: 从LCD1602的CGRAM或DDRAM的当前光标位置读取数据
* 输 入:
* 输 出: byte 从当前光标位置读取到的数据
* 注意事项: 具体是操作CGRAM还是DDRAM要看你最近一次写地址指令是写的CGRAM地址还是DDRAM地址
***************************************************************************************************************/
byte LCD_RD_DAT(void)
{
byte dat=0;
LCD_RS_D();
LCD_RW_R();
LCD_R_8b(dat);
return dat;
}
/**************************************************************************************************************
* 文件名称: hd44cmd.h
* 功能说明: 为方便HD44780控制命令的使用而定义的宏和结构体
* 注意事项: 易混淆点说明: 指令IorDShift控制的是当读写DDRAM/CGRAM的时候的屏幕和光标的移动
* 指令CorDShift则是让屏幕或光标立即移动
* 版本日期: 2010年12月31日
* 作者邮件: xian0901@sina.cn
* 升级记录:
***************************************************************************************************************/
#ifndef __HD44CMD_H__
#define __HD44CMD_H__
ui08 LCD_ClrScreen(ui08 wait);
ui08 LCD_RetunHome(ui08 wait);
ui08 LCD_BlinkSW(ui08 sw,ui08 wait);
ui08 LCD_CursorSW(ui08 sw,ui08 wait);
ui08 LCD_ScreenSW(ui08 sw,ui08 wait);
ui08 LCD_FontSel(ui08 sel,ui08 wait);
ui08 LCD_CursorDir(ui08 dir,ui08 wait);
ui08 LCD_ScreenDir(ui08 dir,ui08 wait);
ui08 LCD_CursorMov(ui08 dir,ui08 wait);
ui08 LCD_ScreenMov(ui08 dir,ui08 wait);
ui08 LCD_CGRAMAddr(ui08 addr,ui08 wait);
ui08 LCD_DDRAMAddr(ui08 addr,ui08 wait);
/***************************************************************************************************************
--------------------------------------------结构体功能说明------------------------------------------------------
*
由于LCD1602控制器HD44780的每条指令可以执行多个操作,比如指令0x20,既可以选择字体,还可以设定数据总线宽度及液
* 晶显示行数,这样当我们想要使用本条指令只完成一个操作,如字体选择时,为了不改变数据总线宽度和液晶显示行数的设定,
* 我们就不得不使用“读-修改-写”这样的操作。
* 但不幸的是,HD44780仅仅给了我们写命令的能力,却没有提供读设置(状态)的操作,为了解决这个问题,我定义了这个结
* 构体作为HD44780内部寄存器的映像,每个被写入HD44780的设定值都会被同步存入这个结构体的变量中,这样我们就可以通过
* 读取此结构体变量的值来了解LCD1602的当前设定和状态,从而可以完成”读-修改-写“这样的局部修改指令了。
*
* 注意,在HD44780的8条设置指令中,前两条“清显示”和“光标归位”完成的操作单一而明确,没有记录状态的必要;而最后两条
* 指令“设置DDRAM地址”和“设置CGRAM地址”也没有记录状态的意义,因为,一则AC的地址是会随着数据的读写自动调整的,二则H-
* D44780提供的对AC的读取操作,根本没有必要软件跟踪记录。
所以,这个结构体仅仅记录了HD44780中8条设置的中间4条
*
* 说明:指令IorDShift控制的是当读写DDRAM/CGRAM的时候的屏幕和光标的移动
* 指令CorDShift则是让屏幕或光标立即移动
***************************************************************************************************************/
struct HDRegisters {
union {
struct { //字符含义:S Screen I Increment
byte S : 1; //S=1 写数据到DDRAM时整个显示屏右移(I=0)或左移(I=1)从而使得光标好像是静止的
//S=0 写数据到DDRAM时整个显示屏没有移动,通常情况下设置是这样子的
byte I : 1; //I=1 从DDRAM、CGRAM读取数据 或 向DDRAM、CGRAM写入数据时光标右移
//I=0 从DDRAM、CGRAM读取数据 或 向DDRAM、CGRAM写入数据时光标左移
byte : 6;
}bits;
byte value;
}IorDShift;
union {
struct { //字符含义:B Blink C Cursor D Display
byte B : 1; //B=1 显示闪烁 B=0 不显示闪烁
byte C : 1; //C=1 显示光标 C=0 不显示光标
byte D : 1; //D=1 开显示 D=0 关显示
byte : 5;
}bits;
byte value;
}DorCorBOn;
union {
struct {
byte : 2; //字符含义:R Right S Screen
byte R : 1; //R=1 右移 R=0 左移
byte S : 1; //S=1 左移或右移的是屏幕而不是光标 S=0 进行左移或右移的不是屏幕而是光标
byte : 4;
}bits;
byte value;
}CorDShift;
union {
struct {
byte : 2; //字符含义:FA Font 10*5 N2 Number of Lines is 2 I8 Interface Bus 8-bit
byte FA : 1; //FA=1 选择字体10*5 FA=0 选择字体8*5
byte N2 : 1; //N2=1 两行显示 N2=0 一行显示
byte I8 : 1; //I8=1 使用8位IO进行传输 I8=0 使用4位IO进行传输
byte : 3;
}bits;
byte value;
}N4or8Font;
};
extern const struct HDRegisters * const pLCDStat;
#define HD_CMD_ClrScreen 0x01 //Clear display by writting space code 20H into all DDRAM addresses.
//And then do all what command "HD_CMD_RetunHome" do
#define HD_CMD_RetunHome 0x02 //Sets DDRAM address 0 in AC, and returns display from being shifted to original position,
//The cursor or blinking go to the left edge of the display
#define HD_CMD_IorDShift 0x04 //Increments (I/D = 1) or decrements (I/D = 0) the DDRAM address by 1 when a character code
//is written into or read from DDRAM. Shifts the entire display either to the right (I/D = 0) or to the left (I/D = 1) when S is 1
#define HD_CMD_DorCorBOn 0x08 //Sets entire Display(D) ,Cursor(C) and Blinking(B) of cursor position character ON/Off.
#define HD_CMD_CorDShift 0x10 //Shifts the Cursor position or Display to the right or left without writing or reading display data
#define HD_CMD_4or8NFont 0x20 //Sets Interface data length 4-bit or 8-bit, Number of display lines (N), and character Font (F)
#define HD_CMD_CGRAMAddr 0x40 //Sets CGRAM address
#define HD_CMD_DDRAMAddr 0x80 //Sets DDRAM address
#endif //__HD44CMD_H__
/**************************************************************************************************************
* 文件名称: hd44cmd.c
* 功能说明: HD44780的基本命令集函数包装
* 注意事项:
* 版本日期: 2010年12月31日
* 作者邮件: xian0901@sina.cn
* 升级记录:
***************************************************************************************************************/
#include "typedef.h"
#include "8052Hal.h"
#include "hd44780.h"
#include "hd44cmd.h"
static struct HDRegisters hdreg; //用于记录LCD1602内部设定状态
const struct HDRegisters * const pLCDStat = &hdreg; //hdreg的一个只读映像,使得上层函数能够访问到LCD的状态却又
//不能够随意更改,从而避免产生破坏
/**************************************************************************************************************
* 函数名称: LCD_ClrScreen()
* 功能说明: 清显示,即将显示器上的显示内容全部清除
* 输 入: ui08 wait 1 如果执行本函数时LCD正在执行内部操作(即忙)而无法接受新命令,则等待LCD忙结束然后发送本
* 命令给LCD进行执行
* 0 如果执行本函数时LCD正在执行内部操作(即忙)而无法接受新命令,则不等待LCD而是立即返回
* 输 出: ui08 报告操作执行的情况,可能的取值有:RET_OK 完成操作,RET_ERR 出错未完成,RET_BSY 忙未完成
* 注意事项: 注意,除了清显示之外,本函数本身还会执行LCD_RetunHome()函数的全部操作
***************************************************************************************************************/
ui08 LCD_ClrScreen(ui08 wait)
{
if(!((wait == 0) ||
(wait == 1)))
{
return RET_ERR;
}
if(wait == 1) //等待LCD忙状态结束,从而尽量完成此次操作
{
while(LCD_IsBusy() == 1)
;
}
else //不等待LCD忙状态结束,如果LCD忙,则立即退出返回
{
if(LCD_IsBusy() == 1)
return RET_BSY;
}
LCD_WR_CMD(HD_CMD_ClrScreen);
return RET_OK;
}
/**************************************************************************************************************
* 函数名称: LCD_RetunHome()
* 功能说明: 将光标移到显示屏最左侧,如果显示字符有被移动的,则将其移到最初位置
* 输 入: ui08 wait 1 如果执行本函数时LCD正在执行内部操作(即忙)而无法接受新命令,则等待LCD忙结束然后发送本
* 命令给LCD进行执行
* 0 如果执行本函数时LCD正在执行内部操作(即忙)而无法接受新命令,则不等待LCD而是立即返回
* 输 出: ui08 报告操作执行的情况,可能的取值有:RET_OK 完成操作,RET_ERR 出错未完成,RET_BSY 忙未完成
* 注意事项:
***************************************************************************************************************/
ui08 LCD_RetunHome(ui08 wait)
{
if(!((wait == 0) ||
(wait == 1)))
{
return RET_ERR;
}
if(wait == 1) //等待LCD忙状态结束,从而尽量完成此次操作
{
while(LCD_IsBusy() == 1)
;
}
else //不等待LCD忙状态结束,如果LCD忙,则立即退出返回
{
if(LCD_IsBusy() == 1)
return RET_BSY;
}
LCD_WR_CMD(HD_CMD_RetunHome);
return RET_OK;
}
/**************************************************************************************************************
* 函数名称: LCD_BlinkSW()
* 功能说明: 是否开启闪烁显示
* 输 入: ui08 sw 1 开启闪烁显示 0 关闭闪烁显示
* ui08 wait 1 如果执行本函数时LCD正在执行内部操作(即忙)而无法接受新命令,则等待LCD忙结束然后发送本
* 命令给LCD进行执行
* 0 如果执行本函数时LCD正在执行内部操作(即忙)而无法接受新命令,则不等待LCD而是立即返回
* 输 出: ui08 报告操作执行的情况,可能的取值有:RET_OK 完成操作,RET_ERR 出错未完成,RET_BSY 忙未完成
* 注意事项:
***************************************************************************************************************/
ui08 LCD_BlinkSW(ui08 sw,ui08 wait)
{
if(!((sw == 0) ||
(sw == 1)))
{
return RET_ERR;
}
if(!((wait == 0) ||
(wait == 1)))
{
return RET_ERR;
}
if(wait == 1) //等待LCD忙状态结束,从而尽量完成此次操作
{
while(LCD_IsBusy() == 1)
;
}
else //不等待LCD忙状态结束,如果LCD忙,则立即退出返回
{
if(LCD_IsBusy() == 1)
return RET_BSY;
}
hdreg.DorCorBOn.bits.B = sw;
LCD_WR_CMD(HD_CMD_DorCorBOn | hdreg.DorCorBOn.value);
return RET_OK;
}
点击此处下载 ourdev_610166H1YV2V.rar(文件大小:681K) (原文件名:LCD1602.rar) |
阿莫论坛20周年了!感谢大家的支持与爱护!!
一只鸟敢站在脆弱的枝条上歇脚,它依仗的不是枝条不会断,而是自己有翅膀,会飞。
|