最近搞到一个51单片机, 看了下有个控制蜂鸣器发声的实验, 挺有意思, 嗯, 就做个记录吧.

如何让蜂鸣器响起来就不多说了, 上图, 硬件上利用了三极管开关, 用51一个管脚就能控制了.

蜂鸣器控制电路

关键难题是如何让蜂鸣器君演奏出旋律, 仔细看了例程, 所幸不是很难. 只要知道曲子中的每个音(即知道音的频率)和每个音的拍子(即持续的时间)就能简单实现了. 下面是找到的例程框架. 可以看到这个例程是利用m(发音频率的参考变量)和一个延迟函数配合, 发出曲谱上的每个音;利用n(发音时间的参考变量)和一个计时器timer0配合, 组成曲谱的拍子.

1
2
3
4
5
6
7
8
9
#include <reg52.h>
sbit Beep = P1^5; //定义P1.5管脚控制蜂鸣器
unsigned char n = 0; //n作为每个音持续时间的参考变量
////////////////////////////////////////////////////曲谱表
unsigned char code music_tab[] = {
……… //编写规则: 一个频率(m), 一个拍子(n)

0x00 //0x00作为结束符 休止符为0xff
};

先把主函数放上来看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void main()
{
unsigned char p, m; //m为音频率参考变量,
unsigned char i = 0; //p在后面作为检测是否播放完毕
TMOD &= 0x0f; //设置timer0为工作方式1
TMOD |= 0x01; //原来可以这么写, 长姿势了
TH0 = (65536-50000)/256; //设置timer0初值, 计时为50ms
TL0 = (65536-50000)%256;
IE = 0x82; //timer中断使能
play: //核心播放部分, 用了goto语句Σ( ° △ °|||)︴
while(1)
{
a: p = music_tab[i]; //从表中取出一个值检测
if(p == 0x00) //若是休止符则计数归零, 延迟1s后重新播放
{
i=0;
delayms(1000);
goto play;
}
else if(p == 0xff) //若是休止符(就是不发音), 计数加1, 延迟100ms
{ //后跳到到下一个音. 这里感觉缺少灵活性, 不同曲子
i = i+1; //的休止符时长不一定相同吧
delayms(100);
goto a;
}
else //没遇到上述特殊情况, mn分别从表中取值
{
m = music_tab[i++];
n = music_tab[i++];
}
TR0 = 1; //启动timer0, 发声开始
while(n != 0)
Beep =~ Beep;
delay(m); //没错, 通过设定频率地不断打开关闭蜂鸣器, 制造
//出特定频率的声音
TR0 = 0; //在由计时器与n设定的发声时间内发声结束, 关闭
} //计时器
}

下面是配合演出的函数们

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void timer0() interrupt 1 //timer0中断函数
{
TH0 = (65536-50000)/256; //每过50ms进入一次, 做n--
TL0 = (65536-50000)%256; //举个栗子, 若一个音发500ms, 曲谱表中那个音的n
n--; //的位置写10即可
}

void delay (unsigned char m)//这个delay函数用于控制频率, 用于us级时间控制
{ //经过仿真, 精确程度还行, while(--i)大概8us
unsigned i = 2*m; //运行一次, 嗯
while(--i);
}

void delayms(unsigned int n)//网上找的比较精确的ms级延迟函数, 替代了例程
{ //中的延迟函数
unsigned int k, j;
for(j=n; j>0; j--)
for(k=112; k>0;k--);
}

最有(dan)趣(teng)的是编曲部分, 从网上找到了音阶对应频率的表

音阶对应频率

比如要蜂鸣器发低音Do, 频率为262Hz, 也就是要在1s内开关蜂鸣器262次, 大概3.8ms(3800us)变换状态, 对于中音与高音变换时间更短, 得使用us级的延时函数delay(). 然后为了让unsigned char型的m(最大装值255)能表示所有音阶, 配上一个系数. 取延时最长的低音Do除以16差不多239, 接近255, 就这么愉快地决定了. 因为delay()函数中while(—i)大概8us执行一次, 所以把传过来的m的值乘2, 就能平衡前面除以16的影响发出正确的音. 好了, 所有音都处理后得到了一张表

音阶与对应m值

然后再稍微补充下学习乐理基础的知识吧, 既然是拿简谱开刀, 没有基础的乐理也是不够的, 其实这里只需关注每个音的延时, 然后利用50ms的计时器与变量n配合即可.

首先必须知道一个标准的拍子(比如四分音符为一拍)是多少时间, 这个我查了下与曲子有关, 不同曲子所要求的弹奏速度是不一样的, 有时还要加入弹奏者的理解自行发挥. 所幸在比较标准的简谱左上角会标出弹奏的标准速度. 比如♩=100, 也就是说一分钟弹奏100个四分音符, 600ms是标准一拍的时间, 此时简谱中纯数字的n写为12就行了(12*50ms=600ms), 然后数字下有一横的表示半拍(n=6), 两横的表示四分之一拍(n=3), 如果是数字后的短横表示延音, 也类似, 一条横线延长一拍(n=24), 以此类推就行了. 事实是有明确标明弹奏速度的简谱并不常见, 一般就以400/500ms一拍的速度就好了.

嗯, 首先找来了一个《小星星》简谱来试试, 顺便记一下, 左上角的1=D表示D调, 简谱可能还得辨识大小调, 从而重新确定音调. 不过实在没必要搞得太麻烦, 又没什么大的区别(我是音痴, 学音乐的不要打我ˋ( ° ▽、° )

4/4表示四分音符为一拍, 每小节四拍, 两条竖线之间为一节吧
这就是编的小星星(一闪一闪亮晶晶那个)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned char code music_tab[] ={   
0xef,0x0a,
0xef,0x0a,
0x9f,0x0a,
0x9f,0x0a,
0x8e,0x0a,
0x8e,0x0a,
0x9f,0x14,

0xb3,0x0a,
0xb3,0x0a,
0xbe,0x0a,
0xbe,0x05,
0xbe,0x05,
0xd5,0x0a,
0xd5,0x0a,
0xef,0x14,

0x00
};

好像不错再编个天空之城主题曲《君をのせて》
不过我打算把音调与拍子分开编, 也挺容易的就是有点…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
unsigned char code music_f[] = //音调谱
{
0xff,0xff,0xff,0x47,
0x3f,0x3b,0x3f,0x3b,
0x2f,0x3f,0x5e,0x5e,
0x47,0x4f,0x47,0x3b,
0x4f,
0x5e,0x59,0x5e,0x59,
0x3b,0x5e,0xff,0x3b,
0x3b,0x3b,0x3f,0x54,
0x59,0x3f,0x3f,0xff,
0x47,0x3f,0x3b,0x3f,
0x3b,0x2f,0x3f,0x5e,
0x5e,0x47,0x4f,0x47,
0x3b,0x4f,0x6a,0x5e,
0x59,0x3b,0x3f,0x3f,
0x3b,0x3b,0x35,0x35,
0x2f,0x3b,0x3b,0x3b,
0x3f,0x47,0x47,0x3f,
0x4b,0x47,
0x00
};
unsigned char code music_t[]= //拍子谱
{
0x0a,0x0a,0x0a,0x05,
0x05,0x0f,0x05,0x0a,
0x0a,0x1e,0x05,0x05,
0x0f,0x05,0x0a,0x0a,
0x1e,
0x0a,0x0f,0x05,0x0a,
0x0a,0x14,0x05,0x05,
0x05,0x05,0x0f,0x05,
0x0a,0x0a,0x14,0x0a,
0x05,0x05,0x0f,0x05,
0x0a,0x0a,0x1e,0x05,
0x05,0x0f,0x05,0x0a,
0x0a,0x1e,0x05,0x05,
0x0a,0x05,0x05,0x05,
0x05,0x0a,0x05,0x05,
0x05,0x05,0x14,0x05,
0x05,0x05,0x05,0x0a,
0x0a,0x1e,
0x00
};

编了一半我傻了, 简单的十进制放着不用, 干嘛非用麻烦的十六进制, 果然是被例程带坏了 ಥ_ಥ

既然明白了原理, 就再改改放一个最终方案作为结束, 上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#include <reg52.h> 
///////////////////中音部
#define c 120
#define cp 113
#define d 106
#define dp 100
#define e 95
#define f 90
#define fp 84
#define g 80
#define gp 75
#define a 71
#define ap 67
#define b 63
///////////////////高音部
#define C 60
#define Cp 56
#define D 53
#define Dp 50
#define E 47
#define F 45
#define Fp 42
#define G 40
#define Gp 38
#define A 36
#define Ap 34
#define B 32
///////////////////低音部
#define cc 239
#define ccp 226
#define dd 213
#define ddp 201
#define ee 189
#define ff 179
#define ffp 169
#define gg 159
#define ggp 151
#define aa 142
#define aap 134
#define bb 127
///////////////////休止符
#define xx 0

sbit Beep = P1^5 ;
unsigned char n=0, i=0, p, m;
unsigned char code music_f[] =
{
C,b,C,E,b,
a,g,a,C,g,
f,e,f,C,b,g,
a,b,C,E,D,
C,b,C,E,b,g,
a,b,C,D,E,E,
F,E,D,C,b,E,gp,b,
a,a,b,C,D,
E,D,
G,F,E,D,C,
b,C,a,
a,a,g,
f,g,a,C,b,
b,C,D,b,
b,xx,a,
a,a,b,C,D,
E,D,
G,F,E,D,C,
b,C,a,
a,a,g,
f,g,a,C,b,
b,C,D,F,E,
E,D,C,
b,gp,
C,b,C,E,b,
a,g,a,C,g,
f,e,f,C,b,g,
a,b,C,E,D,
C,b,C,E,b,g,
a,b,C,D,E,E,
F,E,D,C,b,E,gp,b,
a,
0xff
};
unsigned char code music_t[]=
{
6,6,6,6,24,
6,6,6,6,24,
6,6,6,6,12,12,
6,6,6,6,24,
6,6,6,6,12,12,
6,6,6,6,12,12,
6,6,6,6,6,6,6,6,
36,3,3,3,3,
36,12,
12,12,12,6,6,
36,6,6,
36,6,6,
12,12,6,12,6,
12,12,12,12,
36,6,6,
36,3,3,3,3,
36,12,
12,12,12,6,6,
36,6,6,
36,6,6,
12,12,6,12,6,
12,12,12,12,6,
36,6,6,
24,24,
6,6,6,6,24,
6,6,6,6,24,
6,6,6,6,12,12,
6,6,6,6,24,
6,6,6,6,12,12,
6,6,6,6,12,12,
6,6,6,6,6,6,6,6,
48,
0xff
};

void timer0() interrupt 1
{
TH0 = (65536-50000)/256;
TL0 = (65536-50000)%256;
n--;
}

void delay(unsigned char m)
{
unsigned int h = 2*m;
while(--h);
}
void delayms(unsigned int n)
{
unsigned int k,j;
for(j=n; j>0; j--)
for(k=112; k>0; k--);
}
void main()
{

TMOD &= 0x0f;
TMOD |= 0x01;
TH0 = (65536-50000)/256;
TL0 = (65536-50000)%256;

IE = 0x82;
play:
while(1)
{
p = music_f[i];
if(p == 0xff)
{
i = 0;
delayms(3000);
goto play;
}
else
{
m = music_f[i],
n = music_t[i++];
}
TR0 = 1;
while(n!=0)
{
if (p)
{
Beep =~ Beep;
delay(m);
}
}
TR0 = 0;
}
}

这里做了点改进
1.宏定义了一些符号常量, 便于编谱
2.改变了休止符表示方法, 并把休止符也看成一个音调, 不像原来那样goto语句特殊处理. 可以通过n来控制音长(虽然没有声音)

作为初学者就折腾到这里吧, 不知道结合键盘能不能自己演奏呢. 被蜂鸣器的声音折磨了几个小时, 赶紧听下原版获得救赎吧XD

.
.
.
.
.
.
.
.
.
終わり