我的高中电子班决定购买一些arduino uno套件,我必须说它非常酷。足够的,现在在课堂上我们正在试验压电蜂鸣器(它看起来像this)。我们学会了使用压电蜂鸣器创作歌曲。我们的老师告诉我们要“有创意”。有什么比使用Katy Perry的“Firework”更有创意的方式。
利用一些创作自由,我找到了这首歌的精美钢琴曲(链接here)。现在我是一名钢琴演奏家(我接受了AP音乐理论),而我遇到的问题是我只能用压电蜂鸣器播放一个音符。是否可以在压电蜂鸣器上播放这首歌,因此它听起来像是在钢琴上播放(或者至少接近)。我的意思是在蜂鸣器上同时播放低音和高音谱号音符。
我知道它涉及相移和添加音符的频率,但是如何将其转换为压电蜂鸣器的代码?如果您可以发布一些非常感谢的示例代码。如果没有,你能以最清晰的方式解释它吗?我不是编程大师,但我也不是初学者。
答案 0 :(得分:16)
Arduinos仅提供数字输出:输出为(+ 5V)或关闭(0V)。我希望你在这一点上遇到的tone()
函数输出一个指定频率的方波。
假设你想要一个100Hz的音调。 100Hz表示输出每1/100秒或10ms重复一次。因此tone(PIN,100)
将设置每5ms调用一次定时器中断。第一次调用中断时,它会将输出设置为低电平,并返回到程序正在执行的任何操作。下次调用它会将输出设置为高电平。因此,每输出5ms,输出就会变为50%占空比的方波,这意味着输出正好只有一半的时间。
这一切都很好,但大多数音频波形不是方波。如果你想同时播放两个方波音调,甚至控制单个方波音调的音量,你需要能够输出更多的值而不仅仅是“开”和“关”。
好消息是你可以使用一种称为脉冲宽度调制(通常缩写为PWM)的技巧。我们的想法是,您可能只能将输出设置为两个值之一,但您可以非常快速地完成。人类可以听到高达约20kHz的音频。如果你输出的速度比那个快,比如说200kHz(在Arduino的功能范围内,时钟频率为16MHz),你就听不到单独的输出转换,但平均值超过了更长的时间。
想象一下用tone()
产生200kHz的音调。这听起来太高了,但平均值是开关之间的中间值(占空比为50%,记得吗?)。所以我们现在有三个可能的输出值:on,off和halfway。这足以让我们同时玩两个方波:
高品质音频需要比此更多的价值。 CD存储16位音频,这意味着有65536个可能的值。虽然我们不会从Arduino中获得CD质量的音频,但我们可以通过选择50%以外的占空比来获得更多的输出值。事实上,Arduino为我们提供了硬件。
见面analogWrite()
。这使用Arduino的内置PWM硬件伪装不同的输出电平。坏消息是PWM频率通常为500Hz,这对于调暗LED很好,但对于音频来说太低了。所以我们必须自己编写硬件寄存器。
Secrets of Arduino PWM有更多信息,这里有关于如何在Arduino上实现PWM DAC的detailed reference。
我选择了7位分辨率,这意味着输出为16MHz / 128 = 125kHz方波,占空比为128个。
当然,一旦你有PWM输出工作,乐趣才刚刚开始。 使用多种声音,您无法依靠中断来设置波形的频率,您必须自己拉伸它们。对基本数字信号处理(DSP)的了解将非常方便。您需要严格的代码才能从中断处理程序中生成音频数据,然后您需要一个播放程序来在正确的时间触发正确的音符。天空是极限!
无论如何,这里有一些代码:
#define PIN 9
/* these magic constants were generated by the following perl script:
#!/usr/bin/perl -lw
my $freq = 16000000/256;
my $A4 = 440;
print int(128*$freq/$A4*exp(-log(2)*$_/12)) for (-9..2);
*/
const uint16_t frtab[] = {
30578, 28861, 27241, 25712,
24269, 22907, 21621, 20408,
19262, 18181, 17161, 16198
};
#define VOICES 4
struct voice {
uint16_t freq;
int16_t frac;
uint8_t octave;
uint8_t off;
int8_t vol;
const uint8_t *waveform;
} voice[VOICES];
#define PITCH 50 /* global pitch adjustment */
/* some waveforms. 16 samples each */
const uint8_t square_50[] = {
0, 0, 0, 0, 0, 0, 0, 0,15,15,15,15,15,15,15,15
};
const uint8_t square_25[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,15,15,15,15
};
const uint8_t square_12[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,15,15
};
const uint8_t square_6[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,15
};
const uint8_t sawtooth[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15
};
const uint8_t triangle[] = {
0, 2, 4, 6, 8,10,12,14,15,13,11, 9, 7, 5, 3, 1
};
const uint8_t nicebass[] = {
0, 8,14,18,22,23,24,25,26,25,24,23,22,18,14, 8
};
void setup() {
/* TIMER0 is used by the Arduino environment for millis() etc.
So we use TIMER1.
*/
pinMode(PIN, OUTPUT);
/* fast PWM, no prescaler */
TCCR1A = 0x80;
TCCR1B = 0x11;
/* 7-bit precision => 125kHz PWM frequency */
ICR1H = 0;
ICR1L = 0x7f;
/* enable interrupts on TIMER1 overflow */
TIMSK1 = 1;
OCR1AH = 0; /* hi-byte is unused */
for (uint8_t i=0; i<VOICES; i++)
clear_voice(i);
}
void set_voice(uint8_t v, uint8_t note, uint8_t volume, const uint8_t *waveform) {
note += PITCH;
voice[v].octave = note/12;
voice[v].freq = frtab[note%12];
voice[v].frac = 0;
voice[v].off = 0;
voice[v].waveform = waveform;
voice[v].vol = volume;
}
void clear_voice (uint8_t v) {
voice[v].freq = 0;
}
uint8_t s = 0;
ISR(TIMER1_OVF_vect) {
/* Calculate new data every 4 pulses, i.e. at 125/4 = 31.25kHz.
Being interrupted unnecessarily is kinda wasteful, but using another timer is messy.
*/
if (s++ & 3)
return;
int8_t i;
int8_t out = 0;
for (i=0; i<VOICES; i++) {
if (voice[i].freq) {
voice[i].frac -= 128<<voice[i].octave;
if (voice[i].frac < 0) { /* overflow */
voice[i].frac += voice[i].freq;
voice[i].off++;
}
/* warning: vol isn't a real volume control, only for square waves */
out += (voice[i].waveform[voice[i].off & 15]) & voice[i].vol;
}
}
/* out is in the range 0..127. With 4-bit samples this gives us headroom for 8 voices.
Or we could use more than 4-bit samples (see nicebass).
*/
OCR1AL = out;
}
/* tune data */
const uint8_t bass[8][4] = {
{ 12, 19, 23, 24 },
{ 5, 12, 19, 21 },
{ 12, 19, 23, 24 },
{ 5, 12, 19, 21 },
{ 14, 16, 17, 21 },
{ 7, 19, 14, 19 },
{ 14, 16, 17, 21 },
{ 7, 19, 14, 19 }
};
const uint8_t melody[2][8][16] = {
{/* first voice */
{31, 0, 0, 0, 0, 1, 2, 3,31,29,28,29, 0,28,26,24 },
{ 0, 0, 0, 0, 0, 1, 2, 3,53,54,53,54, 0, 1, 2, 3 },
{31, 0, 0, 0, 0, 1, 2, 3,31,29,28,29, 5,28, 5,26 },
{ 5,28,24, 0, 0, 1, 2, 3,53,54,56,54, 0, 1, 2, 3 },
{29, 0, 0, 0, 0, 1, 2, 3,31,29,28,29, 5, 0,28, 5 },
{28, 5, 0,26, 0, 1, 2, 3,54,56,58,56, 0, 1, 2, 3 },
{29, 0, 0, 0, 0, 1, 2, 3,31,29,28,29, 5, 0,28, 5 },
{28, 5, 0,26, 0, 1, 2, 3, 0,19,21,23,24,26,28,29 },
},
{/* second voice */
{24, 0, 0, 0, 0, 1, 2, 3,24,24,24,24, 0,24,24,21 },
{ 0, 0, 0, 0, 0, 1, 2, 3,49,51,49,51, 0, 1, 2, 3 },
{24, 0, 0, 0, 0, 1, 2, 3,24,24,24,24, 5,24, 5,24 },
{ 5,23,21, 0, 0, 1, 2, 3,49,51,53,51, 0, 1, 2, 3 },
{26, 0, 0, 0, 0, 1, 2, 3,24,26,24,24, 5, 0,24, 5 },
{24, 5, 0,24, 0, 0, 0, 0,51,51,54,54, 0, 1, 2, 3 },
{26, 0, 0, 0, 0, 1, 2, 3,24,26,24,24, 5, 0,24, 5 },
{24, 5, 0,23, 0, 1, 2, 3, 0, 5, 0,19,21,23,24,26 },
}
};
void loop() {
uint8_t pos, i, j;
for (pos=0; pos<8; pos++) {
for (i=0; i<16; i++) {
/* melody: voices 0 and 1 */
for (j=0; j<=1; j++) {
uint8_t m = melody[j][pos][i];
if (m>10) {
/* new note */
if (m > 40) /* hack: new note, keep volume */
set_voice(j, m-30, voice[j].vol, square_50);
else /* new note, full volume */
set_voice(j, m, 15, square_50);
} else {
voice[j].vol--; /* fade existing note */
switch(m) { /* apply effect */
case 1: voice[j].waveform = square_25; break;
case 2: voice[j].waveform = square_12; break;
case 3: voice[j].waveform = square_6; break;
case 4: clear_voice(j); break; /* unused */
case 5: voice[j].vol -= 8; break;
}
if (voice[j].vol < 0)
voice[j].vol = 0; /* just in case */
}
}
/* bass: voices 2 and 3 */
set_voice(2, bass[pos][i%4], 31, nicebass);
set_voice(3, bass[pos][0]-12, 15-i, sawtooth);
delay(120); /* time per event */
}
}
}
这会播放四种语音。我只有一个Arduino Leonardo(好吧,Pro Micro)来测试它,所以你可能需要根据哪个引脚连接到TIMER1A来更改PIN
(如果我正确读取它在Uno上的引脚9)和Mega上的引脚11)。遗憾的是,你无法选择使用哪个引脚。
我也只是用耳机测试过,所以我不知道压电蜂鸣器会发出什么声音...
希望它能让您了解可能性,并为您自己的曲调提供一个潜在的起点。很高兴解释任何不清楚的事情,也谢谢你给我借口写这个:)
答案 1 :(得分:2)
此第三方音色库可以在多个引脚上同时播放方波:Link
您可以在多个引脚和一个扬声器之间连接电阻,以便从一个扬声器中获取所有音调。