使用8086制作LCD显示电压表

最近学了微机原理这门课程,收获还是很大的。虽然早就接触过单片机并编写了大量程序,但是对于汇编语言,自己还是一直发怵。正巧,于是就趁着刚学完8086汇编的热乎劲儿,趁热打铁一番。经过思考,我决定用8086驱动我们常见的1602液晶,用外置ADC制作一个电压表。

鉴于8086年代过于久远,找到实际的芯片的难度太高,于是仿真就成了最好的方法。好在,现在对于这种古老的芯片的仿真技术已经非常完善。这一次我选择了Proteus作为仿真软件,配合上MASM32作为汇编工具,制作了这个简单的设计。完整的工程包(仿真图、源程序)可以在文末找到。

本设计具有如下功能:

1、可以读取外部电压,并显示在屏幕上;

2、可以设置电压上下限,并显示在屏幕上;

3、可以通过外部按键调节电压的上下限;

4、当电压超过上下限时,蜂鸣器鸣响报警;

5、电压上下限设置时无法超过输入电压范围(0-5V),并且最高电圧不能低于最低电压。

程序流程图如下:

程序流程图

最重要的部分之一,就是设计8086处理器的外围电路了。好在这些电路课程上都有学习到,8086芯片与外围数字电路连接即可得到8086最小系统与接口译码部分的原理图:

8086最小系统与接口译码部分

接下来是报警与按键部分电路:

报警与按键部分

液晶显示器部分,选择了之前51时代常用的1602液晶。这一款液晶使用8080并口时序,使用8086的IO地址译码电路,即可将其作为一个内存访问,大大简化了程序的设计。这也解开了我刚接触1602液晶时对于它这种奇怪的驱动模式的疑惑:都是时代的产物。液晶显示器与接口电路如下:

液晶显示器与接口

接下来就是ADC部分了。ADC部分也是选择了一块年代久远的芯片:ADC0805。选择这块芯片的原因有二:一是因为它可以在仿真软件中找到,二是因为它也保留着与1602类似的IO结构,特别适合用8086这种只支持古老的并口时序的处理器驱动。ADC、蜂鸣器接口电路与输入接口电路如下:

ADC、蜂鸣器接口电路与输入接口

在仿真程序的过程中,我发现LCD1602在一开始总是无法正常显示,即便我通过示波器确定通信是没有问题的。我第一个想到的就是LCD1602的速度问题。众所周知,51单片机驱动1602都需要加很多的延时才能保证正常显示。本来我以为对于仿真软件来说,这些延时并没有什么必要,但是在加上延时之后,1602突然就可以正常使用了。仿真软件居然可以仿真到如此的细节,为proteus点赞。

接下来就是完成程序编写了。在汇编器和仿真软件没有bug的情况下,这一部分按部就班的来编写就可以了。完整的汇编程序会贴在文末。

程序运行后,首先显示作品名称与制作人信息,数秒过后,进入主界面,如下图所示。第一行显示电压,第二行显示电压报警上下限。此时调整电位器的位置,改变AD的输入电压,即可在屏幕上看到电压的变化。

主界面

如果按下四个按键,即可分别调整电压报警值的上限与下限。四个按钮从上到下的功能分别为电压上限加、电压上限减、电压下限加、电压下限减,每按一次相应的电压变化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
0

留下评论