汇编实现音乐的播放

      在介绍音乐的播放之前,先来说说如何用汇编发出声音,之后在介绍如何用发出有频率的声音。

      喇叭的构造大致如下图所示,主要由纸盆、线圈、永久磁铁等组成。当有电流通过线圈时,线圈产生的磁场和永久磁铁的磁场相互作用,从而使线圈产生振动。和线圈相连的纸盆也随之移动,若通过线圈的电流时连续变化的,则线圈移动的幅度也会变化,从而牵动纸盆振动,产生声音。

 

       那么PC机的小喇叭是怎样与机器相连的呢?图2-2表示了喇叭与机器简单的相连情况。喇叭的一端连接在电源正极,另一端与机器的61H端口的bit位相连。可以想象,若能连续改变61H端口的bit位0,1状态,就可以使喇叭线圈内的电流时有时无,从而使喇叭发声。我们编制的汇编程序的工作,就是连续改变61H端口的bit位状态。

上面介绍了通过端口61H的bit位发出声音的技术时遗留下一个问题,那就是如何精确的控制声音的频率。现在就解决这个问题。

      早期的PC机有一个专门用于定时的电路,型号为8253/8254。它有三个通道,第一个通道用于控制始终正常运转;第二个通道用于存储器刷新;这两个通道与我们现在要讨论的问题无关。第三个就是一组电路域喇叭相连。

       如下图所示就是PC机中完整的发声电路,定时器通道3的G端与61H端口的bit0位相连,如果把61H端口的bit0位置为1,那么定时器通道3就会被启动,此时将有一组信号从OUT输出,信号的频率可以用程序控制;若61H的端口bit0位为0,则定时器被关闭,out端就恒为1.

 

发声程序设计原理:

       PC机发声系统以8254的2号计数器为核心。系统初始化时,2号计数器I作在“方波发生器”方式,初值为二进制数,,写入顺序为先低后高,CLK2为1.193182MHz,当计数初值为533H时,OUT2输出的方波大约为900Hz,经过简单的滤波之后,送至扬声器。改变1、4号计数器的计数初值就可以使扬声器发出不同频率的音响。

      ROM BIOS 中有个BEEP子程序,这能根据BL中组出的时间计数值控制8254定时器,产生持续时间为1个或几个0.5秒,频率为896HZ的声音,我们可以利用并修改BEEP,使其产生任一频率的声音。为此我们需要做两点修改,首先,BEEP程序只能产生896HZ的声音,我们的通用发声程序应能产生任一频率的声音。其次,BEEP产生声音的持续时间(音长)只能是0.5秒的倍数,我们希望声音的持续时间更易于调整,例如可以是10ms的倍数。

我们知道BEEP能将计数值533H送给定时器2产生896HZ的声音的,那么产生其它频率声音的时间计数值应为:

533H×896÷给定频率=123280H÷给定频率

发声程序包括4个步骤: 

(1)在8253中的42端口送一个控制字0B6H(10110110B),该控制字对定时器2进行初始化,使定时器2准备接收计数初值。 

(2)在8253中的42H端口(Timer2)装入一个16位的计数值(533H×895/频率),以建立将要产生的声音频率。 

(3)把输出端口61H的PB0、PB1两位置1,发出声音。 

      对于发音部分。PC机上的大多数输入/输出(I/O)都是由主板上的8255(或8255A)可编程序外围接口芯片(PPI)管理的。PPI包括3个8位寄存器,两个用于输入功能,一个用于输出功能。输入寄存器分配的I/O端口号为60H和62H,输出寄存器分配的I/O端口号为61H。由PPI输出寄存器中的0、1两位来选择扬声器的驱动方式。

 (4)注意音乐节拍表的频率表的设定。一个频率对应一个节拍,如果频率表和节拍表有问题,同样不会发出声音。

     对于发音部分。PC机上的大多数输入/输出(I/O)都是由主板上的8255(或8255A)可编程序外围接口芯片(PPI)管理的。PPI包括3个8位寄存器,两个用于输入功能,一个用于输出功能。输入寄存器分配的I/O端口号为60H和62H,输出寄存器分配的I/O端口号为61H。由PPI输出寄存器中的0、1两位来选择扬声器的驱动方式。连接到扬声器上的是定时器2,从上图可以看到,GATE2与端口61H的PB0相连,当PB0=1时,GATE2获得高电平,使定时器2可以在模式3(方波)下工作。定时器2的OUT2与端口61H的PB1通过一个与门与扬声器的驱动电路相连。当PB1=1时,允许OUT2的输出信号到达扬声器电路。因此,只有PB0和PB1同时为“1”时,才能驱动扬声器地声。通过以下指令实现: 

IN AL,61H 
OR AL,3 
OUT 61H,AL 

上面的指令用以打开扬声器,如要关闭扬声器时则为: 

AND AL,0FCH 
OUT 61H,AL 

当从8255中采集到输入的数据时,需要确定相应的频率,所以在软件编程时要建立一个数据表: 

TABLE DW 493,440,392,349,329,293,261 

把相应的频率送到一个寄存器上,通过公式: 

计数值=533H×896÷ f=1234DCH÷ f 

算出计数值,再把算得的计数值送给8253,就可产生所要频率的方波。在把计数值送8253前,必须先把8253进行初始化: 

MOV AL,0B6H 
OUT 43H,AL 

      使其选用通道2,工作在方式3下。 就整个电路而言,接好电路后,通过软件编程不断地采集从8255口中输入的信号,而8个开关都接在8255的A口上,只要有开关按下,就会采集到一个数据,根据这个数据与事先编好的表对应,得到一个计数值,把这个计数值送给8253的通道2,8253的通道2工作的方式3下,这样就可以产生满足频率要求的发声方波。这个方波经驱动放大就可以使扬声器发出相应的声音。 所以8255在这里完成两个任务,它不仅从A口中采集到数据,而且B口的PB1和PB0两个位要控制发声。8253的主要任务就是产生所要求发声的不同频率的方波。

;定义数据段
data segment
     infor1 db 0Dh, 0AH, "welocom you to come here listeng! $"

     mus_freg  dw 330,294,262,294,3 dup (330)     ;频率表
               dw 3 dup (294),330,392,392
               dw 330,294,262,294,4 dup (330)
               dw 294,294,330,294,262,-1
     mus_time  dw 6 dup (25),50                   ;节拍表
               dw 2 dup (25,25,50)
               dw 12 dup (25),100
data ends

;栈段定义
stack segment stack
      db 200 dup(?)
stack ends

;--------字符串输出宏----------
SHOWBM MACRO b
     LEA DX,b
     MOV AH,9
     INT 21H
 ENDM

;----------音乐地址宏-----------
ADDRESS MACRO A,B
     LEA SI,A
     LEA BP,DS:B
ENDM
;-------------------------------

;代码段定义
code segment
     assume ds:data, ss:stack, cs:code
start:
     mov ax, data
     mov ds, ax
     mov ax, stack
     mov ss, ax
     mov sp, 200
    
     address mus_freg, mus_time
     call music

exit:     
     mov ah, 4cH
     int 21h

;------------发声-------------
gensound proc near
     push ax
     push bx
     push cx
     push dx
     push di

     mov al, 0b6H
     out 43h, al
     mov dx, 12h
     mov ax, 348ch
     div di
     out 42h, al

     mov al, ah
     out 42h, al

     in al, 61h
     mov ah, al
     or al, 3
     out 61h, al
wait1:
     mov cx, 3314
     call waitf
delay1:
     dec bx
     jnz wait1

     mov al, ah
     out 61h, al

     pop di
     pop dx
     pop cx
     pop bx
     pop ax
     ret 
gensound endp

;--------------------------
waitf proc near
      push ax
waitf1:
      in al,61h
      and al,10h
      cmp al,ah
      je waitf1
      mov ah,al
      loop waitf1
      pop ax
      ret
waitf endp
;--------------发声调用函数----------------
music proc near
      xor ax, ax
freg:
      mov di, [si]
      cmp di, 0FFFFH
      je end_mus
      mov bx, ds:[bp]
      call gensound
      add si, 2
      add bp, 2
      jmp freg
end_mus:
      ret
music endp

code ends
     end start

注意:在实现过程中可能会遇到这样的情况:你用的是DOSBOX虚拟机进行编程,然后进行测试之后,一切通过。但是移植到32位win7的虚拟8086就没有任何声音了。这是我亲身体会的经历。自己也不是很清楚这到底是问什么,会因为声卡的设计不一样?还是说不同的声卡有不同的控制方式?就是建议大家如果合作用汇编编写程序时,要注意平台的统一,不然会出现许多问题。(上面的程序实在DOSBOX中测试成功的~~)


ps:2013/11/27 :经过努力的调试和发现,结果找到了为什么在dosbox下可以发声在cmd中不能的原因。因为dosbox虚拟机的cpu频率比较小,所以可以一次顺利的读取到每个频率点,但是在windows xp下运行时,cpud的频率要比dosbox下的频率快的多,所以当读到第一个频率还没有来得及发声,有读到了下一个频率,以至于频率表已经不是一个完整的频率了,所以不能发声。所以如果要在windows xp下运行发声,必须给每一次读取频率的下一条语句添加一个延迟函数。


  • 28
    点赞
  • 97
    收藏
    觉得还不错? 一键收藏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值