题目:计算机钢琴程序
1.题目要求:
本项目编写程序,程序运行时使PC机成为一架可弹奏的钢琴。
1)程序首先加载的是菜单界面,有3个功能选项:1是音乐演奏,2是钢琴,ctrl+c是退出程序。
2)当选择1号功能时,会播放一首音乐,音乐播放完自动回到菜单界面。
3)当选择2号功能时,进入钢琴界面,按下数字键1-8时,依次发出8个音调。在钢琴界面时,按下ESC键会返回到菜单界面。
4)在键盘输入非功能键时会提示输入错误。
5)按CTRL和C组合键则退出程序。(字母c不区分大小写)
2.题目分析:
对于题目要求中所提到的内容,总的来看可以分为3个方面,分析如下:第一,对于第一个要求,要建立按键与汇编程序之间的联系并且能够重复输入字符直到按下(ctrl+c),这就涉及到对应中断程序的调用以及i/o端口的使用,另外,还要求按1-8中不同的按键发出不同的声音,这就要求要根据中断的返回值调用执行不同的处理子程序,所以子程序的设计不可缺少。第二方面,在解决第一个要求的基础上,只需输出提示错误信息即可,这就需要在数据段预先设置好字符串并通过中断实现对字符串的整体输出。第三方面,相对来说比较简单,因为输入字符中断的返回值时该字符的ascii,也就是说,只要识别出是ctrl+c组合键的ascii,直接调用中断退出程序即可。
另外,在对子程序的设计中,还要分析如何让计算机根据按键发声。
3.题目设计:
总体流程图如图所示:
4.代码实现:
assume cs:code,ds:data,ss:stack
data segment
ratetable dw 524,588,660,784,880,988,1048;频率表
str db 0ah,0dh,'Welcome to the masm player!',0ah,0dh;提示菜单信息供用户选择
db '-----------------------MENU--------------------',0ah,0dh
db '|',' You could choose 1 to play music |',0ah,0dh
db '|',' You could choose 2 to play the pinao |',0ah,0dh
db '|',' Press the (Ctrl+C) to exit the player |',0ah,0dh
db '-----------------------------------------------',0ah,0dh,'$'
pianoStr db 0ah,0dh,'You could enter number 1-8 ' ;钢琴功能提示信息
db 'to play the masm pinao!',0ah,0dh
db 'Press the (Esc) back to memu',0ah,0dh
db 'Press the (Ctrl+C) to exit',0ah,0dh,'$'
mus_freq dw 262,262,262,196 ;音乐功能频率表
dw 330,330,330,262
dw 262,330,392,392
dw 349,330,294
dw 294,330,349,349
dw 330,294,330,262
dw 262,330,294,196
dw 247,294,262,-1
mus_time dw 3 dup(12,12,25,25),12,12,50
dw 3 dup(12,12,25,25),12,12,50
error1 db 0ah,0dh,'Error:No such option, please re-enter!',0ah,0dh,'$';选择功能时提示错误
error2 db 0ah,0dh,'Error: Only number 1 - 8 are allowed!',0ah,0dh;钢琴提示输入错误
db 'Remind: Please enter the correct number!',0ah,0dh,'$'
data ends
stack segment
db 100 dup(0)
stack ends
code segment
start:
mov ax,data;初始换寄存器
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,100
;*****************************************************************
; 标号段:choice
; 功能:用户进行功能选择
; 入口参数:ds,表示数据段地址
; ss,sp,表示栈段地址
; 出口参数:无
; 调用注意事项:无
;-----------------------------------------------------------------
; 中断:Dos下21h下的09h中断
; 功能:输出一个字符串到标准输出设备上
; 入口参数:ah=09h
; Ds:dx=待输出字符的地址
; 出口参数:无
; 调用注意事项:待显示的字符串以‘$’作为结束标志
;-----------------------------------------------------------------
; 中断:Dos下21h下的01h中断
; 功能:从标准输入设备读入一个字符
; 入口参数:ah=01h
; 出口参数:al=输入字符的ascii
; 调用注意事项:此中断会过滤掉控制字符并回显
;*****************************************************************
c: call clear;清屏子程序
lea dx,str
mov ah,09h;输出str字符串
int 21h
choice:
mov ah,01h ;输入字符
int 21h
cmp al,03h ;al保存的是输入字符的ascii,(ctrl+c)的ascii是03h
je quit ;跳出这个choice循环的条件
cmp al,'1'
je playmusic ;执行播放音乐程序
cmp al,'2'
je playpiano ;执行钢琴子程序
lea dx,error1 ;提示错误信息
mov ah,09h ;输出error1字符串
int 21h
jmp choice ;只要输入数据不是功能按键,就循环执行choice
;退出程序
quit:
mov ax,4c00h
int 21h
;清屏
clear:
mov ax, 3h ;调用10H的3号中断
int 10h
ret
playpiano:
call clear
lea dx,pianoStr;lea指令获取数据段pianoStr字符串的首地址并存到dx寄存器
mov ah,09h ;输出pianoStr字符串
int 21h
a:
mov ah,01h ;输入字符
int 21h
cmp al,03h ;al保存的是输入字符的ascii,(ctrl+c)的ascii是03h
je quit ;跳出这个choice循环的条件
cmp al,1Bh ;Esc的ascii
je c
call piano
jmp a
playmusic:
lea si, mus_freq
lea di, mus_time
call music
jmp choice
;*****************************************************************
; 子程序:piano
; 功能:实现钢琴功能
; 入口参数:al——表示从键盘输入字符的ascii
; 出口参数:无
;调用注意事项:主要是进出子程序前后对子程序中要用到的寄存器进行进栈出栈操作
;*****************************************************************
piano proc
push ax;先将子程序中可能要使用的寄存器进栈,方便使用
push bx
push dx
cmp al,'1' ;输入数字1跳转
je one
cmp al,'2' ;输入数字2跳转
je two
cmp al,'3' ;输入数字3跳转
je three
cmp al,'4' ;输入数字4跳转
je four
cmp al,'5' ;输入数字5跳转
je five
cmp al,'6' ;输入数字6跳转
je six
cmp al,'7' ;输入数字7跳转
je seven
cmp al,'8' ;输入数字8跳转
je eight
jmp wrong ;输入其他字符跳转
wrong: ;按下除了1-8之外的其他非控制按键会提示错误信息
lea dx,error2 ;获取数据段error2字符串的首地址
mov ah,09h ;输出字符串
int 21h
jmp exit_piano
;***************
; 标号段:one--eight
; 功能:实现按下键盘1-8执行不同程序
; 入口参数:无
; 出口参数:bx,表示在数据段标号处的偏移地址
;调用注意事项:无
;*****************************************************************
one:
mov bx,0
jmp output
two:
mov bx,2
jmp output
three:
mov bx,4
jmp output
four:
mov bx,6
jmp output
five:
mov bx,8
jmp output
six:
mov bx,10
jmp output
seven:
mov bx,12
jmp output
eight:
mov bx,14
jmp output
;*****************************************************************
; 标号段:output
; 功能:实现演奏功能
; 入口参数:bx——表示数据段ratetable标号处的偏移量
; 出口参数:无
;调用注意事项:无
;*****************************************************************
output:
mov ax,3280h
mov dx,0012h ;置被除数
div ratetable[bx] ;计算频率值(16位),设置声音的频率
mov bx,ax
mov al,0b6h ;设置定时器工作方式,对8253的定时器2进行初始化,因为定时器2连接扬声器
out 43h,al ;将al的值放到43h端口,43h是8253芯片控制口的端口地址
mov ax,bx ;装入16位的计数值
out 42h,al ;设置低位
mov al,ah
out 42h,al ;设置高位
in al,61h ;读设备控制器端口原值,61h端口与喇叭相连
or al,03h ;bit0和bit1都为1时,打开扬声器和定时器
out 61h,al ;接通扬声器,发声
call delay2
in al,61h ;关闭扬声器声音
and al,0fch
out 61h,al
exit_piano: ;退出钢琴子程序,注意将子程序开始时进栈的寄存器出栈
pop dx ;还要注意出栈顺序要与进栈是顺序相反
pop bx
pop ax
ret
piano endp
;*****************************************************************
; 子程序:music
; 功能:实现播放音乐功能
; 入口参数:si--表示数据段频率表偏移量
; 出口参数:无
; 说明:music子程序演奏结束后自动回到MENU界面,音乐演奏中按下键盘会等到演奏结束执行
;*****************************************************************
music proc;播放音乐
play:
mov dx, [si]
cmp dx, -1
je end_play
call sound
add si, 2
add di, 2
jmp play
;演奏一个音符
;入口参数:si - 要演奏的音符的频率的地址
; di - 要演奏的音符的音长的地址
;*****************************************************************
; 标号段:sound
; 功能:实现演奏功能
; 入口参数:si——表示数据段标号mus_freq处的偏移量
; 出口参数:无
;调用注意事项:无
;*****************************************************************
sound:
push ax
push dx
push cx
;8253 芯片(定时/计数器)的设置
mov al,0b6h ;8253初始化
out 43h,al ;43H是8253芯片控制口的端口地址
mov dx,12h
mov ax,34dch
div word ptr [si] ;计算分频值,赋给ax。[si]中存放声音的频率值。
out 42h, al ;先送低8位到计数器,42h是8253芯片通道2的端口地址
mov al, ah
out 42h, al ;后送高8位计数器
;设置8255芯片, 控制扬声器的开/关
in al,61h ;读取8255 B端口原值
mov ah,al ;保存原值
or al,3 ;使低两位置1,以便打开开关
out 61h,al ;开扬声器, 发声
mov dx, [di] ;保持[di]时长
wait1:
mov cx, 28000
delay1: ;延迟子程序
nop
loop delay1
dec dx
jnz wait1
mov al, ah ;恢复扬声器端口原值
out 61h, al
pop cx
pop dx
pop ax
ret
end_play:
ret
music endp
;*****************************************************************
; 子程序:delay2
; 功能:实现延时效果
; 入口参数:bx——表示数据段ratetable标号处的偏移量
; 出口参数:无
; 说明:由于cpu执行一条语句太快,近似1us,我们没法看清楚,所以通过增加cpu执行语句的数量的方式起到延时的效果
;*****************************************************************
delay2 proc ;延迟子程序,使结果更清晰
push cx
mov cx,60000
delay2_loop:
nop
loop delay2_loop
pop cx
ret
delay2 endp
code ends
end start
5.测试结果: