目录
1.1蜂鸣器介绍
1.2驱动电路介绍
1.3基本乐理知识
1.1蜂鸣器介绍
蜂鸣器在我们生活中很常见,可以说很懂地方都有蜂鸣器的存在,常常用来产生按键音和报警音,它的样子主要是这样的:
蜂鸣器分为有源蜂鸣器和无源蜂鸣器,有源蜂鸣器内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定。
这里显然我们单片机上面的蜂鸣器是无源蜂鸣器,需要我们手动编写代码为其配置振荡脉冲的频率,而使其发出不同的音调。
1.2驱动电路介绍
驱动电路主要是一种以微小的数字信号驱动大电流电压的电路,在其中广泛使用的电路元件就是PNP和NPN型三极管。
P即Postive,表示正极,N即Negative,表示负极,NPN三极管需要我们在P端输入一个高电平表示1导通电路,PNP三极管需要我们在N端输入一个低电平表示0导通电路,我们使用三极管就可以使用微弱的芯片信号去驱动更大的电流和电压,这样就可以达到“四两拨千斤”的效果。
在我们的单片机里,蜂鸣器和步进电机共用一个芯片ULN2003
然后我们看到这里的原理图示意:
这里的其实就是每个引脚对应的内部逻辑,就是通过一个个取反电路和放大电路之后得到较大的信号,比如左边输入0,右边就可以输出一个高电平并且电压大大增加
我们的蜂鸣器一端连接VCC,一端连接这个ULN2003芯片的一端输出,对应的输入是单片机的P2_5
所以,我们要想让蜂鸣器响,就要让蜂鸣器接受到一个震荡脉冲,以先高后低为例,我们BEEP就要输出一个先低后高的脉冲(低才能驱动),那么对应P2_5引脚的输入就是先高后低。但是其实我们在使用的时候,并不需要管太多这些低和高,我们只要控制好蜂鸣器频率,即它的震荡周期即可,控制好了周期,我们就可以使用这个蜂鸣器发出对应的音色的声音了。
1.3基本乐理知识
这里我们主要是想要使用这个蜂鸣器演奏一首歌曲,所以我们这里就要学习一点的乐理知识:
蜂鸣器震荡脉冲的频率可以表示不同的音色
这里我们以钢琴键盘为例,以低音6为基准频率,我们可以计算出相应的音符频率值,低音6的基准频率为440Hz,到下一个中音6对应的是880Hz,到高音6变成1760Hz,并且在每两个音符之间的变化是均等的,这样我们就可以写出下面的图示
我们要根据这个计算出对应的周期,但是对于我们而言,我们控制蜂鸣器的变化就要控制蜂鸣器每半个周期的变化,所以我们还要算出半个周期的时间,而我们想要实现蜂鸣器演奏音乐,就要让计时和蜂鸣器响在同一个时间运行,所以,我们还要配置定时器,也就是我们要配置定时器的重装载的值,我们要把计算出赋值给计时器的初始值,所以我们要使用us为单位
这里使用定时器0有这样的表格对应:
然后还有一些乐理知识补充一下:
这样的在音符上方标点在下方标点都是表示高一音和低一音,两点就表示高两音和低两音,主要控制音符的主频率和音调。
这样的表示控制音符的音长,什么是音长?
这就是音长,大部分的乐谱基准符都是4分、8分音符,较为激烈的就是16分音符了,在已知默认音长的情况下,我们再来讨论音符的音长
例如表示的就是原来音长的一半,表示的就是原来的音长乘以1.5,而
这样的就是典型的延音线,每多一个横线,就相当于加上同样的音符在这个位,即“1 -”==“1 1”
像这种就是我们钢琴键盘上的黑键,#1对应的就是1旁边的黑键,一般对应的就是升半音
乐谱上还有类似这样的一根曲线连接,表示两个音符其实是连在一起的,分开写纯属是为了美观,我们只要记住,把这两个当成一个长的音符弹就好了。
1.4代码
这里我们就可以开始弹奏钢琴曲子了:
这里就贴一个代码:
main.c
#include <REGX52.H>
#include "Timer0.h"
#include "Delay.h"//这里的宏定义是我们做的音调函数的索引
//就是为了我们可以方便的用这些生动形象的字母符号来对我们的函数进行调用
//比如L2表示这个低一音的2,HH3表示高二音的3,M2表示中音2
//M2_下划线表示#2这个高半音的黑键
#define LL1 1
#define LL1_ 2
#define LL2 3
#define LL2_ 4
#define LL3 5
#define LL4 6
#define LL4_ 7
#define LL5 8
#define LL5_ 9
#define LL6 10
#define LL6_ 11
#define LL7 12#define L1 13
#define L1_ 14
#define L2 15
#define L2_ 16
#define L3 17
#define L4 18
#define L4_ 19
#define L5 20
#define L5_ 21
#define L6 22
#define L6_ 23
#define L7 24#define M1 25
#define M1_ 26
#define M2 27
#define M2_ 28
#define M3 29
#define M4 30
#define M4_ 31
#define M5 32
#define M5_ 33
#define M6 34
#define M6_ 35
#define M7 36#define H1 37
#define H1_ 38
#define H2 39
#define H2_ 40
#define H3 41
#define H4 42
#define H4_ 43
#define H5 44
#define H5_ 45
#define H6 46
#define H6_ 47
#define H7 48#define HH1 49
#define HH1_ 50
#define HH2 51
#define HH2_ 52
#define HH3 53
#define HH4 54
#define HH4_ 55
#define HH5 56
#define HH5_ 57
#define HH6 58
#define HH6_ 59
#define HH7 60sbit BEEP=P2^5;//定义蜂鸣器管脚//这里我们定义的是我们的乐谱前面的一分钟四分音符的个数
unsigned char SPEED = 126;
//定义旋律音调的热重载值,就是定时器溢出我们要哦定义的初始值
//这里使用code改变数组的储存位置可以让我们的单片机储存下更多的代码
unsigned int code FreqTable[]={0,57844,58290,58687,59126,59438,59789,60101,60434,60728,60991,61262,61471,61719,61939,62135,62331,62506,62679,62833,62985,63132,63263,63390,63512,63628,63731,63835,63928,64021,64103,64185,64260,64331,64400,64463,64524,64580,64633,64684,64732,64777,64820,64860,64898,64934,64968,65000,65030,65058,65085,65110,65134,65157,65178,65198,65217,65235,65252,65268,65283
};//这里定义的是从低2个八度音到中央音到高2个八度(由低到高)//定义歌曲,这里我们用《路小雨》钢琴曲来举例
//采用音调索引加音长时间的方式,时长的单位都是一个十六分音符
//如果有停顿,我们用 0,时长 这样的格式表示就好了
unsigned char code Music[]={H3,2,H4,2,H5,2,H1,2,H1,4,H2,4,//H2,1,M3,1,M5,1,H2,1,H1,1,M3,1,M5,1,H1,1,M7,1,M3,1,M5,1,M7,1,H1,1,M3,1,M5,1,H1,1,//H2,1,M3,1,M5,1,H2,1,H1,1,M3,1,M5,1,H1,1,M7,1,M3,1,M5,1,M7,1,H1,1,M3,1,M5,1,H1,1,//H3,1,M4_,1,M6,1,H3,1,H2,1,M4,1,M6,1,H2,1,H1,1,M4,1,M6,1,H1,1,H2,1,M4,1,M6,1,H2,1,//H3,1,M4_,1,M6,1,H3,1,H2,1,M4,1,M6,1,H2,1,H1,1,M1,1,M4,1,H1,1,M7,1,M2,1,M5,1,M7,1,//H2,1,M3,1,M5,1,H2,1,H1,1,M3,1,M5,1,H1,1,M7,1,M3,1,M5,1,M7,1,H1,1,M3,1,M5,1,H1,1,//H2,1,M3,1,M5,1,H2,1,H1,1,M3,1,M5,1,H1,1,M7,1,M3,1,M5,1,M7,1,H1,1,M3,1,M5,1,H1,1,//H3,1,M4_,1,M6,1,H3,1,H2,1,M4,1,M6,1,H2,1,H1,1,M4,1,M6,1,H1,1,H2,1,M4,1,M6,1,H2,1,//H3,1,M4_,1,M6,1,H3,1,H2,1,M4,1,M6,1,H2,1,H1,1,M1,1,M4,1,H1,1,M7,1,M2,1,M5,1,M7,1,0xff
};unsigned char FreqSelect;//由于FreqTable数组只有61个元素,所以FreqSelect就可以使用unsigned char定义
unsigned int MusicSelect;//这里由于我们的Music数组里面存储的数据过多,超过了unsigned char的最大值,所以我们不得不使用unsigned int定义它
void main()
{unsigned int speed = 15000/SPEED;//计算的是一个十六分音符的时间,单位是msTimer0Init();//初始化定时器while(1){//到了这里每次循环开始之前都会跳转到下面的中断程序里面,然后main函数和Timer0_Routine() interrupt 1同时进行if (Music[MusicSelect])//判断Music的这个是不是0,不是0就可以进行{if (Music[MusicSelect]==0xff)MusicSelect=0; FreqSelect = Music [MusicSelect];//先把压力给到FreqSelect//因为我们的Music数组是先索引后时长,所以我们先来个使用调出音调对应值,然后演奏,所以我们每个while只演奏一个音符TR0=0;Delay(5); MusicSelect++;//Music数组里面从上一个跳到下一个,接下来就是音符长度TR0=1;Delay(speed*Music [MusicSelect]);//这里就是这个函数延时的时长,也是我们定时器演奏音乐的时长//在这段Delay里面我们的定时器在不断翻转演奏音乐直到我们关闭定时器或者演奏下一个音符//演奏完毕,关闭一下定时器,再停顿5ms左右模拟抬手动作TR0=0;//关闭定时器Delay(5);//模拟停顿MusicSelect++;//加一,下一次while循环又是一个音符演奏TR0=1;//重新打开定时器}else {FreqSelect=Music[MusicSelect];MusicSelect++;TR0=0;Delay(speed*Music [MusicSelect]);TR0=1;MusicSelect++;}
}
}
void Timer0_Routine() interrupt 1
{TL0=FreqTable[FreqSelect]%256;//后八位TH0=FreqTable[FreqSelect]/256;//定时器初始值前八位BEEP=!BEEP;//翻转IO让蜂鸣器以特定周期响
}
Delay.c
void Delay(unsigned int time)
{while(time--){unsigned char i, j;_nop_();i = 2;j = 199;do{while (--j);} while (--i);}
}
Timer0.c
#include <REGX52.H>void Timer0Init(void)
{TMOD &= 0xf0;//设置定时器模式TMOD |= 0x01;//设置定时器模式TF0=0; // 消除TF0标志TR0=1; //定时器0开始计时TL0=0X18; //设置定时器初始值TH0=0XFC; //设置定时器初始值ET0=1;EA=1;PT0=0;
}
/*定时器中断函数模板
void Timer0_Routine() interrupt 1
{TL0=0X18;TH0=0XFC;//加上条件以及改计时器值
}
//复制到主函数后面
*/