实现原理
通过PNP三极管提高单片机管脚带负载能力,其中R5电阻为上拉电阻以确保在单片机未置低时保证三极管完全关断。通过控制喇叭响与不响的时间长度得到不同音高,故而需要使用定时器定时开关喇叭。
具体实现
//下列中CD为超低音部,D为低音部,Z为中音部,G为高音部,CG为超高音部
//每组注释第一行为数组序号,第二行为在C调中的简谱符号
//该结构体用于存放定时器初值以改变扬声器震动频率
unsigned int code pinlv[]=
{0,
62013,62211,62398,62574,62740,62897,63045,63185,63317,
//1 2 3 4 5 6 7 8 9
//CD1 CD1.5 CD2 CD2.5 CD3 CD4 CD4.5 CD5 CD5.5
63441,63559,63670,
//10 11 12
//CD6 CD6.5 CD7
63775,63874,63967,64055,64138,64217,64291,64361,64426,
//13 14 15 16 17 18 19 20 21
//D1 D1.5 D2 D2.5 D3 D4 D4.5 D5 D5.5
64489,64548,64603,
//22 23 24
//D6 D6.5 D7
64655,64705,64751,64795,64837,64876,64913,64948,64981,
//25 26 27 28 29 30 31 32 33
//Z1 Z1.5 Z2 Z2.5 Z3 Z4 Z4.5 Z5 Z5.5
65012,65042,65069,
//34 35 36
//Z6 Z6.5 Z7
65096,65121,65144,65166,65187,65206,65225,65242,65259,
//37 38 39 40 41 42 43 44 45
//G1 G1.5 G2 G2.5 G3 G4 G4.5 G5 G5.5
65274,65289,65303,
//46 47 48
//G6 G6.5 G7
65316,65328,65340,65351,65361,65371,65380,65389,65397,
//49 50 51 52 53 54 55 56 57
//CG1 CG1.5 CG2 CG2.5 CG3 CG4 CG4.5 CG5 CG5.5
65405,65412,65420//CG
//58 59 60
//CG6 CG6.5 CG7
};
根据上方的结构体就能编写出大部分你想编写的音符,但是音色取决于你的无源蜂鸣器或者喇叭(能震就行)。算这个费老大劲,有觉得音准不行的自己改改,在下尽力了。
PS:我用的无源蜂鸣器播放的音色不能说娓娓动听吧,但也算是阴曹地府风了。
void Timer0Init(void)
{
TMOD = 0x01;
TL0 = 0x66;
TH0 = 0xFC;
TF0 = 0;
TR0 = 1;
ET0=1;
EA=1;
}
void Timer0_Routine() interrupt 1
{
TL0 = pinlv[expected_pinlv]%256;
TH0 = pinlv[expected_pinlv]/256;
speaker=!speaker;
}
上方是定时器初始化还有中断服务函数,结合一下就这么用。
上面的expected_pinlv变量名称土洋结合了属于是,应该都能看懂,给他赋值就是一开始那个存放频率的结构体的序号,为了方便直接从1开始数,注释都写好了,应该没大问题。
u16 beat_transform(u16 beat)
{
u16 need_ms;
switch(beat)
{
case 1: need_ms=ms_every_beat/8;break;
case 2: need_ms=ms_every_beat/4;break;
case 3: need_ms=ms_every_beat/2;break;
case 4: need_ms=ms_every_beat;break;
case 5: need_ms=ms_every_beat*2;break;
case 6: need_ms=ms_every_beat*3;break;
case 7: need_ms=ms_every_beat*4;break;
case 8: need_ms=ms_every_beat*5;break;
case 9: need_ms=ms_every_beat*6;break;
default: break;
}
return need_ms;
}
void speed_transform(u16 speed)
{
ms_every_beat=60000/speed; //ms_every_beat设置为全局变量较方便
}
要是有点小追求,咱稍微严格点吧节拍速度算个大概,然后用个准确点的1ms软件延时函数,节奏就能比较完满的复刻出来。可惜我搞的软件延时差的还有点大,懒得整了,贴出来你们想玩试试。
下面给个例子哈,是个模板,不一定能用,最后我会分享一个.c文件是我试过能用的,要是不能用那大概率因为硬件不一样,勿diss我。
//太阳照常升起 115速
//每行是一小节
//格式:频率序号,拍数
//拍数规则:1代表1/8拍 2代表1/4拍 3代表1/2拍 4代表1拍 5代表2拍以此类推
unsigned int code music_taiyang[]={
27,4, 34,4, 34,4, 34,3, 34,2, 35,2,
34,5, 34,4, 35,3, 36,3,
39,4, 39,4, 37,4, 37,4,
34,7,
27,4, 32,4, 32,4, 32,3, 34,3,
32,5, 34,5,
37,3, 34,3, 37,5, 29,3, 30,3,
27,8,
27,4, 30,4, 34,4,
39,3, 37,3, 39,5, 39,3, 37,3,
39,3, 37,3, 39,5, 37,3, 37,3,
34,8,
32,4, 39,4, 37,4,
34,3, 32,3,
34,7,
32,4, 39,4, 36,4,
34,3, 32,3, 34,4, 37,4, 39,3, 30,3,
27,7,
100};
int main(void)
{
Timer0Init();
speed_transform(115);
while(1)
{
if(music_taiyang[i]==100) i=0;
expected_pinlv=music_taiyang[i]; i++;
delay_ms(beat_transform(music_taiyang[i])-10); i++;
speaker=1;delay_ms(10); //这里减10ms是为了让音之间转换能清晰点,不用也行
}
}
前面定义过的函数没在上面再写,用大概就这么用。里面的延时函数需要自己写一下,我写的不准就不贴出来浪费大家时间了。
链接:https://pan.baidu.com/s/1F6GjlayIs10k95aMf5DUbA
提取码:2333