数字音乐盒设计

更新时间:2024-05-09 18:22:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

一、摘要

本设计是采用单片机为核心设计的数字音乐盒。它可以实现音乐的播放,可以通过功能键来选

择乐曲,播放或暂停,并且可以通过LCD屏幕显示正在播放的歌曲的序号,及乐曲播放时间,开机时有英文欢迎提示字符,本音乐盒可以播放十首音乐。 本设计是以AT89C51芯片的电路为基础,利用KEIL和PROTEUS仿真软件设计出来的一个多功能音乐盒。音乐盒主要是由四大模块构成,包括AT89C51芯片、蜂鸣器、晶振复位电路和1602显示器。有开机键、暂停键、播放键、上一曲、下一曲及十个用来选择歌曲的键。此外用户可以按照自己的喜好选择音乐并将其转化成机器码存入单片机的存储器中。对于不同型号的单片机只需要相应的改变一下地址即可。该软、硬件系统具有很好的通用性,很高的实际使用价值。

关键字:AT89C51;Keil;1602液晶显示器;计时;音乐盒

二、设计原理

2.1 芯片AT89C51的介绍

AT89C51是一种带4K字节闪存可编程可擦除只读存储器(FPEROM—Flash Programmable and Erasable Read Only Memory)的低电压,高性能CMOS 8位微处理器,俗称单片机。AT89C2051是一种带2K字节闪存可编程可擦除只读存储器的单片机。单片机的可擦除只读存储器可以反复擦除100次。该器件采用ATMEL高密度非易失存储器制造技术制造,与工业标准的MCS-51指令集和输出管脚相兼容。由于将多功能8位CPU和闪烁存储器组合在单个芯片中,ATMEL的AT89C51是一种高效微控制器,AT89C2051是它的一种精简版本。AT89C51单片机为很多嵌入式控制系统提供了一种灵活性高且价廉的方案。外形及引脚排列如图2.1所示

图2.1 AT89C51引脚排列图

1

2.2 LCD1602液晶显示屏

1602共16个管脚,但是编程用到的主要管脚不过三个,分别为:RS(数据命令选择端),R/W(读写选择端),E(使能信号);以后编程便主要围绕这三个管脚展开进行初始化,写命令,写数据。 以下具体阐述这三个管脚:

RS为寄存器选择,高电平选择数据寄存器,低电平选择指令寄存器。 R/W为读写选择,高电平进行读操作,低电平进行写操作。 E端为使能端,后面和时序联系在一起。 除此外,D0~D7分别为8位双向数据。

读取状态字时,注意D7位,D7=1,禁止读写操作;D7=0,允许读写操作;所以对控制器每次进行读写操作前,必须进行读写检测。(即后面的读忙子程序) 2.3 音调的产生

频率的高低决定了音调的高低。音乐的十二平均率规定:每两个八度音(如简谱中的中音1和高音1)之间的频率相差一倍。在两个八度音之间又分为十二个半音。另外,音名A(简谱中的低音6)的频率为440Hz,音名B到C之间、E到F之间为半音,其余为全音。由此可以计算出简谱中从低音1到高音1之间每个音名对应的频率,所有不同频率的信号都是从同一个基准频率分频得到的。

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的

2

T=65536-500000/262=63627 低音DO的T=65536-500000/523=64580 低音DO的T=65536-500000/1047=65059

5、 C调各音符频率与计数值T的对照表如表2.1所示

C调各音符频率与计数值T的对照表

2.4 节拍的确定

若要构成音乐,光有音调是不够的,还需要节拍,让音乐具有旋律(固定的律动),而且可以调节各个音的快满度。“节拍”,即Beat,简单说就是打拍子,就像我们听音乐不自主的随之拍手或跺脚。若1拍实0.5s,则1/4 拍为0.125s。至于1拍多少s,并没有严格规定,就像人的心跳一样,大部分人的心跳是每分钟72下,有些人快一点,有些人慢一点,只要听的悦耳就好。音持续时间的长短即时值,一般用拍数表示。休止符表示暂停发音。 一首音乐是由许多不同的音符组成的,而每个音符对应着不同频率,这样就可以利用不同的频率的组合,加以与拍数对应的延时,构成音乐。了解音乐的一些基础知识,我们可知产生不同频率的音频脉冲即能产生音乐。对于单片机来说,产生不同频率的脉冲是非常方便的,利用单片机的定时/计数器来产生这样的方波频率信号。因此,需要弄清楚音乐中的音符和对应的频率,以及单片机定时计数的关系。

每个音符使用1个字节,字节的高4位代表音符的高低,低4位代表音符的节拍,表2.41为节拍码的对照。如果1拍为0.4秒,1/4拍实0.1秒,只要设定延迟时间就可求得节拍的时间。假设1/4拍为1DELAY,则1拍应为4DELAY,以此类推。所以只要求得1/4拍的DELAY时间,其余的节拍就是它的倍数。

3

2.4.1节拍与节拍码对照

2.4.2 1/4和1/8的时间设定

2.5 音符的编码

do re mi fa so la si分别编码为1~7,重音do编为8,重音re编为9,停顿编为0。播放长度以十六分音符为单位(在本程序中为165ms),一拍即四分音符等于4个十六分音符,编为4,其它的播放时间以此类推。音调作为编码的高4位,而播放时间作为低4位,如此音调和节拍就构成了一个编码。以0xff作为曲谱的结束标志。 举例1:音调do,发音长度为两拍,即二分音符,将其编码为0x18 举例2:音调re,发音长度为半拍,即八分音符,将其编码为0x22 歌曲播放的设计。先将歌曲的简谱进行编码,储存在一个数据类型为unsigned char 的数组中。程序从数组中取出一个数,然后分离出高4位得到音调,接着找出相应的值赋给定时器0,使之定时操作蜂鸣器,得出相应的音调;接着分离出该数的低4位,得到延时时间,接着调用软件延时。

三、程序设计与硬件电路

3.1 设计思路

程序设计部分主要有:字模部分、LCD1602显示、简谱音调及节拍、计时部分。

4

实验控制流程图如下:

硬件设计图如下:

5

3.2 程序设计见附件

四、仿真调试及操作说明

1、按下开机键LCD显示如下

2、按下歌曲按钮播放所选歌曲

3、操作说明

一共13个按钮,按下对应的歌曲选择按钮键,会播放对应的歌曲。

第一首,《挥着翅膀的女孩》 ;第二首,《同一首歌》 ;第三首,《两只蝴蝶》 ;第四首,《母亲》 ; 第五首,《世上只有妈妈好》 ;第六首,《浏阳河》 ;第七首,《同桌的你》 ;第八首,《茉莉花》

第九首,《梁祝》 ;第十首,《生日快乐》

6

其中还有,暂停键、继续键、上一曲、下一曲。

五、心得体会

其实,在开始单片机课程设计之前,我是很担心的。因为单片机这一课程还有很多没学懂的地方,很多命令都还不怎么会使用。以前几乎每次试验都要搞好久,很不熟练。当拿到课程设计的任务时,我很开心,因为相对来说,数字音乐盒的设计比较简单,而且我个人也比较喜欢音乐。所以,之后做起来也很有干劲。通过此次课程设计,首先对于硬件电路的工作原理有了进一步的学习,同时有了一个提升;软件方面,在程序的设计,程序的调试方面都有了很大的进步。

在设计过程中我遇到了许多问题。比如说,1、有时会出现程序一点错误也没有,但就是不能正常运行的现象,最后我们发行是因为程序中有的指令书写得不规范导致的,例如有的RET返回指令一定要按正确格式书写或在两行指令间最好不要留空行。2、编程时要注意,在程序开始时,要写入各定时器中断的入口地址。3、编程前要加流程图,这样会使思路清晰,例如数字音乐盒的设计思路完全可以按着MP3的工作方式列写流程图。等等。还有很多小细节的问题,通过问同学,或是通过上网查询,我不仅是获得了答案,更多的是获得知识的开心和成就感。当听到一首首歌响起的时候,那种无语伦比的开心,充斥了我整个大脑。

这次的课程设计在一定程度上改变了我学习单片机这门课程的态度,从最初的认为学它没有什么实际意义,到如今爱上单片机学习,并希望能将理论运用到实践,设计出更好更完整的系统。我明白了一个人要 想 做 好一件 事,就 必 须 具 备 自 信 心,耐 心,还 要 有 毅 力,要 胆 大心 细,要 勇 于尝试,要手脑并用,最后才能交出一份令人满意的答卷。对我而言,知识上的收获重要,精神上的丰收更加。让我知道了学无止境的道理。我们每一个人永远不能满足于现有的成就,人生就像在爬山,一座山峰的后面还有更高的山峰在等着你。挫折是一份财富,经历是一份拥有。这次课程设计必将成为为我人生旅途上一个非常美好的回忆。

六、参考文献

[1] 谭浩强.C语言程序设计(第二版)[M],北京:清华大学出版社,1991. [2] 曾屹.单片机原理与应用(第一版)[M],中南大学出版社,2009. [3] 美妙的音乐盒--《家庭电子》1994年05期

7

附件:

#include #include #include\

#define SYSTEM_OSC 12000000//11059200 //定义晶振频率12000000HZ #define SOUND_SPACE 4/5 //定义普通音符演奏的长度分率,//每4分音符间隔 #define uchar unsigned char #define uint unsigned int

#define delayNOP(); {_nop_();_nop_();_nop_();_nop_();}; uchar code tab1[]=\AN %uchar code tab2[]=%uchar code tab3[]=%uchar code tab4[]=%uchar code tab5[]=\sbit BeepIO=P1^4; //定义输出管脚 sbit key0=P2^0; sbit key1=P2^1; sbit key2=P2^2; sbit key3=P2^3; sbit key4=P2^4; sbit key5=P2^5; sbit key6=P2^6; sbit key7=P2^7;

sbit key8=P3^0; sbit key9=P3^1; sbit key10=P3^4; sbit key11=P3^5; sbit key12=P3^6; sbit key13=P3^7; sbit LCD_EN=P1^7; sbit LCD_RS=P1^5; sbit LCD_RW=P1^6; sbit led=P1^3; uchar count; uint Point; uint *temp;

unsigned char *Sound;

unsigned int code FreTab[12] = { 262,277,294,311,330,349,369,392,415,440,466,494 }; //原始频率表 unsigned char code SignTab[7] = { 0,2,4,5,7,9,11 }; //1~7在频率表中的位置

8

unsigned char code LengthTab[7]= { 1,2,4,8,16,32,64 }; unsigned char Sound_Temp_TH0,Sound_Temp_TL0; //音符定时器初值暂存 unsigned char Sound_Temp_TH1,Sound_Temp_TL1; //音长定时器初值暂存

uchar flag; uchar lcdflag;

uchar fen; uchar miao; uchar num;

void write_com(uchar com); void write_data(uchar date); void init();

void delay_ms(uint z) { uint x,y; for(x=z;x>0;x--) for(y=110;y>0;y--); }

bit lcd_busy()

{ bit result; LCD_RS = 0; LCD_RW = 1; LCD_EN = 1; delayNOP();

result = (bit)(P0&0x80); LCD_EN = 0; return(result); }

void write_com(uchar cmd)

{ while(lcd_busy()); LCD_RS = 0; LCD_RW = 0; LCD_EN = 0; _nop_(); _nop_(); P0 = cmd; delayNOP(); LCD_EN = 1; delayNOP(); LCD_EN = 0;

9

}

void write_data(uchar dat)

{ while(lcd_busy()); LCD_RS = 1; LCD_RW = 0; LCD_EN = 0; P0 = dat; delayNOP(); LCD_EN = 1; delayNOP(); LCD_EN = 0; }

void init() {

LCD_EN=0; LCD_RW=0;

write_com(0x38); write_com(0x0e); write_com(0x06); write_com(0x01); write_com(0x80);

// write_com(0x80+0x10); }

void wr_lcd_1602(unsigned char add,unsigned char dat) { unsigned char ge,shi; shi=dat/10; ge=dat; write_com(0x80+add); write_data(0x30+shi); write_data(0x30+ge); }

//************************************************************************** void Play(unsigned char Signature,unsigned Octachord,unsigned int Speed) //此处是程序的关键 { unsigned int NewFreTab[12]; //新的频率表 unsigned char i,j;

10

//

unsigned int LDiv,LDiv0,LDiv1,LDiv2,LDiv4,CurrentFre,Temp_T,SoundLength ; unsigned char Tone,Length,SL,SH,SM,SLen,XG,FD; temp=Sound;

for(i=0;i<12;i++) // 根据调号及升降八度来生成新的频率表 { // write_com(0x80+0x48);//() // write_data(tab5[num]); j = i + Signature; if(j > 11) { j = j-12; NewFreTab[i] = FreTab[j]*2; } else NewFreTab[i] = FreTab[j]; if(Octachord == 1) NewFreTab[i]>>=2; else if(Octachord == 3) NewFreTab[i]<=2; }

SoundLength = 0;

while(Sound[SoundLength] != 0x00) //计算歌曲长度 { SoundLength+=2; }

Point = 0;

Tone = Sound[Point]; Length = Sound[Point+1]; // 读出第一个音符和它时时值

LDiv0 = 12000/Speed; // 算出1分音符的长度(几个10ms) LDiv4 = LDiv0/4; // 算出4分音符的长度 LDiv4 = LDiv4-LDiv4*SOUND_SPACE; // 普通音最长间隔标准 TR0 = 0; TR1 = 1;

while(Point

11

if (SM==1) CurrentFre >>= 2; //低音 if (SM==3) CurrentFre <= 2; //高音 Temp_T = 65536-(50000/CurrentFre)*10/(12000000/SYSTEM_OSC);//计算计数器初值 Sound_Temp_TH0 = Temp_T/256; Sound_Temp_TL0 = Temp_T%6; TH0 = Sound_Temp_TH0; TL0 = Sound_Temp_TL0 + 12; //加12是对中断延时的补偿 }

SLen=LengthTab[Length]; //算出是几分音符 XG=Length/10; //算出音符类型(0普通1连音2顿音) FD=Length/100;

LDiv=LDiv0/SLen; //算出连音音符演奏的长度(多少个10ms) if (FD==1) LDiv=LDiv+LDiv/2; if(XG!=1) if(XG==0) //算出普通音符的演奏长度 if (SLen=4) LDiv1=LDiv-LDiv4; else LDiv1=LDiv*SOUND_SPACE; else LDiv1=LDiv/2; //算出顿音的演奏长度 else LDiv1=LDiv; if(SL==0) LDiv1=0; LDiv2=LDiv-LDiv1; //算出不发音的长度 if (SL!=0) { TR0=1; for(i=LDiv1;i>0;i--) //发规定长度的音 { while(TF1==0); TH1 = Sound_Temp_TH1; TL1 = Sound_Temp_TL1; TF1=0; } }

if(LDiv2!=0) { TR0=0; BeepIO=0; for(i=LDiv2;i>0;i--) //音符间的间隔 {

while(TF1==0);

TH1 = Sound_Temp_TH1;

12

TL1 = Sound_Temp_TL1; TF1=0; } } Point+=2; Tone=Sound[Point]; Length=Sound[Point+1]; } BeepIO = 0; }

void Delay1ms(unsigned int count) {

unsigned int i,j; for(i=0;i

void init0() { IP=0x01; // IT0=0; //低电平触发 IT0=1; //下降沿触发 EX0=1;

// IT1=0; //低电平触发 IT1=1; //下降沿触发 EX1=1; EA=1; }

void InitialSound(void) { BeepIO = 0; Sound_Temp_TH1 = (65535-(1/1200)*SYSTEM_OSC)/256; // 计算TL1应装入的初值 (10ms的初装值) Sound_Temp_TL1 = (65535-(1/1200)*SYSTEM_OSC)%6; // 计算TH1应装入的初值 TH1 = Sound_Temp_TH1; TL1 = Sound_Temp_TL1; TMOD |= 0x11; ET0 = 1; ET1 = 0; TR0 = 0;

13

TR1 = 0; EA = 1; }

void w1() { num=1; Point=0; Sound=Music1; lcdflag=1; }

void w2() { num=2; Point=0; Sound=Music2; lcdflag=1; }

void w3() { num=3; Point=0; Sound=Music3; lcdflag=1; }

void w4() { num=4; Point=0; Sound=Music4; lcdflag=1; }

void w5() { num=5; Point=0; Sound=Music5; lcdflag=1; }

void w6() { num=6; Point=0; Sound=Music6; lcdflag=1; }

14

void w7() { num=7; Point=0; Sound=Music7; lcdflag=1; }

void w8() { num=8; Point=0; Sound=Music8; lcdflag=1; }

void w9() { num=9; Point=0; Sound=Music9; lcdflag=1; }

void w10() { num=10; Point=0; Sound=Music10; lcdflag=1; }

void Key0() {

if(key0==0) {

delay_ms(10); if(key0==0) {

while(!key0) { while(!key0) { }

15

flag=0; } } } }

void Key1() {

if(key1==0) {

delay_ms(10); if(key1==0) {

while(!key1) { while(!key1) { } w1(); } } } }

void Key2() {

if(key2==0) {

delay_ms(10); if(key2==0) {

while(!key2) { while(!key2) { } w2(); } } }

16

}

void Key3() {

if(key3==0) {

delay_ms(10); if(key3==0) {

while(!key3) { while(!key3) { } w3(); } } } }

void Key4() {

if(key4==0) {

delay_ms(10); if(key4==0) {

while(!key4) { while(!key4) { } w4(); } } } }

void Key5() {

17

if(key5==0) {

delay_ms(10); if(key5==0) {

while(!key5) { while(!key5) { } w5(); } } } }

void Key6() {

if(key6==0) {

delay_ms(10); if(key6==0) {

while(!key6) { while(!key6) { } w6(); } } } }

void Key7() {

if(key7==0) {

delay_ms(10);

18

if(key7==0) {

while(!key7) { while(!key7) { } w7(); } } } }

void Key8() {

if(key8==0) {

delay_ms(10); if(key8==0) {

while(!key8) { while(!key8) { } w8(); } } } }

void Key9() {

if(key9==0) {

delay_ms(10); if(key9==0) {

while(!key9) {

19

while(!key9) { } w9(); } } } }

void Key10() {

if(key10==0) {

delay_ms(10); if(key10==0) {

while(!key10) { while(!key10) { } w10(); } } } }

void Key11() {

if(key11==0) {

delay_ms(10); if(key11==0) {

while(!key11) { while(!key11) {

20

} num++; if(num==11)num=1; if(num==1)w1(); if(num==2)w2(); if(num==3)w3(); if(num==4)w4(); if(num==5)w5(); if(num==6)w6(); if(num==7)w7(); if(num==8)w8(); if(num==9)w9(); if(num==10)w10();

} } } }

void Key12() {

if(key12==0) {

delay_ms(10); if(key12==0) {

while(!key12) { while(!key12) { } num--; if(num==0)num=10; if(num==1)w1(); if(num==2)w2(); if(num==3)w3(); if(num==4)w4(); if(num==5)w5(); if(num==6)w6(); if(num==7)w7(); if(num==8)w8(); if(num==9)w9();

21

if(num==10)w10(); } } } }

void main(void) { uchar i; init(); init0();

InitialSound(); TMOD|=0X10; TH1=(65535-50000)/256; TL1=(65535-50000)%6; EA=1; ET1=1; TR1=1; for(i=0;i<8;i++) { write_data(tab1[i]); delay_ms(1); } // write_com(1); // write_com(0x80+0x53); write_com(0x80+0x40); for(i=0;i<10;i++) { write_data(tab2[i]); delay_ms(1); } delay_ms(3000); write_com(0x01); write_com(0x80); for(i=0;i<11;i++) { write_data(tab3[i]); delay_ms(1); } write_com(0x80+0x40); for(i=0;i<7;i++) { write_data(tab4[i]); delay_ms(1); }

22

while(1) { // write_data(tab5[num]); Play(0,3,360);

// Delay1ms(500); } }

void timer1() interrupt 3// 定时器1 { TH1=(65536-50000)/256; TL1=(65536-50000)%6; Key1(); Key2(); Key3(); Key4(); Key5(); Key6(); Key7(); Key8(); Key9(); Key10(); Key11(); Key12(); if(lcdflag==1) { count++; // lcdflag=0; write_com(0x80+0x48); write_data(tab5[num/10]); write_data(tab5[num]); if(count==20) { count=0; miao++; if(miao==60) { miao=0;

23

fen++; if(fen==60) { fen=0; } } } write_data(tab5[11]); write_data(tab5[fen/10]); write_data(tab5[fen]); write_data(tab5[10]); write_data(tab5[miao/10]); write_data(tab5[miao]); if(Point==0) { fen=0; miao=0; } } }

void BeepTimer0(void) interrupt 1 //音符发生中断 { BeepIO = !BeepIO; TH0 = Sound_Temp_TH0; TL0 = Sound_Temp_TL0; }

void counter0(void) interrupt 0 //外中断0 { }

void counter1(void) interrupt 2 {

EX1=0;

flag=1; while(flag) {

Key0(); }

EX1=1; }

24

25

本文来源:https://www.bwwdw.com/article/ee8g.html

Top