如何向扬声器发送声音

时间:2009-05-20 21:44:12

标签: c microcontroller arduino electronics atmega

如果我要编程一个微控制器(ATMega128)用扬声器播放一个真实的话,我该怎么做?

我是否需要使用数字/模拟转换器发送不同的幅度值,或者频率变化是否足够?在任何情况下,我如何编码扬声器需要接收的频率和幅度值?我需要某种频率复用吗?我不是说用扬声器制作简单的声音,就像一个音符然后另一个音符。我想播放一首真正的歌曲,包括所有乐器,人声等等。

8 个答案:

答案 0 :(得分:7)

假设您有一个未压缩的8位22.1 kHz单声道文件:

1)剥去标题
2)每1 / 22,100秒:
2.1)读取8位
2.2)使用DAC将其转换为扬声器的电压范围
2.3)将其发送给发言人

这将为您提供[22.1 kHz / 8位/单声道]质量的声音,是一种播放逼真样本的简单方法。

所有频率都是不同合成器所必需的。例如,PC扬声器实际上只有一点。为了获得不同的振幅(比“无”和“最大”),可能需要一些技巧,例如脉冲宽度调制(如你所说,移动频率,因此扬声器的振膜有效地具有比两个更多的位置)。 / p>

但你不需要为此烦恼。您需要做的就是在扬声器上每秒喷出大约22,100或4400个声音样本,每个样本8或16位表示振幅。

答案 1 :(得分:4)

我尝试过类似的东西。首先,微控制器上没有足够的存储空间来存储真实的歌曲。你需要外部存储器来处理它。这意味着使用SPI接口连接外部闪存或EEPROM等。 SD也很好 - 我相信它是一个SPI样式的界面。 ATMegas有代码可以与SD卡连接。

第二个重要的事情是以适当的格式获取数据。我这样做的方法是使用pulse-width modulation(PWM)来创建不同的电压电平。我相信你在微控制器上有一个16位PWM,因此你可以在声音上保持16位保真度。如果您有空间问题,可以使用8位PWM。因此,您的声音数据必须是8位或16位PCM,其中0x0000是最低值,0xFFFF是最高值。如果您想要高保真音乐,您必须拥有44 KHz的采样率才能获得所有良好的谐波等。我相信这是PCM - 就像它在PC上调用一样。

那么你将拥有所有这些值 - 对于五分钟的音乐你将拥有5 * 60 * 44000 = 13,200,000 16位值,即211,200,000位(211兆位,26.4兆字节)。这些是您对原始数据的存储要求。 MP3是可能的 - 有外部芯片,但你仍然有很大的空间需求。

因此,每隔1/44000秒,您将更新PWM寄存器中的值。您的PWM频率必须高4或5 - 即每个值5个PWM周期。

这是您的通用算法 - 更新PWM寄存器中的值并让它一直运行到最后。您将需要至少一个输出滤波器 - 将频率限制在20KHz的可听范围内(如果您是发烧友,则更多)。 RC滤波器可以工作,但我会选择有源滤波器,如果您使用PWM,您的输出范围通常为0到5V,平均电压约为2.5V。扬声器不喜欢直流电平 - 只是信号。漂亮的正弦波,平均电压为0。因此,您的有源滤波器必须调整电压电平并使用双电源来提供负电压。您还可以放大大泵浦低音的信号。只是不要吹灭你的扬声器。

MP3可能是PCM的更好替代品。那里有筹码:http://www.sparkfun.com/commerce/product_info.php?products_id=8892

然而,那就是整个微控制器。你已经拥有一个。但让我们面对现实吧 - 无论你如何对它进行爵士乐,ATMega都不会很快自己做MP3。

看起来上面提到的波士盾基本上就是这样 - 使用SD卡存储PCM和外部放大器用于声音。祝你好运!

答案 2 :(得分:2)

产生稳定音调的一种方法是直接数字合成。你需要一个DAC,一个专用芯片或一个梯形电阻器。

您设置一个计数器以要生成的频率溢出,并在计数器的每个刻度上使用它来索引波表并获得DAC的输出值。

我在New Noises From MidiVox处为Arduino写了几种不同的音调生成技术。 DAC更新代码特定于MidiVox(和Adafruit WaveShield)MCP4921,但正弦波生成应该普遍适用。我试图保持代码大部分都是ATmegas,但有一些Arduino主义悄然进入。

从该帖子粘贴,这里有一些代码可以在Arduino上使用MCP4921在SPI总线上播放440Hz音调:

uint16_t sample = 0;

/* incr = freq * (2^16 / 15625) 
 * So for 440Hz, incr = 1845 */
uint16_t incr = 1845;

/* oscillator position */
uint16_t pos = 0;

const uint8_t sine[] = {
    0x80, 0x83, 0x86, 0x89, 0x8C, 0x8F, 0x92, 0x95, 0x98, 0x9B, 0x9E, 0xA2,
    0xA5, 0xA7, 0xAA, 0xAD, 0xB0, 0xB3, 0xB6, 0xB9, 0xBC, 0xBE, 0xC1, 0xC4,
    0xC6, 0xC9, 0xCB, 0xCE, 0xD0, 0xD3, 0xD5, 0xD7, 0xDA, 0xDC, 0xDE, 0xE0,
    0xE2, 0xE4, 0xE6, 0xE8, 0xEA, 0xEB, 0xED, 0xEE, 0xF0, 0xF1, 0xF3, 0xF4,
    0xF5, 0xF6, 0xF8, 0xF9, 0xFA, 0xFA, 0xFB, 0xFC, 0xFD, 0xFD, 0xFE, 0xFE,
    0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFE, 0xFD,
    0xFD, 0xFC, 0xFB, 0xFA, 0xFA, 0xF9, 0xF8, 0xF6, 0xF5, 0xF4, 0xF3, 0xF1,
    0xF0, 0xEE, 0xED, 0xEB, 0xEA, 0xE8, 0xE6, 0xE4, 0xE2, 0xE0, 0xDE, 0xDC,
    0xDA, 0xD7, 0xD5, 0xD3, 0xD0, 0xCE, 0xCB, 0xC9, 0xC6, 0xC4, 0xC1, 0xBE,
    0xBC, 0xB9, 0xB6, 0xB3, 0xB0, 0xAD, 0xAA, 0xA7, 0xA5, 0xA2, 0x9E, 0x9B,
    0x98, 0x95, 0x92, 0x8F, 0x8C, 0x89, 0x86, 0x83, 0x80, 0x7D, 0x7A, 0x77,
    0x74, 0x71, 0x6E, 0x6B, 0x68, 0x65, 0x62, 0x5E, 0x5B, 0x59, 0x56, 0x53,
    0x50, 0x4D, 0x4A, 0x47, 0x44, 0x42, 0x3F, 0x3C, 0x3A, 0x37, 0x35, 0x32,
    0x30, 0x2D, 0x2B, 0x29, 0x26, 0x24, 0x22, 0x20, 0x1E, 0x1C, 0x1A, 0x18,
    0x16, 0x15, 0x13, 0x12, 0x10, 0x0F, 0x0D, 0x0C, 0x0B, 0x0A, 0x08, 0x07,
    0x06, 0x06, 0x05, 0x04, 0x03, 0x03, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06,
    0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0F, 0x10, 0x12, 0x13, 0x15,
    0x16, 0x18, 0x1A, 0x1C, 0x1E, 0x20, 0x22, 0x24, 0x26, 0x29, 0x2B, 0x2D,
    0x30, 0x32, 0x35, 0x37, 0x3A, 0x3C, 0x3F, 0x42, 0x44, 0x47, 0x4A, 0x4D,
    0x50, 0x53, 0x56, 0x59, 0x5B, 0x5E, 0x62, 0x65, 0x68, 0x6B, 0x6E, 0x71,
    0x74, 0x77, 0x7A, 0x7D
};

void setup() {
    cli();

    /* Enable interrupt on timer2 == 127, with clk/8 prescaler. At 16MHz,
       this gives a timer interrupt at 15625Hz. */
    TIMSK2 = (1 << OCIE2A);
    OCR2A = 127;

    /* clear/reset timer on match */
    TCCR2A = 1<<WGM21 | 0<<WGM20; /* CTC mode, reset on match */
    TCCR2B = 0<<CS22 | 1<<CS21 | 0<<CS20; /* clk, /8 prescaler */

    SPCR = 0x50;
    SPSR = 0x01;
    DDRB |= 0x2E;
    PORTB |= (1<<1);

    sei();
}

ISR(TIMER2_COMPA_vect) {
    /* OCR2A has been cleared, per TCCR2A above */
    OCR2A = 127;

    pos += incr;

    /* shift left a couple of bits for more volume */
    sample = sine[highByte(pos)] << 2;

    PORTB &= ~(1<<1);

    /* buffered, 1x gain, active mode */
    SPDR = highByte(sample) | 0x70;
    while (!(SPSR & (1<<SPIF)));

    SPDR = lowByte(sample);
    while (!(SPSR & (1<<SPIF)));

    PORTB |= (1<<1);
}

void loop() {
}

直接数码合成的优点在于,可以非常轻松地将多个音调组合在一起(通过添加)或以所需音量混合(通过在添加前乘以音量)。

我发现Arduino可以使用这种方法同时播放约30个音调。我的特定应用是Hammond organ simulation,这也可能是有用的阅读。

答案 3 :(得分:2)

今天,从8位微控制器播放MP3文件既简单又便宜。您需要一个存储设备(例如SD卡)和一个MP3芯片组。例如,请参阅此article。您可以在avrfreaks找到更多内容。在那里你也可以找到没有外部芯片播放声音的文章。

您可以使用Pulse-width modulation(PWM)播放基本声音,但对于真正的歌曲,您需要一个DAC。我见过只使用DAC和软件播放MP3文件的项目,但它们涉及功能更强大的ARM微控制器。

答案 4 :(得分:1)

如果您感觉特别有创意,可以使用resistor ladder构建自己的数模转换器。

答案 5 :(得分:0)

您可以查看open source MP3 player并了解他们是如何做到的。我的猜测是你需要一个D / A转换器来产生质量不错的声音。

答案 6 :(得分:0)

你在ATMega128上没有足够的空间来做任何太花哨的事情。连接扬声器(小于2“或更小)的最简单方法是通过电阻器。检查输出的当前吸收容量并相应地计算R.

---------------------- +V
    |
    \
    / R
    \
    /              ----------
    |              |
    |   ------     |
    ----|    |-----| Microcontroller
        /    \     |
       --------    |
        Speaker    ---------

关于产生音调,输出的基本切换将产生基本的stylaphone声音crapolla。您可以使用脉冲宽度调制来产生任何模拟声音的近似值(过于复杂而无法进入此处,AtMega可能没有足够的抽吸或存储空间)。这是用于为没有声卡的PC制作音频驱动程序的方法(只有内置扬声器)...

答案 7 :(得分:0)

如果您使用的是Arduino,可以花22美元购买Lady Ada的WaveShield。 Lady Ada提供许多Arduino goodies值得购买。一些例子是GPS,以太网和步进/伺服屏蔽。