最近学了微机原理这门课程,收获还是很大的。虽然早就接触过单片机并编写了大量程序,但是对于汇编语言,自己还是一直发怵。正巧,于是就趁着刚学完8086汇编的热乎劲儿,趁热打铁一番。经过思考,我决定用8086驱动我们常见的1602液晶,用外置ADC制作一个电压表。
鉴于8086年代过于久远,找到实际的芯片的难度太高,于是仿真就成了最好的方法。好在,现在对于这种古老的芯片的仿真技术已经非常完善。这一次我选择了Proteus作为仿真软件,配合上MASM32作为汇编工具,制作了这个简单的设计。完整的工程包(仿真图、源程序)可以在文末找到。
本设计具有如下功能:
1、可以读取外部电压,并显示在屏幕上;
2、可以设置电压上下限,并显示在屏幕上;
3、可以通过外部按键调节电压的上下限;
4、当电压超过上下限时,蜂鸣器鸣响报警;
5、电压上下限设置时无法超过输入电压范围(0-5V),并且最高电圧不能低于最低电压。

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

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

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

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