单片机通过蜂鸣器播放任意音乐代码实现(1):单片机代码部分

本文介绍了如何利用51单片机通过改进的定时器和中断技术,驱动蜂鸣器播放任意音乐代码,包括频率计算、计数值转换、音乐编码和实时播放过程。通过示例代码展示了如何使用Keil5和Proteus进行仿真,实现低成本音乐播放解决方案。
摘要由CSDN通过智能技术生成

单片机通过蜂鸣器播放任意音乐代码实现(1):单片机代码部分

       有时候我们需要借助单片机实现音乐播放,可行的方案有很多,但成本最低的方案还是通过单片机驱动蜂鸣器来实现音乐播放。本文通过改进常见的单片机驱动蜂鸣器方法,一定程度上实现了任意音乐可以通过蜂鸣器播放(能听出来的程度)。不再需要手动输入乐谱参数,大大节省了时间和提高了准确度。

所用软件

  • keil 5(C51) V5.27.1.0:用于编写、编译C程序并生成hex文件
  • Proteus 8 Professional V8.9:用于单片机仿真

1.根据国际标准音高求出定时器计数值

       国际标准音高包含了哆啦咪法嗦来嘻(表中用1234567)的多个音高的频率,为了尽可能的还原音乐,这里模拟了其中9个频率的音高。下表为音高对应的频率。
![![在这里插入图片描述](https://img-blog.csdnimg.cn/2e02c4196edd48adbef2d8da203904ef.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tldmluMTQ5OQ==,size_16,color_FFFFFF,t_7](https://img-blog.csdnimg.cn/87eadbce769f4df4ad0daaa2070b8697.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2tldmluMTQ5OQ==,size_16,color_FFFFFF,t_70
       接下来根据T=65535-1/频率1/2/1.0851000000+1的公式,在EXCEL表格中添加此公式,可以很快求出各个音高对应的计数值。下图便是上述频率对应的计数值:
在这里插入图片描述       到这里还不算完,因为51单片机定时器计数值分为高位计数值和低位计数值两个部分。所以还需要将上述10进制计数值转为16进制计数值,这样便能直接将2进制的计数值分割成高位和低位两部分。 实现方法很简单,在EXCEL中编辑=DEC2HEX(单元格)便能将对应单元格的10进制计数值转为16进制计数值。下图便是上述10进制计数值对应的16进制计数值:
在这里插入图片描述

2.定时器还原音高、延时函数还原音长

       为了实现音乐的还原,设计采用定时器中断产生一定频率的方波信号驱动无源蜂鸣器还原音高,延时函数将使该频率维持特定时间还原音长。这样便能实现简谱上一个音符的还原,连贯起来便能还原整段音乐。

  • 2.1 添加音高对应的定时器计数值常量至ROM(9个音高)
code unsigned char yingaoh[63]={
0x92,0xC8,0xE4,0xF2,0xF9,0xFC,0xFE,0xFF,0xFF, 	//音符1的9个音高
0x9E,0xCE,0xE7,0xF3,0xF9,0xFC,0xFE,0xFF,0xFF,	//音符2的9个音高
0xA8,0xD4,0xEA,0xF5,0xFA,0xFD,0xFE,0xFF,0xFF, //音符3的9个音高
0xAD,0xD6,0xEB,0xF5,0xFA,0xFD,0xFE,0xFF,0xFF, //音符4的9个音高
0XB6,0XDB,0XED,0XF6,0XFB,0XFD,0XFE,0XFF,0XFF, //音符5的9个音高
0XBE,0XDF,0XEF,0XF7,0XFB,0XFD,0XFE,0XFF,0XFF, //音符6的9个音高
0XC5,0XE2,0XF1,0XF8,0XFC,0XFE,0XFF,0XFF,0XFF, //音符7的9个音高
}; //7个音符的9个八度高位计数值
code unsigned char yingaol[63]={
0x3C,0xF3,0x79,0x3C,0x1E,0x8F,0x47,0x23,0x91, //音符1的9个音高
0x2A,0xF3,0x79,0xBC,0xDE,0xEF,0x77,0x3B,0x9D,	//音符2的9个音高
0x9D,0x4E,0x27,0x13,0x89,0x45,0xA2,0x51,0xA8, //音符3的9个音高
0x6D,0xCE,0x61,0xB0,0xD8,0x6C,0xB6,0x5B,0xAD, //音符4的9个音高
0X86,0X43,0XA1,0XD0,0X68,0XB4,0XDA,0X6D,0XB6, //音符5的9个音高
0X8A,0X45,0XA2,0XD1,0XE8,0XF4,0XFA,0X7D,0XBE, //音符6的9个音高
0XBE,0XD3,0X6C,0XB5,0X5A,0X2D,0X16,0X8B,0XC5,	//音符7的9个音高
}; 7个音符的9个八度低位计数值 
  • 2.2 添加音乐对应的单片机代码(丁香花)
           其中music[0]为从musicxml中获取到的divisions值(下一节会介绍如何使用)、music[1]为每分钟节拍数、数组结尾放入0表示结束。中间的值按顺序每两个表示一个音高和维持的时间(需要通过换算求出实际时间)。
code unsigned char music[]={
8,89,
3,4,4,7,4,4,6,4,4,
7,4,4,6,4,4,6,4,4,
7,4,4,7,4,16,5,4,4,
6,4,4,7,4,4,3,4,4,
5,4,4,3,4,4,5,4,4,
3,4,4,3,4,24,3,4,4,
5,4,4,5,4,4,3,4,4,
5,4,4,6,4,4,6,4,24,
1,5,4,1,5,4,1,5,4,
5,4,4,5,4,4,6,4,4,
7,4,16,7,4,4,3,4,4,
7,4,4,6,4,4,7,4,4,
6,4,4,6,4,4,7,4,4,
7,4,16,7,4,4,3,4,4,
7,4,4,6,4,4,7,4,4,
6,4,4,5,4,4,3,4,4,
3,4,24,3,4,4,5,4,4,
5,4,4,3,4,4,5,4,4,
6,4,4,6,4,16,6,4,4,
7,4,4,1,5,4,1,5,4,
1,5,4,5,4,4,6,4,4,
7,4,4,7,4,24,6,4,4,
5,4,4,6,4,4,5,4,4,
6,4,4,7,4,4,6,4,24,
5,4,4,7,4,4,7,4,4,
6,4,4,6,4,4,5,4,4,
5,4,4,3,4,4,3,4,12,
3,4,4,5,4,4,5,4,4,
5,4,4,5,4,4,5,4,4,
3,4,4,5,4,24,4,4,24,
4,4,24,4,4,24,4,4,24,
4,4,24,3,4,4,4,4,4,
7,4,8,3,5,4,7,4,4,
7,4,8,2,5,4,3,5,4,
3,5,8,3,5,8,3,5,4,
5,5,4,3,5,4,2,5,4,
3,5,4,2,5,4,7,4,4,
6,4,4,7,4,16,3,4,8,
7,4,4,6,4,4,6,4,8,
7,4,4,3,5,4,3,5,4,
5,4,4,5,4,8,7,4,4,
3,5,4,3,5,4,7,4,4,
3,5,4,4,5,4,4,5,4,
7,4,8,3,5,4,7,4,4,
7,4,8,2,5,4,3,5,4,
3,5,8,3,5,8,3,5,4,
5,5,4,3,5,4,2,5,4,
3,5,4,2,5,4,7,4,4,
6,4,4,7,4,16,3,4,8,
7,4,4,6,4,4,6,4,8,
7,4,4,7,4,4,7,4,4,
3,4,4,3,4,8,4,4,8,
4,4,8,4,4,8,6,4,4,
5,4,4,4,4,4,3,4,4,
3,4,4,3,4,16,5,4,12,
6,4,4,6,4,4,7,4,4,
7,4,8,6,4,8,5,4,8,
4,4,2,5,4,4,6,4,8,
3,4,24,3,5,16,7,4,8,
2,5,16,6,4,8,6,4,16,
7,4,8,7,4,16,7,4,8,
1,5,16,1,5,4,2,5,4,
2,5,12,3,5,4,2,5,4,
6,4,4,7,4,8,6,4,4,
7,4,4,7,4,8,6,4,4,
7,4,4,5,4,16,7,4,12,
6,4,4,7,4,4,3,5,4,
2,5,12,6,4,4,6,4,4,
5,4,4,4,4,2,6,4,4,
5,4,4,4,4,4,3,4,24,
3,4,4,7,4,4,6,4,4,
7,4,4,6,4,4,6,4,4,
7,4,4,7,4,16,5,4,4,
6,4,4,7,4,4,3,4,4,
5,4,4,3,4,4,5,4,4,
3,4,4,3,4,24,3,4,4,
5,4,4,5,4,4,3,4,4,
5,4,4,6,4,4,6,4,24,
1,5,4,1,5,4,1,5,4,
5,4,4,5,4,4,6,4,4,
7,4,16,7,4,4,3,4,4,
7,4,4,6,4,4,7,4,4,
6,4,4,6,4,4,7,4,4,
7,4,16,7,4,4,3,4,4,
7,4,4,6,4,4,7,4,4,
6,4,4,5,4,4,3,4,4,
3,4,24,3,4,4,5,4,4,
5,4,4,3,4,4,5,4,4,
6,4,4,6,4,16,6,4,4,
7,4,4,1,5,4,1,5,4,
1,5,4,5,4,4,6,4,4,
7,4,4,7,4,24,6,4,4,
5,4,4,6,4,4,5,4,4,
6,4,4,7,4,4,6,4,24,
5,4,4,7,4,4,7,4,4,
6,4,4,6,4,4,5,4,4,
5,4,4,3,4,4,3,4,12,
3,4,4,5,4,4,5,4,4,
5,4,4,5,4,4,5,4,4,
3,4,4,5,4,24,4,4,24,
4,4,24,4,4,24,4,4,24,
4,4,24,3,4,4,4,4,4,
7,4,8,3,5,4,7,4,4,
7,4,8,2,5,4,3,5,4,
3,5,8,3,5,8,3,5,4,
5,5,4,3,5,4,2,5,4,
3,5,4,2,5,4,7,4,4,
6,4,4,7,4,16,3,4,8,
7,4,4,6,4,4,6,4,8,
7,4,4,3,5,4,3,5,4,
5,4,4,5,4,8,7,4,4,
3,5,4,3,5,4,7,4,4,
3,5,4,4,5,4,4,5,4,
7,4,8,3,5,4,7,4,4,
7,4,8,2,5,4,3,5,4,
3,5,8,3,5,8,3,5,4,
5,5,4,3,5,4,2,5,4,
3,5,4,2,5,4,7,4,4,
6,4,4,7,4,16,3,4,8,
7,4,4,6,4,4,6,4,8,
7,4,4,7,4,4,7,4,4,
3,4,4,3,4,8,4,4,8,
4,4,8,4,4,8,6,4,4,
5,4,4,4,4,4,3,4,4,
3,4,4,3,4,16,7,4,8,
3,5,4,7,4,4,7,4,8,
2,5,4,3,5,4,3,5,8,
3,5,8,3,5,4,5,5,4,
3,5,4,2,5,4,3,5,4,
2,5,4,7,4,4,6,4,4,
7,4,16,3,4,8,7,4,4,
6,4,4,6,4,8,7,4,4,
3,5,4,3,5,4,5,4,4,
5,4,8,4,5,8,7,4,4,
3,5,4,3,5,4,7,4,4,
3,5,6,3,5,1,4,5,4,
7,4,8,3,5,4,7,4,4,
7,4,8,2,5,4,3,5,4,
3,5,8,3,5,8,3,5,4,
5,5,4,3,5,4,2,5,4,
3,5,4,2,5,4,7,4,4,
6,4,4,7,4,16,3,4,8,
7,4,4,6,4,4,6,4,8,
7,4,4,7,4,4,7,4,4,
3,4,4,3,4,8,4,4,8,
4,4,8,4,4,8,6,4,4,
5,4,4,4,4,4,3,4,4,
3,4,4,3,4,16,3,4,4,
7,4,4,6,4,4,7,4,4,
6,4,4,6,4,4,7,4,4,
7,4,16,5,4,4,6,4,4,
7,4,4,6,4,4,6,4,4,
5,4,4,5,4,4,3,4,4,
3,4,24,3,4,4,5,4,4,
5,4,4,3,4,4,5,4,4,
6,4,4,6,4,24,4,4,24,
4,4,24,4,4,24,6,4,4,
5,4,4,4,4,4,3,4,4,
3,4,4,3,4,16,0
};
  • 2.3 音高模拟函数
  • 音高模拟函数实际上就是利用定时器中断产生一定频率方波驱动蜂鸣器发出一定频率的声音。
 void t0int() interrupt 1 //定时器0中断函数
{
	TR0=0;		 //关闭定时器TR0

	beep=!beep;	 //蜂鸣状态取反

	TH0=timeh;	 //把timeh赋给定时0高位计数值

	TL0=timel;	 //把timel赋给定时0低位计数值

	TR0=1;	   //开定时器TR0
}
  • 2.4 音长模拟函数
  • 音长模拟函数实际上就是让音高模拟函数运行一定时间,还原单个音符的发声。
void Delay10ms()		//11.0592MHz下的10ms延时函数
{
	unsigned char i, j;

	i = 108;
	j = 145;
	do
	{
		while (--j);
	} while (--i);
}
void jiepaidelay(float t)  //节拍延时函数
{
		unsigned int time;
		time=sifenyinfu*(t/km); //根据节拍算出10ms的计数次数 
		for(;time>0;time--)
		{
			Delay10ms();
		}
			TR0=0;
}
  • 2.4 主函数设计
  • 主函数负责协调各个子函数的运作,实现循环播放音乐的功能。
 int main(void)
{
	float k;
	unsigned int i;
	unsigned char m;
	float n;
	TMOD=0x01; //置T0定时器工作在方式1
	EA=1; //开总中断
	ET0=1; //开定时器0中断,这两句等效于IE=0x82
	i=2;
	km=music[0];//获取divisions值
	paizishu=music[1];	 //获取曲子每分钟拍子数
	k=100*((60/paizishu)/4)+0.5; //根据每分钟节拍数,求解四分之一音符保持时长,四舍五入单位为10ms 
	sifenyinfu=k;//把四分之一音符保持时长值赋给unsigned int型sifenyinfu
	while(1)
	{
		m=(music[i]-1)*9+music[i+1]; //获取音符对应计数值所在下标
		n=music[i+2]; //获取音符保持时长值
		timeh=yingaoh[m]; //获取音符对应定时器计数值高位
		timel=yingaol[m]; //获取音符对应定时器计数值低位
		TH0=timeh;	//把timeh赋给定时0高位计数值
		TL0=timel;	//把timel赋给定时0低位计数值
		TR0=1; //开定时器0
		jiepaidelay(n); //执行音符保持函数
		i=i+3;	  //读下一个音符
		if(music[i]==0) 
			i=2;	   //如果到末尾则回到开头,循环播放
	}
}

3.使用Proteus 8 Professional V8.9进行仿真

       如果你手边正好没有51单片机,或者你不想动手操作。接下来可以使用Proteus 8 Professional V8.9进行仿真。组成部分很简单,只有单片机和扬声器,注意一定要选择图下所示的扬声器才能发声(为了尽可能还原真实场景,最好把仿真软件中的速度晶振频率调整为11.0592MHz):
在这里插入图片描述
源码下载链接:
点击此处

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

NW嵌入式开发

感谢您的支持,让我们一起进步!

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

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

打赏作者

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

抵扣说明:

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

余额充值