程序思路:该程序是利用51单片机的定时器中断,另外根据音符的频率计算每个音符定时时长从而输出不同音符对应的PWM波驱动蜂鸣器播放音乐。
使用工具:Keil4,protues8;程序具体代码和仿真原理图都已打包放到《基于51单片机的MIDI音乐(可切换)》资源,
定时器
采用13位非自动重装初值模式TMOD=0x00;初值duty由音乐播放函数调用是计算:
duty=460830/music
;其中460830的由来是:460830=10^6/2/1.085
完整计算计数初值的思路:
duty=(1/fren)*10^6/2/机器周期(本文用的11.0592MHz机器周期为:1.085us)
1
2
3
4
代码如下:
/******************定时器******************/
void Init_Timer0() //定时器初始化函数
{
TMOD=0x00; //13位非自动
TH0=(8192-duty)/32;
TL0=(8192-duty)%32;
TF0=0;
ET0=1;
IT0 = 1; //设置中断
EX0 = 1;
EA=1;
}
void Serve_Timer0() interrupt 1
{
TH0=(8192-duty)/32;
TL0=(8192-duty)%32; //重装初值
sound=!sound; //计数值到时输出电平发生反转
}
/******************************************/
音乐播放
在音乐播放中,不断读入乐谱数组,首先根据乐谱的音阶确定定时器输出的PWM波,其次音乐的每个音符之间有拍子,
即每个音符要输出一定的时间,一般乐谱会标出J=...,单位是每分钟多少拍,因此本文用:t=60*1000/J/2 实现,其中t为
每半拍的时间(可根据自己的乐谱改)。另外,本文 实现三首曲子的切换功能,借助state_INT变量记录开关状态实现。
代码如下:(仅写实现一首,其他代码相同)
/*************音乐播放函数*****************/
void MusicPlayer()
{
unsigned char i=0,j=1,p,t;
while(music!=0xff&state_INT==0)
{
t=60*1000/150/2;
duty=460830/music;
TR0=1; //启动定时器T0
for(p=0;p
Delay_ms(t); //延时半个节拍单位
TR0=0; //关闭定时器T0
i+=2; //播放下一个音符
j+=2;
}
}
/******************************************/
音乐库数组,本文格式是:{音阶,拍子,音阶,拍子,...},即在给出音阶的同时就给出对应的拍子, 音阶和拍子的转换思路
如下(本人不是很懂音乐,纯属个人见解):
音阶分为高音、中音、低音,在乐谱中根据音阶的标记可知道,数字头上有标记为高音,数字上下没有标记的为中音,数字下部有标记的为低音
拍子的大致计算总结:单个数字数字下加下划线表示该音阶拍子是1/2节拍;数字加双下划线就是1/4节拍;数字后加点,该点是1/2节拍,加上前
面数字也就是3/2节拍;同理,数字加下划线并且后面加点就是一个节拍,数字后面加横线就是二倍节拍

/**********************我和我的祖国*J=l50**************************************/
unsigned int code music[]=
{
z5,2, z6,2, z5,2, z4,2, z3,2, z2,2,/**/
z1,6, l5,6, /**/
z1,2, z3,2, h1,2, z7,2, z6,4, z3,1,/**/
z5,6, z5,6, /**/
z6,2, z7,2, z6,2, z5,2, z4,2, z3,2, /**/
z2,6, l6,6,/**/
l7,2, l6,2, l5,2, z5,2, z1,4, z2,1,/**/
z3,6, z3,6, /**/
z5,2, z6,2, z5,2, z4,2, z3,2, z2,2, /**/
z1,6, l5,6,/**/
z1,2, z3,2, h1,2, z7,2, h2,4, h1,1, /**/
z6,6, z6,6,/**/
h1,2, z7,2, z6,2, z5,6,/**/
z6,2, z5,2, z4,2, z3,6,/**/
l7,4, l6,2, l5,4, z2,2,/**/
z1,6, z1,6,/**/
h1,2, h2,2, h3,2, h2,2, h1,2, z6,2, /**/
z7,2, z6,4, z3,1, z5,6, z5,6, /**/
h1,2, h2,2, h3,2, h2,2, h1,2, z6,2,/**/
z7,2, z5,4, z3,1, z6,6, z6,6, /**/
z5,2, z4,2, z3,2, z2,6, /**/
l7,2, l6,1, l6,1, l5,2, z3,6, /**/
z4,6, z2,4, z1,2, /**/
z1,6, z1,6, /**/
h1,2, h2,2, h3,2, h2,2, h1,2, z6,2, /**/
z7,2, z6,4, z3,1, z5,6, /**/
h1,2, h2,2, h3,2, h2,2, h1,2, z6,2, /**/
z7,2, z5,4, z3,1, z6,6, /**/
z5,2, z4,2, z3,2, z2,6, /**/
l7,2, l6,2, l5,2, z3,6,/**/
z5,6, h2,4, h1,2, /**/
h1,6,z1,12, /**/
0xff/*停止标志位*/
};
/******************************************************************************/
另外音阶的定义如下:
/*****************音阶对应频率**********************/
#define l1 262 //将“l_dao”宏定义为低音“1”的频率262Hz
#define l2 286 //将“l_re”宏定义为低音“2”的频率286Hz
#define l3 311 //将“l_mi”宏定义为低音“3”的频率311Hz
#define l4 349 //将“l_fa”宏定义为低音“4”的频率349Hz
#define l5 392 //将“l_sao”宏定义为低音“5”的频率392Hz
#define l6 440 //将“l_a”宏定义为低音“6”的频率440Hz
#define l7 494 //将“l_xi”宏定义为低音“7”的频率494Hz
//以下是C调中音的音频宏定义
#define z1 523 //将“dao”宏定义为中音“1”的频率523Hz
#define z2 587 //将“re”宏定义为中音“2”的频率587Hz
#define z3 659 //将“mi”宏定义为中音“3”的频率659Hz
#define z4 698 //将“fa”宏定义为中音“4”的频率698Hz
#define z5 784 //将“sao”宏定义为中音“5”的频率784Hz
#define z6 880 //将“la”宏定义为中音“6”的频率880Hz
#define z7 987 //将“xi”宏定义为中音“7”的频率523H
//以下是C调高音的音频宏定义
#define h1 1046 //将“h_dao”宏定义为高音“1”的频率10
#define h2 1174 //将“h_re”宏定义为高音“2”的频率11
#define h3 1318 //将“h_mi”宏定义为高音“3”的频率13
#define h4 1396 //将“h_fa”宏定义为高音“4”的频率1396
#define h5 1567 //将“h_sao”宏定义为高音“5”的频率1567
#define h6 1760 //将“h_la”宏定义为高音“6”的频率1760Hz
#define h7 1975 //将“h_xi”宏定义为高音“7”的频率19
/**********************************************************/
本文较完整的实现了51单片机定时器中断写MIDI音乐切实现了音乐的切换。改进的地方:超低音、超高音的实现,乐谱休止符等详细乐理内容的加入,使音乐
更加完美的呈现;对于代码,在音乐播放函数有较高的代码重用度可想办法优化,定义音阶对应频率可优化为数组。
程序思路:该程序是利用51单片机的定时器中断,另外根据音符的频率计算每个音符定时时长从而输出不同音符对应的PWM波驱动蜂鸣器播放音乐。
使用工具:Keil4,protues8;程序具体代码和仿真原理图都已打包放到《基于51单片机的MIDI音乐(可切换)》资源,
定时器
采用13位非自动重装初值模式TMOD=0x00;初值duty由音乐播放函数调用是计算:
duty=460830/music
;其中460830的由来是:460830=10^6/2/1.085
完整计算计数初值的思路:
duty=(1/fren)*10^6/2/机器周期(本文用的11.0592MHz机器周期为:1.085us)
1
2
3
4
代码如下:
/******************定时器******************/
void Init_Timer0() //定时器初始化函数
{
TMOD=0x00; //13位非自动
TH0=(8192-duty)/32;
TL0=(8192-duty)%32;
TF0=0;
ET0=1;
IT0 = 1; //设置中断
EX0 = 1;
EA=1;
}
void Serve_Timer0() interrupt 1
{
TH0=(8192-duty)/32;
TL0=(8192-duty)%32; //重装初值
sound=!sound; //计数值到时输出电平发生反转
}
/******************************************/
音乐播放
在音乐播放中,不断读入乐谱数组,首先根据乐谱的音阶确定定时器输出的PWM波,其次音乐的每个音符之间有拍子,
即每个音符要输出一定的时间,一般乐谱会标出J=...,单位是每分钟多少拍,因此本文用:t=60*1000/J/2 实现,其中t为
每半拍的时间(可根据自己的乐谱改)。另外,本文 实现三首曲子的切换功能,借助state_INT变量记录开关状态实现。
代码如下:(仅写实现一首,其他代码相同)
/*************音乐播放函数*****************/
void MusicPlayer()
{
unsigned char i=0,j=1,p,t;
while(music!=0xff&state_INT==0)
{
t=60*1000/150/2;
duty=460830/music;
TR0=1; //启动定时器T0
for(p=0;p
Delay_ms(t); //延时半个节拍单位
TR0=0; //关闭定时器T0
i+=2; //播放下一个音符
j+=2;
}
}
/******************************************/
音乐库数组,本文格式是:{音阶,拍子,音阶,拍子,...},即在给出音阶的同时就给出对应的拍子, 音阶和拍子的转换思路
如下(本人不是很懂音乐,纯属个人见解):
音阶分为高音、中音、低音,在乐谱中根据音阶的标记可知道,数字头上有标记为高音,数字上下没有标记的为中音,数字下部有标记的为低音
拍子的大致计算总结:单个数字数字下加下划线表示该音阶拍子是1/2节拍;数字加双下划线就是1/4节拍;数字后加点,该点是1/2节拍,加上前
面数字也就是3/2节拍;同理,数字加下划线并且后面加点就是一个节拍,数字后面加横线就是二倍节拍

/**********************我和我的祖国*J=l50**************************************/
unsigned int code music[]=
{
z5,2, z6,2, z5,2, z4,2, z3,2, z2,2,/**/
z1,6, l5,6, /**/
z1,2, z3,2, h1,2, z7,2, z6,4, z3,1,/**/
z5,6, z5,6, /**/
z6,2, z7,2, z6,2, z5,2, z4,2, z3,2, /**/
z2,6, l6,6,/**/
l7,2, l6,2, l5,2, z5,2, z1,4, z2,1,/**/
z3,6, z3,6, /**/
z5,2, z6,2, z5,2, z4,2, z3,2, z2,2, /**/
z1,6, l5,6,/**/
z1,2, z3,2, h1,2, z7,2, h2,4, h1,1, /**/
z6,6, z6,6,/**/
h1,2, z7,2, z6,2, z5,6,/**/
z6,2, z5,2, z4,2, z3,6,/**/
l7,4, l6,2, l5,4, z2,2,/**/
z1,6, z1,6,/**/
h1,2, h2,2, h3,2, h2,2, h1,2, z6,2, /**/
z7,2, z6,4, z3,1, z5,6, z5,6, /**/
h1,2, h2,2, h3,2, h2,2, h1,2, z6,2,/**/
z7,2, z5,4, z3,1, z6,6, z6,6, /**/
z5,2, z4,2, z3,2, z2,6, /**/
l7,2, l6,1, l6,1, l5,2, z3,6, /**/
z4,6, z2,4, z1,2, /**/
z1,6, z1,6, /**/
h1,2, h2,2, h3,2, h2,2, h1,2, z6,2, /**/
z7,2, z6,4, z3,1, z5,6, /**/
h1,2, h2,2, h3,2, h2,2, h1,2, z6,2, /**/
z7,2, z5,4, z3,1, z6,6, /**/
z5,2, z4,2, z3,2, z2,6, /**/
l7,2, l6,2, l5,2, z3,6,/**/
z5,6, h2,4, h1,2, /**/
h1,6,z1,12, /**/
0xff/*停止标志位*/
};
/******************************************************************************/
另外音阶的定义如下:
/*****************音阶对应频率**********************/
#define l1 262 //将“l_dao”宏定义为低音“1”的频率262Hz
#define l2 286 //将“l_re”宏定义为低音“2”的频率286Hz
#define l3 311 //将“l_mi”宏定义为低音“3”的频率311Hz
#define l4 349 //将“l_fa”宏定义为低音“4”的频率349Hz
#define l5 392 //将“l_sao”宏定义为低音“5”的频率392Hz
#define l6 440 //将“l_a”宏定义为低音“6”的频率440Hz
#define l7 494 //将“l_xi”宏定义为低音“7”的频率494Hz
//以下是C调中音的音频宏定义
#define z1 523 //将“dao”宏定义为中音“1”的频率523Hz
#define z2 587 //将“re”宏定义为中音“2”的频率587Hz
#define z3 659 //将“mi”宏定义为中音“3”的频率659Hz
#define z4 698 //将“fa”宏定义为中音“4”的频率698Hz
#define z5 784 //将“sao”宏定义为中音“5”的频率784Hz
#define z6 880 //将“la”宏定义为中音“6”的频率880Hz
#define z7 987 //将“xi”宏定义为中音“7”的频率523H
//以下是C调高音的音频宏定义
#define h1 1046 //将“h_dao”宏定义为高音“1”的频率10
#define h2 1174 //将“h_re”宏定义为高音“2”的频率11
#define h3 1318 //将“h_mi”宏定义为高音“3”的频率13
#define h4 1396 //将“h_fa”宏定义为高音“4”的频率1396
#define h5 1567 //将“h_sao”宏定义为高音“5”的频率1567
#define h6 1760 //将“h_la”宏定义为高音“6”的频率1760Hz
#define h7 1975 //将“h_xi”宏定义为高音“7”的频率19
/**********************************************************/
本文较完整的实现了51单片机定时器中断写MIDI音乐切实现了音乐的切换。改进的地方:超低音、超高音的实现,乐谱休止符等详细乐理内容的加入,使音乐
更加完美的呈现;对于代码,在音乐播放函数有较高的代码重用度可想办法优化,定义音阶对应频率可优化为数组。
举报