使用8086制作LCD显示电压表
虽然很早就接触过单片机并写了不少程序,但是对于汇编语言还是有些发怵。正巧刚刚学完了微机原理课程,于是就想趁着刚学完8086汇编的热乎劲儿,趁热打铁做点东西。经过思考,我决定用8086驱动常见的1602液晶,用外置ADC制作一个电压表。
鉴于8086
年代过于久远,找到实际的芯片的难度太高,于是仿真就成了最好的方法。好在,现在对于这种古老的芯片的仿真技术已经非常完善。Proteus
是一款老牌仿真软件,可以很好的仿真8086
,配合MASM32
作为汇编工具,便可以开始进行8086
软硬件的设计了。完整的工程(仿真图、源程序)可以在文末找到。
本设计具有如下功能:
- 读取外部电压并显示在LCD上
- 设置电压上下限并显示在LCD上
- 可以通过按键调节电压的上下限
- 电压超过上下限时蜂鸣器鸣响报警
- 设置电压上下限时,无法超过输入电压范围(0-5V),并且最高电圧不能低于最低电压。
程序流程图如下。
本设计最重要的部分就是8086
处理器及其外围电路了。8086
最小系统与接口译码部分的原理图如下所示。
接下来是报警与按键部分电路。
液晶显示器部分选择了之前51时代常用的1602
液晶。这一款液晶使用8080
并口时序,使用8086
的IO地址译码电路,即可将其作为一个内存访问,大大简化了程序的设计。这也解开了我刚接触1602
液晶时对于它这种奇怪的驱动模式的疑惑:都是时代的产物。液晶显示器与接口电路如下所示。
ADC部分选择了一块年代久远的芯片ADC0805
。选择这块芯片的原因有二:一是因为Multisim
软件支持对这款芯片进行仿真,二是因为它也具有与1602
类似的IO结构,特别适合用8086
这种只支持古老的并口时序的处理器驱动。ADC、蜂鸣器接口电路与输入接口电路如下所示。
在仿真程序的过程中,我发现LCD1602在一开始总是无法正常显示,即便我通过示波器确定通信是没有问题的。我第一个想到的就是LCD1602的速度问题。众所周知,51单片机驱动1602都需要加很多的延时才能保证正常显示。本来我以为对于仿真软件来说,这些延时并没有什么必要,但是在加上延时之后,1602突然就可以正常使用了。不得不令人赞叹,仿真软件居然可以仿真到如此细节。
接下来就是完成程序编写了。程序运行后,首先显示作品名称与制作人信息。数秒过后,进入主界面,如下图所示。LCD的第一行显示电压,第二行显示电压报警上下限。此时调整电位器的位置,即可改变ADC的输入电压,并可以在屏幕上看到电压的变化。
原理图中的四个按键可以分别调整电压报警值的上限与下限。四个按键从上到下的功能分别为电压上限加、电压上限减、电压下限加、电压下限减,每按一次相应的电压变化0.1V。按下以后新的电压值将即时反映在屏幕上。当电位器中点的电压超出所设定的范围后,蜂鸣器响。电压恢复到范围以内之后,蜂鸣器关闭。
.MODEL SMALL
.8086
.data
bootstr1 db 81h,"Voltage Meter",'$'
bootstr2 db 0c1h,"WYZ 1605010419",'$'
line1 db 080h," Voltage: V ",'$'
line2 db 0c0h," H: V L: V",'$'
highvol dw 400
lowvol dw 100
currvol dw 0
.code
.startup
start: cli
call lcdinit
;显示制作者信息
mov bx,offset bootstr1
call lcdstr
mov bx,offset bootstr2
call lcdstr
;延时
call dly1s
;清屏
mov bx,offset line1
call lcdstr
mov bx,offset line2
call lcdstr
while1: mov al,8ah;显示地址
call lcdcmd
call getvol
call datproc
mov bx,offset currvol;保存电压值
mov [bx],ax
call strconv
mov al,0c3h
call lcdcmd
mov bx,offset highvol
mov ax,[bx]
call strconv
mov al,0cbh
call lcdcmd
mov bx,offset lowvol
mov ax,[bx]
call strconv
call keyproc
call almproc
jmp while1
delayms proc near
push cx
mov cx,25000
dlylop:loop dlylop
pop cx
ret
delayms endp
keyproc proc near
push dx
push ax
push bx
push cx
mov dx,298h
in al,dx
mov bx,offset highvol
mov dx,[bx];highvol
mov bx,offset lowvol
mov cx,[bx];lowvol
test al,00000010b;检测H+是否按下
jz next1
;H+代码
cmp dx,500
jae next1
add dx,10
mov bx,offset highvol
mov [bx],dx
next1: test al,00000100b;H-
jz next2
;H-代码
cmp dx,cx
jbe next2
sub dx,10
mov bx,offset highvol
mov [bx],dx
next2: test al,00001000b;L+
jz next3
;L+代码
cmp cx,dx
jae next3
add cx,10
mov bx,offset lowvol
mov [bx],cx
next3: test al,00010000b;L-
jz next4
;L-代码
cmp cx,0
jbe next4
sub cx,10
mov bx,offset lowvol
mov [bx],cx
next4: test al,00011110b
jz endl
call delayms
endl: pop cx
pop bx
pop ax
pop dx
ret
keyproc endp
;蜂鸣器报警处理
almproc proc near
push bx
push ax
push cx
push dx
mov dx,2a0h
mov bx,offset currvol
mov ax,[bx]
mov bx,offset highvol
mov cx,[bx]
cmp ax,cx
jae buzzon
mov bx,offset lowvol
mov cx,[bx]
cmp ax,cx
jbe buzzon
jmp buzzoff
buzzon: mov al,1
out dx,al
jmp exit
buzzoff:mov al,0
out dx,al
exit: pop dx
pop cx
pop ax
pop bx
ret
almproc endp
;延时一秒
dly1s proc near
push bx
push cx
mov bx,200
LP1: mov cx,1000
LP2: loop LP2
dec bx
jnz LP1
pop cx
pop bx
ret
dly1s endp
;将AL的AD值处理为电压值
datproc proc near
push cx
push dx
mov ah,0
mov cx,200
mul cx;AL乘以CX 结果在AX
mov cx,102
mov dx,0
div cx;DX.AX/CX 商在AX 余数在DX
pop dx
pop cx
ret
datproc endp
;将AX的数值显示到屏幕上
strconv proc near
push cx
push dx
push ax
;百位
mov cl,100
div cl;AX除以CL 商在AL中 余数在AH中
add al,'0';转换成ASCII
call lcddat
inc bx
;小数点
mov al,'.'
call lcddat
inc bx
;十位
mov al,ah
mov ah,0;将余数挪到低位 清空高位
mov cl,10
div cl;AX除以CL 商在AL中 余数在AH中
mov dl,ah
add al,'0'
call lcddat
inc bx
;个位
mov al,dl
add al,'0'
call lcddat
pop ax
pop dx
pop cx
ret
strconv endp
;查询等待ADC转换完成
chkbusy proc near
push dx
push ax
rechk: mov dx,298h
in al,dx
test al,1
jnz rechk
pop ax
pop dx
ret
chkbusy endp
;读取AD值到AL
getvol proc near
push dx
mov dx,290h
mov al,0;数据随意
out dx,al;发送一个写信号 启动转换
call chkbusy;等待转换完成
in al,dx;读取AD值到AL
pop dx
ret
getvol endp
;显示BX所指的内存单元中的一段字符串 以$结尾 第一个字节为显示位置
lcdstr proc near
mov al,[bx]
call lcdcmd;设置显示位置
nextchr:inc bx
mov al,[bx]
cmp al,'$'
je gotoret
call lcddat
jmp nextchr
gotoret:ret
lcdstr endp
;将AL的内容写进数据寄存器
lcddat proc near
push dx
mov dx,280h
out dx,al
call delay
mov dx,288h
mov al,00000001b
out dx,al;EN=0 RW=0 RS=1
call delay
mov al,00000101b
out dx,al;EN=1 RW=0 RS=1
call delay
mov al,00000001b
out dx,al;EN=0 RW=0 RS=1
pop dx
ret
lcddat endp
;将AL的内容写进指令寄存器
lcdcmd proc near
push dx
mov dx,280h
out dx,al
call delay
mov dx,288h
mov al,00000000b
out dx,al;EN=0 RW=0 RS=0
call delay
mov al,00000100b
out dx,al;EN=1 RW=0 RS=0
call delay
mov al,00000000b
out dx,al;EN=0 RW=0 RS=0
pop dx
ret
lcdcmd endp
lcdinit proc near
mov al,38h;8线模式 显示2行 5*7点阵
call lcdcmd
mov al,03h;光标归位
call lcdcmd
mov al,06h;光标右移 不移动屏幕
call lcdcmd
mov al,0ch;显示开 无光标 不闪烁
call lcdcmd
mov al,01h;清屏
call lcdcmd
mov al,80h
call lcdcmd
ret
lcdinit endp
delay proc near
push cx
mov cx,50
dlyloop:loop dlyloop
pop cx
ret
delay endp
.data
.stack
END