PC Speaker

来自osdev
跳到导航 跳到搜索

PC扬声器是PC兼容系统上可用的最原始的声音设备。 它的特点是可以发出独特的“哔哔声”和“吱吱声”,因此有时被称为“PC Beeper”或“PC Squeaker” - 蜂鸣器。

原始硬件

扬声器本身有两种可能的位置,“in”和“out”。 此位置可通过键盘控制器上端口0x61的位1来设置。 如果设置此位 (=1),则扬声器将移至 “out” 位置,如果清零(=0),则扬声器将移至 “in” 位置。 如果重复的速度(频率)在扬声器可以重现的范围内,并且人耳可以听到,那么重复的进进出出就会产生可听见的音调。 而且,一次向内或向外的移动都会发出一点咔哒声,因为震动太快了。 因此,频率太低而无法听到作为音调的声音可能会被人听到为嘎嘎声或嗡嗡声。(实际上,该机制产生的任何频率都会产生更高的频率; 如果你感兴趣,可以搜索了解“方波谐波(square wave harmonics)”。)

操作模式

通过可编程间隔定时器(PIT-Programmable Interval Timer)

通过设置端口0x61(=1)的位0,PC扬声器可以直接连接到Programable Interval Timer上的定时器编号2的输出。 在此模式下,当定时器变为 “高” (= 1) 时,扬声器将移至 “out” 位置。 同样,当定时器变为“低”(=0)时,扬声器将移到“in”位置。 通过改变定时器2“ticks”的频率,PC扬声器可以输出相同频率的声音。 这种模式非常流行,因为它易于编程,并且与计算机的其余操作是异步的,这意味着它需要很少的CPU时间。 还应注意,这是对PC扬声器进行编程的“官方”方式,如果有声卡,则应是对PC扬声器进行编程的唯一方式。

脉宽调制

在没有真正的声卡的情况下,PC扬声器可用于输出低质量的数字声音。 如前所述,扬声器本身只有两种可能的位置,“in”和“out”。 不过,要播放数字声音,需要更多的位置。 通常,256个位置 (8位) 被认为足以播放可理解的音频。 许多早期的PC游戏都使用一种流行的方法来克服这种限制,这种方法叫做脉宽调制(pulse width modulation)。 该技术使用扬声器的物理属性来允许其输出存在于8位音频中的(相对)复杂的声音。

它是如何工作的

PC扬声器改变位置大约需要60百万分之一秒。 这意味着,如果在不到60微秒的时间内将扬声器的位置从“In”更改为“Out”,然后又改回,则扬声器没有足够的时间完全到达“Out”位置。 通过精确调整扬声器被 “out” 的时间量,可以将扬声器的位置设置为 “in” 和 “out” 之间的任何位置,从而使扬声器形成更复杂的声音。

设置它

每次需要改变扬声器的位置时,您应该首先让PIT产生中断。 要播放标准音频,CPU需要每秒中断44100次。 最简单的方法是(重新)编程定时器0,以44100Hz的频率中断。

您还需要一种方法来实际更改扬声器的位置。 这可以简单地通过端口0x61的位1来完成,但是这个方法太慢了,不切实际。 相反,最好(重新)编程定时器2,以便在等待下一个中断时为您正确设置扬声器的位置(或使用任何空闲的CPU时间做其他更有用的事情)。

播放音频

当PIT中断时,您需要对PIT定时器2进行编程,以便将PC扬声器置于 “out” 位置,持续60微秒。 对于8位音频,微秒数可以这样计算:

 microseconds = (sample * 60) / 255;

有关更多信息,请参阅维基百科文章 Pulse-width modulation. 论坛中也有发布的示例代码 here.

示例代码

在QEMU中工作如果以-soundhw pcspk选项启动,这将在实际硬件中启用扬声器。 该代码还更改了PIT定时器2的频率,因此当您完成 “哔” 发声时,您必须重置该频率 :)

 //使用内置扬声器播放声音
 static void play_sound(uint32_t nFrequence) {
 	uint32_t Div;
 	uint8_t tmp;
 
        // 将PIT设置为所需的频率
 	Div = 1193180 / nFrequence;
 	outb(0x43, 0xb6);
 	outb(0x42, (uint8_t) (Div) );
 	outb(0x42, (uint8_t) (Div >> 8));
 
        //并使用PC扬声器播放声音
 	tmp = inb(0x61);
  	if (tmp != (tmp | 3)) {
 		outb(0x61, tmp | 3);
 	}
 }
 
 //闭嘴
 static void nosound() {
 	uint8_t tmp = inb(0x61) & 0xFC;
     
 	outb(0x61, tmp);
 }
 
 // 发出哔哔声
 void beep() {
 	 play_sound(1000);
 	 timer_wait(10);
 	 nosound();
          //set_PIT_2(old_frequency);
 }