Arduino案例实操 -- 蜂鸣器演奏歌曲(圣诞歌 Jingle Bells)

你们也知道,圣诞节就快到了,让arduino完成一个比较应景的案例也会让节日过得更有意思。
arduino可以做的放歌案例除了用语音模块外,比较简单成本最低的就是利用蜂鸣器了,只要控制好频率和节拍,蜂鸣器也可以演奏音乐的,但你要准备的东西也会复杂一点。


一、圣诞歌简谱

简谱不理解是什么??!!
很简单,你小学时候的音乐课本上面的谱子就是了,下面这张是Jingle Bells的简谱,歌词是中文版不用太认真,调子是一样的。
在这里插入图片描述
在这里插入图片描述
简谱上除了中文,其他的应该都看不懂了吧

敲黑板,这个地方要好好理解一下,后面要用到!!!

  • 音符节奏分为一拍、半拍、1/4拍、1/8拍,我们规定一拍音符的时间为1,那么半拍为0.5,1/4拍为0.25;1/8拍为0.125……,为每个音符赋予这样的拍子播放出来,音乐的整个基调就出来了。
  • 确定简谱的调,在简谱歌名左下角(即整个简谱的左上角)处,有个 1 = F 4/4 ,这里的 F 表示这个曲子是 F 调的,下面会讲到,而 4/4 表示曲子是四四拍的
  • 音符(简谱中文字上面对应的每个数字)类型说明,举个栗子,Jingle Bells简谱第一小段音符 5 3 2 1 5 0 5 5
    1)这里面5、3、2、1可以看到数字下面都带着下划线,这表示每个音符对应的节拍为0.5拍,即音符5为0.5拍,音符3为0.5拍,音符2和音符1也是0.5拍,0也是0.5拍,仔细看最后两个音符,两个5下面都是2条下划线,这表示0.25拍,这样就好理解了,每个音符都看成1拍,每带一条下划线就当前拍数除以2,这样一条下划线是1/2=0.5拍,两条下划线是1/2/2=0.25拍,ok吧;然后把这一小段音符 5 3 2 1 5 0 5 5的节拍数加起来是0.5+0.5+0.5+0.5+1+0.5+0.25+0.25 = 4拍,这样就跟简谱标的四四拍是吻合的了,也说明你这一小段的节拍数没算错,记住Jingle Bells简谱中每一小段都是4拍(这段话对照着简谱去理解)
    2)聪明的你可能还发现有些音符还带有小点点,当音符不带点时,表示中音,例如第一小段中的音符3,表示中音音符3;当音符下面带小点时,表示低音,例如第一小段中的音符5,表示低音音符5;同样的,当音符上面带小点时,表示高音音符,这在Jingle Bells简谱中没有出现高音
    3)音符后边带小数点的话,表示该音符节拍+0.5拍(音符带下划线的话,在计算完下划线节拍的基础上+0.5拍);音符后边带 - 的,音符+1拍(计算完下划线节拍的基础上+1拍)
    4)有的两个连续的音符上面带弧线,表示连音,可以稍微改下连音后面那个音的频率,比如减少或增加一些数值(需自己调试),这样表现会更流畅,其实不做处理,影响也不大

二、音符频率对照表

简单了解简谱后,该怎么把音符转换成蜂鸣器对应频率呢,看看音符频率表
低音

音符→
音调↓
1# 2# 3# 4# 5# 6# 7#
A221248278294330371416
B248278294330371416467
C131147165175196221248
D147165175196221248278
E165175196221248278312
F175196221234262294330
G196221234262294330371
中音
音符→
音调↓
1 2 3 4 5 6 7
A441495556589661742833
B495556624661742833935
C262294330350393441495
D294330350393441495556
E330350393441495556624
F350393441495556624661
G393441495556624661742
高音
音符→
音调↓
1# 2# 3# 4# 5# 6# 7#
A88299011121178132214841665
B990111211781322148416651869
C525589661700786882990
D5896617007868829901112
E66170078688299011121284
F700786882935104911781322
G7868829901049117813221484
如上的频率表适用于arduino的各种蜂鸣器曲目,由于案例中Jingle Bells是F调的,所以在表中我把F调对应的音符都标红了,可以看到表格中第一列是曲目音调,每首曲子基本都是一个调的,第一行是每个调对应的音符频率1 到 7,也就是简谱中我们看到的那些数字,最后是以高音、中音、低音把频率分成了3个表,看懂了就可以开始编程了

三、案例编程

看明白简谱和频率表就开始写程序啦,首先宏定义曲目要用到的音符频率

//中音NTF 0为空拍
#define NTF0 -1
#define NTF1 350
#define NTF2 393	
#define NTF3 441
#define NTF4 495
#define NTF5 556
#define NTF6 624
#define NTF7 661

//高音NTFH
#define NTFH1 700
#define NTFH2 786
#define NTFH3 882
#define NTFH4 935
#define NTFH5 965
#define NTFH6 996
#define NTFH7 1023

//低音NTFL
#define NTFL1 175
#define NTFL2 196
#define NTFL3 221
#define NTFL4 234
#define NTFL5 262
#define NTFL6 294
#define NTFL7 330

按照Jingle Bells简谱定义音符数组

//音符频率数组 
int tune[]=
{
	NTF3,NTF3,NTF3,NTF3,NTF3,NTF3,
	NTF3,NTF5,NTF1,NTF2,NTF3,NTF0,
	NTF4,NTF4,NTF4,NTF4,NTF4,NTF3,NTF3,NTF3,NTF3,
	NTF5,NTF5,NTF4,NTF2,NTF1,NTF0,

	NTFL5,NTF3,NTF2,NTF1,NTFL5,NTF0,NTFL5,NTFL5,
	NTFL5,NTF3,NTF2,NTF1,NTFL6,NTF0,
	NTFL6,NTF4,NTF3,NTF2,NTFL7,NTF0,
	NTF5,NTF5,NTF4,NTF2,NTF3,NTF1,NTF0,

	NTFL5,NTF3,NTF2,NTF1,NTFL5,NTF0,
	NTFL5,NTF3,NTF2,NTF1,NTFL6,NTF0,NTFL6,
	NTFL6,NTF4,NTF3,NTF2,NTF5,NTF5,NTF5,NTF5,
	NTF6,NTF5,NTF4,NTF2,NTF1,NTF0
};

按简谱定义音符节拍数组

//音符节拍数组
float durt[]=
{
	0.5,0.5,1,0.5,0.5,1,
	0.5,0.5,0.75,0.25,1.5,0.5,
	0.5,0.5,1,0.5,0.5,0.5,0.5,0.25,0.25,
	0.5,0.5,0.5,0.5,1.5,0.5,

	0.5,0.5,0.5,0.5,1,0.5,0.25,0.25,
	0.5,0.5,0.5,0.5,1,1,
	0.5,0.5,0.5,0.5,1,1,
	0.5,0.5,0.5,0.5,1,0.75,0.25,

	0.5,0.5,0.5,0.5,1,1,
	0.5,0.5,0.5,0.5,1,0.5,0.5,
	0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,
	0.5,0.5,0.5,0.5,0.75,0.25
};

接着是主题函数部分

//定义蜂鸣器引脚,音符长度变量
int buzzer_pin = 9;
int length;

//setup函数,初始化引脚,计算长度
void setup()
{
	pinMode(buzzer_pin, OUTPUT);
	length = sizeof(tune)/sizeof(tune[0]);
}

//loop函数
void loop()
{
	//for循环演奏曲子
	for(int x=0;x<length;x++)
	{
		tone(buzzer_pin, tune[x]);
		delay(500*durt[x]);					//这里的500为控制每个音符的时长来定曲子的节奏
		noTone(buzzer_pin);
	}
	delay(500);								//开始下一轮循环的时间间隔
}

程序对于蜂鸣器单独演奏曲子来说简短有效,对于在曲子演奏中间要一边做点其他事情,可能就有些些麻烦了


四、案例扩展

圣诞节案例肯定不能少了圣诞树不是嘛,这种圣诞树小装饰在某宝上十几二十块就能买到带带灯的,我手头就搞了一个出来,店家给的蚊帐布一样的原材料要自己动手,也是花了不少时间搞,主要是灯带的固定很是头疼,下面看一下效果图,还是可以接受的
在这里插入图片描述
商家随机给的颜色布料,粉色的,老夫也是无奈,还有圈上彩灯后的效果,还怪不错的咧
在这里插入图片描述
是吧,节日一摆上那是很有气氛了,还给了些圣诞鞋小挂饰什么的,没给弄上去,两节5号电池供电,常亮状态,为了能达到彩灯控制效果,我把线剪了接到arduino控制的继电器上面,通过继电器控灯,程序也写了,以最原始的方式,另外加了OLED屏幕的节日祝福显示,具体代码如下

#include <U8glib.h>
 
//引脚定义
int buzzer_0 = 5;
int relay_0 = 12;
U8GLIB_SSD1306_128X64 u8g_0(U8G_I2C_OPT_NONE);

//setup函数
void setup() {
    pinMode(buzzer_0, OUTPUT);
    pinMode(relay_0, OUTPUT);

	//OLED屏幕显示 Merry Christmas
    u8g_0.firstPage();
    do {
        u8g_0.setFont(u8g_font_9x18);
        u8g_0.drawStr(5, 30, "Merry");
        u8g_0.drawStr(40, 55, "Christmas");
    } while (u8g_0.nextPage());
}

//loop函数
void loop() {
    tone(buzzer_0, 441, 0);		// T1-1
    delay(0);
    delay(250);
    noTone(buzzer_0);
    tone(buzzer_0, 441, 0);
    delay(0);
    delay(250);
    noTone(buzzer_0);
    tone(buzzer_0, 441, 0);
    delay(0);
    delay(480);
    noTone(buzzer_0);
    digitalWrite(relay_0, HIGH);
    tone(buzzer_0, 441, 0);
    delay(0);
    delay(250);
    noTone(buzzer_0);
    tone(buzzer_0, 441, 0);
    delay(0);
    delay(250);
    noTone(buzzer_0);
    tone(buzzer_0, 441, 0);
    delay(0);
    delay(230);
    noTone(buzzer_0);
    digitalWrite(relay_0, LOW); 
    tone(buzzer_0, 441, 0);			// T1-2
    delay(0);
    delay(250);
    noTone(buzzer_0);
    tone(buzzer_0, 556, 0);
    delay(0);
    delay(250);
    noTone(buzzer_0);
    tone(buzzer_0, 350, 0);
    delay(0);
    delay(355);
    noTone(buzzer_0);
    digitalWrite(relay_0, HIGH);
    tone(buzzer_0, 393, 0);
    delay(0);
    delay(125);
    noTone(buzzer_0);
    tone(buzzer_0, 441, 0);
    delay(0);
    delay(750);
    noTone(buzzer_0);
    tone(buzzer_0, -1, 0);
    delay(0);
    delay(230);
    noTone(buzzer_0);
    digitalWrite(relay_0, LOW); 
    tone(buzzer_0, 495, 0);			// T1-3
    delay(0);
    delay(250);
    noTone(buzzer_0);
    tone(buzzer_0, 495, 0);
    delay(0);
    delay(250);
    noTone(buzzer_0);
    tone(buzzer_0, 495, 0);
    delay(0);
    delay(500);
    noTone(buzzer_0);
    tone(buzzer_0, 495, 0);
    delay(0);
    delay(230);
    noTone(buzzer_0);
    digitalWrite(relay_0, HIGH);
    tone(buzzer_0, 495, 0);
    delay(0);
    delay(250);
    noTone(buzzer_0);
    tone(buzzer_0, 441, 0);
    delay(0);
    delay(250);
    noTone(buzzer_0);
    tone(buzzer_0, 441, 0);
    delay(0);
    delay(250);
    noTone(buzzer_0);
    tone(buzzer_0, 441, 0);
    delay(0);
    delay(125);
    noTone(buzzer_0);
    tone(buzzer_0, 441, 0);
    delay(0);
    delay(105);
    noTone(buzzer_0);
    digitalWrite(relay_0, LOW); 
    tone(buzzer_0, 556, 0);			// T1-4
    delay(0);
    delay(250);
    noTone(buzzer_0);
    tone(buzzer_0, 556, 0);
    delay(0);
    delay(250);
    noTone(buzzer_0);
    tone(buzzer_0, 495, 0);
    delay(0);
    delay(230);
    noTone(buzzer_0);
    digitalWrite(relay_0, HIGH);
    tone(buzzer_0, 393, 0);
    delay(0);
    delay(250);
    noTone(buzzer_0);
    tone(buzzer_0, 350, 0);
    delay(0);
    delay(750);
    noTone(buzzer_0);
    tone(buzzer_0, -1, 0);
    delay(0);
    delay(230);
    noTone(buzzer_0);
	...						//此处省略200+行重复代码
	delay(1500);			//循环演奏的时间间隔
}

上述代码只取了完整曲子的1/3段,因为每个音符都是以tone函数,delay函数,noTone函数的形式在循环,4个小段加起来30个音符左右,还有OLED显示和继电器控制的程序前前后后140行代码左右,全曲82个音符总370行代码,也是敲得心累,上面把后边200+行的重复函数代码省略了,只是频率跟延时不同。


案例扩展实现了简介控制圣诞树灯光闪烁,其实个人觉得接PWM引脚做成呼吸灯效果会更棒,也不用听继电器在滴答滴答叫,这个就看你们个人的idea了,分享到这,下回见~

我可以为您提供一个简单的c51单片机蜂鸣器歌曲示例程序,您可以根据需要进行修改。 ```c #include<reg52.h> #define uint unsigned int #define uchar unsigned char sbit beep=P1^5; //定义蜂鸣器控制引脚 //定义简单的音符和节拍 uchar code song[]={ 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x01, //音符 0x01,0x02,0x03,0x04,0x04,0x05,0x06,0x01, //音符 0x03,0x03,0x04,0x05,0x06,0x06,0x07,0x01, //音符 0x05,0x06,0x07,0x01,0x02,0x03,0x02,0x01, //音符 0x01,0x01,0x02,0x02,0x03,0x03,0x04,0x04, //音符 0x05,0x05,0x06,0x06,0x07,0x07,0x01,0x01, //音符 0x02,0x02,0x03,0x03,0x04,0x04,0x05,0x05, //音符 0x06,0x06,0x07,0x07,0x01,0x01,0x02,0x02, //音符 0x03,0x03,0x04,0x04,0x05,0x05,0x06,0x06, //音符 0x07,0x07,0x01,0x01,0x02,0x02,0x03,0x03, //音符 0x04,0x04,0x05,0x05,0x06,0x06,0x07,0x07, //音符 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, //音符 }; uchar code time[]={ 1,1,1,1,1,1,2,1, //节拍 1,1,1,1,1,1,2,1, //节拍 1,1,1,1,1,1,2,1, //节拍 1,1,1,1,1,1,2,1, //节拍 1,1,1,1,1,1,1,1, //节拍 1,1,1,1,1,1,2,1, //节拍 1,1,1,1,1,1,1,1, //节拍 1,1,1,1,1,1,2,1, //节拍 1,1,1,1,1,1,1,1, //节拍 1,1,1,1,1,1,2,1, //节拍 1,1,1,1,1,1,1,1, //节拍 1,1,1,1,1,1,1,1, //节拍 }; void delay(uint z) //延时函数 { uint x,y; for(x=z;x>0;x--) for(y=110;y>0;y--); } void main() { uchar i,j; while(1) { for(i=0;i<72;i++) //循环播放歌曲 { for(j=0;j<time[i];j++) //根据节拍播放音符 { beep=0; delay(song[i]*10); beep=1; delay(1); } delay(10); //每个音符之间的间隔 } } } ``` 这个示例程序使用了一个简单的歌曲,可以通过修改 `song[]` 和 `time[]` 数组来播放不同的歌曲。同时,根据需要还可以修改每个音符的持续时间和间隔时间。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GenCoder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值