51单片机——蜂鸣器制作音乐“送你一朵小红花“

目录

一、音乐简谱<——>单片机

1.音调(歌)-->频率(单片机)

2.节拍(歌)--->延时(单片机)

3.简谱编码

4.唱歌

二、完整代码

目录

一、音乐简谱<——>单片机

1.音调(歌)-->频率(单片机)

2.节拍(歌)--->延时(单片机)

3.简谱编码

4.唱歌

5.补充——多歌切换、暂停

二、完整代码

1.单独播放一首歌的代码

2.含补充功能的代码

三、效果检验+误差分析+优化思路

1、时长

2.一些待优化的地方


一、音乐简谱<——>单片机

简谱单片机
音调频率
节拍延时

1.音调(歌)-->频率(单片机)

简谱中用1、2、3、4、5、6、7这几个数字作为符号,表示音的高低。上面加一点是高八度,下面加一点是为低八度。

单片机

简谱中的不同音调对应51单片机中提供给蜂鸣器的不同频率的脉冲。

51单片机内部含无源蜂鸣器,通过给蜂鸣器提供不同频率的脉冲,蜂鸣器可以发出不同音调的声音。频率越高,音调越高。

无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音。

以下是C调音符与脉冲频率对应表,以a为基准频率是440Hz,左右两边都是倍数关系,中间是按照等比数列进行平分:

频率的产生可用定时器中断实现,定时器初值(十进制)计算代码逻辑如下:

这里选择定时器工作方式1,为16位定时器(2^16=65535)

//频率表
unsigned int  code FreTab[21]  = {
262,294,330,349,392,440,494,
523,587,659,698,784,880,988,
1046,1175,1318,1397,1568,1760,1976};


//定时器初值
timer0h=(65535-(int)(1000000/(2*FreTab[k])))/256;
timer0l=(65535-(int)(1000000/(2*FreTab[k])))%256;

//定时中断
void t0int() interrupt 1
{
	speaker=!speaker;
	TH0=timer0h;
	TL0=timer0l;
}

2.节拍(歌)--->延时(单片机)

下面这个是谱子的速度,表示一分钟104拍,一拍为60/104=0.5769230766923······s

分数---分母:看谱子的左上角的分数,以四分音符为1拍

数字右边加一个点,相当于加上半拍。

一般四分音符1拍为500ms,二分音符为1拍=1000ms······

这里1拍取整577us,以四分之一拍为单位,1个四分之一拍取整为144us。

分数---分子:每小节4拍

单片机

循环--->延时

//  节奏的快慢,这里是四分之一个节拍的时间
void delay(unsigned char t)
{
	unsigned long t2;
	t2=t*144;
	while(t2--)
	{
	   unsigned char data i, j;
	   i = 2;
	   j = 239;
	   do
	   {
		   while (--j);
	   }    
		 while (--i); 
	}

	TR0=0;// 停止发音	如果屏蔽掉,会出现连音的不正常现象
}

3.简谱编码

以三位数代表1个音符。

第一位:音高(低音0,中音1,高音2),其中0可省略;

第二位:音长(四分之一个节拍为1,其他依次递增,如1个节拍为1*4=4);

第三位:音调(1,2,3,4,5,6,7);

code unsigned char gyz[]={
140,26,25,26,25,120,25,143,123,143,122,121,11,171,
// 送你一朵小红花
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,26,26,26,65,140,140,
//送你一朵小红花,开在你昨天新长的枝丫。
140,26,121,46,140,  135,113,123,125,145,140,  133,116,126,125,145,121,123,  123,123,142,140,140,  
//奖励你有勇气,主动来和我说话。
140,126,126,136,116,126,125,  145,121,122,142,140,   140,123,123,122,143,122,  143,26,121,141,140,   140,121,121,35,16,26,25,  125,123,123,122,142,140,  26,142,122,132,111,121,26,  26,121,181,140,  
//不共戴天的冰水啊,义无反顾的烈酒啊,多么苦难的日子里 ,你都已战胜了它。
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,26,26,26,65,140,140,
//送你一朵小红花,遮住你今天新添的伤疤。
140,26,121,46,120,26,  135,113,123,125,145,140,   126,146,125,145,121,123,  123,162,140,140,
//奖励你在下雨天,还愿意送我回家。
140,126,126,136,116,126,125,  145,121,122,142,140,  140,123,122,123,143,122,  143,26,121,141,140,  140,25,26,85, 135,115,125,123,163,123,  132,112,122,121,142,26,122,  122,161,140,140,
//科罗拉多的风雪啊,喜马拉雅的骤雨啊,只要你相信我,闭上眼就能到达。
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,23,26,26,65,140,140,
//送你一朵小红花,开在那牛羊遍野的天涯,
140,26,121,46,140,  133,115,125,123,143,140,  126,145,125,145,123,121,  121,122,142,140,140,
//奖励你走到哪儿,都不会忘记我啊。
140,126,126,136,116,126,125,  135,115,125,122,142,140, 140,123,123,133,113,123,122,  133,16,26,121,141,140,  140,121,121,131,111,121,25,  125,123,122,123,143,140,  122,142,122,142,26,122,  122,121,181,140,
//洁白如雪的沙滩啊,风平浪静的湖水啊,那些真实的幻影啊,是我给你的牵挂。
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,23,26,26,65,140,140,
//送你一朵小红花,开在你心底最深的泥沙。
140,26,121,46,140,  135,113,123,125,145,140,  133,116,126,125,145,121,123,  123,162,140,140,  
//奖励你能感受,每个命运的挣扎。
140,126,126,136,116,126,125,  135,115,125,122,142,140,  140,123,122,123,143,122,  133,112,122,121,141,140,  140,121,121,141,25,25,  145,123,123,163,123,  132,112,122,121,142,26,122,  122,161,140,140,
//是谁挥霍的时光啊,是谁苦苦的奢望啊,这不是一个问题,也不需要你的回答。
140,26,25,26,45,120,  25,143,123,143,122,121,  181,181,
//送你一朵小红花
140,140,140,140,  140,26,25,26,45,120,  25,143,123,143,122,121,  181,181,
//送你一朵小红花
140,140,140,140,  140,26,25,26,45,120,  25,143,123,143,122,121,  181,181,
//送你一朵小红花
140,140,140,140,  140,26,25,26,45,120,  25,143,123,143,122,121,  181,181,
//送你一朵小红花
0  //0标志 表示结束
};

4.唱歌

蜂鸣器唱歌的整个逻辑就是,循环遍历简谱,得到每个音符对应的频率,由频率计算出定时器初值,让定时器发出对应频率的脉冲给蜂鸣器,然后蜂鸣器发出不同的音调,再看该音符要唱几排,延时相应的时间,连起来就是一首歌了。

以下是song函数:

void song()
{
	unsigned char k=0;
	unsigned long i=0;	//  由于歌词长度问题,需要设置大一点的范围,否则还没唱完一遍又重新开始了
	// 这里这样做就只唱一遍,寻到最后的0值时就停止了
	while(time)	
	{
		if(gyz[i]%10==0) 	 // 音调为0值时停顿处理
		{
			TR0=0;	  // 停止发音
		}
		else
		{
			k=gyz[i]%10+7*(gyz[i]/100)-1;
			timer0h=(65535-(int)(1000000/(2*FreTab[k])))/256;
			timer0l=(65535-(int)(1000000/(2*FreTab[k])))%256;
			TR0=1;	  // 启动T0输出方波去发音
		}
		time=gyz[i]/10%10;
		i=i+1;
		
		delay(time);
	}
}

5.补充——多歌切换、暂停

可以用按键控制放哪首歌,在歌前加一个按键值的条件语句即可;想在歌播放途中暂停,可用外部中断0,(缺)但是以下程序只能使歌暂停,无法继续播放,要想再播歌需按选歌的按键,但也只是从头播放。

void key3_interrupt() interrupt 0
{
	if(key3==0)
	{
		 time=0; 
		  
	 }
}

二、完整代码

1.单独播放一首歌的代码

#include <REG52.H>    
#include <INTRINS.H>   
sbit speaker=P1^5; 
unsigned char timer0h,timer0l,time=1;
//送你一朵小红花
code unsigned char xhh[]={
140,26,25,26,25,120,25,143,123,143,122,121,11,171,
//送你一朵小红花
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,26,26,26,65,140,140,
//送你一朵小红花,开在你昨天新长的枝丫。
140,26,121,46,140,  135,113,123,125,145,140,  133,116,126,125,145,121,123,  123,123,142,140,140,  
//奖励你有勇气,主动来和我说话。
140,126,126,136,116,126,125,  145,121,122,142,140,   140,123,123,122,143,122,  143,26,121,141,140,   140,121,121,35,16,26,25,  125,123,123,122,142,140,  26,142,122,132,111,121,26,  26,121,181,140,  
//不共戴天的冰水啊,义无反顾的烈酒啊,多么苦难的日子里 ,你都已战胜了它。
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,26,26,26,65,140,140,
//送你一朵小红花,遮住你今天新添的伤疤。
140,26,121,46,120,26,  135,113,123,125,145,140,   126,146,125,145,121,123,  123,162,140,140,
//奖励你在下雨天,还愿意送我回家。
140,126,126,136,116,126,125,  145,121,122,142,140,  140,123,122,123,143,122,  143,26,121,141,140,  140,25,26,85, 135,115,125,123,163,123,  132,112,122,121,142,26,122,  122,161,140,140,
//科罗拉多的风雪啊,喜马拉雅的骤雨啊,只要你相信我,闭上眼就能到达。
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,23,26,26,65,140,140,
//送你一朵小红花,开在那牛羊遍野的天涯,
140,26,121,46,140,  133,115,125,123,143,140,  126,145,125,145,123,121,  121,122,142,140,140,
//奖励你走到哪儿,都不会忘记我啊。
140,126,126,136,116,126,125,  135,115,125,122,142,140, 140,123,123,133,113,123,122,  133,16,26,121,141,140,  140,121,121,131,111,121,25,  125,123,122,123,143,140,  122,142,122,142,26,122,  122,121,181,140,
//洁白如雪的沙滩啊,风平浪静的湖水啊,那些真实的幻影啊,是我给你的牵挂。
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,23,26,26,65,140,140,
//送你一朵小红花,开在你心底最深的泥沙。
140,26,121,46,140,  135,113,123,125,145,140,  133,116,126,125,145,121,123,  123,162,140,140,  
//奖励你能感受,每个命运的挣扎。
140,126,126,136,116,126,125,  135,115,125,122,142,140,  140,123,122,123,143,122,  133,112,122,121,141,140,  140,121,121,141,25,25,  145,123,123,163,123,  132,112,122,121,142,26,122,  122,161,140,140,
//是谁挥霍的时光啊,是谁苦苦的奢望啊,这不是一个问题,也不需要你的回答。
140,26,25,26,45,120,  25,143,123,143,122,121,  181,181,
//送你一朵小红花
140,140,140,140,  140,26,25,26,45,120,  25,143,123,143,122,121,  181,181,
//送你一朵小红花
140,140,140,140,  140,26,25,26,45,120,  25,143,123,143,122,121,  181,181,
//送你一朵小红花
140,140,140,140,  140,26,25,26,45,120,  25,143,123,143,122,121,  181,181,
//送你一朵小红花
0 
};


//频率表
unsigned int  code FreTab[21]  = {
262,294,330,349,392,440,494,
523,587,659,698,784,880,988,
1046,1175,1318,1397,1568,1760,1976};

void t0int() interrupt 1
{
	speaker=!speaker;
	TH0=timer0h;
	TL0=timer0l;
}

//  节奏的快慢,这里是四分之一个节拍的时间
void delay(unsigned char t)
{
	unsigned long t2;
	t2=t*144;
	while(t2--)
	{
	   unsigned char data i, j;
	   i = 2;
	   j = 239;
	   do
	   {
		   while (--j);
	   }    
		 while (--i); 
	}

	TR0=0;// 停止发音	如果屏蔽掉,会出现连音的不正常现象
}
/**/
void song()
{
	unsigned char k=0;
	unsigned long i=0;	//  由于歌词长度问题,需要设置大一点的范围,否则还没唱完一遍又重新开始了
	// 这里这样做就只唱一遍,寻到最后的0值时就停止了
	while(time)	
	{
		if(xhh[i]%10==0) 	 // 音调为0值时停顿处理
		{
			TR0=0;	  // 停止发音
		}
		else
		{
			k=xhh[i]%10+7*(xhh[i]/100)-1;
			timer0h=(65535-(int)(1000000/(2*FreTab[k])))/256;
			timer0l=(65535-(int)(1000000/(2*FreTab[k])))%256;
			TR0=1;	  // 启动T0输出方波去发音
		}
		time=xhh[i]/10%10;
		i=i+1;
		
		delay(time);
	}
}
void main(void)
{
	TMOD=1;		// 设置定时器0为16位计数模式 
	EA=1;ET0=1;	// 开定时器0中断
	while(1)
	{
		song();
	}
}

2.含补充功能的代码

#include <REG52.H>    
  
sbit speaker=P1^5; 
sbit key1= P3^1;
sbit key2= P3^0;
sbit key3= P3^2;
unsigned char timer0h,timer0l,time=1;



// 送你一朵小红花
code unsigned char xhh[]={
140,26,25,26,25,120,25,143,123,143,122,121,11,171,
//(原版)送你一朵小红花
//(毕业歌)送你一朵小红花
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,26,26,26,65,140,140,
//(原版)送你一朵小红花,开在你昨天新长的枝丫。
//(毕业歌)送你一朵小红花,开在你昨天新长的枝丫。
140,26,121,46,140,  135,113,123,125,145,140,  133,116,126,125,145,121,123,  123,123,142,140,140,  
//(原版)奖励你有勇气,主动来和我说话。
//(毕业歌)奖励你有勇气,主动来和我说话。
140,126,126,136,116,126,125,  145,121,122,142,140,   140,123,123,122,143,122,  143,26,121,141,140,   140,121,121,35,16,26,25,  125,123,123,122,142,140,  26,142,122,132,111,121,26,  26,121,181,140,  
//(原版)不共戴天的冰水啊,义无反顾的烈酒啊,多么苦难的日子里 ,你都已战胜了它。
//(毕业歌)爸爸妈妈的叮咛啊,老师同学的祝福啊,多么温暖的日子里,挥挥手又要出发。
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,26,26,26,65,140,140,
//(原版)送你一朵小红花,遮住你今天新添的伤疤。
//(毕业歌)送你一朵小红花,遮住你今天新添的伤疤。
140,26,121,46,120,26,  135,113,123,125,145,140,   126,146,125,145,121,123,  123,162,140,140,
//(原版)奖励你在下雨天,还愿意送我回家。
//(毕业歌)奖励你在下雨天,还愿意送我回家。
140,126,126,136,116,126,125,  145,121,122,142,140,  140,123,122,123,143,122,  143,26,121,141,140,  140,25,26,85, 135,115,125,123,163,123,  132,112,122,121,142,26,122,  122,161,140,140,
//(原版)科罗拉多的风雪啊,喜马拉雅的骤雨啊,只要你相信我,闭上眼就能到达。
//(毕业歌)风华赛道的黎明啊,数图窗外的星光啊,只要你相信我,坚持走就能到达。
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,23,26,26,65,140,140,
//(原版)送你一朵小红花,开在那牛羊遍野的天涯,
//(毕业歌)送你一朵小红花,开在那牛羊遍野的天涯,
140,26,121,46,140,  133,115,125,123,143,140,  126,145,125,145,123,121,  121,122,142,140,140,
//(原版)奖励你走到哪儿,都不会忘记我啊。
//(毕业歌)奖励你走到哪儿,都不会忘记我啊。
140,126,126,136,116,126,125,  135,115,125,122,142,140, 140,123,123,133,113,123,122,  133,16,26,121,141,140,  140,121,121,131,111,121,25,  125,123,122,123,143,140,  122,142,122,142,26,122,  122,121,181,140,
//(原版)洁白如雪的沙滩啊,风平浪静的湖水啊,那些真实的幻影啊,是我给你的牵挂。
//(毕业歌)催人攀登的天梯啊,青春奋进的身影啊,生生不息的校园啊,是我给你的牵挂。
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,23,26,26,65,140,140,
//(原版)送你一朵小红花,开在你心底最深的泥沙。
//(毕业歌)送你一朵小红花,开在你心底最深的泥沙。
140,26,121,46,140,  135,113,123,125,145,140,  133,116,126,125,145,121,123,  123,162,140,140,  
//(原版)奖励你能感受,每个命运的挣扎。
//(毕业歌)奖励你能感受,每段闪光的年华。
140,126,126,136,116,126,125,  135,115,125,122,142,140,  140,123,122,123,143,122,  133,112,122,121,141,140,  140,121,121,141,25,25,  145,123,123,163,123,  132,112,122,121,142,26,122,  122,161,140,140,
//(原版)是谁挥霍的时光啊,是谁苦苦的奢望啊,这不是一个问题,也不需要你的回答。
//(毕业歌)披荆斩棘的时光啊,乘风破浪的旅途啊,这不是一个问题,期待你明天的回答。
140,26,25,26,45,120,  25,143,123,143,122,121,  181,181,
//送你一朵小红花
140,140,140,140,  140,26,25,26,45,120,  25,143,123,143,122,121,  181,181,
//送你一朵小红花
140,140,140,140,  140,26,25,26,45,120,  25,143,123,143,122,121,  181,181,
//送你一朵小红花
140,140,140,140,  140,26,25,26,45,120,  25,143,123,143,122,121,  181,181,
//送你一朵小红花
0 
};

//生日歌
code unsigned char srg[]={
140,140,125,125,  146,145,241,  187,125,125,  146,145,242,  281,125,125,  245,243,241, 147,146,124,124,
243,241,243,  282,125,125,  146,145,241,  187,125,125,  146,145,242,  281,125,125,  245,243,241,  147,146,124,124,  243,241,242, 241,241,241, 0};

	
	
	
//频率表
unsigned int  code FreTab[21]  = {
262,294,330,349,392,440,494,
523,587,659,698,784,880,988,
1046,1175,1318,1397,1568,1760,1976};

void t0int() interrupt 1
{
	speaker=!speaker;
	TH0=timer0h;
	TL0=timer0l;
}

//  节奏的快慢,这里是四分之一个节拍的时间
void delay(unsigned char t)
{
	unsigned long t2;
	t2=t*144;
	while(t2--)
	{
	   unsigned char data i, j;
	   i = 2;
	   j = 239;
	   do
	   {
		   while (--j);
	   }    
		 while (--i); 
	}

	TR0=0;// 停止发音	如果屏蔽掉,会出现连音的不正常现象
}
/**/
void song()
{
	unsigned char k=0;
	unsigned long i1=0,i2=0,i3=0;	//  由于歌词长度问题,需要设置大一点的范围,否则还没唱完一遍又重新开始了

	  if(key1==0)
	  {
			time=1;
	    while(time)	
	    {
					
		    if(xhh[i1]%10==0) 	 // 音调为0值时停顿处理
		    {
			     TR0=0;	  // 停止发音
		     }
		    else
		    {
			     k=xhh[i1]%10+7*(xhh[i1]/100)-1;
			     timer0h=(65535-(int)(1000000/(2*FreTab[k])))/256;
			     timer0l=(65535-(int)(1000000/(2*FreTab[k])))%256;
			     TR0=1;	  // 启动T0输出方波去发音
		     }
		    time=xhh[i1]/10%10;// 这里这样做就只唱一遍,寻到最后的0值时就停止了
		    i1=i1+1;
		
		    delay(time);
			 }
			time=1;i1=0;
	  
	   }
	
	 if(key2==0)
	{
		time=1;
	    while(time)	
	    {
		    if(srg[i2]%10==0) 	 // 音调为0值时停顿处理
		    {
			     TR0=0;	  // 停止发音
		     }
		    else
		    {
			     k=srg[i2]%10+7*(srg[i2]/100)-1;
			     timer0h=(65535-(int)(1000000/(2*FreTab[k])))/256;
			     timer0l=(65535-(int)(1000000/(2*FreTab[k])))%256;
			     TR0=1;	  // 启动T0输出方波去发音
		     }
		    time=srg[i2]/10%10;
		    i2=i2+1;
		
		    delay(time);
			 }
	    time=1; i2=0;
	 }
	
}
void main(void)
{
	TMOD=1;		// 设置定时器0为16位计数模式 
	EA=1;ET0=1;	// 开定时器0中断
	EX0=1;IT0=1;
	while(1)
	{
		song();
	}
}


void key3_interrupt() interrupt 0
{
	if(key3==0)
	{
		 time=0; 
		  
	 }
}

三、效果检验+误差分析+优化思路

1、时长

谱子的速度为一分钟104拍,该谱总共有344拍,总共3.3076923076······分钟,约198.46s

蜂鸣器实际播放一首歌的时间为223s

误差分析:多出的时间可能是因为程序执行需要时间,此外,节拍延时函数中1拍取得时间本身就是比实际大的数值,慢慢累积会造成较大误差。

2.一些待优化的地方

(1)节拍:由于这里是以四分之一拍为单位,那么四拍对应的数字就是16,大于9,无法用1个数表示。本文的处理方法是将四拍换成两个二拍,虽然两个二拍的时间长度与四拍差不多,但是两个二拍发音之间会有停顿现象(节拍延时函数的最后设置了TR0=0停止发音,防止连音不正常现象)。

或许可以用9(未用到)来代替四拍,然后再延时函数中判断一下,如果传入的为9,则令t=16。

(2)暂停:这里用外部中断0实现暂停,外部中断0的优先级比定时中断0的高,可能会影响定时,从而使频率有点偏差。此外,同上文所述,控制外部中断的按键只能使歌暂停,无法继续播放,要想再播歌仍需按选歌的按键,但也只是从头播放。

怎样让歌继续播放小白还需进一步研究。或许可以在外部中断函数中标记此刻暂停的位置,执行song函数时不再从0开始,而是从标志位开始(即改变i值)。

  • 16
    点赞
  • 110
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值