??最近学习stm32芯片,使用的是蓝桥杯的f103的旧板子,看到上面有蜂鸣器,所以就想写代码来控制蜂鸣器播放一首音乐。 ??这里我参考了这篇文章基于STM32F103,用蜂鸣器播放歌曲。同这篇文章一样,我也遇到了蜂鸣器发出的声音不对。参考了这篇文章,以及查找网上的其他资料,最终完成了蜂鸣器的调试,以及歌曲的编写,文章最后会附上代码。 ??先对音符这类东西进行说明吧。因为自己也不是学音乐的,一些关于音乐的知识都是网上搜集的,所以如果有讲得不对的地方,也请各位读者在评论指正,我会及时改正。 ??首先在百度上搜索每个音符的频率,这里我参考的是音符与频率对照表 其实对于哪个调来说,我感觉不出来什么差别(可能我没有什么艺术细胞吧),而且在代码里我的音符的频率对应图片里的音符的频率是高一个八度。【如下图】
uc16 tone[] = {247,262,294,330,349,392,440,494,523,587,659,698,784,880,988,1046,0};
??也许我上面说的有些迷糊,但是我们通过上面那张表就可以得到每个音符所对应的频率,这个是我们需要的。 ??有了每个音符的频率还不够,我们还需要知道每个音符持续的时间,也就是音符时值。 ??这里我拿我的选的一个曲子举例子。 ??
Eb
\text{Eb}
Eb指的是曲子的调是Eb调,(每个调之前的区别,我个人感觉不出大差别,所以我的音符频率是按C调频率来的),后面的
4
4
\frac44
44?指的是以四分音符为一拍,一小节四拍。在后面的音符=120,指的是一分钟120拍,所以我们从这里就可以知道,一个四分音符的时值是0.5s. 那么什么是四分音符呢?
??如上图,音符下面有三条横线的,就是三十二分音符,有两条横线的就是十六分音符,一条横线的就是八分音符,没有横线的就是四分音符。 ??这些音符对应的时值分别为(设四分音符的时值为
t
t
t)
- 四分音符=
t
t
t
- 八分音符=
t
/
2
t/2
t/2
- 十六分音符=
t
/
4
t/4
t/4
- 三十二音符=
t
/
8
t/8
t/8
??除了上面四个音符外,有时谱子里还会遇到一个音符跟着一个小点,这种称为附点。 有附点的音符的时值,与附点跟随的音符有关。附点的时值为跟随的音符时值的一半。所以有附点的音符的时值为 跟随的音符的时值×1.5
例如,图中的
3
˙
.
 ̄
\underline{\dot 3.}
3˙.?的时值计算如下 设四分音符的时值为
t
t
t
3
˙
 ̄
\underline{\dot 3}
3˙?为八分音符,时值为
t
/
2
t/2
t/2 附点的时值为跟随音符的时值的一半,在这里也就是
t
/
4
t/4
t/4 因此
3
˙
.
 ̄
\underline{\dot 3.}
3˙.? 的时值为
t
/
2
+
t
/
4
=
3
t
/
4
t/2+t/4=3t/4
t/2+t/4=3t/4 //-------------------------------------------------------------------------- 举一反三
6.
6.
6.的时值计算如下 设四分音符的时值为
t
t
t
6
6
6为四分音符,所以时值为
t
t
t 附点为跟随音符的时值的一半,为
t
/
2
t/2
t/2 所以
6.
6.
6.的时值为
t
+
t
/
2
=
3
t
/
2
t+t/2=3t/2
t+t/2=3t/2
??关于音符的介绍差不多就到这里了,接下来就是代码相关的东西了。 【蜂鸣器的控制就不用我说了吧,一般原理图上都是用三极管驱动蜂鸣器,所以引脚出高低就能控制蜂鸣器的叫与不叫,至于引脚出高还是低,蜂鸣器响,具体的还是看自己板子上的原理图吧】 首先是定义一个音符的频率数组,至于数组中的数从哪来,可以参考我前面发的音符频率对照表,或者要是知道音符频率的关系,自己算也可以。
uc16 tone[] = {247,262,294,330,349,392,440,494,523,587,659,698,784,880,988,1046,0};
接下来就是写谱子的音符,这里我就截我选的曲子 蓝色线截选的音符,就是我让单片机播放的曲子片段。 相应的音符数组如下
u8 BrokenMoon[]={
3,5,6,8,
9,8,9,10,8,6,
5,3,8,9,6,6,8,
9,8,9,10,12,13,
15,14,13,14,13,12,13,12,10,
9,10,8,9,8,9,
10,6,8,9,8,6,6,5,
6,5,6,8,9,10,9,5,
6
};
数组里对应的数字,就是tone 数组的元素下标,也就是每个音符。 ??然后是关于音符时值的数组【每个音符具体的时值与你选的谱子的曲速和音符种类有关,我在上面都讲解了一下,如果还是没看懂的话,可以去B站看看相关知识,大概几分钟就可以看完】
u8 scale = 16;
u8 BrokenMoonTime[]={
8,8,8,8,
16,8,8,16,8,8,
8,8,8,8,16,8,8,
16,8,8,16,8,8,
8,8,2,2,4,8,16,8,8,
16,8,8,16,8,8,
12,4,4,4,8,16,8,8,
12,2,2,8,8,8,8,16,
32
};
??准备好音符和时值数组之后,一个重要的函数就来了。
void BUZZER_Play(u16 frq){
u32 time;
if(frq){
time = 50000/((u32)frq);
BUZZER_ON();
delay_us(time);
BUZZER_OFF();
delay_us(time*9);
}
else delay_us(1000);
}
??这是用蜂鸣器播放一个脉冲的函数。函数的参数,频率frq ,即单个音符对应频率。函数执行的就是蜂鸣器播放该频率下的一个脉冲。 ??根据,之前的参考的文章,他们的设置高电平的占空比为50%,但我具体测试的时候,发现修改time 的值虽然会有使音色改善,但也造成了播放速度过快,后面想了想,觉得蜂鸣器的占空比为50%不太合理,根据测试,选择了10%的占空比,大家具体测试的时候,如果发现音色不好,可以修改高电平的占空比。
具体方法如下:
- 先将
time= 500000/((u32)frq); 写到BUZZER_Play() 函数里,然后让蜂鸣器响和不响的延时时间都相同,为time - 下载代码测试音色,根据实际情况,修改
time = 500000/((u32)frq) 中的500000 的值,直到自己一个满意的效果。然后比较修改后time的值与原来time = 500000/((u32)frq) 的。将原来time值假设为
t
0
t_0
t0?,调试完之后的值为
t
1
t_1
t1? - 将蜂鸣器响的延迟时间设置为
t
1
t_1
t1?,将蜂鸣器关闭的延迟时间设置为
t
0
?
t
1
t_0-t_1
t0??t1?
修改完的代码,样子如下
void BUZZER_Play(u16 frq){
u32 time;
if(frq){
time = m/((u32)frq);
BUZZER_ON();
delay_us(time);
BUZZER_OFF();
delay_us(t_0-t_1);
}
else delay_us(1000);
}
??写完播放一个固定频率的脉冲之后,就是写在一段时间内播放固定频率的脉冲的函数。
void MUSIC_Play(void){
u16 i,j;
for(i=0;i<i<sizeof(music)/sizeof(music[0]);i++)
for(j=0;j<((u16)time[i])*tone[music[i]]/scale;j++)
BUZZER_Play((u32)tone[music[i]]);
}
对于第一个循环语句
for(i=0;i<i<sizeof(music)/sizeof(music[0]);i++)
sizeof(music)/sizeof(music[0]) 的效果等价于求music 数组的长度,所以这里的循环就是读取数组中的每个音符。
这个函数里最重要的一句就是
for(j=0;j<((u16)time[i])*tone[music[i]]/scale;j++)
??前面说过time 数组是music 中对应音符的时值,scale 是将音符时值放大的比例,之所以要放大音符的时值就是为了避免数组中存在小数,方便书写和储存。因此在计算脉冲个数的时候就需要变换回实际的时值,所以除以放大的比例。【可以发现这是整数除法,会舍弃小数,所以存在一点误差,不过问题也不大】 ??所以(u16)time[i])*tone[music[i]]/scale 表示的就是脉冲的个数,也就是执行BUZZER_Play() 的次数。
??好了,讲完了所用的函数,我们就应该从总体上来分析,我们为什么这么写。首先蜂鸣器的发声频率不同就会产生不同的声音,因此我们控制蜂鸣器开关时间就可以控制蜂鸣器的产生不同频率的声波。
具体就是控制高低电平的时间,产生不同的声音,产生脉冲的个数就是其持续的时间。
最后贴一个我的代码。
uc16 tone[] = {247,262,294,330,349,392,440,494,523,587,659,698,784,880,988,1046,0};
u8 scale = 16;
u8 BrokenMoon[]={
3,5,6,8,
9,8,9,10,8,6,
5,3,8,9,6,6,8,
9,8,9,10,12,13,
15,14,13,14,13,12,13,12,10,
9,10,8,9,8,9,
10,6,8,9,8,6,6,5,
6,5,6,8,9,10,9,5,
6
};
u8 BrokenMoonTime[]={
8,8,8,8,
16,8,8,16,8,8,
8,8,8,8,16,8,8,
16,8,8,16,8,8,
8,8,2,2,4,8,16,8,8,
16,8,8,16,8,8,
12,4,4,4,8,16,8,8,
12,2,2,8,8,8,8,16,
32
};
void BUZZER_Play(u16 frq){
u32 time;
if(frq){
time = 50000/((u32)frq);
BUZZER_ON();
delay_us(time);
BUZZER_OFF();
delay_us(time*9);
}
else delay_us(1000);
}
void MUSIC_Play(u8 *music,u8 *time,u16 length){
u16 i,j;
for(i=0;i<length;i++)
for(j=0;j<((u16)time[i])*tone[music[i]]/scale;j++)
BUZZER_Play((u32)tone[music[i]]);
}
void MUSIC1_Play(void){
MUSIC_Play(BrokenMoon,BrokenMoonTime,sizeof(BrokenMoon)/sizeof(BrokenMoon[0]));
}
这里我想修改代码方便调用,就将MUSIC_Play(u8 *music,u8 *time,u16 length) 封装起来方便调用,暂时还没测试可行性,等我测试完之后,看实际情况进行修改,大家如果有什么代码建议也可以发表在评论,一起讨论讨论
|