dw 2 dup(330,349,392)
dw 2 dup(415,370,330,262)
dw 2 dup(249,392,262)
dw 0ffffh 少了一个结束标志,自然会溢出memory,不一定要用0ffff,其他也行。
OUT BIT P2.3 //该引脚接蜂鸣器ORG 0000H
LJMP START
ORG 000BH
INC 20H 中断服务,中断计数器加1
MOV TH0,#0D8H
MOV TL0,#0EFH 12M晶振,形成10毫秒中断
RETI
START:
MOV SP,#50H
MOV TH0,#0D8H
MOV TL0,#0EFH
MOV TMOD,#01H
MOV IE,#82H
MUSIC0:
NOP
MOV DPTR,#DAT 表头地址送DPTR
MOV 20H,#00H 中断计数器清0
MOV B,#00H 表序号清0
MUSIC1:
NOP
CLR A
MOVC A,@A+DPTR 查表取代码
JZ END0 是00H,则结束
CJNE A,#0FFH,MUSIC5
LJMP MUSIC3
MUSIC5:
NOP
MOV R6,A
INC DPTR
MOV A,B
MOVC A,@A+DPTR 取节拍代码送R7
MOV R7,A
SETB TR0 启动计数
MUSIC2:
NOP
CPL OUT
MOV A,R6
MOV R3,A
LCALL DEL
MOV A,R7
CJNE A,20H,MUSIC2 中断计数器(20H)=R7否?
不等,则继续循环
MOV 20H,#00H 等于,则取下一代码
INC DPTR
INC B
LJMP MUSIC1
MUSIC3:
NOP
CLR TR0 休止100毫秒
MOV R2,#0DH
MUSIC4:
NOP
MOV R3,#0FFH
LCALL DEL
DJNZ R2,MUSIC4
INC DPTR
LJMP MUSIC1
END0:
NOP
MOV R2,#64H 歌曲结束,延时1秒后继续
MUSIC6:
MOV R3,#00H
LCALL DEL
DJNZ R2,MUSIC6
LJMP MUSIC0
DEL:
NOP
DEL3:
MOV R4,#02H
DEL4:
NOP
DJNZ R4,DEL4
NOP
DJNZ R3,DEL3
RET
NOP
DAT:
db 26h,20h,20h,20h,20h,20h,26h,10h,20h,10h,20h,80h,26h,20h,30h,20h
db 30h,20h,39h,10h,30h,10h,30h,80h,26h,20h,20h,20h,20h,20h,1ch,20h
db 20h,80h,2bh,20h,26h,20h,20h,20h,2bh,10h,26h,10h,2bh,80h,26h,20h
db 30h,20h,30h,20h,39h,10h,26h,10h,26h,60h,40h,10h,39h,10h,26h,20h
db 30h,20h,30h,20h,39h,10h,26h,10h,26h,80h,26h,20h,2bh,10h,2bh,10h
db 2bh,20h,30h,10h,39h,10h,26h,10h,2bh,10h,2bh,20h,2bh,40h,40h,20h
db 20h,10h,20h,10h,2bh,10h,26h,30h,30h,80h,18h,20h,18h,20h,26h,20h
db 20h,20h,20h,40h,26h,20h,2bh,20h,30h,20h,30h,20h,1ch,20h,20h,20h
db 20h,80h,1ch,20h,1ch,20h,1ch,20h,30h,20h,30h,60h,39h,10h,30h,10h
db 20h,20h,2bh,10h,26h,10h,2bh,10h,26h,10h,26h,10h,2bh,10h,2bh,80h
db 18h,20h,18h,20h,26h,20h,20h,20h,20h,60h,26h,10h,2bh,20h,30h,20h
db 30h,20h,1ch,20h,20h,20h,20h,80h,26h,20h,30h,10h,30h,10h,30h,20h
db 39h,20h,26h,10h,2bh,10h,2bh,20h,2bh,40h,40h,10h,40h,10h,20h,10h
db 20h,10h,2bh,10h,26h,30h,30h,80h,00H
END
早期的PC系列机中有一个专门用于定时的集成电路,型号是8253/8254。它有三个通道,第一个通道用于控制系统时钟正常运转;第二个通道用于存储器刷新;这两个通道与我们现在讨论的问题无关。第三个通道是最有意思的,它通过一组电路与喇叭相联。图4-1所示即为PC机中完整的发声电路。定时器通道3的G端与61H端口的bit0位相联,如果将61H端口的bit0位置成1,那么定时器通道3就被启动,此时将有一组信号从OUT端输出,信号的频率可以用程序控制;若61H端口bit0位为0,则定时器被关闭,OUT端就会恒定为1
此电路用在这里相当一个"可控开关",如果将61H端口的bit0、bit1位都置成1,则相当于既打开了定时器又打开了开关,这时候定时器产生的声音信号就会送到放大器推动喇叭发声;若将bit0位置0,则定时器关闭,此时OUT端为1,这时候如果连续改变bit1位的状态,也可以从喇叭中听到声音,这就是我们在第二章中所用的方法;若将bit1位置0,则开关关闭,此时即使打开定时器也不能听到声音。
这一点可以通过DEBUG加以验证:进入DEBUG,在"-"后打入"O61 3",即可听到喇叭发出连续的叫声。(在纯DOS下实验)
向61H端口输出"03",相当于打开定时器和开关,此时将有连续的声音发出,这个声音的频率约是896Hz,和我们刚开机时听到的蜂鸣音频率一样。
有趣的是声音一旦发出就不会停止,而且不干扰用户的任何 *** 作。
停止这种声音的唯一方法就是进入DEBUG,打入命令"O61 0(也可以是1或2)"。之所以有这种现象是因为定时器的工作并不需CPU直接参与,CPU只要给定时设定好工作状态和频率值并打开定时器,此时定时器就会自主工作,CPU即可去做别的事情。这个特性十分有用,它是实现"背景音乐"的前提。
那么如何改变声音的频率呢?请注意定时器的通道3还有一个输入端CLK,这一端输入了一个固定的信号,频率是1193181.6Hz。输出信号与此信号具有如下关系:
--------------------------------------------------------------------------------
F(OUT)=F(CLK)/N
--------------------------------------------------------------------------------
其中N是一个16bit数据,它的值可以由程序设定。方法很简单:将此16位数据分成高、低两个8位,先把低8位送至42H端口,紧接着再把高8位送至42H端口,输出信号的频率就会改变。我们可以试一下:
C:\ASM\>DEBUG[Enter]
-O61 3[Enter]
-O42 0[Enter]
-O42 3[Enter]
设定新的N值是300H,对应的F(OUT)是1193191.6/300H=1553Hz。声音马上变尖了。
有一点必须说明,定时器具有多种工作状态,并非每种工作状态都能产生声音,所以当我们想通过定时器产生声音时,我们应首先"初始化"定时器,为其建立正确的工作状态。初始化定时器并不复杂,向端口43H输出数据0B6H即可。这个数据的二进制形式是10110110,有些书籍把这个数称为"幻数"(MAGIC BYTE)。
有了上面介绍的这些知识,我们就可以编程控制定时器发出给定频率的声音。程序PROG6可以使喇叭发出1000Hz的声音
------------------------------------------------
0A3E:0100 MOV AL,B6 ;AL寄存器装入定时器初始化设置码
0A3E:0102 OUT 43,AL ;将设置码输出到43H端口 初始化
0A3E:0104 MOV AX,04A9 1193181.6Hz/1000=1193hz =04A9 hexadecimal AX寄存器置入N值
0A3E:0107 OUT 42,AL ;将N值分两次输出到42H端口 因为是8位
0A3E:0109 MOV AL,AH
0A3E:010B OUT 42,AL
0A3E:010D IN AL,61 ;取得61H端口的当前状态
0A3E:010F PUSH AX ;入栈
0A3E:0110 OR AL,03 ;0111
0A3E:0112 OUT 61,AL ;打开定时器及电子开关
0A3E:0114 MOV AH,01 ;AH = 01h Return: AL = character read 等待输入
character is echoed to standard output(回显)
0A3E:0116 INT 21
0A3E:0118 POP AX ;恢复61H
0A3E:0119 OUT 61,AL
0A3E:011B RET
0A3E:011C
我们已经讨论了如何通过定时器的通道3发出确定频率的声音,这一节我们要一起学习怎样精确地定时,这样才能解决演奏音乐的问题。
PC中的定时电路有三个通道,通道3用于发声,通道1用于控制系统内部的时钟。大家都十分清楚用DOS的"TIME"命令可以观察并修改系统内部的一个时钟,这个时钟之所以能连续运转主要依靠定时器的通道1。
通道1的工作方式和通道3一样,但是系统启动时设定其发出一个频率固定为18.2Hz的信号,这个信号直接送到系统中的"中断控制器"。每一个"Hz"都产生一个硬件中断,一般称这个硬中断为"IRQ0",对应的中断号是08H。也就是说,当计算机启动后,我们的机器看上去十分平静,但实际上CPU非常忙碌。在定时器的控制下每隔55毫秒就要执行一个08H号中断,这个中断的主要工作就是连续地计数。
在内存"0040H:006CH"处有四字节的存储空间专门用于保存计数值,CPU每执行一次08H中断,这四字节的计数值就被加1,不难算出这个计数值每增加1091后时间恰好过了1分钟,每增加65454后时间恰好过了1小时。系统内部的时钟之所以能准确走时,靠得就是08H中断和这四字节的计数值。因此我们要想精确的定时,必须依靠时钟计数值才行
---一个能准确发出1000Hz声音的程序,声音持续时间为5秒钟---------------
PORTB equ 61H
code segment
assume cs:code,ds:code
org 100h
main proc near
mov al,10110110b ;初始化定时器
out 43h,al
mov ax,4a9h ;设置N值为04A9H
out 42h,al
mov al,ah
out 42h,al
in al,PORT_B ;打开定时器及与门
or al,3
out PORT_B,al
;------------------以下为定时部分---------------
mov ah,0 ;选择1AH中断的0号功能
int 1ah ;调用1AH中断取得当前时钟计数
add dx,91 ;在当前时钟计数上加91---5秒
mov bx,dx ;保存定时终了时的计数值
delay: int 1ah ;两次调用1AH中断取得时钟计数值
cmp dx,bx ;到达定时终了时的计数值了吗?
jne delay ; 没有到达,则返回DELAY处继续
;----------------------------------------------------
in al,PORT_B ;定时终止,关闭定时器及与门
and al,0fch 1111 1100
out PORT_B,al
int 20h ;结束程序
main endp
code ends
参考资料:PC机汇编语言实践精解/李春生编著。
datasegment
freqdw196,220
dw262,262,262,262,262,220,196
dw262,262,262,262,294,262,220,262
dw294,294,294,294,294,262,220
dw294,294,294,294,330,294,330,392
dw440,440,392,440,392,330
dw294,294,330,294,262,220,196,220
dw262,262,262,262,262,220
dw262,196,220
dw440,440,392,440,524,440
dw392,330,294,262,220,196,220
dw262,262,262,262,294,262
dw262,330,392
dw440,440,440,440,524,440
dw392,392,392,440,392,330,294
dw262,262,262,262,294
dw330,330,294
dw262,262,262,262,524,440
dw392,392,392,440,392,330,392
dw440,524,524,440,392
dw392,330,392
dw440,440,440,440,524,440
dw392,392,392,440,392,330,294
dw262,262,262,262,392
dw330,330,294
dw262,262,262,262,294,330
dw392,392,330,392,330,392
dw440
dw9,9,196,660,294,294,262
dw262,-1
timedw400,400
dw400,200,400,400,800,400,400
dw400,200,400,200,200,800,400,400
dw400,200,400,400,800,400,400
dw400,200,400,200,200,800,400,400
dw400,800,400,800,400,400
dw400,200,200,400,400,800,400,400
dw400,200,400,400,800,800
dw1600,800,800
dw400,800,400,800,400,400
dw400,400,400,400,800,400,400
dw400,800,400,800,400,200
dw2400,400,400
dw400,800,400,800,400,400
dw400,800,200,200,800,400,400
dw400,800,400,800,800
dw2400,400,400
dw400,800,400,800,400,400
dw400,800,200,200,800,400,400
dw800,400,800,400,200
dw2400,400,400
dw400,800,400,800,400,400
dw400,800,200,200,800,400,400
dw400,800,400,800,800
dw2400,400,400
dw400,800,400,800,400,400
dw400,800,400,800,400,400
dw3200
dw800,400,400,400,400,400,400
dw4000
dataends
codesegment
assumecs:code,ds:data
mainprocfar
start:movax,data
movds,ax
movsi,offsetfreq
movdi,offsettime
l1: movcx,[si]
cmpcx,-1
jeexit
movbx,[di]
callgensound
addsi,2
adddi,2
jmpl1
exit:movax,4c00h
int21h
mainendp
gensoundprocnear
pushdx
moval,0b6h
out43h,al
movdx,08h
movax,3208h
divcx
out42h,al
moval,ah
out42h,al
inal,61h
movah,al
oral,3
out61h,al
l2: pushdx
pushax
movdx,8h
movax,0f05h
s1: subax,1
sbbdx,0
jnzs1
popax
popdx
decbx
jnzl2
moval,ah
out61h,al
popdx
ret
gensoundendp
codeends
endstart
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)