【问题标题】:Assembly 8086 - DOSBOX - How to produce beep sound?组装 8086 - DOSBOX - 如何产生哔声?
【发布时间】:2017-05-16 08:58:36
【问题描述】:

我正在组装一个“西蒙”游戏,我需要在按钮打开时发出哔声,哔声也应该彼此不同。 谢谢

【问题讨论】:

标签: assembly x86 dos x86-16 dosbox


【解决方案1】:

您可以使用speaker 来简化您的设计。
扬声器可让您以不同的频率播放方波,it can actually be used to reproduce digital audio 但这更复杂。

扬声器只是一个电磁铁,当电流流过它时,它会被拉回,否则它会停留在默认位置。
通过前后移动扬声器,可以产生声波。

扬声器可以手动移动,也可以使用PIT的通道2。
端口 61h 的位 0 控制扬声器源(0 = 手动,1 = PIT),同一端口的位 1 是使用 PIT 时的“扬声器启用”位(不使用时扬声器的“位置”)。

这是缺少手动驾驶部分的示意图(来自page):

PIT 通过端口 40h-43h 控制,我们将使用模式 3(方波发生器)设置每次分频器的两个字节。
PIT 有一个运行在大约 1.193180 MHz 的振荡器,分频器用于控制方波的周期。
无需处理内部结构:在 PIT 振荡器的每个滴答声中,加载的分频器都会递减。方波的周期等于 PIT 将分频器递减到零所需的时间。

只需使用所需的分频器对 PIT 进行编程并启用扬声器即可。
稍后,我们需要禁用它。
一个简单的方法是使用每秒调用 18.2 次的 int 1ch

通过在第一次播放声音时将持续时间保存在变量中,通过在 int 1ch 的每个滴答声中递减它并在计数达到零时禁用扬声器,可以控制哔声的持续时间。

使用 int 1ch 需要设置函数 (beep_setup) 和拆卸函数 (beep_teardown)。

BITS 16

ORG 100h

__start__:

 ;Setup
 call beep_setup

 ;Sample beep of ~2sec
 mov ax, 2000
 mov bx, 36
 call beep_play

 ;Wait for input
 xor ax, ax
 int 16h

 ;Tear down
 call beep_teardown

 mov ax, 4c00h
 int 21h

 ;-------------------------------------------------

 ;
 ;Setup the beep ISR
 ;

 beep_setup:
  push es
  push ax

  xor ax, ax
  mov es, ax

  ;Save the original ISR
  mov ax, WORD [es: TIMER_INT * 4]
  mov WORD [cs:original_timer_isr], ax
  mov ax, WORD [es: TIMER_INT * 4 + 2]
  mov WORD [cs:original_timer_isr + 2], ax

  ;Setup the new ISR

  cli
  mov ax, beep_isr
  mov WORD [es: TIMER_INT * 4], ax
  mov ax, cs
  mov WORD [es: TIMER_INT * 4 + 2], ax
  sti

  pop ax
  pop es
  ret 


 ;
 ;Tear down the beep ISR
 ;

 beep_teardown:
  push es
  push ax

  call beep_stop

  xor ax, ax
  mov es, ax

  ;Restore the old ISR

  cli
  mov ax, WORD [cs:original_timer_isr]
  mov WORD [es: TIMER_INT * 4], ax
  mov ax, WORD [cs:original_timer_isr + 2]
  mov WORD [es: TIMER_INT * 4 + 2], ax
  sti

  pop ax
  pop es
  ret 

 ;
 ;Beep ISR
 ;
 beep_isr:
  cmp BYTE [cs:sound_playing], 0
  je _bi_end

  cmp WORD [cs:sound_counter], 0
  je _bi_stop

  dec WORD [cs:sound_counter]

 jmp _bi_end

_bi_stop:
  call beep_stop

_bi_end:
  ;Chain
  jmp FAR [cs:original_timer_isr]

 ;
 ;Stop beep
 ;
 beep_stop:
  push ax

  ;Stop the sound

  in al, 61h
  and al, 0fch    ;Clear bit 0 (PIT to speaker) and bit 1 (Speaker enable)
  out 61h, al

  ;Disable countdown

  mov BYTE [cs:sound_playing], 0

  pop ax
  ret

 ;
 ;Beep
 ;
 ;AX = 1193180 / frequency
 ;BX = duration in 18.2th of sec
 beep_play:
  push ax
  push dx

  mov dx, ax

  mov al, 0b6h
  out 43h, al

  mov ax, dx
  out 42h, al
  mov al, ah
  out 42h, al


  ;Set the countdown
  mov WORD [cs:sound_counter], bx

  ;Start the sound

  in al, 61h
  or al, 3h    ;Set bit 0 (PIT to speaker) and bit 1 (Speaker enable)
  out 61h, al


  ;Start the countdown

  mov BYTE [cs:sound_playing], 1

  pop dx
  pop ax
  ret

 ;Keep these in the code segment
 sound_playing      db  0
 sound_counter      dw  0
 original_timer_isr     dd  0

 TIMER_INT      EQU     1ch

特别感谢 Sep Roland 修复了原始代码中的缺陷!

您可以使用beep_play 来播放哔声,使用的单位是上述硬件配置的“自然”单位。
如果您的频率和持续时间是固定的,这些单元可以免费简化代码。

哔声在给定时间后停止,您可以使用beep_stop 强制停止它。

一次播放多个声音是不可能的(如果不使用 PWM 技术,即使混合它们也是不可能的)。
在发出另一声哔声时调用 beep_play 将产生停止当前哔声并开始新的哔声的效果。

【讨论】:

  • 一个很好的答案。 +1 但是 beep_isr 中断服务程序有一个重要问题!您不能只使用AX 寄存器而不保留它。如果你写:cmp WORD [cs:sound_counter], 0je _bi_stopdec WORD [cs:sound_counter],解决方案很简单。根本不需要使用AX
  • 非常感谢@SepRoland!我正在用学分更新答案:)
  • 对于希望在 MBR 引导加载程序或其他非 .com 情况下使用它的未来读者,可能只需设置 DS 以匹配您的 org,然后删除每个 cs:,而不是使用默认值DS 段。我认为在这里使用 CS 只是为了避免设置 DS。
  • @PeterCordes 是的,完全正确。在引导加载程序中,CS 可能等于 DS,所以两者都可以。
  • 如果要在引导加载程序中访问静态数据,则需要手动设置 DS。它可能和 CS 一样,但你不知道它是 0 还是 0x7C0。 (因为你想要一个 base=0 的段 reg 来访问 IVT,可能也只使用 DS,org 0x7C00,所以一切都可以使用 DS。静态存储“变量”将具有像 0x7C 这样的绝对偏移量? , 而不是 0)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-30
  • 1970-01-01
相关资源
最近更新 更多