用AT89S51单片机控制FM1602C液晶LCD的显示

来源:互联网 发布:db2 设置tcp ip 端口 编辑:程序博客网 时间:2024/05/02 16:03
今天学习到控制LCD的显示了,用了一夜时间终于把1602类型的液晶搞定了,鉴于网上关于介绍用单片机控制1602类型液晶的资料非常少,写在这里供后来的朋友参考。

概述:FM1602C液晶LCD是1602类型的通用型的双行16字符点阵液晶模块,内含数字、字母、符号192种(无汉字)字符库,可通过8位或4位的单片机进行显示字符的控制,通过编程可实现字行的上下滚页、左右移动,通过硬件连线可控制背景灯的对比度,背景灯的开关。

准备:首先拿到一个1602后,可以发现有16个引脚。16个引脚的定义如下:
1脚VSS: 接地
2脚VCC: +5V
3脚VO: LCD屏的对比度调整端。LCD屏驱动电压范围为5V~0V。当VO接地时,对比度最强。
4脚RS: 寄存器选择。RS=0时,选择命令寄存器;RS=1时,选择数据寄存器。
5脚R/W: 读写控制端,置高电平1时,可读;置低电平0时,可写。
6脚Enable:使能控制端,置高电平1时,使能;置低电平0时,禁止。
7脚~14脚: 数据总线D1-D7,用于单片机写入数据。
15脚LCD+:背景光源,接+5V;
16脚LCD-:背景光源,接地。

连线:此处以51系列单片机为例进行引脚连线。
P0.0-P0.7:接7-14脚(数据总线D1-D7)。
P2.0: 接4脚(寄存器选择RS)
P2.1: 接5脚(读写控制端R/W)
P2.2: 接6脚(使能控制端Enable)

驱动程序:每个硬件都有自己的命令,也可以理解为硬件的驱动程序。在编程的过程中,首先要把硬件初始化才可以进行使用。下面是1602LCD的驱动功能列表:
FM1602驱动代码



















编程思路:下面我就说一下如何从零开始让一个LCD显示我们想让它显示的文字内容。
程序上要实现这个过程,需要3个步骤:
第一步:对LCD初始化,也就是把驱动程序写入LCD中。
第二步:把要显示的文字用ASCII码列出,写入LCD中显示。
第三步:编程实现文字的显示方式,比如翻页或者滚屏等。
当然,以上步骤在做的时候需要一些细节,比如:
1、写数据的时候,需要编写一段程序来读P0的状态,检验LCD数据端口的闲忙,如果是忙就需要等待,不忙时才可以写入数据。闲忙的标志是P0.7口的电平。用汇编实现的方式是检验ACC.7的电平。
2、显示更新的文字之前,需要对LCD进行清屏和光标(第一个文字所在位置)进行处理,以便让LCD在适合的位置显示文字。
3、1602型的LCD每次读入数据(要显示的文字)大小为80字节,即如果用一行显示的方式,可一次读入80个字节的文字,如果用双行显示方式,则每行一次可读入40个字节的文字。(一个英文字母是1字节)。
4、设计显示文字的方案时,需要注意的是LCD屏幕的宽度是16个字符,也就是你在读入数据之后,如果让它静态显示(不翻页、不滚屏)的话,每次最多是16个字符,否则就不能完整显示。
还要注意的方面有很多,具体看一下下面的程序。

实现程序:以下程序为汇编语言源文件,在KEIL环境下仿真/写入均测试通过。编译后的目标文件大小为2.5K多一点,完全可写在80C51这样只有4K的RAM的单片机上。

; LCD 1602液晶显示89C51控制实验
;*****************LCD的硬件参数******************
;1脚VSS, 接地
;2脚VCC, 5V
;3脚VO, LCD的对比度调整端。LCD屏驱动电压范围为Vdd~VO。当VO接地时,对比度最强
;4脚RS, 寄存器选择端,RS为0时,选择命令寄存器IR;RS为1时,选择数据寄存器DR;
;5脚R/W, 读写控制端,为1时,选择读出;为0时,则选择写入;
;6脚Enable,使能控制端,Enable为1时,使能;Enable为0,禁止;
;7脚~14脚, 数据总线D1-D7;
;15脚LCD+:背景光源,接+5V;
;16脚LCD-:背景光源,接地。
;*****************51接线法*****************
;P0.0-P0.7接数据总线D1-D7
;P2.0接RS
;P2.1接R/W
;P2.2接Enable
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

NUM_LONGER EQU 30H ;LCD显示的最大字符数

ORG 0000H
LJMP MAIN
ORG 0030H

;R7是指令代码
;R3是显示字符的个数
;R2是显示的数据
;R1是屏幕移动次数
;R4、R5、R6是延时数

;######################################################################
MAIN:
CLR C ;位标志清0,初始化。这是判断应该显示第一屏还是第二屏的标志
LCALL INT_STUATS ;用到的数据状态初始化
LCALL INT_LCD ;LCD初始化
LCALL SHOW_LCD ;LCD显示

INT_STUATS:
MOV A,#32 ;LCD显示的最大字符数为:单行80个字符,双行每行40个字符。
MOV NUM_LONGER,A

MOV A,#16 ;LCD屏幕移动的次数=总字符-面板宽度,这里1602的宽度是16字符
MOV R1,A 
MOV A,NUM_LONGER
SET_R1:
DEC A
DJNZ R1,SET_R1
MOV R1,A
RET
;######################################################################
;子程序:初始化LCD
INT_LCD:
MOV R7,#1B ;命令代码:清屏,消除上次的屏幕,或屏幕初始化
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_CMD ;开始写入指令
LCALL DLY ;延时程序,给1602反应一会

MOV R7,#10B ;命令代码:光标和画面归HOME位
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_CMD ;开始写入指令
LCALL DLY ;延时程序,给1602反应一会

MOV R7,#110B ;命令代码:减量方式输入+显示整体不移动 的输入模式
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_CMD ;开始写入指令
LCALL DLY ;延时程序,给1602反应一会

MOV R7,#1100B ;命令代码:显示内容+无光标+无闪烁 的显示模式。
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_CMD ;开始写入指令
LCALL DLY ;延时程序,给1602反应一会

MOV R7,#111000B ;命令代码:以8位+2行+5*7点阵 的工作模式
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_CMD ;开始写入指令
LCALL DLY ;延时程序,给1602反应一会
RET
;######################################################################
;子程序,命令代码:定义第1行数据的存储地址(系统默认为40H)
SET_LINE1:
MOV R7,#10000000B ;命令代码:定义第1行数据的存储地址(系统默认为40H)
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_CMD ;开始写入指令
LCALL DLY ;延时程序,给1602反应一会
RET

;子程序,命令代码:定义第2行数据的存储地址(系统默认为40H)
SET_LINE2:
MOV R7,#11000000B ;命令代码:定义第2行数据的存储地址(系统默认为40H)
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_CMD ;开始写入指令
LCALL DLY ;延时程序,给1602反应一会
RET
;######################################################################
;子程序:LCD显示
SHOW_LCD:

JC SCREEN3 ;位标志不为0时转移,(第一次运行不为0,不转移)也就是交换取第1页和第2页的第一行的数据 
LCALL SHOW_PAGE ;第一种显示方式
SCREEN3:
;第二种方显示式
LCALL SHOW_LINE1 ;写入第1行数据
LCALL SHOW_LINE2 ;写入第2行数据
LCALL SHOW_MOVE ;滚动屏幕
RET

;######################################################################
;子程序:第一种显示方式
;显示第1行数据
SHOW_PAGE:
LCALL SET_LINE1 ;定义第1行数据的存储地址
MOV DPTR, #WORD_PAGE1 ;写入第1屏的数据的指针
MOV R3,#16 ;R3是显示字符的个数,因为是上下滚屏,因此设置为16
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_DATA_LOOP ;开始写入数据
;显示第2行数据
SHOW_PAGE2:
LCALL SET_LINE2 ;定义第2行数据的存储地址
MOV DPTR, #WORD_PAGE2 ;写入第1屏的数据的指针
MOV R3,#16 ;R3是显示字符的个数,因为是上下滚屏,因此设置为16
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_DATA_LOOP ;开始写入数据
LCALL SHOW_MOVE_UP ;延时
;------------------------------------
;显示第2行数据
LCALL SET_LINE1 ;定义第1行数据的存储地址
MOV DPTR, #WORD_PAGE2 ;写入第1屏的数据的指针
MOV R3,#16 ;R3是显示字符的个数,因为是上下滚屏,因此设置为16
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_DATA_LOOP ;开始写入数据
;;显示第3行数据
LCALL SET_LINE2 ;定义第2行数据的存储地址
MOV DPTR, #WORD_PAGE3 ;写入第1屏的数据的指针
MOV R3,#16 ;R3是显示字符的个数,因为是上下滚屏,因此设置为16
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_DATA_LOOP ;开始写入数据
LCALL SHOW_MOVE_UP ;延时
;------------------------------------(上)完,请接(下)。
;欢迎转载,请注明出处:
;我的博客:http://window.tianyablog.com

;EMIAL:abc@amxv.com


接:《用AT89S51单片机控制FM1602C液晶LCD的显示(上)》
;------------------------------------
;显示第3行数据
LCALL SET_LINE1 ;定义第1行数据的存储地址
MOV DPTR, #WORD_PAGE3 ;写入第1屏的数据的指针
MOV R3,#16 ;R3是显示字符的个数,因为是上下滚屏,因此设置为16
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_DATA_LOOP ;开始写入数据
;;显示第4行数据
LCALL SET_LINE2 ;定义第2行数据的存储地址
MOV DPTR, #WORD_PAGE4 ;写入第1屏的数据的指针
MOV R3,#16 ;R3是显示字符的个数,因为是上下滚屏,因此设置为16
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_DATA_LOOP ;开始写入数据
LCALL SHOW_MOVE_UP ;延时
;------------------------------------
;显示第4行数据
LCALL SET_LINE1 ;定义第1行数据的存储地址
MOV DPTR, #WORD_PAGE4 ;写入第1屏的数据的指针
MOV R3,#16 ;R3是显示字符的个数,因为是上下滚屏,因此设置为16
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_DATA_LOOP ;开始写入数据
;;显示第5行数据
LCALL SET_LINE2 ;定义第2行数据的存储地址
MOV DPTR, #WORD_PAGE5 ;写入第1屏的数据的指针
MOV R3,#16 ;R3是显示字符的个数,因为是上下滚屏,因此设置为16
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_DATA_LOOP ;开始写入数据
LCALL SHOW_MOVE_UP ;延时
;------------------------------------
;显示第5行数据
LCALL SET_LINE1 ;定义第1行数据的存储地址
MOV DPTR, #WORD_PAGE5 ;写入第1屏的数据的指针
MOV R3,#16 ;R3是显示字符的个数,因为是上下滚屏,因此设置为16
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_DATA_LOOP ;开始写入数据
;;显示第6行数据
LCALL SET_LINE2 ;定义第2行数据的存储地址
MOV DPTR, #WORD_PAGE6 ;写入第1屏的数据的指针
MOV R3,#16 ;R3是显示字符的个数,因为是上下滚屏,因此设置为16
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_DATA_LOOP ;开始写入数据
LCALL SHOW_MOVE_UP ;延时
;------------------------------------
;显示第6行数据
LCALL SET_LINE1 ;定义第1行数据的存储地址
MOV DPTR, #WORD_PAGE6 ;写入第1屏的数据的指针
MOV R3,#16 ;R3是显示字符的个数,因为是上下滚屏,因此设置为16
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_DATA_LOOP ;开始写入数据
;;显示第7行数据
LCALL SET_LINE2 ;定义第2行数据的存储地址
MOV DPTR, #WORD_PAGE7 ;写入第1屏的数据的指针
MOV R3,#16 ;R3是显示字符的个数,因为是上下滚屏,因此设置为16
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_DATA_LOOP ;开始写入数据
LCALL SHOW_MOVE_UP ;延时
RET
;------------------------------------
;子程序:延时
SHOW_MOVE_UP:
LCALL SHOW_DLY
LCALL SHOW_DLY
LCALL SHOW_DLY
LCALL SHOW_DLY
RET
;######################################################################

;子程序:第二种显示方式显示第1行数据
SHOW_LINE1:
LCALL SET_LINE1 ;定义第1行数据的存储地址
MOV DPTR, #WORD_LINE1 ;写入第1屏的数据的指针
JNC SCRREN1 ;位标志为0时转移,也就是交换取第1页和第2页的第一行的数据
MOV DPTR, #WORD_LINE3 ;写入第2屏的数据的指针
SCRREN1:
MOV R3,NUM_LONGER ;R3是显示字符的个数
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_DATA_LOOP ;开始写入数据
RET

;子程序:第二种显示方式显示第2行数据
SHOW_LINE2:
LCALL SET_LINE2 ;定义第2行数据的存储地址
MOV DPTR, #WORD_LINE2 ;写入第1屏的数据的指针
JNC SCRREN2 ;位标志为0时转移,也就是交换取第1页和第2页的第二行的数据
MOV DPTR, #WORD_LINE4 ;写入第2屏的数据的指针
SCRREN2:
MOV R3,NUM_LONGER ;R3是显示字符的个数
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_DATA_LOOP ;开始写入数据
RET

;子程序:第二种显示方式,滚动屏幕画面
SHOW_MOVE:
LCALL SHOW_DLY ;因为刚刚显示,所以多延时一会
LCALL SHOW_DLY
MOVE_LOOP:
LCALL SHOW_DLY
MOV R7,#11000B ;命令代码:画面滚动,左移一个字符
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_CMD ;开始写入指令
LCALL DLY ;延时程序,给1602反应一会
DJNZ R1,MOVE_LOOP ;当滚动16次之后,屏幕已经完整显示一次,继续往下执行。
LCALL SHOW_DLY ;因为要换页,所以多延时一会
LCALL SHOW_DLY
LCALL SHOW_DLY
LCALL SHOW_DLY
CPL C ;第1或第2屏幕标志取反,用于下一个循环的判断
LCALL INT_STUATS ;数据状态初始化,用于下一个循环
LCALL INT_AC_RESET ;AC光标指针归HOME位
LCALL SHOW_LCD
RET
;######################################################################
;子程序,属于初始化LCD的范畴之内C光标指针归HOME位,这里单独调用
INT_AC_RESET:
MOV R7,#10B ;命令代码:AC指针归HOME位。
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
LCALL WRITE_CMD ;开始写入指令
LCALL DLY ;延时程序,给1602反应一会

;子程序:判断LCD闲忙
CHECK_BUSY:
CLR P2.0 ;令RS=0,即选择”命令寄存器
SETB P2.1 ;令R/W=1,即选择读数据模式
SETB P2.2 ;使能高电平
NOP
NOP
MOV A,P0 ;读入P0口状态
JB ACC.7,CHECK_BUSY ;P0.7是忙闲信号(BF)反馈口,如果等于0(不忙)就继续往下执行
NOP
NOP
CLR P2.2 ;使能低电平
RET
;######################################################################
;子程序:往命令寄存器中写入数据
WRITE_CMD:
CLR P2.0 ;令RS=0,即选择”命令寄存器
CLR P2.1 ;令R/W=0,即选择写数据模式
SETB P2.2 ;使能高电平
NOP
NOP
NOP
NOP
MOV A,R7 ;从R7中提取命令代码
MOV P0,A ;写入命令数据
NOP
NOP
NOP
NOP
CLR P2.2 ;使能低电平
RET

;子程序:往数据寄存器中写入数据的循环程序
WRITE_DATA_LOOP:
MOV A,#0
MOVC A,@A+DPTR
INC DPTR
MOV R2,A
LCALL WRITE_DATA_ONE
DJNZ R3,WRITE_DATA_LOOP
RET

;子程序:数据寄存器中写入数据
WRITE_DATA_ONE:
LCALL CHECK_BUSY ;检测LCD状态,不忙时写入指令。
SETB P2.0 ;令RS=1,即选择数据存储器
CLR P2.1 ;令R/W=0,即选择写数据模式
SETB P2.2 ;使能高电平
NOP
NOP
NOP
NOP
MOV A,R2
MOV P0,A
NOP
NOP
NOP
NOP
CLR P2.2 ;使能低电平
RET
;######################################################################
;子程序:程序的延时
DLY:MOV R5,#250
DL1:
NOP
NOP
DJNZ R5,DL1
RET

;子程序:显示的延时(用于屏幕移动)
SHOW_DLY:
D1:MOV R6,#255
D2:MOV R5,#255
D3:MOV R4,#3
DJNZ R4,$
DJNZ R5,D3
DJNZ R6,D2
RET
;######################################################################
;第一种显示方式的内容
WORD_PAGE1:DB 54H,68H,69H,73H,20H,70H,72H,6FH,67H,72H,61H,6DH,20H,6FH,66H,20H ;ASCII: This program of 
WORD_PAGE2:DB 74H,68H,65H,20H,4CH,43H,44H,20H,77H,68H,69H,63H,68H,20H,20H,20H ;ASCII: the LCD which 
WORD_PAGE3:DB 74H,79H,70H,65H,20H,69H,73H,20H,46H,4DH,31H,36H,32H,30H,43H,20H ;ASCII: type is FM1620C
WORD_PAGE4:DB 69H,73H,20H,77H,72H,69H,74H,65H,6EH,20H,62H,79H,20H,58H,4CH,2EH ;ASCII: is writen by XL.
WORD_PAGE5:DB 45H,4DH,3AH,20H,61H,62H,63H,40H,61H,6DH,78H,76H,2EH,63H,6FH,6DH ;ASCII: EM: abc@amxv.com
WORD_PAGE6:DB 20H,20H,20H,20H,20H,20H,20H,20H,20H,20H,20H,20H,20H,20H,20H,20H ;空白
WORD_PAGE7:DB 20H,20H,20H,20H,20H,20H,20H,20H,20H,20H,20H,20H,20H,20H,20H,20H ;空白


;第二种显示方式,第1页的数据内容
WORD_LINE1: ;ASCII code: this is a test for the lcd 1602.
DB 74H,68H,69H,73H,20H,69H,73H,20H,61H,20H
DB 74H,65H,73H,74H,20H,66H,6FH,72H,20H,74H
DB 68H,65H,20H,6CH,63H,64H,20H,31H,36H,30H
DB 32H,2EH

WORD_LINE2: ;ASCII code:THIS IS A TEST OFR THE LCD 1602。
DB 54H,48H,49H,53H,20H,49H,53H,20H,41H,20H
DB 54H,45H,53H,54H,20H,46H,4FH,52H,20H,54H
DB 48H,45H,20H,4CH,43H,44H,20H,31H,36H,30H
DB 32H,2EH

;第二种显示方式,第2页的数据内容
WORD_LINE3: ;ASCII:I hope you like it :)
DB 49H,20H,68H,6FH,70H,65H,20H,79H,6FH,75H
DB 20H,6CH,69H,6BH,65H,20H,69H,74H,20H,3AH
DB 29H,20H,20H,20H,20H,20H,20H,20H,20H,20H
DB 20H,20H 

WORD_LINE4: ;ASCII:May you have a good dream !
DB 4DH,61H,79H,