STACK SEGMENT
DB 1024 DUP(?)
STACK ENDS
DATA SEGMENT
STR0 DB 13,10,3 DUP(9),'
[-1-2-3-4-5-6-7-]$'
STR1 DB 13,10,3 DUP(9),'H [ Q W E R T Y U ]$'
STR2 DB 13,10,3 DUP(9),'M [ A S D F G H J ]$'
STR3 DB 13,10,3 DUP(9),'L [ Z X C V B N M ]$'
SAVE DW 1024 DUP(0FFFFH)
TUNE DW 220,247,278,294,330,371,416
DW 441,495,556,589,661,742,833
DW 882,990,1112,1178,1322,1484,1655
;低音
;中音
;高音
;按键状态缓存,用于判断是否有手动按键,1 为有,0 为无
;按键编号缓存,用于提取对应的按键位置
;延时缓存,用于播放时存入 BX 中来计数
KEY_TUNE DW 0
KEYNUM DW 1
DELAY_TIME DW 7000
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE ,DS:DATA
START:MOV AX,DATA
MOV DS,AX
MOV SI,OFFSET STR0
CALL SHOW
MOV SI,OFFSET STR1
CALL SHOW
MOV SI,OFFSET STR2
CALL SHOW
MOV SI,OFFSET STR3
CALL SHOW
MOV CX,0
MOV SI,OFFSET SAVE
NEW_SCAN:
MOV AH,0
INT 16H
MOV DL,AL
ASCII 码来检测
CMP AH,01H
JNE FLAG_SCAN
CALL EXIT
FLAG_SCAN:
KEY_L1:
CMP DL,'Z'
JNE KEY_L2
;读键盘,键盘扫描码存入 AH,ASCII 码存入 AL
;AL 中的 ASCII 码存入 DL,为了便于修改,通过
;是否是结束键,ESC 键作为结束键,其扫描码为 01H
;若不是继续检测
;若是则退出
;是否是 Z
;不是则继续扫描
MOV KEYNUM,0
MOV KEY_TUNE,1
JMP SCAN_END
KEY_L2:
CMP DL,'X'
JNE KEY_L3
MOV KEYNUM,2
MOV KEY_TUNE,1
JMP SCAN_END
KEY_L3:
CMP DL,'C'
JNE KEY_L4
MOV KEYNUM,4
MOV KEY_TUNE,1
JMP SCAN_END
KEY_L4:
CMP DL,'V'
JNE KEY_L5
MOV KEYNUM,6
MOV KEY_TUNE,1
JMP SCAN_END
KEY_L5:
CMP DL,'B'
JNE KEY_L6
MOV KEYNUM,8
MOV KEY_TUNE,1
JMP SCAN_END
KEY_L6:
CMP DL,'N'
JNE KEY_L7
MOV KEYNUM,10
MOV KEY_TUNE,1
JMP SCAN_END
KEY_L7:
CMP DL,'M'
JNE KEY_1
MOV KEYNUM,12
MOV KEY_TUNE,1
JMP SCAN_END
KEY_1:
CMP DL,'A'
JNE KEY_2
MOV KEYNUM,14
;是则改变按键编号缓存
;改变按键状态缓存
;键扫描结束
;是否是 X
;是否是 Y
;是否是 V
;是否是 B
;是否是 N
;是否是 M
;是否是 A
MOV KEY_TUNE,1
JMP SCAN_END
KEY_2:
CMP DL,'S'
JNE KEY_3
MOV KEYNUM,16
MOV KEY_TUNE,1
JMP SCAN_END
KEY_3:
CMP DL,'D'
JNE KEY_4
MOV KEYNUM,18
MOV KEY_TUNE,1
JMP SCAN_END
KEY_4:
CMP DL,'F'
JNE KEY_5
MOV KEYNUM,20
MOV KEY_TUNE,1
JMP SCAN_END
KEY_5:
CMP DL,'G'
JNE KEY_6
MOV KEYNUM,22
MOV KEY_TUNE,1
JMP SCAN_END
KEY_6:
CMP DL,'H'
JNE KEY_7
MOV KEYNUM,24
MOV KEY_TUNE,1
JMP SCAN_END
KEY_7:
CMP DL,'J'
JNE KEY_H1
MOV KEYNUM,26
MOV KEY_TUNE,1
JMP SCAN_END
KEY_H1:
CMP DL,'Q'
JNE KEY_H2
MOV KEYNUM,28
MOV KEY_TUNE,1
;是否是 S
;是否是 D
;是否是 F
;是否是 G
;是否是 H
;是否是 J
;是否是 Q
JMP SCAN_END
KEY_H2:
CMP DL,'W'
JNE KEY_H3
MOV KEYNUM,30
MOV KEY_TUNE,1
JMP SCAN_END
KEY_H3:
CMP DL,'E'
JNE KEY_H4
MOV KEYNUM,32
MOV KEY_TUNE,1
JMP SCAN_END
KEY_H4:
CMP DL,'R'
JNE KEY_H5
MOV KEYNUM,34
MOV KEY_TUNE,1
JMP SCAN_END
KEY_H5:
CMP DL,'T'
JNE KEY_H6
MOV KEYNUM,36
MOV KEY_TUNE,1
JMP SCAN_END
KEY_H6:
CMP DL,'Y'
JNE KEY_H7
MOV KEYNUM,38
MOV KEY_TUNE,1
JMP SCAN_END
KEY_H7:
CMP DL,'U'
JNE SCAN_END
MOV KEYNUM,40
MOV KEY_TUNE,1
SCAN_END:
CMP DL,'P'
JNZ HAND_PLAY
CALL PLAY
JMP NEW_SCAN
HAND_PLAY:
CMP KEY_TUNE,1
0 为无
;是否是 W
;是否是 E
;是否是 R
;是否是 T
;是否是 Y
;是否是 U
;判断是否按下了自动播放键
;若不是,继续扫描
;若是,调用演奏音乐子程序
;演奏结束,返回 RESTART
;取按键状态缓存值,判断是否手动演奏,1 为有,
JNZ AGAIN;若不是,重新扫描有无键按下
MOV AX,KEYNUM
ADD CX,2
MOV BX,CX
MOV WORD PTR[SI+BX-2],AX
JMP SOUND
AGAIN: JMP NEW_SCAN
SOUND: LEA BX,TUNE
MOV SI,KEYNUM
MOV DI,[BX+SI]
DI
CALL PLAY_MIU
JMP NEW_SCAN
;装入对应频率段地址到 BX 中
;取按键编号缓存值,存入 SI
;取对应编号的音符频率值,存入
;调用手动发声程序
MOV AH,1 ;直到有字符输入才结束
INT 21H
MOV AX,3 ;获取光标信息
INT 10H
MOV AH,4CH ;不带返回码结束
INT 21H
PLAY_MIU PROC NEAR
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH DI
MOV KEY_TUNE,0
MOV AL,0B6H
OUT 43H,AL
;在 8253 的 43 端送出一个控制字 0B6H(10110110B)
;从而对定时器 2 初始化,准备接收计数初值
MOV DX,12H
MOV AX,348CH
;1.1931MHZ/给定频率=12348H/给定频率=计数初值
;12348H 存入 DX:AX 中,作 32 位运算,商存入 AX,余数存入
DX
AH
DIV DI
OUT 42H,AL
MOV AL,AH
OUT 42H,AL
IN AL,61H
MOV AH,AL
;DI 中存放给定频率
;给定时器 2 写入 AX 中存放的计数初值
;开扬声器
;把 8255 输出端的 61H 的 0、1 两位置 1,并把端口状态存入
OR AL,3
;打开扬声器
OUT 61H,AL
WAIT1: CALL WAITF
;调用延时
IN AL,60H
码+80H>80H
TEST AL,80H
JZ WAIT1
MOV AL,AH
OUT 61H,AL
POP DI
POP DX
POP CX
POP BX
POP AX
RET
PLAY_MIU ENDP
WAITF PROC NEAR
PUSH AX
PUSH CX
MOV CX,DELAY_TIME
WAITF1:
;读取键盘扫描码,按下和放开都有相应的扫描码,放开码=按下
;AL 和 80H 相与,影响标志位
;未放开则继续延时
;恢复 8255 端口 61H 的状态,关扬声器
;恢复寄存器状态
;AX 入栈
;T=DELAY_TIME*15.08US
;读 8255 的 PB 端口
;检查 PB4 口
;判断 AL 变化否
;等待 AL 变化
;保存 PB4 的新状态(AL 中)
;CX-1 不为 0 则回到 WAITF
;AX 出栈
IN AL,61H
AND AL,10H
CMP AL,AH
JE WAITF1
MOV AH,AL
LOOP WAITF1
POP CX
POP AX
RET
WAITF ENDP
PLAY PROC
LEA BX,TUNE ;装入对应频率段地址到 BX 中
READ: MOV SI,WORD PTR[SAVE]
存入 SI
MOV DI,[BX+SI]
ADD SAVE ,2
CMP WORD PTR[SAVE],0FFFFH
JZ TT
PUSH AX
;取按键编号缓存值,
PUSH BX
PUSH CX
PUSH DX
MOV AL,0B6H
OUT 43H,AL
;在 8253 的 43 端送出一个控制字 0B6H(10110110B)
;从而对定时器 2 初始化,准备接收计数初值
MOV DX,12H
MOV AX,348CH
;1.1931MHZ/给定频率=12348H/给定频率=计数初值
;12348H 存入 DX:AX 中,作 32 位运算,商存入 AX,余数存入
DX
AH
DIV DI
OUT 42H,AL
MOV AL,AH
OUT 42H,AL
IN AL,61H
MOV AH,AL
OR AL,3
OUT 61H,AL
CALL WAITF
CALL WAITF
CALL WAITF
CALL WAITF
CALL WAITF
CALL WAITF
CALL WAITF
CALL WAITF
CALL WAITF
CALL WAITF
MOV AL,AH
OUT 61H,AL
;DI 中存放给定频率
;给定时器 2 写入 AX 中存放的计数初值
;开扬声器
;把 8255 输出端的 61H 的 0、1 两位置 1,并把端口状态存入
;打开扬声器
;恢复 8255 端口 61H 的状态,关扬声器
POP DX ;恢复寄存器状态
POP CX
POP BX
POP AX
LOOP READ
TT: MOV CX,0
JMP NEW_SCAN
RET
PLAY ENDP
EXIT PROC NEAR
MOV AH,4CH
INT 21H
RET
EXIT ENDP
SHOW PROC
MOV DX,SI
MOV AH,9
INT 21H
RET
SHOW ENDP
CODE ENDS
END START