51单片机门铃教程-发音原理 - 单片机学习教程(初学者入门)
在上面的帖子里,我们讲了发音原理,重点讲述了如何针对不同频率的声音,设置中断。下面我们讲音乐旋律篇。
你要弹琴,吹奏,需要乐谱,需要知道他们的音符与节拍,这在程序里就需要两个数组。下面是欢乐颂的例子
unsigned char code HLS_Note_Verse[] = {
10,10,11,13,
13,11,10,9,
8,8,9,10,
10,9,9,
10,10,11,13,
13,11,10,9,
8,8,9,10,
9,8,8
};
这个数组是旋律的音符表
unsigned char code HLS_Beat_Verse[] = {
4,4,4,4,
4,4,4,4,
4,4,4,4,
4,4,8,
4,4,4,4,
4,4,4,4,
4,4,4,4,
4,4,8
};
这个数组对应每个音符的节拍。
每个音符还得有对应的频率与每个频率对应的计时器装载值(原理见上面的一个链接,否则你就要在程序里编程自己算)。这又需要两个数组。参见下面程序:
#include <reg52.h>
sbit BUZZ=P2^6;
sbit KEY = P2^7;
bit KeySta = 1;
unsigned int code NoteFrequ[] = {
523, 587, 659, 698, 784, 880, 988,
1047, 1175, 1319, 1397, 1568, 1760, 1976 //C调中高音的音符频率对应表
};
unsigned int code NoteReload[] = { //每个频率对应的计时器设置初值
65536 - (11059200/12) / (523*2), //中音 1
65536 - (11059200/12) / (587*2), //2
65536 - (11059200/12) / (659*2), //3
65536 - (11059200/12) / (698*2), //4
65536 - (11059200/12) / (784*2), //5
65536 - (11059200/12) / (880*2), //6
65536 - (11059200/12) / (988*2), //7
65536 - (11059200/12) / (1047*2), //高音1
65536 - (11059200/12) / (1175*2), //2
65536 - (11059200/12) / (1319*2), //3
65536 - (11059200/12) / (1397*2), //4
65536 - (11059200/12) / (1568*2), //5
65536 - (11059200/12) / (1760*2), //6
65536 - (11059200/12) / (1976*2), //7
};
bit enable = 1;
bit tmrflag = 0; //中断完成标志
unsigned char T0RH = 0xFF;
unsigned char T0RL = 0x00; //启动中断
void PlayTwoTiger();
void main()
{
bit backup = 1;
unsigned int i;
EA = 1;
TMOD = 0x11;
TH0 = T0RH;
TL0 = T0RL;
TH1 = 0xF8; // T1 计时2ms中断
TL1 = 0xCD;
ET1=1;
TR1=1;
while (1)
{
if (KeySta != backup)
{
if (backup == !0)
{
ET0 = 1; //打开T0 中断开始唱歌
TR0 = 1;
PlayTwoTiger();
for (i=0; i<40000; i++); //安静一段时间
}
else
{
ET0 = 0;
TR0 = 0;
}
backup = KeySta;
}
}
}
void PlayTwoTiger()
{
unsigned char beat;
unsigned char note;
unsigned int time = 0;
unsigned int beatTime = 0;
unsigned int soundTime = 0;
unsigned char code TwoTigerNote[] = {
1, 2, 3, 1, 1, 2, 3, 1, 3, 4, 5, 3, 4, 5,
5,6, 5,4, 3, 1, 5,6, 5,4, 3, 1, 1, 5, 1, 1, 5, 1,
};
unsigned char code TwoTigerBeat[] = {
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 4, 4, 8,
3,1, 3,1, 4, 4, 3,1, 3,1, 4, 4, 4, 4, 8, 4, 4, 8,
};
for (beat=0; beat<sizeof(TwoTigerNote); )
{
while (!tmrflag); //
tmrflag = 0;
if (time == 0)
{
note = TwoTigerNote[beat] - 1;
T0RH = NoteReload[note] >> 8;
T0RL = NoteReload[note];
beatTime = (TwoTigerBeat[beat] * NoteFrequ[note]) >> 2;
soundTime = beatTime - (beatTime >> 2);
enable = 1;
time++;
}
else
{
if (time >= beatTime)
{
time = 0;
beat++;
}
else
{
time++;
if (time == soundTime)
{
enable = 0;
}
}
}
}
}
void InterruptTiger() interrupt 1
{
TH0 = T0RH;
TL0 = T0RL;
tmrflag = 1;
if (enable)
BUZZ = ~BUZZ;
else
BUZZ = 1;
}
void InterruptTimer1() interrupt 3
{
static unsigned char keybuf = 0xFF;
TH1 = 0xF8; //0xF8CD,?? 2ms
TL1 = 0xCD;
keybuf = (keybuf<<1) | KEY;
if (keybuf == 0x00)
{
KeySta = 0;
}
else if (keybuf == 0xFF)
{
KeySta = 1;
}
else
{}
}
重点讲述:
第二个中断函数主要用按键检测与消抖。在主函数里进行按键是否为弹起到落下的一个完整过程,如果是,则执行蜂鸣器音乐播放。
PlayTwoTiger()这个函数用来播放两只老虎的旋律。整个函数都是用来配置播放时间。
在第一个中断函数中,用2f个中断来播放1秒的音乐。那么我用f个中断来计数只能播放500ms。常规就用0.5秒来做为一拍。计数f/4个中断为1/4拍,一个完整节拍是4*f/4,两拍是8*f/4。 右移2位是除以4,方便快速计算。
while (!tmrflag); 这句的作用是避免在计算T0RH与T0RL时发生中断,导致在中断函数里,为TH0,TL0装载了错误数值。
总的发声时间算出来后,还不够,为了避免前后声音粘连,我们用总时间的3/4发声,1/4时间静默。这就是整函数的原理。
若你不理解上述理论,由于我们启用了T1中断,你也可以把T1中断设置成1ms,然后用计时500ms来做为音符的一个完整节拍,同样可以完成音乐播放。
|