《单片机课程设计报告电子琴》由会员分享,可在线阅读,更多相关《单片机课程设计报告电子琴(23页珍藏版)》请在金锄头文库上搜索。
1、 电子音调发生器 一、实验目的 1。 了解计算机发声原理。 2. 熟悉定时器和键盘扫描电路的工作原理及编程方法。 二、实验完成的功能 1。 利用键盘 17 进行音调选择,即按下音符产生对应音调。 2. 事先存储三首歌曲,并可进行选择播放。 3。 谱曲功能:通过按键对 LCD 菜单选项进行选择,进入谱曲界面,通过按键 17 分别输入音高与几分音符类型,由按键输入若干数据完成谱曲. 4. 在播放存储歌曲与谱曲播放时,对应音符及其节奏 LCD 显示对应频谱。 5. 在播放音乐时按“返回”键出现返回界面,由键盘按“确认”键选择返回主菜单或循环播放. 三、实验原理 1. 音节由不同频率的方波产生,音节与
2、频率的关系如表(1)所示.要产生音频方波,只要计算出某一音频的周期(1 / 频率) ,然后将此周期除以 2,即为半周期的时间。利用计时器计时此半周期时间,每当计时到后就将输出方波的 I/O(P1.7)反相,然后重复计时此半周期时间再对 I/O 反相,就可在 P1。7 脚得到此频率的方波.将 P1。7 经过驱动电路与蜂鸣器相连,随着 P1.7 口输出不同频率的方波,蜂鸣器便会发出不同的声音.音乐的节拍是由延时实现的,如果 1 拍的时间为 0。4 秒,1/4 拍是 0.1 秒。只要设定延时时间,就可得到节拍的时间。延时实现基本延时时间,节拍值只能是它的整数倍。 每个音节相应的定时器初值计算公式如下
3、: (1/2)(1/f)=(12/fose)*(216x) 即 x=216-(fose/24f) 其中,f 是音调频率,当晶振 fosc=11.0592MHz 时,音节“1相应的定时器初值为x,则可得到 x=63777D=F921H,其它的可同样得到. 表(1) 音节与频率的关系 音调 频率(Hz) X(HEX) 1 262 F921 2 294 F9E1 3 330 FA8C 4 349 FAD8 5 392 FB68 6 440 FBE9 7 494 FC5B 3 五.实验程序 #ifndef _DRIVEFUTION_H_ define _DRIVEFUTION_H_ /*EEPROM
4、函数*/ /*/ /* 关闭 ISP,IAP 功能 */ void ISP_IAP_disable(void) ISP_CONTR = 0X00; ISP_CMD = 0X00; ISP_TRIG = 0x00; /*字节读*/ uchar Byte_read(uint byte_addr) EA = 0; /关中断 ISP_CONTR = En_Wait_TIME; /开启 ISP/IAP;并送等待时间 ISP_CMD = Read_COM; /送字节读命令字 ISP_ADDRH = (uchar) (byte_addr 8) ; /送地址高字节 ISP_ADDRL = (uchar)(by
5、te_addr & 0X00FF); /送地址低字节 ISP_TRIG = 0X46; /送触发命令字 0X46、0XB9 ISP_TRIG = 0XB9; 4 _nop_(); ISP_IAP_disable() ; /关闭 ISP/IAP 功能 EA = 1; /开中断 return (ISP_DATA); /*字节编程*/ void Byte_program(uint byte_addr, uchar isp_iap_data) EA = 0; /关中断 ISP_CONTR = En_Wait_TIME; /开启 ISP/IAP;并送等待时间 ISP_CMD = Prog_COM; /送
6、字节编程命令字 ISP_ADDRH = (uchar) (byte_addr 8); /送地址高字节 ISP_ADDRL = (uchar)(byte_addr & 0X00FF) ; /送地址低字节 ISP_DATA = isp_iap_data; /送数据进 ISP_DATA ISP_TRIG = 0X46; /送触发命令字 0X46、0XB9 ISP_TRIG = 0XB9; _nop_(); ISP_IAP_disable(); /关闭 ISP/IAP 功能 EA = 1; /开中断 /* 扇区擦除*/ void Sector_erase(uint sector_addr) EA =
7、0; /关中断 ISP_CONTR = En_Wait_TIME; /开启 ISP/IAP;并送等待时间 ISP_CMD = Dele_COM; /送扇区擦除命令字 ISP_ADDRH = (uchar) (sector_addr 8) ; /送地址高字节 ISP_ADDRL = (uchar)(sector_addr 0X00FF); /送地址低字节 ISP_TRIG = 0X46; /送触发命令字 0X46、0XB9 ISP_TRIG = 0XB9; _nop_(); ISP_IAP_disable(); /关闭 ISP/IAP 功能 EA = 1; /*写入函数*/ void EEPRO
8、M_write(uint addr, uchar in_data) Sector_erase(addr); Byte_program(addr,in_data); /*EEPROM 函数*/ /*/ 5 /*音乐播放相关子函数*/ /*/ void InitialSound(void) /初始化定时器 BeepIO = 0; Sound_Temp_TH1 = (65535-(1/1200)*SYSTEM_OSC)/256; / 计算 TL1 应装入的初值 (10ms 的初装值) Sound_Temp_TL1 = (65535(1/1200)*SYSTEM_OSC)256; / 计算 TH1 应
9、装入的初值 TH1 = Sound_Temp_TH1; TL1 = Sound_Temp_TL1; TMOD = 0x11; ET0 = 1; ET1 = 0; TR0 = 0; TR1 = 0; EA = 1; void BeepTimer0(void) interrupt 1 /音符发生中断函数 BeepIO = !BeepIO; TH0 = Sound_Temp_TH0; TL0 = Sound_Temp_TL0; /*END*/ /*/ /*LCD 操作子函数*/ /*/ void wrcomd(char comd) /写入命令 cs = 0; _nop_(); rs = 0; _no
10、p_() ; e = 1; _nop_(); rw = 0; _nop_(); P0 = comd; _nop_(); rw = 0; _nop_(); _nop_() ; rw = 1; _nop_() ; _nop_(); _nop_() ; _nop_() ; 6 void wrdata(char data_wri) /往液晶中写入数据 cs = 0; _nop_(); rs = 1; _nop_(); e = 1; _nop_(); rw = 0; _nop_(); P0 = data_wri; _nop_(); rw = 0; _nop_(); _nop_(); rw = 1; _n
11、op_(); _nop_() ; _nop_() ; _nop_(); void bmp (char data_write,int row_start, int row_end,uchar column_start, uchar column_end) /刷屏子函数 /初始化设置, 对屏幕清零,默认值, 为 0,0,8,2,132 unsigned char i,j; int num_3; char num_0=0xb0; /uchar column_h,column_l; num_0+=row_start; for(i=row_start;i4); /设置列,先写入高 4 位再写入低四位 w
12、rcomd(0x00|(0x0fcolumn_start)); for(j=column_start;jnum_3;j+) wrdata(data_write) ; num_0+; void delay(long int time_delay) /延时子程序,执行 time_delay 个_nop_()延时 long int i; for(i=0;itime_delay;i+) _nop_(); void write_word(char *p_word,int num,int pos_x,int pos_y,int width,int height) /写汉字/ 7 字符子函数 /对应参数 数
13、据数组名,数组中位置, 行位置,列位置,宽,高 char page; / 页号 unsigned int i,j,k,p; p = (unsigned int)num2width; /p 确定汉字字模数据初始位置 i = height%8; k = (i)?(height/8+1) :(height/8); page = 0xb0 + pos_y; for(i=0;i4) ; wrcomd(0x00|(0x0fpos_x)); for(j=0;jwidth;j+) wrdata(p_wordj+p ) ; p=p+width; page+; if(page-(unsigned char)0xb
14、07page(unsigned char)0xb00) return; /*END*/ /*/ /* 键盘扫描函数*/ /*/ /*判断是否有键盘输入*/ bit CheckState(void) /键盘状态判断子函数 bit state=0; P1=0x0F; /键盘扫描 if(P1!=0x0F)state=1; /检测是否有按键输入 else state=0; return(state); /*获取键盘输入值*/ uchar GetKeys(void) /键盘值获取子函数 8 uchar column,line; scan=0xFE; while((scan&0x10) !=0) P1=s
15、can; if((P1&0xF0)!=0xF0) /line 1 have key pressed column=(P10x0F); line=(scan0xF0); key=column+line; return(key) ; else scan=(scan 1)|0x01; return(0); /*应用子函数*/ /*/ void csh(void) /初始化,清屏 int i; res = 0; delay(time_def) ; delay(time_def); res = 1; delay(time_def); cs = 0; /片选一直有效 delay(time_def); de
16、lay(time_def); for(i=0;i12;i+) /初始命令写入 wrcomd(tab1i); delay(time_def); bmp(0,0,8,0,132); void start_print(void) /开机显示画面子函数 int i,j; for(i=0;i4;i+) write_word(star_welcome,i,36+i16,2,16,16); if( (i+4)=0;j-) write_word(number,j,12+516,6,8,16); delay(5000) ; void menu_first_symbol(int choose) /菜单选择符刷新子
17、函数,即“打钩项” bmp(0,0,8,4,18); write_word(menu_one,20,4,choose2,16,16); /*音乐播放显示函数*/ /*/ void music_display_one(unsigned char s,unsigned int x) /显示最底层子函数 int i; bmp(0,2,8,18+x16,34+x16); for(i=3;i0;i-) if(s!=0) write_word(display,1,20+16*x,2*i,16,16) ; s=s-1; else write_word(display,0,20+16x,2i,16,16);
18、/delay(200); 10 void music_display_two(unsigned int p_x) /显示次底层子函数 int sign; sign=mc_sizep_x0; if(sign) if(!sizep_x) sign=0; mc_sizep_x0=sign; mc_sizep_x1=0; else music_display_one(sizep_x,p_x); mc_sizep_x1+=1; if(mc_sizep_x1 3) sizep_x=1; else sizep_x+=1; /*/ else bmp(0,2,8,18+p_x16,34+p_x*16); wri
19、te_word(display,2,20+16*p_x,6,16,16); /delay(500); /*/ void music_display_three(unsigned int mc) /音符状态置位函数 mc_sizemc-10=1; mc_sizemc-11=1; sizemc1=1; void music_display_four(void) /显示次顶层子函数 int i; for(i=0;i7;i+) music_display_two(i); 11 void music_display_five() /显示顶层子函数 int i; allsize=0; for(i=0;i7
20、;i+) allsize+=mc_sizei0 ; music_display_four(); /* void music_display(void) /显示主函数 int i; for(i=0;i10;i+) music_display_three(music_codei) ; music_display_five(); while(allsize) music_display_five() ; */ void display_between_musicplay(unsigned int tone) /播放音乐同时显示音符显示界面 music_display_three(tone); mus
21、ic_display_five(); /*/ /*播放子程序*/ void music_play_one(uint NewFreTab,uchar Tone,uchar Length,uint LDiv0,uint LDiv1) / 频 率 表 , 音 符 , 音 长 , 12000/Speed,( ( 12000/Speed)/4 )-((12000/Speed)/4)SOUND_SPACE unsigned char SL,SH,SM,SLen,XG,FD; uint i,Temp_T,CurrentFre,LDiv,LDiv2,LDiv3; SL=Tone%10; /计算出音符 SM=T
22、one/10%10; /计算出高低音 SH=Tone/100; /计算出是否升半 CurrentFre = NewFreTabSignTabSL1+SH; /查出对应音符的频率 if(SL!=0) if (SM=1) CurrentFre = 2; /低音 12 if (SM=3) CurrentFre = 2; /高音 Temp_T = 65536-(50000/CurrentFre)*10/(12000000/SYSTEM_OSC);/计算计数器初值 Sound_Temp_TH0 = Temp_T/256; Sound_Temp_TL0 = Temp_T256; TH0 = Sound_T
23、emp_TH0; TL0 = Sound_Temp_TL0 + 12; /加 12 是对中断延时的补偿 SLen=LengthTabLength10; /算出是几分音符 XG=Length/1010; /算出音符类型(0 普通 1 连音 2 顿音) FD=Length/100; LDiv=LDiv0/SLen; /算出连音音符演奏的长度(多少个 10ms) if (FD=1) LDiv=LDiv+LDiv/2; if(XG!=1) if(XG=0) /算出普通音符的演奏长度 if (SLen0;i-) /音符间的间隔 13 while(TF1=0); TH1 = Sound_Temp_TH1;
24、 TL1 = Sound_Temp_TL1; TF1=0; /*/ /*/ void music_play_two_1(uchar *Sound,uint *NewFreTab,uint SoundLength,uint LDiv0,uint LDiv1) /播放原存储的音乐 uint xdata Point; uchar xdata Tone,Length,i; Point = 0; TR0 = 0; TR1 = 1; for(i=0;i4;i+) write_word(menu_one,i+1,4+(i+2)*16,0,16,16); while(Point SoundLength) To
25、ne = SoundPoint ; Length = SoundPoint+1; / 读出第一个音符和它时时值 music_play_one(NewFreTab,Tone,Length,LDiv0,LDiv1); Point+=2; BeepIO = 0; while(allsize) music_display_five(); void music_play_two_2(uint *NewFreTab,uint SoundLength,uint LDiv0,uint LDiv1) /播放谱曲存储的音乐 uint xdata Point; uchar xdata Tone,Length,i;
26、Point = 0; TR0 = 0; TR1 = 1; 14 for(i=0;i 11) j = j-12; NewFreTabi = FreTabj2; else NewFreTabi = FreTabj; if(Octachord = 1) NewFreTabi =2; else if(Octachord = 3) NewFreTabi=2; LDiv0=12000/Speed; / 算出 1 分音符的长度(几个 10ms) LDiv1= LDiv0/4; / LDiv 为算出 4 分音符的长度 LDiv1= LDiv1-LDiv1SOUND_SPACE; / LDiv 最后为普通音最长
27、间隔标准 15 SoundLength = 0; if(Sound!=Music_Memory) / while(SoundSoundLength != 0x00) /计算歌曲长度 SoundLength+=2; for(i=0;i4;i+) write_word(menu_one,i+1,4+(i+2)16,0,16,16); while(menu_state) / music_play_two_1(Sound,NewFreTab,SoundLength,LDiv0,LDiv1); bmp(0,0,8,2,132) ; for(i=0;i5;i+) write_word(restar,i,4
28、+(i+2)*16,2,16,16) ; while(!CheckState(); if(GetKeys()=0x11) /确认键返回,F 键 menu_state=0; delay(1000); bmp(0,0,8,2,132) ; else while(Byte_read(start_add+SoundLength) !=0x00) /计算歌曲长度 SoundLength+=2; for(i=0;i4;i+) write_word(menu_one,i+1,4+(i+2)16,0,16,16); while(menu_state) music_play_two_2(NewFreTab,So
29、undLength,LDiv0,LDiv1); bmp(0,0,8,2,132); for(i=0;i5;i+) write_word(restar,i,4+(i+2)16,2,16,16); while(!CheckState()) ; if(GetKeys()=0x11) /确认键返回,F 键 menu_state=0; delay(1000); bmp(0,0,8,2,132); /*音符转换函数*/ void ChangeKey(uchar Gkey) /把输入的音符转换成相应的数据形式, 存储到 16 EEPROM 中 / int i; if(Gkey!=0) delay(5000)
30、; if((music_counter%48)=0) bmp(0,2,8,0,132); switch(Gkey) case 0x11: if(music_counter2) bmp(0,0,8,0,132);music_state=1; break; case 0x42: Byte_program( (start_add+music_counter),0); write_word ( number , 0,4+( ( music_counter%48)%16 )*8,2+(music_counter48)/16)2,8,16) ; +music_counter;break; case 0x8
31、8: Byte_program((start_add+music_counter),1) ; write_word ( number,1 , 4+(music_counter%48 ) %16)*8 , 2+( (music_counter48)/16)2,8,16) ; +music_counter;break; case 0x48: Byte_program(start_add+music_counter),2); write_word(number,2,4+((music_counter%48)16)*8,2+( (music_counter%48)/16)2,8,16); +music
32、_counter;break; case 0x28: Byte_program( (start_add+music_counter) ,3); write_word(number,3,4+(music_counter48)16)*8,2+((music_counter48)/16)2,8,16) ; +music_counter;break; case 0x18: Byte_program(start_add+music_counter) ,4); write_word ( number , 4,4+( ( music_counter%48)%16)*8 , 2+( (music_counte
33、r%64)/16)2,8,16); +music_counter;break; case 0x84: Byte_program( (start_add+music_counter),5); write_word(number,5,4+((music_counter48)16)8,2+( (music_counter%48)/16)*2,8,16); +music_counter;break; case 0x44: Byte_program( (start_add+music_counter),6) ; write_word(number,6,4+( (music_counter%48)16)8
34、,2+((music_counter%48)/16)*2,8,16); +music_counter;break; case 0x24: Byte_program((start_add+music_counter),7); write_word(number,7,4+((music_counter%48)16)8,2+( (music_counter%48)/16)2,8,16) ; +music_counter;break; 17 default:break; void Entry_Music(void) /输入乐谱播放函数 uchar gkey,i; bmp(0,0,8,2,132) ;
35、while(menu_state) for(i=0;i4;i+) write_word(Music_Mry,i,4+(i+2)16,0,16,16); while(!music_state) if(CheckState() ) if(music_state=0) ChangeKey(GetKeys()); bmp(0,2,8,2,132); InitialSound(); Play_Two(Music_Memory,Signature,Octa); bmp(0,2,8,2,132); for(i=0;i4;i+) write_word(restar,i+5,4+(i+2)*16,2,16,16
36、) ; for(i=0;i5;i+) write_word(restar,i,4+(i+2)*16,4,16,16) ; while(!CheckState(); gkey=GetKeys(); delay(500); if(gkey=0x11) /确认键返回,F 键 menu_state=0; delay(1000); bmp(0,0,8,2,132); /*/ /*菜单函数*/ void menu_first(int choose) /主菜单 int i,j; bmp(0,0,8,2,132) ; for(i=0;i4;i+) 18 write_word(menu_one,i*10,20,
37、i*2,8,16); write_word(menu_one,1+i*10,28,i2,8,16); for(j=1;j500realvalue0?360:realvalue; else if(sign) realvalue+=1; else realvalue-=1; if(cho=1) realvalue=realvalue11|realvalue0?0:realvalue; else realvalue=realvalue3|realvalue0?3:realvalue; return(realvalue); uint menu_four1(uint value,uchar cho) /
38、参数修改次底层函数,用于选择那个参数项的值 uint xdata gkey; while(menu_state) while(!CheckState()) ; 21 gkey=GetKeys(); delay(10000); switch(gkey) case 0x41:value=chovalue(value,cho,0) ;menu_four0(value);break; case 0x21:value=chovalue(value,cho,1);menu_four0(value);break; case 0x11:menu_state=0; bmp (0, 6,8,0, 132) ; b
39、reak; / value=chovalue(value, cho) ; default:break; return(value) ; void menu_four(uchar cho) /参数修改顶层子函数,用于修改参数 uchar xdata i,value; bmp(0,6,8,0,132); /刷新最后一行 switch(cho) case 0: menu_four0(Speed) ; for(i=0;i4;i+) write_word(menu_four_1,i,4+i16,6,16,16) ; write_word(menu_four_1,22,66,6,8,16); value=
40、Speed;Speed=menu_four1(value,cho) ;break; case 1: menu_four0(Signature) ; for(i=0;i3;i+) write_word(menu_four_1,i+4,20+i*16,6,16,16); write_word(menu_four_1,22,66,6,8,16); value=Signature;Signature=menu_four1(value,cho) ;break; case 2: menu_four0(Octa); for(i=0;i2?0:cho; cho=cho0?2:cho; void menu_se
41、cond(int choose) /主菜单子函数,用于选中功能子菜单,进入相应的功能子菜单函数 /menu_state=0; switch(choose) case 0:menu_state=0;menu_three1();menu_state=0;break; case 1:music_state=0;Entry_Music();break; case 2:menu_state=0;menu_three2() ;menu_state=0;break; case 3:menu_state=0;menu_three3();menu_state=0;break; /*/ #endif 六仿真图及操
42、作流程图 23 七实验中出现的问题及解决方法 1在刚开始编写子函数时,编程思想不够明确,子函数设置的比较混乱.经过一段时间的摸主菜单: 1 音乐播放 2 数字谱曲 3 音乐弹奏 4 参数修改 1. 让世界充满爱 2. 梦醒时分 3. 千千阙歌 选 1 由按键输入音符音长谱曲。 由键盘 17控制进行弹奏。 1 修改播放速度 2 修改歌曲调号 3 修改升降八度 选 2 选 3 选 4 开始界面: (电子音调发生器) 24 索,懂得如何合理分配子函数,比如可以直接调用子函数,将它们互相嵌套,这么做可以极大程度减少程序中的冗余成分,使程序更加简洁明了。 2原先我设定蜂鸣器是一端接P3。7,另一端接地,
43、这样的话蜂鸣器是低电平驱动.但是由于单片机输出的驱动电流过小,影响蜂鸣器的播放效果,所以后来将蜂鸣器改成高电平驱动的形式,即使蜂鸣器一端接P3。7,另一端接高电平,使高电平输出的电流从蜂鸣器流过,使蜂鸣器的音量增大。 3本次实验的拓展功能之一是LCD显示屏与播放的音符同步显示对应频谱,在编程过程中如何使两者实现同步是一个难点。通过分析之后,采取了尽使播放的音符与显示的频谱之间的延时尽量减少的方法来实现它们的同步。 八实验心得与体会 本次实验的时间是在小学期,我从七月初开始着手实验准备.首先第一步就是选题,从老师给的题目中我选择了趣味性较大的电子音调发生器 , 通过阅读单片机课程设计题目中的有关
44、内容,我大致了解了实验的内容、任务与基本原理。然后我开始上网查阅资料,阅读相关程序,利用资源开始了程序编写的工作。应该说这个题目的网上资源较为丰富,所以基 本功能编程的大体框架不难确定。除了实现课程设计题目要求的基本功能外,本次实验还实现了一些拓展功能,这些在第二步已进行了详细的阐述,在此我就不再赘述。由于要完成的功能并不少,所以我选择用C语言编程,这样的话比起用汇编来得更方便些。编程占用了我整个实验过程的大部分时间,通过不断的尝试,资料的查阅,同学间互相的帮助,编程工作顺利完成。接下来就是焊板子的工作,由于此实验使用的是单片机,功能比较强大,很多工作可以用程序代替,所以这次板子的构造比较简单
45、,我花了一个下午的时间就将板子焊完了。在调试过程中出现了一些小问题,比如键盘编码问题,仿真电路图也有一些小错误等,在这些问题改正了以后板子就能正常工作了。实验的调试过程还是很迅速的,因为编程阶段,我是一边用开发板烧程序一边用实物直接显示结果的,这样对以后的调试过程的意义很大,能够较大程度的减少调试的工作量,使实验过程更顺利。通过本次的单片机课程设计实验,我重新复习了C语言的使用方法,对于利用单片机在实现功能方面有了更深的体会,尤其是现在可以很熟练的编写歌曲代码了,虽然实验过程中出现了许多的小问题,但每次解决问题的过程对我来说都是一个收获,提醒着我在以后的生活中应认真对待每一件事.总之,通过本次实验学会了很多知识,在以后的学习工作过程中我会学以致用。