目录
目录
一、音乐简谱<——>单片机
简谱 | 单片机 |
音调 | 频率 |
节拍 | 延时 |
1.音调(歌)-->频率(单片机)
简谱中用1、2、3、4、5、6、7这几个数字作为符号,表示音的高低。上面加一点是高八度,下面加一点是为低八度。
单片机
简谱中的不同音调对应51单片机中提供给蜂鸣器的不同频率的脉冲。
51单片机内部含无源蜂鸣器,通过给蜂鸣器提供不同频率的脉冲,蜂鸣器可以发出不同音调的声音。频率越高,音调越高。
无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音。
以下是C调音符与脉冲频率对应表,以a为基准频率是440Hz,左右两边都是倍数关系,中间是按照等比数列进行平分:
频率的产生可用定时器中断实现,定时器初值(十进制)计算代码逻辑如下:
这里选择定时器工作方式1,为16位定时器(2^16=65535)
//频率表
unsigned int code FreTab[21] = {
262,294,330,349,392,440,494,
523,587,659,698,784,880,988,
1046,1175,1318,1397,1568,1760,1976};
//定时器初值
timer0h=(65535-(int)(1000000/(2*FreTab[k])))/256;
timer0l=(65535-(int)(1000000/(2*FreTab[k])))%256;
//定时中断
void t0int() interrupt 1
{
speaker=!speaker;
TH0=timer0h;
TL0=timer0l;
}
2.节拍(歌)--->延时(单片机)
下面这个是谱子的速度,表示一分钟104拍,一拍为60/104=0.5769230766923······s
分数---分母:看谱子的左上角的分数,以四分音符为1拍
数字右边加一个点,相当于加上半拍。
一般四分音符1拍为500ms,二分音符为1拍=1000ms······
这里1拍取整577us,以四分之一拍为单位,1个四分之一拍取整为144us。
分数---分子:每小节4拍
单片机
循环--->延时
// 节奏的快慢,这里是四分之一个节拍的时间
void delay(unsigned char t)
{
unsigned long t2;
t2=t*144;
while(t2--)
{
unsigned char data i, j;
i = 2;
j = 239;
do
{
while (--j);
}
while (--i);
}
TR0=0;// 停止发音 如果屏蔽掉,会出现连音的不正常现象
}
3.简谱编码
以三位数代表1个音符。
第一位:音高(低音0,中音1,高音2),其中0可省略;
第二位:音长(四分之一个节拍为1,其他依次递增,如1个节拍为1*4=4);
第三位:音调(1,2,3,4,5,6,7);
code unsigned char gyz[]={
140,26,25,26,25,120,25,143,123,143,122,121,11,171,
// 送你一朵小红花
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,26,26,26,65,140,140,
//送你一朵小红花,开在你昨天新长的枝丫。
140,26,121,46,140, 135,113,123,125,145,140, 133,116,126,125,145,121,123, 123,123,142,140,140,
//奖励你有勇气,主动来和我说话。
140,126,126,136,116,126,125, 145,121,122,142,140, 140,123,123,122,143,122, 143,26,121,141,140, 140,121,121,35,16,26,25, 125,123,123,122,142,140, 26,142,122,132,111,121,26, 26,121,181,140,
//不共戴天的冰水啊,义无反顾的烈酒啊,多么苦难的日子里 ,你都已战胜了它。
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,26,26,26,65,140,140,
//送你一朵小红花,遮住你今天新添的伤疤。
140,26,121,46,120,26, 135,113,123,125,145,140, 126,146,125,145,121,123, 123,162,140,140,
//奖励你在下雨天,还愿意送我回家。
140,126,126,136,116,126,125, 145,121,122,142,140, 140,123,122,123,143,122, 143,26,121,141,140, 140,25,26,85, 135,115,125,123,163,123, 132,112,122,121,142,26,122, 122,161,140,140,
//科罗拉多的风雪啊,喜马拉雅的骤雨啊,只要你相信我,闭上眼就能到达。
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,23,26,26,65,140,140,
//送你一朵小红花,开在那牛羊遍野的天涯,
140,26,121,46,140, 133,115,125,123,143,140, 126,145,125,145,123,121, 121,122,142,140,140,
//奖励你走到哪儿,都不会忘记我啊。
140,126,126,136,116,126,125, 135,115,125,122,142,140, 140,123,123,133,113,123,122, 133,16,26,121,141,140, 140,121,121,131,111,121,25, 125,123,122,123,143,140, 122,142,122,142,26,122, 122,121,181,140,
//洁白如雪的沙滩啊,风平浪静的湖水啊,那些真实的幻影啊,是我给你的牵挂。
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,23,26,26,65,140,140,
//送你一朵小红花,开在你心底最深的泥沙。
140,26,121,46,140, 135,113,123,125,145,140, 133,116,126,125,145,121,123, 123,162,140,140,
//奖励你能感受,每个命运的挣扎。
140,126,126,136,116,126,125, 135,115,125,122,142,140, 140,123,122,123,143,122, 133,112,122,121,141,140, 140,121,121,141,25,25, 145,123,123,163,123, 132,112,122,121,142,26,122, 122,161,140,140,
//是谁挥霍的时光啊,是谁苦苦的奢望啊,这不是一个问题,也不需要你的回答。
140,26,25,26,45,120, 25,143,123,143,122,121, 181,181,
//送你一朵小红花
140,140,140,140, 140,26,25,26,45,120, 25,143,123,143,122,121, 181,181,
//送你一朵小红花
140,140,140,140, 140,26,25,26,45,120, 25,143,123,143,122,121, 181,181,
//送你一朵小红花
140,140,140,140, 140,26,25,26,45,120, 25,143,123,143,122,121, 181,181,
//送你一朵小红花
0 //0标志 表示结束
};
4.唱歌
蜂鸣器唱歌的整个逻辑就是,循环遍历简谱,得到每个音符对应的频率,由频率计算出定时器初值,让定时器发出对应频率的脉冲给蜂鸣器,然后蜂鸣器发出不同的音调,再看该音符要唱几排,延时相应的时间,连起来就是一首歌了。
以下是song函数:
void song()
{
unsigned char k=0;
unsigned long i=0; // 由于歌词长度问题,需要设置大一点的范围,否则还没唱完一遍又重新开始了
// 这里这样做就只唱一遍,寻到最后的0值时就停止了
while(time)
{
if(gyz[i]%10==0) // 音调为0值时停顿处理
{
TR0=0; // 停止发音
}
else
{
k=gyz[i]%10+7*(gyz[i]/100)-1;
timer0h=(65535-(int)(1000000/(2*FreTab[k])))/256;
timer0l=(65535-(int)(1000000/(2*FreTab[k])))%256;
TR0=1; // 启动T0输出方波去发音
}
time=gyz[i]/10%10;
i=i+1;
delay(time);
}
}
5.补充——多歌切换、暂停
可以用按键控制放哪首歌,在歌前加一个按键值的条件语句即可;想在歌播放途中暂停,可用外部中断0,(缺)但是以下程序只能使歌暂停,无法继续播放,要想再播歌需按选歌的按键,但也只是从头播放。
void key3_interrupt() interrupt 0
{
if(key3==0)
{
time=0;
}
}
二、完整代码
1.单独播放一首歌的代码
#include <REG52.H>
#include <INTRINS.H>
sbit speaker=P1^5;
unsigned char timer0h,timer0l,time=1;
//送你一朵小红花
code unsigned char xhh[]={
140,26,25,26,25,120,25,143,123,143,122,121,11,171,
//送你一朵小红花
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,26,26,26,65,140,140,
//送你一朵小红花,开在你昨天新长的枝丫。
140,26,121,46,140, 135,113,123,125,145,140, 133,116,126,125,145,121,123, 123,123,142,140,140,
//奖励你有勇气,主动来和我说话。
140,126,126,136,116,126,125, 145,121,122,142,140, 140,123,123,122,143,122, 143,26,121,141,140, 140,121,121,35,16,26,25, 125,123,123,122,142,140, 26,142,122,132,111,121,26, 26,121,181,140,
//不共戴天的冰水啊,义无反顾的烈酒啊,多么苦难的日子里 ,你都已战胜了它。
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,26,26,26,65,140,140,
//送你一朵小红花,遮住你今天新添的伤疤。
140,26,121,46,120,26, 135,113,123,125,145,140, 126,146,125,145,121,123, 123,162,140,140,
//奖励你在下雨天,还愿意送我回家。
140,126,126,136,116,126,125, 145,121,122,142,140, 140,123,122,123,143,122, 143,26,121,141,140, 140,25,26,85, 135,115,125,123,163,123, 132,112,122,121,142,26,122, 122,161,140,140,
//科罗拉多的风雪啊,喜马拉雅的骤雨啊,只要你相信我,闭上眼就能到达。
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,23,26,26,65,140,140,
//送你一朵小红花,开在那牛羊遍野的天涯,
140,26,121,46,140, 133,115,125,123,143,140, 126,145,125,145,123,121, 121,122,142,140,140,
//奖励你走到哪儿,都不会忘记我啊。
140,126,126,136,116,126,125, 135,115,125,122,142,140, 140,123,123,133,113,123,122, 133,16,26,121,141,140, 140,121,121,131,111,121,25, 125,123,122,123,143,140, 122,142,122,142,26,122, 122,121,181,140,
//洁白如雪的沙滩啊,风平浪静的湖水啊,那些真实的幻影啊,是我给你的牵挂。
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,23,26,26,65,140,140,
//送你一朵小红花,开在你心底最深的泥沙。
140,26,121,46,140, 135,113,123,125,145,140, 133,116,126,125,145,121,123, 123,162,140,140,
//奖励你能感受,每个命运的挣扎。
140,126,126,136,116,126,125, 135,115,125,122,142,140, 140,123,122,123,143,122, 133,112,122,121,141,140, 140,121,121,141,25,25, 145,123,123,163,123, 132,112,122,121,142,26,122, 122,161,140,140,
//是谁挥霍的时光啊,是谁苦苦的奢望啊,这不是一个问题,也不需要你的回答。
140,26,25,26,45,120, 25,143,123,143,122,121, 181,181,
//送你一朵小红花
140,140,140,140, 140,26,25,26,45,120, 25,143,123,143,122,121, 181,181,
//送你一朵小红花
140,140,140,140, 140,26,25,26,45,120, 25,143,123,143,122,121, 181,181,
//送你一朵小红花
140,140,140,140, 140,26,25,26,45,120, 25,143,123,143,122,121, 181,181,
//送你一朵小红花
0
};
//频率表
unsigned int code FreTab[21] = {
262,294,330,349,392,440,494,
523,587,659,698,784,880,988,
1046,1175,1318,1397,1568,1760,1976};
void t0int() interrupt 1
{
speaker=!speaker;
TH0=timer0h;
TL0=timer0l;
}
// 节奏的快慢,这里是四分之一个节拍的时间
void delay(unsigned char t)
{
unsigned long t2;
t2=t*144;
while(t2--)
{
unsigned char data i, j;
i = 2;
j = 239;
do
{
while (--j);
}
while (--i);
}
TR0=0;// 停止发音 如果屏蔽掉,会出现连音的不正常现象
}
/**/
void song()
{
unsigned char k=0;
unsigned long i=0; // 由于歌词长度问题,需要设置大一点的范围,否则还没唱完一遍又重新开始了
// 这里这样做就只唱一遍,寻到最后的0值时就停止了
while(time)
{
if(xhh[i]%10==0) // 音调为0值时停顿处理
{
TR0=0; // 停止发音
}
else
{
k=xhh[i]%10+7*(xhh[i]/100)-1;
timer0h=(65535-(int)(1000000/(2*FreTab[k])))/256;
timer0l=(65535-(int)(1000000/(2*FreTab[k])))%256;
TR0=1; // 启动T0输出方波去发音
}
time=xhh[i]/10%10;
i=i+1;
delay(time);
}
}
void main(void)
{
TMOD=1; // 设置定时器0为16位计数模式
EA=1;ET0=1; // 开定时器0中断
while(1)
{
song();
}
}
2.含补充功能的代码
#include <REG52.H>
sbit speaker=P1^5;
sbit key1= P3^1;
sbit key2= P3^0;
sbit key3= P3^2;
unsigned char timer0h,timer0l,time=1;
// 送你一朵小红花
code unsigned char xhh[]={
140,26,25,26,25,120,25,143,123,143,122,121,11,171,
//(原版)送你一朵小红花
//(毕业歌)送你一朵小红花
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,26,26,26,65,140,140,
//(原版)送你一朵小红花,开在你昨天新长的枝丫。
//(毕业歌)送你一朵小红花,开在你昨天新长的枝丫。
140,26,121,46,140, 135,113,123,125,145,140, 133,116,126,125,145,121,123, 123,123,142,140,140,
//(原版)奖励你有勇气,主动来和我说话。
//(毕业歌)奖励你有勇气,主动来和我说话。
140,126,126,136,116,126,125, 145,121,122,142,140, 140,123,123,122,143,122, 143,26,121,141,140, 140,121,121,35,16,26,25, 125,123,123,122,142,140, 26,142,122,132,111,121,26, 26,121,181,140,
//(原版)不共戴天的冰水啊,义无反顾的烈酒啊,多么苦难的日子里 ,你都已战胜了它。
//(毕业歌)爸爸妈妈的叮咛啊,老师同学的祝福啊,多么温暖的日子里,挥挥手又要出发。
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,26,26,26,65,140,140,
//(原版)送你一朵小红花,遮住你今天新添的伤疤。
//(毕业歌)送你一朵小红花,遮住你今天新添的伤疤。
140,26,121,46,120,26, 135,113,123,125,145,140, 126,146,125,145,121,123, 123,162,140,140,
//(原版)奖励你在下雨天,还愿意送我回家。
//(毕业歌)奖励你在下雨天,还愿意送我回家。
140,126,126,136,116,126,125, 145,121,122,142,140, 140,123,122,123,143,122, 143,26,121,141,140, 140,25,26,85, 135,115,125,123,163,123, 132,112,122,121,142,26,122, 122,161,140,140,
//(原版)科罗拉多的风雪啊,喜马拉雅的骤雨啊,只要你相信我,闭上眼就能到达。
//(毕业歌)风华赛道的黎明啊,数图窗外的星光啊,只要你相信我,坚持走就能到达。
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,23,26,26,65,140,140,
//(原版)送你一朵小红花,开在那牛羊遍野的天涯,
//(毕业歌)送你一朵小红花,开在那牛羊遍野的天涯,
140,26,121,46,140, 133,115,125,123,143,140, 126,145,125,145,123,121, 121,122,142,140,140,
//(原版)奖励你走到哪儿,都不会忘记我啊。
//(毕业歌)奖励你走到哪儿,都不会忘记我啊。
140,126,126,136,116,126,125, 135,115,125,122,142,140, 140,123,123,133,113,123,122, 133,16,26,121,141,140, 140,121,121,131,111,121,25, 125,123,122,123,143,140, 122,142,122,142,26,122, 122,121,181,140,
//(原版)洁白如雪的沙滩啊,风平浪静的湖水啊,那些真实的幻影啊,是我给你的牵挂。
//(毕业歌)催人攀登的天梯啊,青春奋进的身影啊,生生不息的校园啊,是我给你的牵挂。
140,140,140,140,140,26,25,26,25,120,25,143,123,120,123,123,121,122,141,121,141,23,26,26,65,140,140,
//(原版)送你一朵小红花,开在你心底最深的泥沙。
//(毕业歌)送你一朵小红花,开在你心底最深的泥沙。
140,26,121,46,140, 135,113,123,125,145,140, 133,116,126,125,145,121,123, 123,162,140,140,
//(原版)奖励你能感受,每个命运的挣扎。
//(毕业歌)奖励你能感受,每段闪光的年华。
140,126,126,136,116,126,125, 135,115,125,122,142,140, 140,123,122,123,143,122, 133,112,122,121,141,140, 140,121,121,141,25,25, 145,123,123,163,123, 132,112,122,121,142,26,122, 122,161,140,140,
//(原版)是谁挥霍的时光啊,是谁苦苦的奢望啊,这不是一个问题,也不需要你的回答。
//(毕业歌)披荆斩棘的时光啊,乘风破浪的旅途啊,这不是一个问题,期待你明天的回答。
140,26,25,26,45,120, 25,143,123,143,122,121, 181,181,
//送你一朵小红花
140,140,140,140, 140,26,25,26,45,120, 25,143,123,143,122,121, 181,181,
//送你一朵小红花
140,140,140,140, 140,26,25,26,45,120, 25,143,123,143,122,121, 181,181,
//送你一朵小红花
140,140,140,140, 140,26,25,26,45,120, 25,143,123,143,122,121, 181,181,
//送你一朵小红花
0
};
//生日歌
code unsigned char srg[]={
140,140,125,125, 146,145,241, 187,125,125, 146,145,242, 281,125,125, 245,243,241, 147,146,124,124,
243,241,243, 282,125,125, 146,145,241, 187,125,125, 146,145,242, 281,125,125, 245,243,241, 147,146,124,124, 243,241,242, 241,241,241, 0};
//频率表
unsigned int code FreTab[21] = {
262,294,330,349,392,440,494,
523,587,659,698,784,880,988,
1046,1175,1318,1397,1568,1760,1976};
void t0int() interrupt 1
{
speaker=!speaker;
TH0=timer0h;
TL0=timer0l;
}
// 节奏的快慢,这里是四分之一个节拍的时间
void delay(unsigned char t)
{
unsigned long t2;
t2=t*144;
while(t2--)
{
unsigned char data i, j;
i = 2;
j = 239;
do
{
while (--j);
}
while (--i);
}
TR0=0;// 停止发音 如果屏蔽掉,会出现连音的不正常现象
}
/**/
void song()
{
unsigned char k=0;
unsigned long i1=0,i2=0,i3=0; // 由于歌词长度问题,需要设置大一点的范围,否则还没唱完一遍又重新开始了
if(key1==0)
{
time=1;
while(time)
{
if(xhh[i1]%10==0) // 音调为0值时停顿处理
{
TR0=0; // 停止发音
}
else
{
k=xhh[i1]%10+7*(xhh[i1]/100)-1;
timer0h=(65535-(int)(1000000/(2*FreTab[k])))/256;
timer0l=(65535-(int)(1000000/(2*FreTab[k])))%256;
TR0=1; // 启动T0输出方波去发音
}
time=xhh[i1]/10%10;// 这里这样做就只唱一遍,寻到最后的0值时就停止了
i1=i1+1;
delay(time);
}
time=1;i1=0;
}
if(key2==0)
{
time=1;
while(time)
{
if(srg[i2]%10==0) // 音调为0值时停顿处理
{
TR0=0; // 停止发音
}
else
{
k=srg[i2]%10+7*(srg[i2]/100)-1;
timer0h=(65535-(int)(1000000/(2*FreTab[k])))/256;
timer0l=(65535-(int)(1000000/(2*FreTab[k])))%256;
TR0=1; // 启动T0输出方波去发音
}
time=srg[i2]/10%10;
i2=i2+1;
delay(time);
}
time=1; i2=0;
}
}
void main(void)
{
TMOD=1; // 设置定时器0为16位计数模式
EA=1;ET0=1; // 开定时器0中断
EX0=1;IT0=1;
while(1)
{
song();
}
}
void key3_interrupt() interrupt 0
{
if(key3==0)
{
time=0;
}
}
三、效果检验+误差分析+优化思路
1、时长
谱子的速度为一分钟104拍,该谱总共有344拍,总共3.3076923076······分钟,约198.46s
蜂鸣器实际播放一首歌的时间为223s。
误差分析:多出的时间可能是因为程序执行需要时间,此外,节拍延时函数中1拍取得时间本身就是比实际大的数值,慢慢累积会造成较大误差。
2.一些待优化的地方
(1)节拍:由于这里是以四分之一拍为单位,那么四拍对应的数字就是16,大于9,无法用1个数表示。本文的处理方法是将四拍换成两个二拍,虽然两个二拍的时间长度与四拍差不多,但是两个二拍发音之间会有停顿现象(节拍延时函数的最后设置了TR0=0停止发音,防止连音不正常现象)。
或许可以用9(未用到)来代替四拍,然后再延时函数中判断一下,如果传入的为9,则令t=16。
(2)暂停:这里用外部中断0实现暂停,外部中断0的优先级比定时中断0的高,可能会影响定时,从而使频率有点偏差。此外,同上文所述,控制外部中断的按键只能使歌暂停,无法继续播放,要想再播歌仍需按选歌的按键,但也只是从头播放。
怎样让歌继续播放小白还需进一步研究。或许可以在外部中断函数中标记此刻暂停的位置,执行song函数时不再从0开始,而是从标志位开始(即改变i值)。