基于51单片机的音乐盒的设计.docx
《基于51单片机的音乐盒的设计.docx》由会员分享,可在线阅读,更多相关《基于51单片机的音乐盒的设计.docx(26页珍藏版)》请在冰豆网上搜索。
![基于51单片机的音乐盒的设计.docx](https://file1.bdocx.com/fileroot1/2023-1/24/04f83c2e-5f32-4d31-8b90-990e91a57300/04f83c2e-5f32-4d31-8b90-990e91a573001.gif)
基于51单片机的音乐盒的设计
基于AT89C51单片机的音乐盒的设计
《单片机原理及应用》是高校工程专业的一门专业基础课,该门课程具有很强的实践性。
通过课程的学习,使我能够掌握基本概念、基本理论和基本技能,为今后从事相应的生产设计和科研工作打下一定的基础。
因此,除了在课堂的理论学习和做实验外,课程设计也是一个重要的实践学习环节。
通过课程设计,进一步培养我们理论联系实际的能力,学会正确地分析工程实际问题,善于查阅参考文献,准确地选择相应的数据、参数,具备全面地解决实际问题的素质,同时课程设计也为今后的毕业设计打下基础。
单片机自20世纪70年代问世以来,以其极高的性能价格比,受到人们的重视和关注,应用很广、发展很快。
而51单片机是各单片机中最为典型和最有代表性的一种。
本音乐播放器是利用89C51单片机结合内部定时系统,设计一个简易的微电脑音乐盒。
引言
《单片机原理及应用》是高校工程专业的一门专业基础课,该门课程具有很强的实践性。
通过课程的学习,使我能够掌握基本概念、基本理论和基本技能,为今后从事相应的生产设计和科研工作打下一定的基础。
因此,除了在课堂的理论学习和做实验外,课程设计也是一个重要的实践学习环节。
通过课程设计,进一步培养我们理论联系实际的能力,学会正确地分析工程实际问题,善于查阅参考文献,准确地选择相应的数据、参数,具备全面地解决实际问题的素质,同时课程设计也为今后的毕业设计打下基础。
单片机自20世纪70年代问世以来,以其极高的性能价格比,受到人们的重视和关注,应用很广、发展很快。
而51单片机是各单片机中最为典型和最有代表性的一种。
本音乐播放器是利用89C51单片机结合内部定时系统,设计一个简易的微电脑音乐盒。
1概述
通过按键给单片机的P2口输入低电平,进而利用程序来判断是否执行某一播放功能。
而利用单片机的定时器0中断来控制播放乐曲。
1.1课题意义
音乐盒的起源,可追溯至中世纪欧洲文艺复兴时期。
当时为使教会的钟塔报时,而将大小的钟表装上机械装置,被称为“可发出声音的组钟”。
音乐盒有着300多年的发展历史,是人类文明发展的历史见证。
传统的音乐盒多是机械音乐盒,其工作原理是通过齿轮带动一个带有铁钉的铁桶转动,铁桶上的铁钉撞击铁片制成的琴键,从而发出声音。
但是,机械式的音乐盒体积比较大,比较笨重,且发音单调。
水、灰尘等外在因素,容易使内部金属发音条变形,从而造成发音跑调。
另外,机械音乐盒放音时为了让音色稳定,必须放平不能动摇,而且价格昂贵,不能实现大批量生产。
本文设计的音乐盒,是基于单片机设计制作的电子式音乐盒。
与传统的机械式音乐盒相比更小巧,音质更优美且能演奏和弦音乐。
电子式音乐盒动力来源是电池,制作工艺简单,可进行批量生产,所以价格便宜。
基于单片机制作的电子式音乐盒,控制功能强大,可根据需要选歌,使用方便。
根据存储容量的大小,可以尽可能多的存储歌曲。
另外,可以设计彩灯外观效果,使音乐盒的功能更加丰富。
1.2设计方案
设计一个基于AT89C51系列单片机的音乐盒,利用按键切换演奏出不同的乐曲。
1.3研究内容
1)电路工作模式:
演奏音乐模式。
演奏完整的一首的歌曲,随着音乐变化,蜂鸣器随着发出音乐声。
2)按下按键进入演奏音乐模式,再按切换歌曲,共两首歌曲。
2设计原理
通过按键给单片机的P2口输入低电平,进而利用程序来判断是否执行某一播放功能。
而利用单片机的定时器0中断来控制播放乐曲。
3时钟振荡电路、复位电路
利用12MHZ的晶振做外部时钟,AT89C51中有一个用于构成内部振荡器的高增益反相放大器,引脚XTAL1和XTAL2分别是该放大器的输入端和输出端。
这个放大器与作为反馈元件的片外石英晶体(陶瓷)谐振器一起构成自然振荡器。
外接石英晶体及电容C1、C2接在放大器的反馈回路中构成并联振荡电路。
对外接电容C1,C2虽然没有什么严格的要求,但电容容量的大小会轻微影响振荡频率的高低、振荡器工作的稳定性、起振的难易程序及温度稳定性。
电路如下图1。
图1晶振电路、复位电路
4蜂鸣器电路
利用NPN管(9012)放大驱动。
基极接10K欧姆的电阻,发射极接蜂鸣器,集电极接电源。
蜂鸣器电路连接如下图2
图2蜂鸣器电路
5控制电路
本次设计中,按键有7个.键分别接于7根I/O线(P2口),各按键在实物连接上相互独立,彼此的工作状态互不影响,单片机自带上拉电阻因此无需外接上拉电阻,用查询法可完成此按键功能。
电路连接如下图3
图3键盘控制电路
6仿真调试及说明
6.1调试结果
图4仿真调试
6.2性能分析
用AT89C51单片机的I/O口产生一定频率的方波,驱动蜂鸣器,发出不同的音调,从而演奏乐曲。
共有4支乐曲,每首乐曲都由相应的按键控制,并且有开关键、暂停键、上一曲以及下一曲控制键。
将源程序导入单片机后,在proteus软件中进行仿真。
我通过仿真发现,按下按键时,存在一定的延时,不能立刻反应,当按下按键时,应必须有足够的时间。
7系统软件设计
图5源程序流程图
7音调、节拍以及编码的确定方法
一般说来,单片机演奏音乐基本都是单音频率,它不包含相应幅度的谐波频率,也就是说不能像电子琴那样能奏出多种音色的声音。
因此单片机奏乐只需弄清楚两个概念即可,也就是“音调”和节拍表示一个音符唱多长的时间。
7.1.1音调的确定
不同音高的乐音是用C、D、E、F、G、A、B来表示,这7个字母就是音乐的音名,它们一般依次唱成DO、RE、MI、FA、SO、LA、SI,即唱成简谱的1、2、3、4、5、6、7,相当于汉字“多来米发梭拉西”的读音,这是唱曲时乐音的发音,所以叫“音调”,即Tone。
把C、D、E、F、G、A、B这一组音的距离分成12个等份,每一个等份叫一个“半音”。
两个音之间的距离有两个“半音”,就叫“全音”。
在钢琴等键盘乐器上,C–D、D–E、F–G、G–A、A–B两音之间隔着一个黑键,他们之间的距离就是全音;E–F、B–C两音之间没有黑键相隔,它们之间的距离就是半音。
通常唱成1、2、3、4、5、6、7的音叫自然音,那些在它们的左上角加上﹟号或者b号的叫变化音。
﹟叫升记号,表示把音在原来的基础上升高半音,b叫降记音,表示在原来的基础上降低半音。
例如高音DO的频率(1046Hz)刚好是中音DO的频率(523Hz)的一倍,中音DO的频率(523Hz)刚好是低音DO频率(266Hz)的一倍;同样的,高音RE的频率(1175Hz)刚好是中音RE的频率(587Hz)的一倍,中音RE的频率(587Hz)刚好是低音RE频率(294Hz)的一倍。
1)要产生音频脉冲,只要算出某一音频的周期(1/频率),然后将此周期除以2,即为半周期的时间。
利用定时器计时这半个周期时间,每当计时到后就将输出脉冲的I/O反相,然后重复计时此半周期时间再对I/O反相,就可在I/O脚上得到此频率的脉冲。
2)利用AT89C51的内部定时器使其工作在计数器模式MODE1下,改变计数值TH0及TL0以产生不同频率的方法。
此外结束符和休止符可以分别用代码00H和FFH来表示,若查表结果为00H,则表示曲子终了;若查表结果为FFH,则产生相应的停顿效果。
3)例如频率为523Hz,其周期T=1/523=1912us,因此只要令计数器计时956us/1us=956,在每次技术956次时将I/O反相,就可得到中音DO(523Hz)。
计数脉冲值与频率的关系公式如下:
N=Fi
2
Fr
N:
计算值;Fi:
内部计时一次为1us,故其频率为1MHz;
4)其计数值的求法如下:
T=65536-N=65536-Fi
2
Fr
例如:
设K=65536,F=1000000=Fi=1MHz,球低音DO(261Hz)。
中音DO(523Hz)。
高音的DO(1046Hz)的计算值
T=65536-N=65536-Fi
2
Fr=65536-1000000
2
Fr=65536-500000/Fr
低音DO的T=65536-500000/262=63627
低音DO的T=65536-500000/523=64580
低音DO的T=65536-500000/1047=65059
5)C调各音符频率与计数值T的对照表如表4.1所示。
表4.1C调各音符频率与计数值T的对照表
7.1.2节拍的确定
若要构成音乐,光有音调是不够的,还需要节拍,让音乐具有旋律(固定的律动),而且可以调节各个音的快满度。
“节拍”,即Beat,简单说就是打拍子,就像我们听音乐不自主的随之拍手或跺脚。
若1拍实0.5s,则1/4拍为0.125s。
至于1拍多少s,并没有严格规定,就像人的心跳一样,大部分人的心跳是每分钟72下,有些人快一点,有些人慢一点,只要听的悦耳就好。
音持续时间的长短即时值,一般用拍数表示。
休止符表示暂停发音。
一首音乐是由许多不同的音符组成的,而每个音符对应着不同频率,这样就可以利用不同的频率的组合,加以与拍数对应的延时,构成音乐。
了解音乐的一些基础知识,我们可知产生不同频率的音频脉冲即能产生音乐。
对于单片机来说,产生不同频率的脉冲是非常方便的,利用单片机的定时/计数器来产生这样的方波频率信号。
因此,需要弄清楚音乐中的音符和对应的频率,以及单片机定时计数的关系。
表4.2节拍与节拍码对照
7.1.3编码
doremifasolasi分别编码为1~7,重音do编为8,重音re编为9,停顿编为0。
播放长度以十六分音符为单位(在本程序中为165ms),一拍即四分音符等于4个十六分音符,编为4,其它的播放时间以此类推。
音调作为编码的高4位,而播放时间作为低4位,如此音调和节拍就构成了一个编码。
以0xff作为曲谱的结束标志。
举例1:
音调do,发音长度为两拍,即二分音符,将其编码为0x18。
举例2:
音调re,发音长度为半拍,即八分音符,将其编码为0x22
歌曲播放的设计。
先将歌曲的简谱进行编码,储存在一个数据类型为unsignedchar的数组中。
程序从数组中取出一个数,然后分离出高4位得到音调,接着找出相应的值赋给定时器0,使之定时操作蜂鸣器,得出相应的音调;接着分离出该数的低4位,得到延时时间,接着调用软件延时。
表4.4简谱对应的简谱码、T值、节拍数
8总结
通过对单片机的学习,发现对单片机的硬件设计,软件设计掌握的深度不够,但通过此次课程设计,却改变了很多,首先对于硬件电路的工作原理有了进一步的学习,同样就有了进一步的认识;其次软件方面,在程序的设计,程序的调试方面都学到了很多东西,这是第一次编写单片机的大程序,很有成就感。
在一个好的氛围里才能踏下心来做东西,在这几天课设的时间里,编程中出现问题时,一定要戒骄戒躁,脚踏实地,认真看书,仔细分析,仔细调试,就一定会发现错误,克服困难,我也是这么做的,这在课设中十分重要。
在本次设计的过程中,我发现很多的问题,虽然以前没有做过这样的设计但通过这次设计我学会了很多东西,单片机课程设计重点就在于软件算法的设计,需要有很巧妙的程序算法,虽然以前写过几次程序,但我觉的写好一个程序并不是一件简单的事,比如写一个程序看其功能很少认为编写程序简单,但到编程的时候才发现一些细微的知识或低级错误经常犯做不到最后常常失败,所以有些东西只有学精弄懂并且要细心才行,只学习理论有些东西是很难理解的,更谈不上掌握。
从这次的课程设计中,我真真正正的意识到,在以后的学习中,要理论联系实际,把所学的理论知识用到实际当中,学习单机片机更是如此,程序只有在经常的练习的过程中才能提高,我想这就是我在这次课程设计中的最大收获。
参考文献
[1]胡有彬.现代微机原理与接口技术[M].北京:
电子工业出版社,2002.
[2]张友德,赵志英,涂时亮.单片微型机原理.应用与实验[M].上海:
复旦大学社,2003.
[3]张洪润,张亚凡.单片机原理及应用[M].北京:
清华大学出版社,2005.
[4]张靖武,周灵彬.单片机系统的Proteus设计与仿真[M].北京:
电子工业出社,2007.
[5]何立明.单片机应用系统设计[M].北京:
北京航空航天大学出版社,1990.
[6]凌玉华.单片机原理及应用系统设计[M].长沙:
中南大学出版社,2006.
[7]张毅刚.MCS-51单片机应用设计[M].哈尔宾:
哈尔宾工业大学出版社,1997.
[8]谭浩强.C程序设计[M].北京:
清华大学出版社,2009.
[9]王思明,张金敏,张鑫等.单片机原理及应用系统设计[M].北京:
科学出版社,2012.
附录源程序代码
#include
#defineucharunsignedchar
#defineuintunsignedint
sbitBEEP=P1^4;//蜂鸣器
sbitk0=P2^0;
sbitk1=P2^1;
sbitk2=P2^2;
sbitk3=P2^3;
sbitk4=P2^4;
sbitk5=P2^5;
sbitk6=P2^6;
uintcodeTone_Delay_Table[]={64021,64103,64260,64400,64524,64580,64684,64777,64820,64898,64968,65030,65058,65110,65157,65178};
ucharcodeSong1_Tone[]={1,1,5,5,6,6,5,4,4,3,3,2,2,1,5,5,4,4,3,3,2,5,5,4,4,3,3,2,1,1,5,5,6,6,5,4,4,3,3,2,2,1,0xff};
ucharcodeSong1_Time[]={2,2,2,2,2,3,4,2,2,2,2,2,2,4,2,2,2,2,2,2,4,2,2,2,2,2,2,5,2,2,2,2,2,2,5,2,2,2,2,2,2,5,0xFF};
ucharcodeSong2_Tone[]={1,2,3,1,1,2,3,1,3,4,5,3,4,5,5,6,5,4,3,1,5,6,5,4,3,1,1,5,1,0xFF};
ucharcodeSong2_Time[]={2,2,3,2,2,2,3,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,0xFF};
ucharcodeSong3_Tone[]={1,3,3,3,3,5,4,2,5,3,7,6,5,5,7,4,4,3,6,7,2,1,0xFF};
ucharcodeSong3_Time[]={2,1,1,2,1,1,1,2,1,1,3,2,1,1,2,4,1,1,2,1,1,1,0xFF};
ucharcodeSong4_Tone[]={8,9,2,3,7,6,2,3,10,11,1,2,3,1,2,3,3,4,5,6,5,3,5,6,5,3,5,3,2,1,1,2,3,0xFF};
ucharcodeSong4_Time[]={3,6,7,2,4,5,8,1,2,2,5,5,1,9,1,1,1,1,6,1,1,2,4,1,1,2,1,1,1,1,1,2,2,1,0xFF};
ucharSong_Index=0,Tone_Index=0;//音乐片段索引,音符索引
uchar*Song_Tone_Pointer,*Song_Time_Pointer;//音符指针,延时指针
uchari=0;
ucharj=0,k=0,m=0;//从当前数组中取音符的位置
voidDelayMS(uintms)//延时
{
uchart;
while(ms--)for(t=0;t<120;t++);
}
voidplay0()//按键产生的INT0
{
ET0=1;
TR0=0;
k0=1;
Song_Index=(Song_Index+1)%4;//切换到下一音乐
switch(Song_Index)
{
case0:
Song_Tone_Pointer=Song1_Tone;
Song_Time_Pointer=Song1_Time;
break;
case1:
Song_Tone_Pointer=Song2_Tone;
Song_Time_Pointer=Song2_Time;
break;
case2:
Song_Tone_Pointer=Song3_Tone;
Song_Time_Pointer=Song3_Time;
break;
case3:
Song_Tone_Pointer=Song4_Tone;
Song_Time_Pointer=Song4_Time;
break;
}
//从下一段音乐的第0个音符开始
i=0;
while(k0==1&&k1==1&&k2==1&&k3==1&&k4==1&&k5==1&&k6==1)
{
Tone_Index=Song_Tone_Pointer[i];
if(Tone_Index==0xFF)
{
i=0;
DelayMS(2000);//每段音乐播放结束后停顿一段时间
continue;//继续播放
}
TR0=1;
DelayMS(Song_Time_Pointer[Tone_Index]*240);
TR0=0;
i++;
}
return;
}
voidplay1()
{
ET0=1;
TR0=0;
k1=1;
Song_Index=(Song_Index+3)%4;//切换到上一段音乐
switch(Song_Index)
{
case0:
Song_Tone_Pointer=Song1_Tone;
Song_Time_Pointer=Song1_Time;
break;
case1:
Song_Tone_Pointer=Song2_Tone;
Song_Time_Pointer=Song2_Time;
break;
case2:
Song_Tone_Pointer=Song3_Tone;
Song_Time_Pointer=Song3_Time;
break;
case3:
Song_Tone_Pointer=Song4_Tone;
Song_Time_Pointer=Song4_Time;
break;
}
i=0;//从上一段音乐的第0个音符开始
while(k0==1&&k1==1&&k2==1&&k3==1&&k4==1&&k5==1&&k6==1)
{
Tone_Index=Song_Tone_Pointer[i];
if(Tone_Index==0xFF)
{
i=0;
DelayMS(2000);//每段音乐播放结束后停顿一段时间
continue;//继续播放
}
TR0=1;
DelayMS(Song_Time_Pointer[Tone_Index]*240);
TR0=0;
i++;
}
return;
}
voidplay2()
{
m=!
m;
TR0=0;
k2=1;
if(m==0)
{
j=1;
TR0=0;
ET0=0;
}
if(m==1)
{
ET0=1;
k=1;
if(j==1)//播放被暂停的音乐
{
switch(Song_Index)
{
case0:
Song_Tone_Pointer=Song1_Tone;
Song_Time_Pointer=Song1_Time;
break;
case1:
Song_Tone_Pointer=Song2_Tone;
Song_Time_Pointer=Song2_Time;
break;
case2:
Song_Tone_Pointer=Song3_Tone;
Song_Time_Pointer=Song3_Time;
break;
case3:
Song_Tone_Pointer=Song4_Tone;
Song_Time_Pointer=Song4_Time;
break;
}
//从本一段音乐的第i个音符开始
while(k0==1&&k1==1&&k2==1&&k3==1&&k4==1&&k5==1&&k6==1)
{
Tone_Index=Song_Tone_Pointer[i];
if(Tone_Index==0xFF)
{
i=0;
DelayMS(2000);//每段音乐播放结束后停顿一段时间
Song_Index=(Song_Index+1)%4;//播放下一首音乐
switch(Song_Index)
{
case0:
Song_Tone_Pointer=Song1_Tone;
Song_Time_Pointer=Song1_Time;
break;
case1:
Song_Tone_Pointer=Song2_Tone;
Song_Time_Pointer=Song2_Time;
break;
case2:
Song_Tone_Pointer=Song3_Tone;
Song_Time_Pointer=Song3_Time;
break;
case3:
Song_Tone_Pointer=Song4_Tone;
Song_Time_Pointer=Song4_Time;
break;
}
continue;
}
TR0=1;
DelayMS(Song_Time_Pointer[Tone_Index]*240);
TR0=0;
i++;
}
}
if(j==0)
{j=1;
Song_Tone_Pointer=Song1_Tone;//开始播放音乐
Song_Time_Pointer=Song1_Time;
i=0;
while(k0==1&&k1==1&&k2==1&&k3==1&&k4==1&&k5==1&&k6