在复习微机原理硬件考试的时候给本学期的附加题项目写了一下批注,在这里顺便简单分享一下我的思路。
题目如下:用8086、8255、8254(8253)实现电子跑表,分别显示小时、分钟、秒的计数,60秒后分钟进一,60分钟后小时进一。使用按键控制秒表的状态,按下一个键时开始计时,按下另一个键时停止计时,按下又另一个键时退出程序。8255的地址为320H、324H、328H、32CH。8254的地址为300H、304H、308H、30CH。(题目描述可能不太清晰,大致是这样)
我的代码:
.486 ;软件要求,根据实际情况编写
CODES SEGMENT USE16
ASSUME CS:CODES
ORG 1000H
START:
MOV DX,32CH ;8255
MOV AL,10001001B ;C IN, A\B OUT
OUT DX,AL
MOV DX,30CH ;8254初始化,选用方式3,生成周期为1s的时钟信号
MOV AL,00110110B
OUT DX,AL
MOV DX,300H
MOV AX,47000 ;8254收到的频率为47000hz
OUT DX,AL
MOV AL,AH
OUT DX,AL ;1S
K1:
MOV CX,0
CALL AGAIN
CALL CHECK1 ;等待键盘输入信息
CMP AL,8EH ;当键盘0按下时,开始计时
JZ BEGIN1
CMP AL,8DH ;当键盘1按下时,停止计时
JZ STOP
CMP AL,8BH ;当键盘2按下时,退出
JZ QIUT
JMP K1
BEGIN1: ;计数按键
MOV CX,39590D ;预设值,可以更改
T1:
CALL AGAIN ;显示现在寄存的时间值
CALL CHECK1 ;检测键盘情况
CMP AL,8DH ;当键盘1按下时,停止计时
JZ STOP
CMP AL,8BH ;当键盘2按下时,退出
JZ QIUT
MOV DX,328H ;C口输入
IN AL,DX ;时钟信号由c0口输入
TEST AL,01
JNZ T1 ;判断时钟0信号是否到来
T2:
CALL AGAIN ;显示现在寄存的时间值
CALL CHECK1 ;检测键盘情况
CMP AL,8DH ;当键盘1按下时,停止计时
JZ STOP
CMP AL,8BH ;当键盘2按下时,退出
JZ QIUT
MOV DX,328H
IN AL,DX
TEST AL,01 ;c口输入检测时钟1信号是否到来
JZ T2 ;时钟仍然为0,保持原计数值并显示,继续下一次检测
INC CX ;时钟1到来,说明1s已经过去,计数值加一,跳转到显示程序
JMP T1
STOP: ;停止按键
CALL AGAIN ;显示现在寄存的值
CALL CHECK1 ;检测键盘输入情况,之后判断是否有功能键被按下
CMP AL,8EH ;当键盘0按下时,重新从预设值开始计时
JZ BEGIN1
CMP AL,8DH ;当键盘1按下时,开始计时
JZ T1
CMP AL,8BH ;当键盘2按下时,退出
JZ QIUT
MOV DX,328H ;C口输入
IN AL,DX ;时钟信号由c0口输入
TEST AL,01
JNZ STOP ;判断时钟0信号是否到来
STOP1:
CALL AGAIN
CALL CHECK1
CMP AL,8EH
JZ BEGIN1
CMP AL,8DH
JZ T1
CMP AL,8BH
JZ QIUT
MOV DX,328H
IN AL,DX
TEST AL,01 ;c口输入检测时钟1信号是否到来
JZ STOP1 ;时钟仍然为0,保持原计数值并显示,继续下一次检测
JMP STOP ;时钟1到来,说明1s已经过去,计数值加一,跳转到显示程序
;理论上来讲STOP1这部分可以不写,但我记得在硬件实操时可能由于延时不够长导致检测到死循环而崩溃
QIUT: MOV CX,0H ;退出按键
CALL AGAIN ;显示寄存的值
JMP WT ;死循环终止程序
WT: JMP WT
AGAIN PROC ;时间计算与显示子程序
MOV AX,CX
CMP AX,65535D ;判断是否超寄存器范围
JA QIUT ;超出则直接退出
MOV DX,0H ;清零DX(原因见下)
MOV BX,3600D ;BX=3600S=1H
DIV BX ;此处DIV的意义为DX:AX/BX,也解释了上面DX清零的原因
PUSH DX ;DX中为计算结果的余数,压栈保存
MOV BL,10D ;BL=10
DIV BL ;此处DIV的意义为AX/BL,AX为前面除法结果的商,运算后AL为商,即小时的十位,AH为余数,即小时的个位
PUSH AX ;压栈保存AX
MOV AH,20H ;AH给数码管位选LED6
CALL DISP ;显示小时的十位
POP AX ;弹栈AX,根据FILO,弹出的AL为商,即小时的十位,AH为余数,即小时的个位
MOV AL,AH ;个位数字送AL
MOV AH,10H ;位选LED5
CALL DISP ;显示小时个位
POP DX ;弹栈,弹出余数
MOV AX,DX
MOV BL,60D ;BL=60S=1MIN
DIV BL ;AX/BL 运算后AL为商,为分钟数,AH为余数,为秒数
PUSH AX ;压栈保存除法结果
MOV BL,10D ;BL=10
AND AH,0
DIV BL ;AX/BL 运算后AL为商,为分钟的十位,AH为余数,为分钟的个位
MOV BX,AX
PUSH BX ;压栈保存分钟数个位
MOV AH,08H ;位选LED4
CALL DISP ;显示分钟十位
POP BX ;弹栈
MOV AL,BH
MOV AH,04H ;位选LED3
CALL DISP ;显示分钟个位
POP AX ;弹栈秒数
MOV AL,AH
MOV BL,10D ;BL=10
AND AH,0
DIV BL ;AX/BL AL为商为秒数的十位,AH为余数,为秒数的个位
PUSH AX
MOV AH,02H ;位选LED2
CALL DISP ;显示秒数十位
POP AX
MOV AL,AH
MOV AH,01H ;位选LED1
CALL DISP ;显示秒数个位
RET
AGAIN ENDP
DLY PROC ;延时程序
PUSH CX ;压栈cx
MOV CX,4FH ;根据硬件情况选择cx的值
DELY:
LOOP DELY
POP CX
RET
DLY ENDP
DISP PROC ;显示程序
LEA BX,SEGPT ;字型码表首地址给bx
XLAT ;AX=[BX+AL],执行该操作后AL=原AL数字的字形型码
MOV DX,320H
OUT DX,AL
MOV DX,324H
MOV AL,AH
OUT DX,AL ;位选,字型码输出
CALL DLY ;延时
RET
DISP ENDP
CHECK1 PROC ;检测键盘程序
MOV AL,00H
MOV DX,328H ;只用一列键盘所以不用进行扫描,直接读键特征值
IN AL,DX
AND AL,0F0H ;键盘信息输入到c口高四位
MOV AH,80H
PUSH CX
MOV CX,4
SHR AL,CL ;将AL高四位移到低四位,即键盘信息
POP CX
OR AL,AH ;键盘特征值为0:8EH 1:8DH 2:8BH
RET
CHECK1 ENDP
SEGPT DB 3FH,06H,5BH,4FH,66H,6DH,7DH,07H,7FH,6FH ;字型码
CODES ENDS
END START
硬件连接我用proteus简单演示了一下,其中地址译码模块我没有放进去,根据自己需求做就好了(我的Proteus有点问题跑不了仿真就不演示仿真了)
我收到很多私信向我询问原理图,先说明一件事,proteus仿真我并没有得到比较好的仿真结果,因为我没有时间去搭建和调试8086的系统,而且我也不打算做这件事了。所以我没有展示完整的原理图,因为我害怕有朋友直接照抄结果抄不出来...
其次,我上面的原理图片的主要目的是为了展示我这个小实验的接线情况,这比我直接甩一张实验箱接线图要直观得多。所以上面的图包含了所有的硬件接线,只是左边我省略了一个8086系统,大家不知道怎么搭的话,同站上搜一下,也有很多教程的。
最后,别再找我要原理图啦,确实没有。但是大家想要进一步做仿真的话,欢迎和我讨论,有空我会回复😀