一.实验目的
掌握输入/输出指令的使用方法,并且完成一个具有复杂程序结构的输入/输出汇编程序。
二.预备知识
1、乐曲简谱中的每个音符及其节拍,在微机中对应了扬声器的发声频率和持续时间。
2、如何使 PC 机的扬声器发出指定频率的声音?下面简单介绍一下 PC 机的发声原理:
IBM-PC 系列机的主机箱装有一个小扬声器,系统板上的定时器 8253(或 8254)利用工作方式
3 产生一定频率信号,通过可编程的并行外围接口芯片 8255(或 8255A)控制其发音。
可编程的并行接口芯片 8255 有三个 8 位的并行端口:A 口、B 口和 C 口。在 IBM 系列微机中,
BIOS 在开机自检后已将 8255 初始化为 A 口和 C 口用于输入,B 口用于输出。B 口的 I/O 端口地址
为
61H
。
由图可见,8255 的 B 口的低两位用来控制扬声器驱动,当
61H 端口的 D0 位为“1”时
,控制
8254 定时器产生驱动扬声器发声的音频信号,该位为“0”则不发信号。8254 有三个定时器,分为
0 号、1 号和 2 号定时器,驱动扬声器的是 2 号定时器,该定时器工作在方式 3,是一个频率发生器,
它负责向扬声器发送指定频率的脉冲信号。
输出端口 61H 的 D1 位为“1”或为“0”时
,将使控制驱动器的与门电路接通或关闭,使 8254
所发出的音频信号能到达驱动器或被阻断。这样通过控制 D1 位的变化,可使扬声器接通和断开,控
制扬声器是否能发出声音。此外,通过控制 D1 位的通断时间,就能发出不同的音长。
故当 8255 输出端口 61H 的 D1 位为“1”时,在 61H 的 D0 位为“1”,8254 发出指定频率
的声音信号的前提下,声音信号通过与门到达驱动器驱动扬声器发声。即是,如要 8255 控制 8254
的 2 号定时器驱动扬声器发声,则需要的汇编命令如下:
OR AL, 00000011B
OUT 61H,AL
关闭扬声器的汇编命令如下:
AND AL, 11111100B
OUT 61H,AL
同时,定时器 8254 的 2 号定时器使用 1.19MHz 的基准频率,故若要 8254 驱动扬声器发出指
定频率的声音,则需要向 2 号定时器的计数常数寄存器(即 I/O 端口 42H)存放基准频率除以指定
频率的商(即 122870H/指定频率),该商需分两次送往 I/O 端口 42H,先送商的低字节,再送商
的高字节。同时,在使用定时器 8254 的 2 号定时器之前,需要初始化,即往 8254 的 2 号定时器
的控制寄存器(即 I/O 端口 43H)写控制字 0B6H:
MOV AL, 0B6H
OUT 43H,AL
3、键盘扫描码
(1)使用 IN AL, 60H 指令将会得到按键的扫描码,并存储在 AL 中。
(2)键盘扫描码包含通码(mark)和断码(break)两种。
(3)当按下某个按键时,产生 mark 码,并产生一次 IRQ1 键盘中断;放开按键时,产生 break
码,并产生一次 IRQ1 键盘中断。因此一个按键从按下到放开,实际上共产生了两次键盘中断。
(4)break 码和 mark 码的关系:break 码是由 mark 码最高位置 1 得来,即:break = mark
+ 0x80。
(5)使用键盘扫描码可以检测某个按键是否被按下或者弹起。
(6)实验所需的按键扫描码如下:

4、调试须知
(1)可在 EMU8086 里面进行程序的编辑和调试,但是最终需要在 DOSBox 里面使用
MASM+LINK 进行编译和链接。注意:MASM 对语法的要求会更加严格。
(2)极个别的 Windows 系统的个人计算机如果在调试过程中,出现代码编写正确但听不到声
音的情况,这是模拟 DOS 的缘故,需要到实地址的 DOS 平台上才能听到声音。建议使用 Ubuntu
(虚拟机亦可)+ DOSBox + MASM 的方式进行程序调试,此种方法不存在以上问题。附 DOSBox
for Ubuntu 官方下载地址:https://packages.ubuntu.com/bionic/dosbox
三.实验内容
试设计一个程序,能够使用键盘中字母键模拟钢琴按键发音。其中,按照字母在键盘中的排列方
式,字母键
z/x/c/v/b/n/m
分别发出低 1—低 7 共 7 个低音音符,字母键
a/s/d/f/g/h/j
分别发
出中 1—中 7 共 7 个中音音符,字母键
q/w/e/r/t/y/u
分别发出高 1—高 7 共 7 个高音音符。
按 ESC
键退出程序
。
当按键按下时持续发音,当按键弹起时停止发音。
四.实验代码
; multi-segment executable file with function calls
data segment
; 定义各音调频率
high dw 524, 587, 659, 698, 784, 880, 988, 0 ;高音
middle dw 262, 294, 330, 349, 392, 440, 494, 0 ;中音
low dw 131, 147, 165, 175, 196, 220, 247, 0 ;低音
pair_1 db 10h,1eh,2ch,90h,9eh,0ach ;键盘值有效范围 起始
pair_2 db 16h,24h,32h,96h,0a4h,0b2h ;键盘值有效范围 结束
frequency dw 0012H, 2870H ;基准频率
data ends
code segment
assume cs:code, ds:data
;子程序
;检查al值是不是在[pair_1[bx],pair_2[bx]]范围内
;结果在cx中,如果在范围内cx=1,否则cx=0
is_in_range proc
mov cl,pair_1[bx]
cmp cl, al
ja out_range
mov cl,pair_2[bx]
cmp al, cl
ja out_range
mov cx, 1
ret
out_range:
mov cx, 0
ret
is_in_range endp
start:
mov ax, data
mov ds, ax
; 初始化声音的子程序
call init_sound
m_loop:
call ch_keypress
cmp al, 01H ; 如果按下Esc则退出
je quit
; 处理不同按键
call pro_keypress
jmp m_loop
quit:
call stop_sound
mov ax, 4C00H
int 21H
init_sound proc
; 初始化8253控制字
mov al, 0B6H
out 43H, al
ret
init_sound endp
ch_keypress proc
in al, 60H
ret
ch_keypress endp
pro_keypress proc
; 忽略无关按键 循环测试每一对值
mov dx,0
in_loop:
add dx,1
mov bx,dx
sub bx,1
call is_in_range
cmp cx,1
je normal
cmp dx,6
jbe in_loop
ret
normal:
; 大于90则是break码,停止发声
cmp al, 90H
jb get_offset
call stop_sound
ret
get_offset:
call cal_offset
call generate_sound
ret
pro_keypress endp
cal_offset:
; 获取音调频率在内存中的偏移量
; si: 0高音 1中音 2低音
mov si, 0
check:
cmp al, 16H
jle finish
sub al, 0EH
inc si
jmp check
finish:
sub al, 10H
; 如果小于0则不对,不能发声
cmp al, 0
jl skip
ret
;子程序
generate_sound proc
; 通过si,al获取频率
mov cl, 4
shl si, cl
shl al, 1
mov ah, 0
mov bx, si
mov si, ax
mov cx, high[bx][si]
mov dx, frequency ; 基准频率高字节
mov ax, frequency[2] ; 基准频率低字节
div cx ; 基准频率除以音调频率
; 商分两次传送
out 42H, al
mov al, ah
out 42H, al
; 发声
or al, 03H
out 61H, al
ret
generate_sound endp
;子程序
stop_sound proc
; 停止发声
and al, 0FCH
out 61H, al
ret
stop_sound endp
skip:
ret
code ends
end start