程序设计目标及程序运行效果说明
程序设计目标:通过无源蜂鸣器与按键key1、按键key2两外接模块实现电子音乐的播放、暂停、切换功能。
程序运行效果说明:下载程序后,通过按下key1来进行音乐的播放,通过再次按下key1可以暂停音乐的播放;按下key2可以切换到下一曲。
程序相关电路及原理说明
1.原理说明
本实验板采用的是无源蜂鸣器,无源内部不带震荡源,所以如果用直流信号无法令其鸣叫。必须用2K~5K的方波去驱动它。相比与有源蜂鸣器,无源蜂鸣器的优点在于价格便宜,可以通过控制其振动频率来改变发出的声音,做出“多来米发索拉西”的效果。因此,无源蜂鸣器可以用于音乐的播放。而有源蜂鸣器的优点在于使用简单,不需要编写“乐谱”。本实验板使用的无源蜂鸣器是电磁式蜂鸣器,电磁式蜂鸣器由振荡器、电磁线圈、磁铁、振动膜片及外壳等组成。接通电源后,接收到的音频信号电流通过电磁线圈,使电磁线圈产生磁场。振动膜片在电磁线圈和磁铁的相互作用下,周期性地振动发声。
每一个音符的发声频率是不同的,我们需要用计时器来精确计时,用以产生方波,这样才能发出不用的音符声音。C调各音符频率与计数值如图1所示,以下的简谱码是在晶振为12MHz的情况下计算的,换算为16进制的简谱码如程序中quzi[]数组所示。
本程序中,数组music[]即是要播放的音乐,格式为音符,节拍,音符,节拍,如此循环下去。音符为要发出的音调,而节拍则是声音的持续时间。如图1,在数组music[]中,音符表示的格式为:十位代表是低八度,中八度还是高八度,1代表高八度,2代表中八度,3代表高八度,个位代表简谱的音符,例如,0x15代表低八度的S0,如图即是低5S0,0x21代表中八度的DO,如图即是中1 D0。音符中,0x00代表结束符,表示整首歌曲演唱完毕,而0xff代表休止符,表示要休止100ms。遇到这两种情况,都应该重新执行循环中的第一步。其余情况则是正常播放。
2.程序相关电路
(1)无源蜂鸣器电路原理图
(2)按键控制电路
芯片相关引脚:Beep——P3^4;
代码如下:
#include<STC15F2K60S2.H>
#define uint unsigned int
#define uchar unsigned char
sbit beep=P3^4; //蜂鸣器
sbit key1=P3^2; //按键1
sbit key2=P3^3; //按键2
uchar timeh,timel,jindu=0,gequ=0; //定义定时器的重装值
bit flag; //播放标志位
uchar code music1[] ={ //音乐代码,歌曲为《同一首歌》,格式为: 音符, 节拍, 音符, 节拍,
0x15,0x20,0x21,0x10, //音符的十位代表是低八度,中八度还是高八度,1代表低八度,2代表中八度,3代表高八度
0x22,0x10,0x23,0x15, //个位代表简谱的音符,例如0x15代表低八度的S0,0x21代表中八度的DO。
0x24,0x05,0x23,0x10, //节拍则是代表音长,例如:0x10代表一拍,0x20代表两拍,0x05代表1/2拍
0x21,0x10,0x22,0x20,
0x21,0x10,0x16,0x10,
0x21,0x40,0x15,0x20,
0x21,0x10,0x22,0x10,
0x23,0x10,0x23,0x05,
0x24,0x05,0x25,0x10,
0x21,0x10,0x24,0x15,
0x23,0x05,0x25,0x10,
0x22,0x05,0x23,0x05,
0x23,0x05,0x22,0x05,
0x22,0x30,0x23,0x20,
0x25,0x10,0x31,0x10,
0x27,0x15,0x26,0x05,
0x26,0x20,0x25,0x10,
0x25,0x05,0x26,0x05,
0x27,0x10,0x26,0x05,
0x25,0x05,0x23,0x40,
0x24,0x15,0x24,0x05,
0x25,0x10,0x26,0x10,
0x25,0x10,0x24,0x05,
0x23,0x05,0x22,0x20,
0x17,0x10,0x17,0x05,
0x16,0x05,0x15,0x10,
0x16,0x10,0x21,0x40,
0x00,0x00
};
uchar code music2[] ={ //歌曲为小毛驴
0x21,0x05,0x21,0x05,
0x21,0x05,0x23,0x05,
0x25,0x05,0x25,0x05,
0x25,0x05,0x25,0x05,
0x26,0x05,0x26,0x05,
0x26,0x05,0x31,0x05,
0x25,0x20,0x24,0x05,
0x24,0x05,0x24,0x05,
0x26,0x05,0x23,0x05,
0x23,0x05,0x23,0x05,
0x23,0x05,0x22,0x05,
0x22,0x05,0x22,0x05,
0x22,0x05,0x25,0x15,
0x25,0x05,0x21,0x05,
0x21,0x05,0x21,0x05,
0x23,0x05,0x25,0x05,
0x25,0x05,0x25,0x05,
0x25,0x05,0x26,0x05,
0x26,0x05,0x26,0x05,
0x31,0x05,0x25,0x20,
0x24,0x05,0x24,0x05,
0x24,0x05,0x26,0x05,
0x21,0x05,0x21,0x05,
0x21,0x05,0x23,0x05,
0x25,0x05,0x25,0x05,
0x25,0x05,0x25,0x05,
0x26,0x05,0x26,0x05,
0x26,0x05,0x31,0x05,
0x25,0x20,0x24,0x05,
0x24,0x05,0x24,0x05,
0x23,0x03,0x23,0x03,
0x23,0x03,0x23,0x03,
0x23,0x05,0x23,0x05,
0x22,0x05,0x22,0x05,
0x22,0x05,0x23,0x05,
0x21,0x20,0x00,0x00
};
uchar code music3[] ={ //歌曲为天空之城
0x16,0x05,0x17,0x05,
0x21,0x15,0x17,0x05,
0x21,0x10,0x23,0x10,
0x17,0x30,0x23,0x10,
0x16,0x15,0x15,0x05,
0x16,0x10,0x21,0x10,
0x15,0x30,0x13,0x10,
0x14,0x15,0x13,0x05,
0x14,0x05,0x21,0x05,
0x21,0x10,0x13,0x30,
0x21,0x10,0x17,0x10,
0x14,0x05,0x14,0x10,
0x17,0x10,0x17,0x20,
0xff,
0x16,0x05,0x17,0x05,
0x21,0x15,0x17,0x05,
0x21,0x10,0x23,0x10,
0x17,0x30,0x13,0x05,
0x13,0x05,0x16,0x15,
0x15,0x05,0x16,0x10,
0x21,0x10,0x15,0x30,
0x13,0x10,0x14,0x10,
0x21,0x05,0x17,0x05,
0x17,0x10,0x21,0x10,
0x22,0x10,0x23,0x05,
0x21,0x20,0xff,
0x21,0x05,0x17,0x05,
0x16,0x10,0x17,0x10,
0x15,0x10,0x16,0x20,
0xff,
0x21,0x05,0x22,0x05,
0x23,0x15,0x22,0x10,
0x23,0x10,0x24,0x10,
0x25,0x05,0x22,0x30,
0x15,0x10,0x22,0x03,
0x21,0x03,0x17,0x03,
0x21,0x03,0x21,0x10,
0x21,0x05,0x22,0x10,
0x23,0x05,0x23,0x40,
0x16,0x05,0x17,0x05,
0x21,0x10,0x17,0x05,
0x21,0x05,0x22,0x10,
0x21,0x15,0x15,0x05,
0x15,0x20,0x24,0x10,
0x23,0x10,0x22,0x10,
0x21,0x10,0x23,0x30,
0x16,0x05,0x17,0x05,
0x21,0x15,0x17,0x05,
0x21,0x10,0x23,0x10,
0x17,0x30,0x13,0x10,
0x16,0x15,0x15,0x05,
0x16,0x10,0x21,0x10,
0x15,0x30,0x13,0x10,
0x14,0x10,0x21,0x05,
0x17,0x05,0x17,0x10,
0x21,0x10,0x22,0x10,
0x23,0x05,0x21,0x05,
0x21,0x20,0x21,0x05,
0x17,0x05,0x16,0x10,
0x17,0x10,0x15,0x10,
0x16,0x40,0x00,0x00
};
uchar code quzi[] ={ //此数组数据为各个音符在定时器中的重装值,第一列是高位,第二列是低位 //时间
0xf8,0x8c, //低八度,低1
0xf9,0x5b,
0xfa,0x15, //低3
0xfa,0x67,
0xfb,0x04, //低5
0xfb,0x90,
0xfc,0x0c, //低7
0xfc,0x44, //中央C调
0xfc,0xac, //中2
0xfd,0x09,
0xfd,0x34, //中4
0xfd,0x82,
0xfd,0xc8, //中6
0xfe,0x06,
0xfe,0x22, //高八度,高1
0xfe,0x56,
0xfe,0x6e, //高3
0xfe,0x9a,
0xfe,0xc1, //高5
0xfe,0xe4,
0xff,0x03 //高7
};
void delay(unsigned int xms)
{
uint i,j;
for(i=xms;i>0;i--)
for(j=124;j>0;j--);
}
/**********************
功能描述:在quzi数组中,找到music数组定义的简谱音符的重装值,并返回其在quzi数组中的位置
入口参数:tem:music数组中定义的简谱音符
出口参数:返回的是tem音符在quzi数组中的位置值
***********************/
uchar quyin(uchar tem)
{
uchar qudiao,jp,weizhi; //定义曲调,音符和位置
qudiao=tem/16; //高4位是曲调值
jp=tem%16; //低4位是音符
if(qudiao==1) //当曲调值为1时,即是低八度,低八度在quzi数组中基址为0
qudiao=0;
else if(qudiao==2) //当曲调值为2时,即是中八度,中八度在quzi数组中基址为14
qudiao=14;
else if(qudiao==3) //当曲调值为3时,即是高八度,高八度在quzi数组中,基址为28
qudiao=28;
weizhi=qudiao+(jp-1)*2; //通过基址加上音符作为偏移量,即可定位此音符在quzi数组中的位置
return weizhi; //返回这一个位置值
}
void playmusic()//播放音乐
{
uchar p,m,tem; //m为节拍
while(1)
{
if(flag==1) //若播放的标志位为1则播放音乐
{
if(gequ==0) //歌曲选择位为0,则播放同一首歌
{
p=music1[jindu];
if(p==0x00) //如果碰到结束符,延时1秒,回到开始再来一遍
{
jindu=0;
delay(1000);
break;
}
else if(p==0xff) //若碰到休止符,延时100ms,继续取下一音符
{
jindu=jindu+1;
delay(100);
TR0=0;
break;
}
else //正常情况下取音符和节拍
{
tem=quyin(music1[jindu]); //取出当前音符在quzi数组中的位置值
timeh=quzi[tem]; //把音符相应的计时器重装载值赋予timeh和timel
timel=quzi[tem+1];
jindu++;
TH0=timeh; //把timeh和timel赋予计时器
TL0=timel;
m=music1[jindu]; //取得节拍
jindu++;
}
TR0=1; //开定时器1
delay(m*180); //等待节拍完成, 通过P3^4口输出音频
TR0=0; //关定时器1
beep=0; //使beep端口置0,起保护蜂鸣器作用
}
else if(gequ==1) //歌曲选择位为1,则播放小毛驴
{
p=music2[jindu];
if(p==0x00) //如果碰到结束符,延时1秒,回到开始再来一遍
{
jindu=0;
delay(1000);
break;
}
else if(p==0xff) //若碰到休止符,延时100ms,继续取下一音符
{
jindu=jindu+1;
delay(100);
TR0=0;
break;
}
else //正常情况下取音符和节拍
{
tem=quyin(music2[jindu]); //取出当前音符在quzi数组中的位置值
timeh=quzi[tem]; //把音符相应的计时器重装载值赋予timeh和timel
timel=quzi[tem+1];
jindu++;
TH0=timeh; //把timeh和timel赋予计时器
TL0=timel;
m=music2[jindu]; //取得节拍
jindu++;
}
TR0=1; //开定时器1
delay(m*300); //等待节拍完成, 通过P3^4口输出音频
TR0=0; //关定时器1
beep=0; //使beep端口置0,起保护蜂鸣器作用
}
else if(gequ==2) //歌曲选择位为2,则播放天空之城
{
p=music3[jindu];
if(p==0x00) //如果碰到结束符,延时1秒,回到开始再来一遍
{
jindu=0;
delay(1000);
break;
}
else if(p==0xff) //若碰到休止符,延时100ms,继续取下一音符
{
jindu=jindu+1;
delay(100);
TR0=0;
break;
}
else //正常情况下取音符和节拍
{
tem=quyin(music3[jindu]); //取出当前音符在quzi数组中的位置值
timeh=quzi[tem]; //把音符相应的计时器重装载值赋予timeh和timel
timel=quzi[tem+1];
jindu++;
TH0=timeh; //把timeh和timel赋予计时器
TL0=timel;
m=music3[jindu]; //取得节拍
jindu++;
}
TR0=1; //开定时器1
delay(m*200); //等待节拍完成, 通过P3^4口输出音频
TR0=0; //关定时器1
beep=0; //使beep端口置0,起保护蜂鸣器作用
}
}
else //播放标志位不为1时,暂停播放音乐
while(flag!=1);
}
}
void init_sys()//初始化函数
{
P0M0=0xff; //设置推挽模式
P0M1=0x00;
P2M0=0x08;
P2M1=0x00;
P3M0=0x10;
P3M1=0x00;
}
void init()//定时器和外部中断的初始化
{
TMOD=0x01; //设置定时器0,定时方式1,16位手动重装模式
TH0=0xD8; //设置定时初值
TL0=0xEF;
IE=0x87; // 1000 0111 EA=1,EX0=1,ET0=1,EX1=0;
IP=0x02; // 0000 0010 PT0=1; 定时器0优先级高
TR0=0; //定时器0开始运行
beep=0; //使beep端口置0,起保护蜂鸣器作用
}
void main()
{
init_sys(); //系统初始化
init();
P0=0x00;
key1=1; //两个按键设为输入状态,检测中断
key2=1;
flag=0; //播放标志位清零
while(1)
playmusic(); //播放音乐函数
}
//定时器0中断处理,重新装值,并把beep值取反,产生方波
void tim1()interrupt 1 //计时器控制频率
{
TH0=timeh; //赋初值
TL0=timel;
beep=~beep; //中断使得beep翻转产生方波
}
//功能描述:按下按键1的外部中断,对标志位取反,功能是暂停和播放音乐
void ex1()interrupt 0
{
delay(5);
if(key1==0) //判断key1是否按下
{
delay(5);
if(key1==0)
{
while(!key1);
flag=~flag; //播放中断位取反,播放或者暂停
}
}
}
//按下按键2的外部中断,作用为切换歌曲
void ex2()interrupt 2
{
delay(5);
if(key2==0) //按键key2按下,切换歌曲
{
jindu=0; //歌曲的进度清零,从头开始演唱
gequ++; //下一曲
if(gequ==3) //到达最后一首调到第一首
gequ=0;
}
}