你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】基于STM32F103蜂鸣器播放歌曲

[复制链接]
STMCU-管管 发布时间:2021-6-1 15:26
一,参考网上例程
1、有源蜂鸣器与无源蜂鸣器的区别
首先大家要了解有源和无源这里的“源”不是指电源,而是指震荡源。也就是说,有源蜂鸣器内部带震荡源,所以只要一通电就会叫。而无源内部不带震荡源,所以如果用直流信号无法令其鸣叫。必须用2K~5K的方波去驱动它。有源蜂鸣器往往比无源的贵,就是因为里面多个震荡电路。这就是通过驱动原理来分别的方法。


然后我们再来看看外观上区别吧(如下图)
1.png
从图a、b外观上看,两种蜂鸣器好像一样,但仔细看,两者的高度略有区别,有源蜂鸣器a,高度为9mm,而无源蜂鸣器b的高度为8mm。如将两种蜂鸣器的引脚郡朝上放置时,可以看出有绿色电路板的一种是无源蜂鸣器,没有电路板而用黑胶封闭的一种是有源蜂鸣器。


万用表测电阻区别


用万用表电阻档Rxl档测试:用黑表笔接蜂鸣器 "+"引脚,红表笔在另一引脚上来回碰触,如果触发出咔、咔声的且电阻只有8Ω(或16Ω)的是无源蜂鸣器;如果能发出持续声音的,且电阻在几百欧以上的,是有源蜂鸣器。同时有源蜂鸣器直接接上额定电源(新的蜂鸣器在标签上都有注明)就可连续发声;而无源蜂鸣器则和电磁扬声器一样,需要接在音频输出电路中才能发声。


无源蜂鸣器的优点是:


1、便宜
2、声音频率可控,可以做出“多来米发索拉西”的效果
3、在一些特例中,可以和LED复用一个控制口


有源蜂鸣器的优点是:程序控制方便。


因为无源蜂鸣器便宜,所以大部分人手里拿到的都是无源蜂鸣器,我这里使用的是也是无源蜂鸣器。


2、编程思路
这里思路很简单,就是做一个可变时间的延时开控制发声,关于延时可以使用定时器来做,但是这里为了简便,就是用了一个简单的延时来完成。


延时时间的长短可以改变声音的频率,这是本程序的关键。


但是这里有一个问题,如果要播放频率变化特别快的音乐,比如下面的《红尘情歌》,必须控制输出电流大小,直接连接单片机IO口是不行的,加一个100K欧姆的电阻可以听出来,但是声音特别小,不适合做提示。所以,定义了一个简单的提示音数组melody,来完成功能。


如下图所示,是蜂鸣器与STM32连接的电路图,程序中的“PBeep”就是图中的“BEEP”IO口的宏定义,一般在头文件中进行定义:
#define PBeep PBout(8)
2.png

二,移植,运行,查看代码,尽力理解代码
说明一下,我使用的芯片类型是stm32F103C8,集成开发环境用的是Keil5 MDK-ARM,仿真器使用JLINK。
查看板子上的蜂鸣器接口,相应修改下代码中的引脚。这个和硬件相关,每个人只能根据自己的硬件来调整。
然后是将这个引脚配置成推挽输出,默认是低电平,输出高电平可以驱动蜂鸣器响报警。
然后是开关的操作,Buzzer_On(),Buzzer_Off()来替换;
然后就开始运行,能听到蜂鸣声响,但是刺刺啦啦的,不像音乐。
没有办法,偷懒不行啦,就只好开始看程序了,尽力去理解代码。毕竟代码在手,任我蹂躏,哈哈!
大体理解了一遍程序,调整了几个延时相关的值,发现效果有所好转,比较像音乐了。
但是,到底对不对,对于例程中的歌曲《红尘情歌》,我没听过啊,比较不出来对不对,好不好。
如果能添加一首自己熟悉的歌曲,就能判断了。不过,先别着急,慢慢来。先来个最简单的,就是先听听“多瑞咪”吧。
  1. u8 music[]={13,1,2,3,4,5,6,7,8};//音调 测试基础音
  2.     u8 time[] ={4, 4,4,4,4,4,4,4,4};//持续时长
复制代码
例子中,歌曲由两个数组来表示,一个是音调,一个是这个音调的持续时长。
音调数组music[],里面的数字看起来就是123,与曲谱上的一样,它是怎么实现那个声音的呢?
看代码,是这么调用的:   
  1. tone[music[i]]
复制代码
也就是说,是把它作为索引传给了tone[]数组,而tone[]的定义如下:   
  1. //             低7  1   2   3   4   5   6   7  高1 高2  高3 高4 高5 不发音
  2.         uc16 tone[] = {247,262,294,330,349,392,440,294,523,587,659,698,784,1000};//音频数据表
复制代码
这个数组中的数值,又有什么用呢?
注释说是音频数据。根据蜂鸣器的原理,就是输出不同频率的PWM波,即可发出不同的声音(音符)。然后将不同的音符组合起来就是一个曲子。所以这里的数据应该是用于控制蜂鸣器的发声频率的。
看看调用的地方:
  1. for(e=0;e<((u16)time[i])*tone[music[i]]/yanshi;e++){
  2.             Sound((u32)tone[music[i]]);
  3.         }
复制代码
查看一下Sound()这个函数,这可是核心函数:
  1. void Sound(u16 frq)
  2. {
  3.     u32 time;
  4.     if(frq != 1000)
  5.     {
  6.         time = 500000/((u32)frq);
  7.         PBeep = 1;
  8.         delay_us(time);
  9.         PBeep = 0;
  10.         delay_us(time);
  11.     }else
  12.         delay_us(1000);
  13. }
复制代码
其实也很简单,就是打开蜂鸣器,持续一会,然后关闭蜂鸣器,持续同样时间。
然后再看看调用的地方:
  1.      for(e=0;e<((u16)time[i])*tone[music[i]]/yanshi;e++){
  2.             Sound((u32)tone[music[i]]);
  3.         }
复制代码
是用时长来做控制,重复调用了许多次Sound()播放声音。这样持续起来,听起来就是一个音符了。

跑起来程序,让我们来听听“多瑞咪”!
嗯~~有点问题,有个音听起来不对劲!仔细听,数了数,对应的应该是7(第8个音),再仔细看一遍:
  1. //             低7  1   2   3   4   5   6   7  高1 高2  高3 高4 高5 不发音
  2.         uc16 tone[] = {247,262,294,330,349,392,440,294,523,587,659,698,784,1000};//音频数据表
复制代码
这个音对应的是294,在这个数组中有些不协调啊!
其他的数据,都是按从小到大的顺序排列的,这个数怎么比左右的数都小啊?
难道写错了?如果也按是顺序来的话,我看改为494比较协调。
那就改改呗,代码在手嘛,哈哈!
改了下,一听,还真就顺耳了,我是不是天才,哈哈!(A:天才?我看是多了两横。B:我看不光是要去掉两横,还要去掉一个“才”字。)
闲话少说,继续调试。   


三,调整测试参数
    到现在,大体能听出来是个音乐了,但是播放太快了(与板子上硬件相关),不太舒服,调调延时吧。
    大体相关的有几个参数:
    1,持续时长,就是time[]数组,但是,一旦调整,需要每个参数都修改一遍,太累了。
    2,有没有修改一处,所有音符的播放时长都变的?
    有!就是这个:
    yanshi = 2;//10;
    把它调小,循环播放的次数是增加的,也就是音符的持续时间变长了。
    3,还有一个值,就是前面Sound()函数中的time值,这个值影响的是音调准不准。具体怎么计算的,我也没搞明白,就是多试了几个值,挑了个听着舒服的。

对这几个参数一顿调整后,“多瑞咪”听着就很顺畅了。

下面,最激动人心的时刻到了!


四,添加一首歌曲

早就想添加一首歌曲了,不过真要开始添加,也还有点忐忑,得弄首自己熟悉的吧,不能弄太复杂的吧,嗯,来首儿歌吧,就选《小燕子》吧。
先上网找个谱子,仔细看看,嗯,也就是搞定两个数组嘛,一个音调,一个音长。
查看乐谱,如下:

真到了转换简谱到数组的时候,这时就能发现选择儿歌的好处了,所有的音调都在tone[]数组中,也就是说,在低音7到高音5之间。让我们再看一遍这个数组:
  1. //             低7  1   2   3   4   5   6   7  高1 高2  高3 高4 高5 不发音
  2.         uc16 tone[] = {247,262,294,330,349,392,440,494,523,587,659,698,784,1000};//音频数据表
复制代码
如果搞了一个复杂的歌曲,音调的范围超过了这个范围,就需要自己研究下那些新音符对应的是什么频率值了。
至于时长,这里是用的4表示1拍的时长。这样,半拍(一个音符下有一道横线)就是2,四分之一拍(一个音符下有两道横线)就是1了,还有两拍的,就是8。
这样,音调与时长都搞定了,是不是就ok了?
嗯,基本上可以这么说,不过,还有些小细节,例如,在两个音符中间的一个小点是什么?
我在这里就犯过错,我还以为是休止符(不知道是从那里获取的错误信息!也许真的有必要将大脑中的知识点都梳理一遍),后来查了一下,这玩意叫附点,看介绍:
附点的符号很简单,就是单纯音符后面的小圆点。附点的作用是:将前面挨着它的单纯音符的时值延长一半。
还有一个小细节,就是在一句歌词唱完之后,需要停顿一会,这时候,就用上了音频数据表中一个特殊的值:1000,前面注意看的话,在Sound()中就有对它的特殊处理,就是不开启蜂鸣器,只是一个延时。
这时再听听,就有些感觉了哈!
后面就是根据整体感觉,做点微调了,例如,我就调整了下尾部拖音的时长,本来是8(两拍),我改为6(一拍半),似乎听起来更好听了,哈哈!

下面是我修改后的代码:
  1. #include "beep.h"
  2. #include "stm32f10x.h"
  3. #include "gpioHandler.h"
  4. #include "timerHandler.h"

  5. void BEEP_Init(void)
  6. {   
  7.     GPIO_InitTypeDef  GPIO_InitStructure;

  8.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);         //使能A端口时钟
  9.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;         
  10.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;                  //推挽输出
  11.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
  12.     GPIO_Init(GPIOB, &GPIO_InitStructure);          //初始化GPIOD3,6
  13.     GPIO_SetBits(GPIOB,GPIO_Pin_5);       
  14. }

  15. void Sound(u16 frq)
  16. {
  17.         u32 time;
  18.         if(frq != 1000)
  19.         {
  20. //                time = 500000/((u32)frq);
  21.                 time = 100000/((u32)frq);
  22. //                PBeep = 1;
  23.                 Buzzer_On();//打开蜂鸣器--根据自己的硬件情况调整,通常就是控制蜂鸣器的gpio引脚置1

  24.                 delay_us(time);
  25. //                PBeep = 0;
  26.                 Buzzer_Off();//关闭蜂鸣器--根据自己的硬件情况调整,通常就是控制蜂鸣器的gpio引脚置0
  27.                 delay_us(time);
  28.         }else
  29.                 delay_us(1000);
  30. }
  31. void play_music(void)
  32. {
  33.         //             低7  1   2   3   4   5   6   7  高1 高2  高3 高4 高5 不发音
  34.         uc16 tone[] = {247,262,294,330,349,392,440,494,523,587,659,698,784,1000};//音频数据表

  35.         //小燕子
  36.                 u8 music[]={3,5,8,6,5,13,//音调
  37.                         3,5,6,8,5,13,
  38.                         8,10,9,8,9,8,6,8,5,13,
  39.                                         3,5,6,5,6,8,9,5,6,13,
  40.                                         3,2,1,2,13,
  41.                                         2,2,3,5,5,8,2,3,5,13};
  42.                 u8 time[] ={2,2,2,2,6,4,//时间  
  43.                                 2,2,2,2,6,4,
  44.                 6,2,4,4,2,2,2,2,6,4,
  45.                                 6,2,4,2,2,4,2,2,6,4,
  46.                                 2,2,4,6,4,
  47.                                 4,2,2,4,4,4,2,2,6,4};
  48. //        u8 music[]={13,1,2,3,4,5,6,7,8};//测试基础音
  49. //        u8 time[] ={4, 4,4,4,4,4,4,4,4};

  50.         u32 yanshi;
  51.         u16 i,e;
  52.         yanshi = 2;//10;  4;  2
  53.         for(i=0;i<sizeof(music)/sizeof(music[0]);i++){
  54.                 for(e=0;e<((u16)time[i])*tone[music[i]]/yanshi;e++){
  55.                         Sound((u32)tone[music[i]]);
  56.                 }       
  57.         }
  58. }
复制代码
调用的时候,就是两句代码:
  1. BEEP_Init();
  2.         play_music();
复制代码






收藏 评论0 发布时间:2021-6-1 15:26

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版