姓 名:
班 级: 学 号: 小班学号: 同组姓名: 电子邮件:
实验课题:基于AVR单片机电子琴的制作 实验日期:2013.09.02—2013.09.15
目录:
一、实验简介
1.1实验目的.....................................................................4 1.2实验关键词……………………….…………………………………….4 1.3基础知识.....................................................................4
二、实验器材
2.1核心部件:ATmega16单片机.....................................5 2.2电路元件......................................................................6 2.3管脚说明…………………………………………………………………..6
三、实验原理
3.1实验原理图………………………………………………………………7 3.2实验流程图………………………………………………………………8 3.3实物效果图………………………………………………………………8
四、操作过程与运行结果
4.1操作过程示意图……………………………………………………….9 4.2电子琴系统主要部分……………………………………………….11
4.3具体操作过程…………………………………………………….11
4.4运行结果………………………………………………………………….12
五、实验中的排错、体会与建议
5.1调试与排错过程………………………………………………………..12 5.2体会与建议………………………………………………………………..14 5.3小组分工…………………………………………………………………….15
5.4参考文献…………………………………………………………………….15
六、附录
6.1流水灯实验…………………………………………………………………16 6.2蜂鸣器实验…………………………………………………………………17 6.3秒表实验…………………………………………………………………….18 6.4简易交通灯实验…………………………………………………………25 6.5电子琴源代码…………………………………………………………….30
一、实验简介
1.1实验目的
通过设计一个模拟电子琴系统熟悉ATmega16单片机的使用及其基本功能。使其能够通过键盘控制实现播放预存音乐,弹奏、储存弹奏音乐,变速播放,暂停及继续播放,停止播放功能。
1.2实验关键词
按键检测 预存播放 弹奏 储存 变速 暂停 停止
1.3基础知识
声波是振动产生的。频率即表示每秒钟振动的次数,采用CTC方式时avr单片机通过特定的端口(PD4及PD5)输出一定频率的方波,TCCR1A设为比较匹配时OC1A/OC1B电平取反,TCCR1B的计数上限为OC1A,根据公式OCnA=f/2N(1+OCRnA)计算出7个频率音阶所需的OCR1A,则只需将喇叭接在PD4或PD5,通过程序控制端口输出特定频率的方波波形(发声使用正弦波最好,方波效果稍次但影响不大),喇叭就会发出七种不同的声音,依照人听觉分辨7个音阶分为三组,分别为高,中,低音阶频率,经计算可得,当OCR1A=(8000000/musicmem[i]-1)时,{131,147,165,175,196,220,247}存放低音阶频率,{262,294,330,349,392,440,494}存放中音阶频率,{524,588,660,698,784,880,988}则存放高音阶频率,所以需要定义三个数组存放各音阶的频率值。
除了音符频率以外还需要音长,所以定义两个数组表示一段音乐,第一个表示频率,第二个表示音长,播放时先访问频率数组,使喇叭发声,之后访问音长数组,确定喇叭发声时间。而有了音符频率数组,只要再得到任意一首歌的简谱,就可以将其转化为两个数组的形式,由音符对应的频率得出频率数组,然后再根据每个音符的音长,将其通过乐曲的节拍和音符的拍数计算出音符持续时间即可得出音长数组。
下面我们举例来看《两只老虎》的简谱:
图一 《两只老虎》简谱图
①音符数组为:uint sound[32] = {262,294,330,262,262,294,330,262,330,349,392,330,349,392,392,440,392,349,330,262,392,440,392,349,330,262,294,196,262,294,196,262}; ②音长数组为:uint time[32] = {500,500,500,500,500,500,500,500,500,500,1000,500,500,1000,
375,125,375,125,500,500,375,125,375,125,500,500,500,500,1000,500,500,1000};
二、实验器材
2.1核心部件:ATmega16单片机
图二 ATmega16单片机外观图
图三 ATmega16单片机引脚图
2.2电路元件
2.3管脚说明
(1)4*4键盘:接PA0~PA7端口。
(2)LED灯:黄灯(接PC1端口),绿灯(接PC0端口),红灯(接PD7端口)。 (3)数码管:接PB0~PB3端口,PB5~PB7端口。 (4)扬声器:“+”接PD5端口,“—”接AGND。
三、实验原理
3.1实验原理图
3.1.1原理简单说明
单片机输出频率CTC不同的信号来产生各种音符,将歌曲各个音符转化成相应的数字信号,存放在单片机程序寄存器中,当播放音乐时,程序从该寄存器里读取数据,然后以CTC信号的模式实现放音乐的功能。
3.2实验流程图
3.3实物效果图
四、操作过程与运行结果
4.1操作过程示意图
4.1.1子程序操作过程分别演示
1)按键操作原理 (3)播放模式
2) 弹奏模式
( (
4.2电子琴系统主要部分
共分为五大部分: (1)ATmega16单片机 (2)4*4键盘 (3)LED显示灯 (4)数码管 (5)扬声器
4.3具体操作过程
(1)键盘区:数字1~7代表7个音符,8代表曲子加速播放,9代表曲子减速播放,0代表曲子恢复原速播放。另外,B、F可以进入演奏模式:按B可以播放我们预存的曲子一,按F可以播放预存曲子二。C、D、E分别代表L大调、C大调、H大调。按键C还有另一个功能:按下它可以进入弹奏模式,可以自己弹奏一曲(音符默认为L大调),弹奏完后按F可以播放自己先前弹奏的音符,有存储的功能。除此之外,2键有暂停的功能,3键有继续的功能,实现曲子的暂停与继续播放。1键有停止的功能,实现模式之间的转换,如从演奏模式转入弹奏模式。最后,在我们按1~7七个音符时,数码管会给予相应的数字显示,按下A键有清屏的功能。
(2)LED灯显示区:共有红、黄、绿三个灯。
红:电子琴整个开启的过程中,红灯一直保持亮的状态,代表电子琴处于工作状
态。
红、绿:代表电子琴进入演奏模式,可以播放预存曲子。 红、黄:代表电子琴进入弹奏模式,可以任意弹奏音符。
(3)数码管:在播放预存曲子时,显示为数字8。起到预先测定电子琴工作状
态是否正常的作用。
既不是演奏模式又不是弹奏模式时,显示为数字0,等待下一步
按键。
进入弹奏模式后,显示按下音符所代表的数字。 (4)整体操作过程(如视频中):
通电后,红灯亮,代表电子琴进入工作模式;同时自动播放储存音乐,数码
管显示为8,代表工作正常。按下B键进入演奏模式,此时红绿灯亮;电子琴播放预存曲子一,按下2键曲子暂停,再按下3键曲子继续播放;按下1键就跳出了演奏模式,等待下一步指示。然后我们再次按下B键播放曲子,以测试8、9、0键的作用。如视频中所示,8键曲子播放速度加快,9键速度变慢,0键回到正常速度。紧接着我们按下C键进入弹奏模式,此时红黄灯亮;在弹奏了一串音符后,按下F键可以播放我们刚才弹的音乐,同时数码管显示相应音符。在弹奏音符时,按下A键有数码管清屏的作用。在键盘未接收到任何指示时,数码管显示为0。以上为电子琴的整个工作过程。
4.4运行结果
图四 电子琴工作状态图
如视频中所示,一切运行正常。
五、实验中的排错、体会与建议
5.1调试与排错过程
(1)在此次实验过程中,我尝试过采用ICC AVR 7作为编译软件。同样在AVR
Studio 4软件(GCC环境)中编译成功的代码在ICC环境下却不能编译成功。出现了cannot include source file”avr/io.h”;file not found的error。在问过老师后,将头文件中的换成后编译成功。原来是GCC环境下的头文件,是ICC环境下的头文件,二者发挥同等作用。
(2)实验开始前期有点无从下手,在设计好了大的框架后开始着手分块写程序。最初的问题是4*4键盘的使用,首先通过资料等学习了4*4键盘的原理和内部结构,接下来写了单段的键盘测试代码。如下: switch(key)
{
case 0XEE : key=0; break; case 0XDE : key=1; break; case 0XBE : key=2; break; case 0X7E : key=3; break; case 0XED : key=4; break; case 0XDD : key=5; break; case 0XBD : key=6; break; case 0X7D : key=7; break; case 0XEB : key=8; break; case 0XDB : key=9; break; case 0XBB : key=10; break; case 0X7B : key=11; break; case 0XE7 : key=12; break; case 0XD7 : key=13; break; case 0XB7 : key=14; break; case 0X77 : key=15; break; default : key=16; break; }
(3)在用8段数码管做秒表和交通灯实验时,我们首先对数码管各段控制显示
的部分进行了测试,依此完成了0~9数字的代码表示。在组内成员分别进行代码的编写时,我们统一了数字表示代码和端口的使用,这样在实验板交替下载时,保证不用更改端口设置,节省了时间。
(4)在进行代码的编写时,我们注意了除循环变量i、j外,其他变量都尽量不重名,以防调用的函数里面的局部变量和主函数的局部变量名相同下载后有时会有BUG。同时,编写单个功能时是直接调用主函数的,但是多功能共同实现时需要将单个功能的部分封装起来,使主函数简短,执行过程明确,同时减少可能的逻辑错误。通过观看老师给我们的视频资料,决定采用CTC模式产生方波作为音频信号输出。
(5)在基本完成代码的整个部分时,编译没有错误,下载到实验板上出现了扬声器一直无规律振动不停止,发声不正常的现象,发出了类似于蜜蜂“嗡嗡”的声音,而非预存的音乐。开始我们认为是代码中频率设置不合理,在把L、C、H大调都提高频率设置后,依然出现上述现象。之后我们怀疑是硬件的问题,我们借来其他组的实验板重新进行下载,结果扬声器发音正常了,播放出了预存的音乐,而且还能进行弹奏。于是我们跟老师反映了情况,换了一个实验板,这才运行正常。
5.2体会与建议
在之前的实验中,我们从来没有接触过AVR单片机。通过老师给我们的学习资料,我了解了单片机的原理及应用,其中比较重要的部分包括AVR单片机IO口的配置、延时函数、扫描频率、键盘的作用和检测原理以及如何消抖、中断与定时器的原理和应用等等。刚开始连配置编译环境和下载都完全不懂,我就按照视频上的讲解一步步跟着做,所谓万事开头难,唯有做好充足的心理准备才能学好单片机。
在用AVR Studio 4进行编程时,我们严格得遵循了代码缩进的原则,必要语句添加注释,便于之后的读取代码与排错过程。三个人的工程需要密切协作,同时也要保证各自的进度。本次实验中,汪彤负责了电子琴基本功能代码的编写,宗妍负责了代码的后续完善,梁宝月负责了硬件的连接与调试过程。当然分工并不是严格对立分开,每个人都有自己主要负责的部分,其他人作为协助,谁有好的点子大家就予以尝试。在这个过程中,我们三个体会到了编音符数组与音长数
组的乐趣。我们选取了三段彼此很喜欢的音乐片段,根据C大调的音阶频率编写音符数组,也由于音乐知识的匮乏,在编写音长数组时对每个音符持续的拍节长短不是很能确定。于是我们就进行下载测试,播放过程中觉得长的音符就裁短,短的音符就加长,最终使得演奏出的曲子接近原本的旋律。在繁杂的编代码过程中,这是一个很有趣的尝试。
总之,这次小学期我受益良多。我不仅学会了多种软件的使用方法、单片机的原理和应用,巩固了大一学过的C++编程知识,而且还锻炼了完成工科工作所需要的心理素质,即耐心、细心以及合作意识。通过组内成员有效分工,我们保证了实验的进度,而且一个人的新思路往往能够启发另一个人的创新意识,这点在实验过程中我们体会地淋漓尽致。同时,在每次实验结束后我都能对实验台上的工具加以摆放和整理,将电脑及时关机,清理实验过程中的导线胶皮、纸屑等垃圾,用过的网线再插回原处,保持了实验台的整洁美观。实验过程中的纪律问题虽然是容易让我们忽视的方面,但是养成良好的卫生习惯也有助于提升我们的整体素质,为今后步入社会积累宝贵的经验与财富。唯由此,我们才不枉称自己是北邮的学子。
一言以蔽之,AVR单片机实验锻炼了自主学习能力,动手实践能力,小组合作能力,系统工程能力,给我带来很多启示,希望以后还能有这样的机会,进一步提高自己。
5.3小组分工
汪 彤:负责代码的初步编写,使得电子琴具备基本功能,如播放和变速、弹奏
等。
宗 妍:负责代码的后期完善,给电子琴加入一些新的功能,如储存、LED灯显
示工作状态等。
同时,宗妍和汪彤两人完成了代码的整个调试过程,为后续的硬件下载做好了准备。
梁宝月:负责电子琴设计报告的撰写,硬件电路的连接与调试过程,资料查找。
5.4参考文献
1. 《C语言程序设计》 谭浩强 编著 清华大学出版社
2. 《AVR单片机C语言开发入门指导》 沈文 Eagle lee 詹卫前 编著 清华大
学出版社
3. 《AVR单片机嵌入式系统原理与应用实践》 马潮 编著 北京航空航天大学出版社
4. AVR单片机说明文档cn_mega16-16L.pdf
5. AVR单片机软硬件设计视频教程-入门篇-第二讲-AVR硬件电路设计教程 6.AVR单片机软硬件设计视频教程-入门篇-第三讲-AVR开发基础知识 7. AVR单片机软硬件设计视频教程-入门篇-第六讲-中断与定时器
六、附录
6.1流水灯实验
6.1.1实物效果图
6.1.2代码
#include
int main(void) {
char temp;
unsigned int i,j;
}
DDRA =0xff; PORTA=0b11111110; while(1) {
temp =PORTA;
PORTA=PORTA
PORTA=PORTA | 0b00000001; }
for(i=0;i
{ }
for(j=0;j
6.2蜂鸣器实验
6.2.1实物效果图
6.2.2代码
#include
int main(void) {
DDRC &=~(1
DDRC |=(1
while(1)
{ if (PINC & (1
PORTC &= ~(1
else // {
PORTC |= (1
}
6.3秒表实验
6.3.1实物效果图
做为输出 若无按键按下 蜂鸣器不响 按键按下,蜂鸣器响
6.3.2代码
6.3.2.1无暂停、继续功能 #include
int main(void) {
int i,j;
int b=0,d=0; char ND[10]= {
0b1111110, //数码管显示0
0b0011000, //数码管显示1 0b1101101, //数码管显示2 0b0111101, //数码管显示3 0b0011011, //数码管显示4 0b0110111, //数码管显示5 0b1110111, //数码管显示6 0b0011100, //数码管显示7 0b1111111, //数码管显示8 0b0111111, //数码管显示9
}; char NB[10]= { 0b11101110, 0b00101000, 0b11001101,
0b01101101,
0b00101011, 0b01100111, 0b11100111, 0b00101100, 0b11101111, 0b01101111,
};
DDRC &=~(1
DDRB = 0xff; PORTB = NB[b];
DDRD = 0xff; PORTD = ND[d];
while(1) {
if((~PINC)&(1
while(1)
if( (d
PORTD = ND[d];
}
else if ((b
PORTD = ND[d];
PORTB = NB[b];
}
else {
b=d=0; PORTD = ND[d];
PORTB = NB[b];
}
for(i=0;i
for(j=0;j
}
}
} }
6.3.2.2有暂停、继续功能 #include #include
int b=0,a=0;
char NA[10]= {
0b1111110, 0b0011000, 0b1101101, 0b0111101, 0b0011011, 0b0110111, 0b1110111, 0b0011100, 0b1111111, 0b0111111,
};
char NB[10]= { 0b11101110, 0b00101000, 0b11001101, 0b01101101, 0b00101011, 0b01100111, 0b11100111, 0b00101100, 0b11101111, 0b01101111,
};
int main(void) {
DDRD &= ~(1
DDRB = 0xff; PORTB = NB[b];
DDRA = 0xff;
PORTA = NA[a];
for(i=0;i
MCUCR |= (1
GICR |= (1
sei();
while(1); }
SIGNAL(SIG_INTERRUPT1) {
do
{ PORTA = NA[a]; PORTB = NB[b];
}while((~PIND)&(1
SIGNAL(SIG_INTERRUPT0) { if((~PIND)&(1
{
do
{
if( (a
PORTA = NA[a];
}
else if ((b
PORTA = NA[a];
PORTB = NB[b];
} else {
b=a=0; PORTA = NA[a];
PORTB = NB[b];
}
for(i=0;i
}
}
6.3.3秒表、蜂鸣器、流水灯综合实物图
6.4简易交通灯实验
6.4.1实物效果图
6.4.2代码
#include #include
volatile unsigned int col = 0,time = 10,j=0;//颜色、时间 char out[10]= {
0b11101110, 0b00101000,
0b11001101,
0b01101101, 0b00101011, 0b01100111, 0b11100111, 0b00101100, 0b11101111, 0b01101111,
};
char led[3]= { 0b00000001, 0b00000010, 0b00000100,
};
int main(void) {
DDRB = 0xff; //
DDRD = 0xff; //十位 个位
DDRA = 0xff; //灯亮控制 DDRC |= (1
TCNT0 = 55; //初始化计时器
//TIMSK |= (1
{
for(j=0;j
while(!(TIFR & (1
TCNT0 = 55; } switch(col) {
case 0:PORTA=0b00000001; PORTC |= (1
case 1:PORTA=0b00000010; PORTC &=~(1
case 2:PORTA=0b00000100; PORTC &=~(1
default:PORTA=0b00000001;
PORTC |= (1
int ge = time; //取个位 int shi = (10-time); //取十位
switch(shi) //B口控制十位显示 {
case 0:PORTB=out[1];
break;
default:PORTB=out[0];
};
switch(ge) //D {
case 0:PORTD=out[0];
break;
case 1:PORTD=out[1];
break;
case 2:PORTD=out[2];
break;
case 3:PORTD=out[3];
break;
case 4:PORTD=out[4];
break;
case 5:PORTD=out[5]; break;
case 6:PORTD=out[6]; break;
case 7:PORTD=out[7];
break;
口控制个位显示
break;
case 9:PORTD=out[9];
break;
default:PORTD=out[0]; };
if(time==0) // {
time=10; if(col==2) col = 0; else col++; } else time--; }while(1);
} /*
volatile unsigned int cnt = 0; //SINGAL(SIG_OVERFLOWO) //{
TCNT0 = 55; cnt++;
if(cnt >= 10000) {
cnt = 0;
if(time==0) //倒计时到0就换灯颜色 并且重新倒计时计数器溢出的周期数 时钟溢出中断 倒计时到0就换灯颜色 并且重新倒计时
} */
time=10; if(col==2) col = 0; else col++; } }
6.5电子琴源代码
#include #include #include
#define uchar unsigned char #define uint unsigned int
uchar temp = 0; //获取按键的 int speed=20; //控制播放速度
uint music_L[7]={524,588,660,698,784,880,988}; //存放L大调音阶频率
uint music_C[7]={1047,1175,1319,1397,1568,1760,1976};//存放C大调音阶频率
uint music_H[7]={2093,2349,2637,2793,3136,3520,3951};//存放H大调音阶频率
uint music[7]={1047,1175,1319,1397,1568,1760,1976}; //存放C大调音阶频率并且在变调时将其他调的频率写入该数组
uint musicmem[100]={}; //储存弹奏的音乐频率
uint tune[100]={}; //储存弹奏的音乐的某个音符音长
int flag=0; //音长
char display[17]= { 0,
0b00101000, // 0b11001101, // 0b01101101, // 0b00101011, // 0b01100111, 0b11100111, // 0b00101100, // 0b11101111, // 0b01101111, // 0b11101110, //
0,0,0,0,0,0,
}; //显示
void init() //{
DDRD |=0x30; // DDRA = 0xf0; //
DDRB = 0xff; //8数码管显示1 数码管显示2 数码管显示3 数码管显示4 数码管显示5 数码管显示6 数码管显示7 数码管显示8 数码管显示9 数码管显示0 控制8段数码管初始化 发声 按键 段数码管
//
speed = 20; //播放速度 }
void delay(int ms) //延迟函数,单位为毫秒,用于在按键按下后提供延迟响应 滤除误按的影响。同时用来控制发声时间 {
TCNT0 = 55; //设置TC寄存器的初值
TCCR0 |=(1
for(int j=0;j
{
while(!(TIFR & (1
TCNT0 = 55;
} //一个计时器周期
} }
uchar keyscan() //获取按键 {
DDRA = 0xf0; PORTA = 0x0f; uchar Key = 0;
delay(10); //防止误按、排除
干扰 if ((PINA & 0x0f) != 0x0f) { delay(10);
if ((PINA & 0x0f) != 0x0f) { uchar i,j; for (i=0;i
if ((PINA & 0x0f) != 0x0f) { for (j=0;j
}
}
}
}
}
delay(10);
if(Key==8) //8键加速 {speed=10;}
else if(Key==9) //9键减速 {speed=40;}
else if(Key==10) //0键原速 {speed=20;} return Key;
}
void player(uint* x1,uint* x2,int l) //{
uint* x3=x1+l-1; while(x1
if(temp3==1) //1 x1=x3;
else if(temp3==2) //2 while(1) {
DDRD&=~0x30; uchar temp4=keyscan(); if(temp4==3)
break;
}; //3 if(*x1!=0) {
DDRD|=0x30;
播放某组音乐 停止 暂停 继续
波 TCCR1B=0x09;
OCR1A=(8000000/(*x1)-1); //CTC方式产生方
} //end if else {
DDRD&=~0x30; }
keyscan(); //键函数,按8 9 0时加速、减速、原速。
delay(*x2);
x1++;
x2++;
} //end while DDRD&=~0x30;
}
void play() //{
int flag=0; // while (1) {
int i=1; uchar j = 0; temp = keyscan();
if (temp != 0) {
调用一次获取按
弹奏+录制函数 音长
{
for(int i=0;i
else if(temp ==14) //按D,切换到C调
{
for(int i=0;i
else if(temp ==15) // {
for(int i=0;i
}
else if(temp ==16) //音乐
{
for(int i=0;i
j=tune[i];
DDRD |=0x30;
PORTB = display[j];
TCCR1A=0x50;
TCCR1B=0x09;
OCR1A=(8000000/musicmem[i]-1); delay(225); DDRD &=~0x30;
按E,切换到H调
按F,播放储存的
delay(150);
keyscan();
}
flag=0;
}
else if(temp==12) //play函数 break;
else //
OCR1A=(8000000/music[temp-1]-1);
j = keyscan();
DDRD &= ~0x30;
i=1;
} //end出声
{ DDRD |=0x30;
PORTB = display[temp]; TCCR1A=0x50; TCCR1B=0x09;
while(i) // { if(j == 0) // { i=0;
} //end else 按B,直接跳出扫描键盘弹奏区
判断是否连续出声 不连续弹奏 不连续弹奏 判断是否连续
musicmem[flag]=music[temp-1]; //储存频率
tune[flag]=temp; //储存音长
flag++;
} //end扫描键盘弹奏区 }
int main() {
init();
//一千年以后 uint sound[15] = }
}
{988,2093,2349,2637,2349,2093,1568,2349,2637,2349,2093,1976,880,660,988};
uint time[15] =
{1000,1625,1625,1625,1300,1800,1625,2800,1250,2000,1500,1800,2000,1625,1750};
//手掌心
uint
sound2[11]={1319,1568,1760,1319,1568,1175,1319,1568,1175,1319,880,}; uint
time2[11]={1000,2000,1500,2700,1650,2500,1500,2500,2000,1500,1650};
//倔强
uint sound3[33] =
{1319,1047,1319,1397,1568,1568,1397,1319,1175,1047,1175,1319,1319, 1175,1047,988,1047,1175,1047,1760,1976,1976,1760,1568,1319,1568,
1760,524,1047,1760,1976,1319,1568}; uint time3[33] =
{1500,500,1000,500,1000,1000,1000,1000,1000,2500,500,1000,1000,1000,1 000,1000,1000,500,1000,500,1000,1000,1000,1000,1000,500,1000,500,1500,500,1000,500,1500};
DDRC |= (1
PORTC &= ~(1
PORTC &= ~(1
PORTB = 0b11111111; PORTD |=(1
player(&sound,&time,15); //以后
uchar temp1;
while(1) //数 对应电子琴主菜单 {
temp1=keyscan(); if(temp1!=0) {
if(temp1==16) // {
PORTB = 0b11111111;
PORTC |= (1
自动播放一千年最外层的while函按F播放手掌心
player(&sound2,&time2,11); PORTC &= ~(1
}
else if(temp1==12) //按B播放倔强 {
PORTB = 0b11111111; PORTC |= (1
player(&sound3,&time3,33);
PORTC &= ~(1
}
else if(temp1==13) //存 {
PORTC |= (1
play();
PORTC &= ~(1
PORTB = 0b11101110;
}
PORTD &= ~(1
按C进入播放和储
姓 名:
班 级: 学 号: 小班学号: 同组姓名: 电子邮件:
实验课题:基于AVR单片机电子琴的制作 实验日期:2013.09.02—2013.09.15
目录:
一、实验简介
1.1实验目的.....................................................................4 1.2实验关键词……………………….…………………………………….4 1.3基础知识.....................................................................4
二、实验器材
2.1核心部件:ATmega16单片机.....................................5 2.2电路元件......................................................................6 2.3管脚说明…………………………………………………………………..6
三、实验原理
3.1实验原理图………………………………………………………………7 3.2实验流程图………………………………………………………………8 3.3实物效果图………………………………………………………………8
四、操作过程与运行结果
4.1操作过程示意图……………………………………………………….9 4.2电子琴系统主要部分……………………………………………….11
4.3具体操作过程…………………………………………………….11
4.4运行结果………………………………………………………………….12
五、实验中的排错、体会与建议
5.1调试与排错过程………………………………………………………..12 5.2体会与建议………………………………………………………………..14 5.3小组分工…………………………………………………………………….15
5.4参考文献…………………………………………………………………….15
六、附录
6.1流水灯实验…………………………………………………………………16 6.2蜂鸣器实验…………………………………………………………………17 6.3秒表实验…………………………………………………………………….18 6.4简易交通灯实验…………………………………………………………25 6.5电子琴源代码…………………………………………………………….30
一、实验简介
1.1实验目的
通过设计一个模拟电子琴系统熟悉ATmega16单片机的使用及其基本功能。使其能够通过键盘控制实现播放预存音乐,弹奏、储存弹奏音乐,变速播放,暂停及继续播放,停止播放功能。
1.2实验关键词
按键检测 预存播放 弹奏 储存 变速 暂停 停止
1.3基础知识
声波是振动产生的。频率即表示每秒钟振动的次数,采用CTC方式时avr单片机通过特定的端口(PD4及PD5)输出一定频率的方波,TCCR1A设为比较匹配时OC1A/OC1B电平取反,TCCR1B的计数上限为OC1A,根据公式OCnA=f/2N(1+OCRnA)计算出7个频率音阶所需的OCR1A,则只需将喇叭接在PD4或PD5,通过程序控制端口输出特定频率的方波波形(发声使用正弦波最好,方波效果稍次但影响不大),喇叭就会发出七种不同的声音,依照人听觉分辨7个音阶分为三组,分别为高,中,低音阶频率,经计算可得,当OCR1A=(8000000/musicmem[i]-1)时,{131,147,165,175,196,220,247}存放低音阶频率,{262,294,330,349,392,440,494}存放中音阶频率,{524,588,660,698,784,880,988}则存放高音阶频率,所以需要定义三个数组存放各音阶的频率值。
除了音符频率以外还需要音长,所以定义两个数组表示一段音乐,第一个表示频率,第二个表示音长,播放时先访问频率数组,使喇叭发声,之后访问音长数组,确定喇叭发声时间。而有了音符频率数组,只要再得到任意一首歌的简谱,就可以将其转化为两个数组的形式,由音符对应的频率得出频率数组,然后再根据每个音符的音长,将其通过乐曲的节拍和音符的拍数计算出音符持续时间即可得出音长数组。
下面我们举例来看《两只老虎》的简谱:
图一 《两只老虎》简谱图
①音符数组为:uint sound[32] = {262,294,330,262,262,294,330,262,330,349,392,330,349,392,392,440,392,349,330,262,392,440,392,349,330,262,294,196,262,294,196,262}; ②音长数组为:uint time[32] = {500,500,500,500,500,500,500,500,500,500,1000,500,500,1000,
375,125,375,125,500,500,375,125,375,125,500,500,500,500,1000,500,500,1000};
二、实验器材
2.1核心部件:ATmega16单片机
图二 ATmega16单片机外观图
图三 ATmega16单片机引脚图
2.2电路元件
2.3管脚说明
(1)4*4键盘:接PA0~PA7端口。
(2)LED灯:黄灯(接PC1端口),绿灯(接PC0端口),红灯(接PD7端口)。 (3)数码管:接PB0~PB3端口,PB5~PB7端口。 (4)扬声器:“+”接PD5端口,“—”接AGND。
三、实验原理
3.1实验原理图
3.1.1原理简单说明
单片机输出频率CTC不同的信号来产生各种音符,将歌曲各个音符转化成相应的数字信号,存放在单片机程序寄存器中,当播放音乐时,程序从该寄存器里读取数据,然后以CTC信号的模式实现放音乐的功能。
3.2实验流程图
3.3实物效果图
四、操作过程与运行结果
4.1操作过程示意图
4.1.1子程序操作过程分别演示
1)按键操作原理 (3)播放模式
2) 弹奏模式
( (
4.2电子琴系统主要部分
共分为五大部分: (1)ATmega16单片机 (2)4*4键盘 (3)LED显示灯 (4)数码管 (5)扬声器
4.3具体操作过程
(1)键盘区:数字1~7代表7个音符,8代表曲子加速播放,9代表曲子减速播放,0代表曲子恢复原速播放。另外,B、F可以进入演奏模式:按B可以播放我们预存的曲子一,按F可以播放预存曲子二。C、D、E分别代表L大调、C大调、H大调。按键C还有另一个功能:按下它可以进入弹奏模式,可以自己弹奏一曲(音符默认为L大调),弹奏完后按F可以播放自己先前弹奏的音符,有存储的功能。除此之外,2键有暂停的功能,3键有继续的功能,实现曲子的暂停与继续播放。1键有停止的功能,实现模式之间的转换,如从演奏模式转入弹奏模式。最后,在我们按1~7七个音符时,数码管会给予相应的数字显示,按下A键有清屏的功能。
(2)LED灯显示区:共有红、黄、绿三个灯。
红:电子琴整个开启的过程中,红灯一直保持亮的状态,代表电子琴处于工作状
态。
红、绿:代表电子琴进入演奏模式,可以播放预存曲子。 红、黄:代表电子琴进入弹奏模式,可以任意弹奏音符。
(3)数码管:在播放预存曲子时,显示为数字8。起到预先测定电子琴工作状
态是否正常的作用。
既不是演奏模式又不是弹奏模式时,显示为数字0,等待下一步
按键。
进入弹奏模式后,显示按下音符所代表的数字。 (4)整体操作过程(如视频中):
通电后,红灯亮,代表电子琴进入工作模式;同时自动播放储存音乐,数码
管显示为8,代表工作正常。按下B键进入演奏模式,此时红绿灯亮;电子琴播放预存曲子一,按下2键曲子暂停,再按下3键曲子继续播放;按下1键就跳出了演奏模式,等待下一步指示。然后我们再次按下B键播放曲子,以测试8、9、0键的作用。如视频中所示,8键曲子播放速度加快,9键速度变慢,0键回到正常速度。紧接着我们按下C键进入弹奏模式,此时红黄灯亮;在弹奏了一串音符后,按下F键可以播放我们刚才弹的音乐,同时数码管显示相应音符。在弹奏音符时,按下A键有数码管清屏的作用。在键盘未接收到任何指示时,数码管显示为0。以上为电子琴的整个工作过程。
4.4运行结果
图四 电子琴工作状态图
如视频中所示,一切运行正常。
五、实验中的排错、体会与建议
5.1调试与排错过程
(1)在此次实验过程中,我尝试过采用ICC AVR 7作为编译软件。同样在AVR
Studio 4软件(GCC环境)中编译成功的代码在ICC环境下却不能编译成功。出现了cannot include source file”avr/io.h”;file not found的error。在问过老师后,将头文件中的换成后编译成功。原来是GCC环境下的头文件,是ICC环境下的头文件,二者发挥同等作用。
(2)实验开始前期有点无从下手,在设计好了大的框架后开始着手分块写程序。最初的问题是4*4键盘的使用,首先通过资料等学习了4*4键盘的原理和内部结构,接下来写了单段的键盘测试代码。如下: switch(key)
{
case 0XEE : key=0; break; case 0XDE : key=1; break; case 0XBE : key=2; break; case 0X7E : key=3; break; case 0XED : key=4; break; case 0XDD : key=5; break; case 0XBD : key=6; break; case 0X7D : key=7; break; case 0XEB : key=8; break; case 0XDB : key=9; break; case 0XBB : key=10; break; case 0X7B : key=11; break; case 0XE7 : key=12; break; case 0XD7 : key=13; break; case 0XB7 : key=14; break; case 0X77 : key=15; break; default : key=16; break; }
(3)在用8段数码管做秒表和交通灯实验时,我们首先对数码管各段控制显示
的部分进行了测试,依此完成了0~9数字的代码表示。在组内成员分别进行代码的编写时,我们统一了数字表示代码和端口的使用,这样在实验板交替下载时,保证不用更改端口设置,节省了时间。
(4)在进行代码的编写时,我们注意了除循环变量i、j外,其他变量都尽量不重名,以防调用的函数里面的局部变量和主函数的局部变量名相同下载后有时会有BUG。同时,编写单个功能时是直接调用主函数的,但是多功能共同实现时需要将单个功能的部分封装起来,使主函数简短,执行过程明确,同时减少可能的逻辑错误。通过观看老师给我们的视频资料,决定采用CTC模式产生方波作为音频信号输出。
(5)在基本完成代码的整个部分时,编译没有错误,下载到实验板上出现了扬声器一直无规律振动不停止,发声不正常的现象,发出了类似于蜜蜂“嗡嗡”的声音,而非预存的音乐。开始我们认为是代码中频率设置不合理,在把L、C、H大调都提高频率设置后,依然出现上述现象。之后我们怀疑是硬件的问题,我们借来其他组的实验板重新进行下载,结果扬声器发音正常了,播放出了预存的音乐,而且还能进行弹奏。于是我们跟老师反映了情况,换了一个实验板,这才运行正常。
5.2体会与建议
在之前的实验中,我们从来没有接触过AVR单片机。通过老师给我们的学习资料,我了解了单片机的原理及应用,其中比较重要的部分包括AVR单片机IO口的配置、延时函数、扫描频率、键盘的作用和检测原理以及如何消抖、中断与定时器的原理和应用等等。刚开始连配置编译环境和下载都完全不懂,我就按照视频上的讲解一步步跟着做,所谓万事开头难,唯有做好充足的心理准备才能学好单片机。
在用AVR Studio 4进行编程时,我们严格得遵循了代码缩进的原则,必要语句添加注释,便于之后的读取代码与排错过程。三个人的工程需要密切协作,同时也要保证各自的进度。本次实验中,汪彤负责了电子琴基本功能代码的编写,宗妍负责了代码的后续完善,梁宝月负责了硬件的连接与调试过程。当然分工并不是严格对立分开,每个人都有自己主要负责的部分,其他人作为协助,谁有好的点子大家就予以尝试。在这个过程中,我们三个体会到了编音符数组与音长数
组的乐趣。我们选取了三段彼此很喜欢的音乐片段,根据C大调的音阶频率编写音符数组,也由于音乐知识的匮乏,在编写音长数组时对每个音符持续的拍节长短不是很能确定。于是我们就进行下载测试,播放过程中觉得长的音符就裁短,短的音符就加长,最终使得演奏出的曲子接近原本的旋律。在繁杂的编代码过程中,这是一个很有趣的尝试。
总之,这次小学期我受益良多。我不仅学会了多种软件的使用方法、单片机的原理和应用,巩固了大一学过的C++编程知识,而且还锻炼了完成工科工作所需要的心理素质,即耐心、细心以及合作意识。通过组内成员有效分工,我们保证了实验的进度,而且一个人的新思路往往能够启发另一个人的创新意识,这点在实验过程中我们体会地淋漓尽致。同时,在每次实验结束后我都能对实验台上的工具加以摆放和整理,将电脑及时关机,清理实验过程中的导线胶皮、纸屑等垃圾,用过的网线再插回原处,保持了实验台的整洁美观。实验过程中的纪律问题虽然是容易让我们忽视的方面,但是养成良好的卫生习惯也有助于提升我们的整体素质,为今后步入社会积累宝贵的经验与财富。唯由此,我们才不枉称自己是北邮的学子。
一言以蔽之,AVR单片机实验锻炼了自主学习能力,动手实践能力,小组合作能力,系统工程能力,给我带来很多启示,希望以后还能有这样的机会,进一步提高自己。
5.3小组分工
汪 彤:负责代码的初步编写,使得电子琴具备基本功能,如播放和变速、弹奏
等。
宗 妍:负责代码的后期完善,给电子琴加入一些新的功能,如储存、LED灯显
示工作状态等。
同时,宗妍和汪彤两人完成了代码的整个调试过程,为后续的硬件下载做好了准备。
梁宝月:负责电子琴设计报告的撰写,硬件电路的连接与调试过程,资料查找。
5.4参考文献
1. 《C语言程序设计》 谭浩强 编著 清华大学出版社
2. 《AVR单片机C语言开发入门指导》 沈文 Eagle lee 詹卫前 编著 清华大
学出版社
3. 《AVR单片机嵌入式系统原理与应用实践》 马潮 编著 北京航空航天大学出版社
4. AVR单片机说明文档cn_mega16-16L.pdf
5. AVR单片机软硬件设计视频教程-入门篇-第二讲-AVR硬件电路设计教程 6.AVR单片机软硬件设计视频教程-入门篇-第三讲-AVR开发基础知识 7. AVR单片机软硬件设计视频教程-入门篇-第六讲-中断与定时器
六、附录
6.1流水灯实验
6.1.1实物效果图
6.1.2代码
#include
int main(void) {
char temp;
unsigned int i,j;
}
DDRA =0xff; PORTA=0b11111110; while(1) {
temp =PORTA;
PORTA=PORTA
PORTA=PORTA | 0b00000001; }
for(i=0;i
{ }
for(j=0;j
6.2蜂鸣器实验
6.2.1实物效果图
6.2.2代码
#include
int main(void) {
DDRC &=~(1
DDRC |=(1
while(1)
{ if (PINC & (1
PORTC &= ~(1
else // {
PORTC |= (1
}
6.3秒表实验
6.3.1实物效果图
做为输出 若无按键按下 蜂鸣器不响 按键按下,蜂鸣器响
6.3.2代码
6.3.2.1无暂停、继续功能 #include
int main(void) {
int i,j;
int b=0,d=0; char ND[10]= {
0b1111110, //数码管显示0
0b0011000, //数码管显示1 0b1101101, //数码管显示2 0b0111101, //数码管显示3 0b0011011, //数码管显示4 0b0110111, //数码管显示5 0b1110111, //数码管显示6 0b0011100, //数码管显示7 0b1111111, //数码管显示8 0b0111111, //数码管显示9
}; char NB[10]= { 0b11101110, 0b00101000, 0b11001101,
0b01101101,
0b00101011, 0b01100111, 0b11100111, 0b00101100, 0b11101111, 0b01101111,
};
DDRC &=~(1
DDRB = 0xff; PORTB = NB[b];
DDRD = 0xff; PORTD = ND[d];
while(1) {
if((~PINC)&(1
while(1)
if( (d
PORTD = ND[d];
}
else if ((b
PORTD = ND[d];
PORTB = NB[b];
}
else {
b=d=0; PORTD = ND[d];
PORTB = NB[b];
}
for(i=0;i
for(j=0;j
}
}
} }
6.3.2.2有暂停、继续功能 #include #include
int b=0,a=0;
char NA[10]= {
0b1111110, 0b0011000, 0b1101101, 0b0111101, 0b0011011, 0b0110111, 0b1110111, 0b0011100, 0b1111111, 0b0111111,
};
char NB[10]= { 0b11101110, 0b00101000, 0b11001101, 0b01101101, 0b00101011, 0b01100111, 0b11100111, 0b00101100, 0b11101111, 0b01101111,
};
int main(void) {
DDRD &= ~(1
DDRB = 0xff; PORTB = NB[b];
DDRA = 0xff;
PORTA = NA[a];
for(i=0;i
MCUCR |= (1
GICR |= (1
sei();
while(1); }
SIGNAL(SIG_INTERRUPT1) {
do
{ PORTA = NA[a]; PORTB = NB[b];
}while((~PIND)&(1
SIGNAL(SIG_INTERRUPT0) { if((~PIND)&(1
{
do
{
if( (a
PORTA = NA[a];
}
else if ((b
PORTA = NA[a];
PORTB = NB[b];
} else {
b=a=0; PORTA = NA[a];
PORTB = NB[b];
}
for(i=0;i
}
}
6.3.3秒表、蜂鸣器、流水灯综合实物图
6.4简易交通灯实验
6.4.1实物效果图
6.4.2代码
#include #include
volatile unsigned int col = 0,time = 10,j=0;//颜色、时间 char out[10]= {
0b11101110, 0b00101000,
0b11001101,
0b01101101, 0b00101011, 0b01100111, 0b11100111, 0b00101100, 0b11101111, 0b01101111,
};
char led[3]= { 0b00000001, 0b00000010, 0b00000100,
};
int main(void) {
DDRB = 0xff; //
DDRD = 0xff; //十位 个位
DDRA = 0xff; //灯亮控制 DDRC |= (1
TCNT0 = 55; //初始化计时器
//TIMSK |= (1
{
for(j=0;j
while(!(TIFR & (1
TCNT0 = 55; } switch(col) {
case 0:PORTA=0b00000001; PORTC |= (1
case 1:PORTA=0b00000010; PORTC &=~(1
case 2:PORTA=0b00000100; PORTC &=~(1
default:PORTA=0b00000001;
PORTC |= (1
int ge = time; //取个位 int shi = (10-time); //取十位
switch(shi) //B口控制十位显示 {
case 0:PORTB=out[1];
break;
default:PORTB=out[0];
};
switch(ge) //D {
case 0:PORTD=out[0];
break;
case 1:PORTD=out[1];
break;
case 2:PORTD=out[2];
break;
case 3:PORTD=out[3];
break;
case 4:PORTD=out[4];
break;
case 5:PORTD=out[5]; break;
case 6:PORTD=out[6]; break;
case 7:PORTD=out[7];
break;
口控制个位显示
break;
case 9:PORTD=out[9];
break;
default:PORTD=out[0]; };
if(time==0) // {
time=10; if(col==2) col = 0; else col++; } else time--; }while(1);
} /*
volatile unsigned int cnt = 0; //SINGAL(SIG_OVERFLOWO) //{
TCNT0 = 55; cnt++;
if(cnt >= 10000) {
cnt = 0;
if(time==0) //倒计时到0就换灯颜色 并且重新倒计时计数器溢出的周期数 时钟溢出中断 倒计时到0就换灯颜色 并且重新倒计时
} */
time=10; if(col==2) col = 0; else col++; } }
6.5电子琴源代码
#include #include #include
#define uchar unsigned char #define uint unsigned int
uchar temp = 0; //获取按键的 int speed=20; //控制播放速度
uint music_L[7]={524,588,660,698,784,880,988}; //存放L大调音阶频率
uint music_C[7]={1047,1175,1319,1397,1568,1760,1976};//存放C大调音阶频率
uint music_H[7]={2093,2349,2637,2793,3136,3520,3951};//存放H大调音阶频率
uint music[7]={1047,1175,1319,1397,1568,1760,1976}; //存放C大调音阶频率并且在变调时将其他调的频率写入该数组
uint musicmem[100]={}; //储存弹奏的音乐频率
uint tune[100]={}; //储存弹奏的音乐的某个音符音长
int flag=0; //音长
char display[17]= { 0,
0b00101000, // 0b11001101, // 0b01101101, // 0b00101011, // 0b01100111, 0b11100111, // 0b00101100, // 0b11101111, // 0b01101111, // 0b11101110, //
0,0,0,0,0,0,
}; //显示
void init() //{
DDRD |=0x30; // DDRA = 0xf0; //
DDRB = 0xff; //8数码管显示1 数码管显示2 数码管显示3 数码管显示4 数码管显示5 数码管显示6 数码管显示7 数码管显示8 数码管显示9 数码管显示0 控制8段数码管初始化 发声 按键 段数码管
//
speed = 20; //播放速度 }
void delay(int ms) //延迟函数,单位为毫秒,用于在按键按下后提供延迟响应 滤除误按的影响。同时用来控制发声时间 {
TCNT0 = 55; //设置TC寄存器的初值
TCCR0 |=(1
for(int j=0;j
{
while(!(TIFR & (1
TCNT0 = 55;
} //一个计时器周期
} }
uchar keyscan() //获取按键 {
DDRA = 0xf0; PORTA = 0x0f; uchar Key = 0;
delay(10); //防止误按、排除
干扰 if ((PINA & 0x0f) != 0x0f) { delay(10);
if ((PINA & 0x0f) != 0x0f) { uchar i,j; for (i=0;i
if ((PINA & 0x0f) != 0x0f) { for (j=0;j
}
}
}
}
}
delay(10);
if(Key==8) //8键加速 {speed=10;}
else if(Key==9) //9键减速 {speed=40;}
else if(Key==10) //0键原速 {speed=20;} return Key;
}
void player(uint* x1,uint* x2,int l) //{
uint* x3=x1+l-1; while(x1
if(temp3==1) //1 x1=x3;
else if(temp3==2) //2 while(1) {
DDRD&=~0x30; uchar temp4=keyscan(); if(temp4==3)
break;
}; //3 if(*x1!=0) {
DDRD|=0x30;
播放某组音乐 停止 暂停 继续
波 TCCR1B=0x09;
OCR1A=(8000000/(*x1)-1); //CTC方式产生方
} //end if else {
DDRD&=~0x30; }
keyscan(); //键函数,按8 9 0时加速、减速、原速。
delay(*x2);
x1++;
x2++;
} //end while DDRD&=~0x30;
}
void play() //{
int flag=0; // while (1) {
int i=1; uchar j = 0; temp = keyscan();
if (temp != 0) {
调用一次获取按
弹奏+录制函数 音长
{
for(int i=0;i
else if(temp ==14) //按D,切换到C调
{
for(int i=0;i
else if(temp ==15) // {
for(int i=0;i
}
else if(temp ==16) //音乐
{
for(int i=0;i
j=tune[i];
DDRD |=0x30;
PORTB = display[j];
TCCR1A=0x50;
TCCR1B=0x09;
OCR1A=(8000000/musicmem[i]-1); delay(225); DDRD &=~0x30;
按E,切换到H调
按F,播放储存的
delay(150);
keyscan();
}
flag=0;
}
else if(temp==12) //play函数 break;
else //
OCR1A=(8000000/music[temp-1]-1);
j = keyscan();
DDRD &= ~0x30;
i=1;
} //end出声
{ DDRD |=0x30;
PORTB = display[temp]; TCCR1A=0x50; TCCR1B=0x09;
while(i) // { if(j == 0) // { i=0;
} //end else 按B,直接跳出扫描键盘弹奏区
判断是否连续出声 不连续弹奏 不连续弹奏 判断是否连续
musicmem[flag]=music[temp-1]; //储存频率
tune[flag]=temp; //储存音长
flag++;
} //end扫描键盘弹奏区 }
int main() {
init();
//一千年以后 uint sound[15] = }
}
{988,2093,2349,2637,2349,2093,1568,2349,2637,2349,2093,1976,880,660,988};
uint time[15] =
{1000,1625,1625,1625,1300,1800,1625,2800,1250,2000,1500,1800,2000,1625,1750};
//手掌心
uint
sound2[11]={1319,1568,1760,1319,1568,1175,1319,1568,1175,1319,880,}; uint
time2[11]={1000,2000,1500,2700,1650,2500,1500,2500,2000,1500,1650};
//倔强
uint sound3[33] =
{1319,1047,1319,1397,1568,1568,1397,1319,1175,1047,1175,1319,1319, 1175,1047,988,1047,1175,1047,1760,1976,1976,1760,1568,1319,1568,
1760,524,1047,1760,1976,1319,1568}; uint time3[33] =
{1500,500,1000,500,1000,1000,1000,1000,1000,2500,500,1000,1000,1000,1 000,1000,1000,500,1000,500,1000,1000,1000,1000,1000,500,1000,500,1500,500,1000,500,1500};
DDRC |= (1
PORTC &= ~(1
PORTC &= ~(1
PORTB = 0b11111111; PORTD |=(1
player(&sound,&time,15); //以后
uchar temp1;
while(1) //数 对应电子琴主菜单 {
temp1=keyscan(); if(temp1!=0) {
if(temp1==16) // {
PORTB = 0b11111111;
PORTC |= (1
自动播放一千年最外层的while函按F播放手掌心
player(&sound2,&time2,11); PORTC &= ~(1
}
else if(temp1==12) //按B播放倔强 {
PORTB = 0b11111111; PORTC |= (1
player(&sound3,&time3,33);
PORTC &= ~(1
}
else if(temp1==13) //存 {
PORTC |= (1
play();
PORTC &= ~(1
PORTB = 0b11101110;
}
PORTD &= ~(1
按C进入播放和储