原理图设计
汇编代码
; I/O 端口地址定义
IOY0 EQU 0600H
IOY1 EQU 0640H
IOY2 EQU 0680H
MY8255_A EQU IOY0+00H*2 ; 8255 A 口端口地址
MY8255_B EQU IOY0+01H*2 ; 8255 B 口端口地址
MY8255_C EQU IOY0+02H*2 ; 8255 C 口端口地址
MY8255_MODE EQU IOY0+03H*2 ; 8255 模式控制端口地址
MY8253_COUNT0 EQU IOY1+00H*2 ;8053 计时器0端口地址
MY8253_COUNT1 EQU IOY1+01H*2 ;8053 计时器1端口地址
MY8253_COUNT2 EQU IOY1+02H*2 ;8053 计时器2端口地址
MY8253_MODE EQU IOY1+03H*2 ;8253 模式控制端口地址
MY8259_ODD EQU IOY2
MY8259_EVEN EQU IOY2+01H*2
CODE SEGMENT
ASSUME CS:CODE, DS:DATA,SS:STACK1
START:
MOV AX,STACK1
MOV SS,AX
MOV AX,0000H
MOV DS,AX
;写NMI中断(重置)向量表
MOV AX,OFFSET INTNMI
MOV SI,02H*4
MOV [SI],AX
MOV AX,CS
MOV [SI+2],AX
;写0号中断(慢速)向量表
MOV AX,OFFSET INT0
MOV SI,20H*4
MOV [SI],AX
MOV AX,CS
MOV [SI+2],AX
;写1号中断(快速)向量表
MOV AX,OFFSET INT1
MOV SI,21H*4
MOV [SI],AX
MOV AX,CS
MOV [SI+2],AX
;写2号中断(超时)向量表
MOV AX,OFFSET INT2
MOV SI,22H*4
MOV [SI],AX
MOV AX,CS
MOV [SI+2],AX
;写3号中断(失败)向量表
MOV AX,OFFSET INT3
MOV SI,23H*4
MOV [SI],AX
MOV AX,CS
MOV [SI+2],AX
;这时候再装入数据段,之前让DS段超越是0000(中断向量表特有的要求)
MOV AX,DATA
MOV DS, AX
;8255工作方式
MOV AL,90H ;A输入 B输出 C输出
MOV DX,MY8255_MODE
OUT DX,AL
;8253工作方式 //写在8259前正好ignore掉定时器2因为装载工作字产生的第一次跳变
MOV DX, MY8253_MODE
MOV AL, 00110100B ;定时器0,双字节,方式3
OUT DX, AL
MOV AL,01111000B ;定时器1,双字节,方式4
OUT DX,AL
MOV AL, 10110100B ;定时器2,双字节,方式2
OUT DX,AL
;8259工作方式
MOV AL,13H
MOV DX,MY8259_ODD
OUT DX,AL
MOV DX,MY8259_EVEN
MOV AL,20H
OUT DX,AL
MOV AL,01H
OUT DX,AL
MOV AL,0E0H
OUT DX,AL
RESET:
STI
MOV RESETR,0
MOV SCORER,0
MOV FAILR,0
;8255初始化
;B全1
MOV AL,0FFH ;B输出全1
MOV DX,MY8255_B
OUT DX,AL
;PC7(发声器)无效,数码管,老鼠灯全部有效
MOV AL,00H
MOV DX,MY8255_C
OUT DX,AL
;8253初始化
;计数器1 60秒一次性中断信号
MOV AX,TIME
MOV BX,1000
MUL BX
MOV DX,MY8253_COUNT1
OUT DX,AL
MOV AL,AH
OUT DX,AL
;计数器2 SPEED秒一次老鼠信号
MOV AX,SPEED
MOV BX,1000
MUL BX
MOV DX,MY8253_COUNT2
OUT DX,AL
MOV AL,AH
OUT DX,AL
;主工作循环
A0:
CALL SHOWDATA ;显示数据
CALL SCAN ;扫描键盘
CMP ENDR,1
JE ED
JMP NED
ED:
CLI
MOV DX,MY8255_C
MOV AL, 07FH
OUT DX,AL
MOV DX,MY8255_B
MOV AL,00H
OUT DX,AL
JMP A0
NED:
CMP RESETR,1
JE RESET
JMP A0
;NMI号中断,处理重置,完成
INTNMI PROC
CLI
MOV RESETR,1
MOV ENDR,0
STI
IRET
INTNMI ENDP
; 0号中断,摸鱼模式,完成
INT0 PROC
CLI
CMP MODE, 0
JZ A1
MOV SPEED, 5
CALL SETSPEED
MOV AL,20H
MOV DX,IOY2
OUT DX,AL
STI
A1:
IRET
INT0 ENDP
; 1号中断,鸡血模式,完成
INT1 PROC
CLI
CMP MODE, 1
JZ A2
MOV SPEED, 2
CALL SETSPEED ;修改游戏速度
MOV AL,20H
MOV DX,IOY2
OUT DX,AL
STI
A2:
IRET
INT1 ENDP
; 2号中断,计时结束,完成
INT2 PROC
CLI
MOV ENDR,1
MOV AL,20H
MOV DX,IOY2
OUT DX,AL
STI
IRET
INT2 ENDP
; 3号中断,老鼠
INT3 PROC
CLI
CMP ALIVE, 0
JZ A3
JMP A4
A3: ;若老鼠死了
MOV ALIVE,1;死老鼠复活
MOV SOLVE,1
CALL RAND
MOV AL,20H
MOV DX,IOY2
OUT DX,AL
STI
IRET
A4: ;若老鼠活着
MOV ALIVE,0;杀死老鼠
XOR AL,AL
MOV AL,SOLVE
ADD AL,FAILR
CMP AL, 2
JNA A9
MOV ENDR,1
A9:
MOV FAILR,AL
MOV AL,20H
MOV DX,IOY2
OUT DX,AL
STI
IRET
INT3 ENDP
; 生成0到5的随机数,并将其转换成老鼠放在RATR中
RAND PROC
; 线性同余法参数
MOV AX, [SEED] ; 加载当前种子
MOV BX, 110355 ; 乘数
IMUL BX ; AX = AX * BX,结果存储在 DX:AX
ADD AX, 12345 ; 加增量 c
MOV BX, 6 ; 限制范围到 0-5
XOR DX, DX ; 清空 DX
DIV BX ; (DX:AX) / BX,余数在 DX
MOV SEED, AX ; 更新种子
MOV AL, DL ; 保存灯号(余数)
ADD AL,1
MOV RATR,AL
RET
RAND ENDP
DALLY2 PROC ;防抖延迟
D0:
MOV CX, 8
D1:
MOV AX, 7
D2:
DEC AX
JNZ D2
LOOP D1
DEC DL
JNZ D0
RET
DALLY2 ENDP
; 胜利处理过程
WIN PROC
INC SCORER
MOV SOLVE, 0
WIN_LODE: ;打开蜂鸣器,加载音乐
MOV DX,MY8255_C
MOV AL,11111111B;
OUT DX,AL
MOV SI, OFFSET WIN_FREQ_LIST
MOV DI, OFFSET WIN_TIME_LIST
WIN_PLAY: ;播放音乐
MOV DX, 0FH
MOV AX, 4240H
DIV WORD PTR [SI]
MOV DX, MY8253_COUNT0
OUT DX, AL
MOV AL, AH
OUT DX, AL
MOV DL, [DI]
CALL DALLY3
ADD SI, 2
INC DI
CMP WORD PTR [SI], 0
JE NOTWIN
JMP WIN_PLAY
NOTWIN:
MOV DX,MY8255_C
MOV AL,01111111B;
OUT DX,AL
RET
WIN ENDP
; 失败处理过程
LOSE PROC
INC FAILR
MOV SOLVE, 0
CMP FAILR, 2
JNA NOTED
MOV ENDR, 1
NOTED:
LOSE_LODE: ;打开蜂鸣器,加载音乐
MOV DX,MY8255_C
MOV AL,11111111B;
OUT DX,AL
MOV SI, OFFSET LOSE_FREQ_LIST
MOV DI, OFFSET LOSE_TIME_LIST
LOSE_PLAY: ;播放音乐
MOV DX, 0FH
MOV AX, 4240H
DIV WORD PTR [SI]
MOV DX, MY8253_COUNT0
OUT DX, AL
MOV AL, AH
OUT DX, AL
MOV DL, [DI]
CALL DALLY3
ADD SI, 2
INC DI
CMP WORD PTR [SI], 0
JE NOTLOSE
JMP LOSE_PLAY
NOTLOSE:
MOV DX,MY8255_C
MOV AL,01111111B;
OUT DX,AL
RET
LOSE ENDP
SETSPEED PROC ;设定游戏速度
MOV AX, SPEED
MOV BX,1000
MUL BX
MOV DX, MY8253_COUNT2
OUT DX, AL
MOV AL,AH
OUT DX,AL
RET
SETSPEED ENDP
SHOWDATA PROC ;展示数据到数码管
CALL PUTBUF
CALL DISDATA
RET
SHOWDATA ENDP
DISDATA PROC ;读取缓冲区,数码管显示
MOV SI, 3005H ; 将SI寄存器指向显示缓冲区起始地址
MOV CX,6
AGAIN:
MOV AL,1
DEC CL
ROL AL,CL
INC CL
NOT AL
AND AL,01111111B
MOV DX, MY8255_C ;位选
OUT DX, AL ; 输出显示码到端口C
MOV AL, [SI] ; 读取缓冲区中的字符
MOV BX, OFFSET DTABLE
AND AX, 00FFH ; 获取字符的低字节
ADD BX, AX ; 计算DTABLE的偏移地址
MOV AL, [BX] ; 读取显示码
MOV DX, MY8255_B
OUT DX, AL ; 输出显示码到端口B
CALL DALLY
DEC SI ; 移动到下一个缓冲区位置
LOOP AGAIN ; 继续输出字符
CMP ALIVE,0
JE OMIT
MOV AL,00111111B
MOV DX,MY8255_C
OUT DX,AL
MOV AL, RATR ; 读取老鼠位置
MOV BX, OFFSET DTABLE
AND AX, 00FFH ; 获取字符的低字节
ADD BX, AX ; 计算DTABLE的偏移地址
MOV AL, [BX] ; 读取显示码
MOV DX, MY8255_B
OUT DX, AL ; 输出显示码到端口B
CALL DALLY
OMIT:
RET ; 返回
DISDATA ENDP
PUTBUF PROC ;写入缓冲区
;读计数器1(游戏时间)计数器值
;MOV AL,80H
;MOV DX,MY8253_MODE ;锁存
;OUT DX,AL
;MOV DX,MY8253_COUNT1
;IN AL,DX
;MOV AH,AL
;IN AL,DX
;XCHG AH,AL ;读出计数值
;XOR DX,DX
;MOV BX,1000
;DIV BX ;得到秒数
MOV SI, 3000H
;MOV BL,10
;DIV BL
;此时AH是余数个位,AL是十位
MOV AL,0
MOV AH,0
MOV [SI], AL
MOV [SI+1], AH
XOR AH,AH
MOV AL,SCORER
MOV BL,10
DIV BL
;此时AH是余数个位,AL是十位
MOV [SI+2], AL
MOV [SI+3], AH
XOR AH,AH
MOV AL,FAILR
MOV BL,10
DIV BL
;此时AH是余数个位,AL是十位
MOV [SI+4], AL
MOV [SI+5], AH
RET
PUTBUF ENDP
DALLY PROC ;显示延迟
PUSH CX ; 保存CX寄存器的值
MOV CX, 8 ; 设置延时计数器
T1:
MOV AX, 40 ; 进入内层循环
T2:
DEC AX ; 递减AX
JNZ T2 ; 如果AX不为零,继续循环
LOOP T1 ; 每次循环,CX寄存器减1
POP CX ; 恢复CX寄存器的值
RET ; 返回
DALLY ENDP
DALLY3 PROC ;音乐延时
PUSH CX
PUSH AX
A6:
MOV CX,16
A7:
MOV AX,200
A8:
DEC AX
JNZ A8
LOOP A7
DEC DL
JNZ A6
POP AX
POP CX
RET
DALLY3 ENDP
CLEAR PROC
MOV DX, MY8255_B
MOV AL, 00H
OUT DX, AL ; 清除显示数据
RET
CLEAR ENDP
SCAN PROC ;扫描按键
MOV DX,MY8255_A
IN AL,DX
NOT AL
AND AL, 00111111B
MOV HITR,AL
CMP HITR, 0
JZ A5
CALL DALLY2
CMP HITR,0
JZ A5 ;防抖
JNZ DICIDE
A5:
RET
SCAN ENDP
DICIDE PROC ;决策函数
CMP ALIVE,1
JNE DICIDEL
; 将HITR和RATR加载到AL和BL中
MOV AL, HITR
MOV BL, RATR
DEC BL
; 将RATR转换成对应的位掩码
XOR CX, CX ; 清除CX,用于计数
MOV CL, BL ; 将RATR的值移动到CL作为循环计数
MOV BH, 01H ; 准备初始掩码00000001b
ROL BH,CL
COMPARE:
AND AL, BH ; 比较HITR和转换后的RATR掩码
JNZ DICIDEW ; 如果两个值相等,跳转到WIN
JMP DICIDEL ; 否则跳转到LOSE
DICIDEW:
CALL WIN
RET
DICIDEL:
CALL LOSE
RET
DICIDE ENDP
CODE ENDS
STACK1 SEGMENT PARA STACK
DW 1000H DUP(?)
STACK1 ENDS
DATA SEGMENT
; 数据段定义
;时间相关
TIME DW 60 ; 游戏时间设置 60S
SPEED DW 5 ;老鼠出现时间间隔 5s
;计数相关
;时间计数器由计数器2负责
SCORER DB 1 ; 打中地鼠的数量计数器
FAILR DB 2 ; 失败次数计数器
MODE DB 0 ;游戏模式,0摸鱼模式,1鸡血模式
;重置相关
RESETR DB 0 ;重置表示,1重置
;结束相关
ENDR DB 0 ;结束表示,1结束
;音乐相关
WIN_FREQ_LIST DW 1100, 800,0
LOSE_FREQ_LIST DW 392, 120,0
WIN_TIME_LIST DB 1,1
LOSE_TIME_LIST DB 1,1
;老鼠存在相关
ALIVE DB 0;老鼠存在表示,1存在
;决策相关
RATR DB 0
HITR DB 0
SOLVE DB 0
;数码管相关
DTABLE DB 3FH,06H,5BH,4FH,66H,6DH,7DH,07H,7FH,6FH ; 0~9的显示码
; LCG 参数
SEED DW 12345D ; 生成随机数种子
DATA ENDS
END START
Proteus中Unknown 1-byte opcode / Unknown 2-byte opcode错误的可能解决方案
- 没用的电路要断开,或者连了外设就要写控制方式字
- 数据段要在写完中断向量表之后装载到DS中(这里主要是因为用了DS:SI)
- 使用单片8259也要写ICW4
- 数据段放在代码段后面
- 栈开大一点
- 8086内存给够