Proteus中要对8086输入信号要使用到16*16矩阵键盘,
键盘输入在查阅资料后发现主要有两种方法,第一种是先将行设为输出,列设为输入,判断按键位于哪一列。将行线全部置为低,输入列的电平数据判断是否有列当前为低电平,如果有延时后继续判断该低电平信号是否存在仍然存在记录该列的序号,如果没有继续检测。如果记录了列的数据之后,将行设为输入,列设为输出,判断是否有行当前为低电平,如果有延时一段时间之后继续检测如果该信号仍然存在记录该行的序号,如果没有继续检测,这样按下一个按键之后分别检测行和列的数据。
还有一种方法是每一行一次输出低电平检测该行的所有列是否有低电平即是否有键被按下,如果有延时后继续判断该低电平信号是否存在如果仍然存在记录输入的列键值,如果没有继续扫描下一行。扫描完成一轮后循环检测继续从第一行开始扫描。因为一次扫描的每一行因此行数是已知的,只需要判断列数。
下面讲给出两种方法的代码和利弊:
第一种方法先读取行再读取列:
DATAS SEGMENT
;此处输入数据段代码
IOYO equ 0E000h;对应的端口地址
MY8255_A equ IOYO+00H*4
MY8255_B equ IOYO+01H*4
MY8255_C equ IOYO+02H*4
MY8255_MODE equ IOYO+03H*4
LED db 3fh,06h,5bh,4fh,66h,6dh,7dh,07h
db 7fh,6fh,77h,7ch,39h,5eh,79h,71h,00h
Current_led db 10h
DATAS ENDS
STACKS SEGMENT
;此处输入堆栈段代码
dw 50 dup(?)
top label byte
STACKS ENDS
CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
mov ax,stacks
mov ss,ax
mov sp,offset top
MOV AX,DATAS
MOV DS,AX
;此处输入代码段代码
;写入控制字
Restart:
mov al,10000001B;A口和B口都为输出,C口的高位为列输出,C口的低位为行写入
mov dx,MY8255_MODE
out dx,al
call disp
;列全为低找第几行
Waitkey_row:
mov al,00h
mov dx,MY8255_C
out dx,al;先让列都为低
in al,dx;读取列中的数据
and al,0fh;只有低四位有效
cmp al,0fh
jnc waitkey_row;没有改变,继续检测
;检测到列存在低电平,进行延时
mov cx,0f00h
loop $
mov al,00h
out dx,al
in al,dx
and al,0fh
cmp al,0fh
jnc waitkey_row;两次读入都有数据
mov dl,0;dl用来存储行数据
mov cx,4
Find_row:
shr al,1
jnc record_row;进位标志位0说明就在当前行
inc dl
loop Find_row
Record_row:
mov bl,dl
mov al,10001000B;A口和B口都为输出,C口的高位为列输入,C口的低位为行输出
mov dx,MY8255_MODE
out dx,al
Waitkey_line:
mov al,00h
mov dx,MY8255_C
out dx,al
in al,dx
and al,0f0h
cmp al,0f0h
jnc waitkey_line
mov cx,0f00h
loop $
mov al,00h
out dx,al
in al,dx
and al,0f0h
cmp al,0f0h
jnc waitkey_line
mov cl,4;al在高四位因此要移动到第四位
shr al,cl
mov dl,00h
mov cx,4
Find_line:
shr al,1
jnc Record_line
inc dl
loop find_line
Record_line:
mov al,bl;行的数据保存在bl中,列的数据保存在dl中
shl al,1
shl al,1
add al,dl
mov current_led,al
call disp
jmp restart
Disp proc near;数码管显示部分
mov bl,current_led
mov bh,00h
mov al,LED[bx]
mov dx,MY8255_A
out dx,al
ret
Disp endp
Exit:
MOV AH,4CH
INT 21H
CODES ENDS
END START
第二种方法按行进行扫描:
DATAS SEGMENT
;此处输入数据段代码
IOYO equ 0E000h;对应的端口地址
MY8255_A equ IOYO+00H*4
MY8255_B equ IOYO+01H*4
MY8255_C equ IOYO+02H*4
MY8255_MODE equ IOYO+03H*4
LED db 3fh,06h,5bh,4fh,66h,6dh,7dh,07h
db 7fh,6fh,77h,7ch,39h,5eh,79h,71h,00h
Current_led db 10h
Row_disp db 0feh,0fdh,0fbh,0f7h
DATAS ENDS
STACKS SEGMENT
;此处输入堆栈段代码
dw 50 dup(?)
top label byte
STACKS ENDS
CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
mov ax,stacks
mov ss,ax
mov sp,offset top
MOV AX,DATAS
MOV DS,AX
;此处输入代码段代码
;写入控制字
mov al,10001000B;A为输出信号,C的高位为输入信号列,低位为输出信号行
mov dx,MY8255_mode
out dx,al
call disp
mov bh,00h;从第零行开始扫描
mov bl,00h
Restart:
call disp
Detect_row:
mov dx,MY8255_C
mov al,row_disp[bx]
out dx,al
in al,dx
and al,0f0h
cmp al,0f0h
jnc next_line;无输入直接转到下一行
mov cx,5000h
loop $
in al,dx
and al,0f0h
cmp al,0f0h
jnc next_line
mov dl,0;用于记录该行中列的数据
mov cx,4
Find_line:
shl al,1
jnc record_line;无进位说明是列
inc dl
loop Find_line
Record_line:
mov al,bl;行的数据保存在bl中,列的数据保存在dl中
shl al,1
shl al,1
mov dh,03h
sub dh,dl
add al,dh
mov current_led,al
call disp
;记录之后继续扫描下一行
Next_line:
call delay
add bl,01h
and bl,0fbh
jmp restart
Disp proc near;数码管显示部分
mov bl,current_led
mov bh,00h
mov al,LED[bx]
mov dx,MY8255_A
out dx,al
ret
Disp endp
delay proc near;软件延时程序
push cx
push ax
mov cx,011h
D1:
mov ax,00ffh
D2:
dec ax
jnz d2
loop d1
pop ax
pop cx
ret
delay endp
Exit:
MOV AH,4CH
INT 21H
CODES ENDS
END START
完整电路图:
两种方法的比较,使用的第一种方法经常会出现串行的情况,明明输入的5,5被点亮之后1也被点亮了,或是按下的1点亮的5。一开始考虑是不是进位表示没有被清零,按键时间长之后,判断有按键按下,因为进位标志没有被清零会有这种情况,加入了进位标志清零命令后仍然偶尔会出现这种情况。第一种方法还会出现这种情况,就是只按了一次某一个键可是8086确记为多次,因此要将两次检测的间隔试剂设置得比较长。
第二种方法不会出现第一种方法的问题,第二种方法的要调整好两次扫描的间隔时间和两行扫描的间隔时间,不然会出现按键之后没有被检测到的情况。
个人觉得第二种方法的检测更加准确。
如果输入的数据是一组数放在一个数组中,为了防止误按的情况可以检测这一次输入的数据和上一次输入的数据是否相同。
比如这样,数组为seq,这一次的数据放在al中,num为seq中数据的长度:
compare:
mov bl,num;num表示数据的长度
dec bl;上一次的数据
mov dl,seq[bx];上一次的数据存储在dl中,seq的末尾
cmp dl,al;这一次的数据存储在al中
je stop1;如果相等退出,不记录这一次的结果