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

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

[复制链接]
STMCU-管管 发布时间:2021-6-1 15:26
一,参考网上例程
4 s) H; e6 k! t8 n2 K  B1、有源蜂鸣器与无源蜂鸣器的区别/ Q+ W7 [  z1 ]- R+ _$ }
首先大家要了解有源和无源这里的“源”不是指电源,而是指震荡源。也就是说,有源蜂鸣器内部带震荡源,所以只要一通电就会叫。而无源内部不带震荡源,所以如果用直流信号无法令其鸣叫。必须用2K~5K的方波去驱动它。有源蜂鸣器往往比无源的贵,就是因为里面多个震荡电路。这就是通过驱动原理来分别的方法。
1 a* b: H5 V" r$ f1 W9 G/ V/ J7 @2 p# Z0 t% u, j7 d
8 x& t3 B6 F' H
然后我们再来看看外观上区别吧(如下图)
6 C/ z  C* ]& J3 l- E" Z 1.png
4 k" i5 C0 V# v从图a、b外观上看,两种蜂鸣器好像一样,但仔细看,两者的高度略有区别,有源蜂鸣器a,高度为9mm,而无源蜂鸣器b的高度为8mm。如将两种蜂鸣器的引脚郡朝上放置时,可以看出有绿色电路板的一种是无源蜂鸣器,没有电路板而用黑胶封闭的一种是有源蜂鸣器。
7 r  ~, r' L: ]( U4 V
6 Y4 _; X1 y# u0 h. ^* [5 J
) N7 s+ P( A/ ]- R
万用表测电阻区别
2 e- v. a; _3 o  M) r% r4 F8 _) _" ]: X
& s* R8 W7 Q  Y& J3 H6 z+ F
用万用表电阻档Rxl档测试:用黑表笔接蜂鸣器 "+"引脚,红表笔在另一引脚上来回碰触,如果触发出咔、咔声的且电阻只有8Ω(或16Ω)的是无源蜂鸣器;如果能发出持续声音的,且电阻在几百欧以上的,是有源蜂鸣器。同时有源蜂鸣器直接接上额定电源(新的蜂鸣器在标签上都有注明)就可连续发声;而无源蜂鸣器则和电磁扬声器一样,需要接在音频输出电路中才能发声。7 v$ x7 I8 d2 v# U: k- K  G6 U, W" e
/ q) {4 u$ `! @9 a
- w9 U( n( w  v* w: Z6 F' [
无源蜂鸣器的优点是:! Y# g2 l6 ^2 p. u& S1 E7 X. V0 x

, `1 r0 Y) C7 c& H) R
. M5 {( v% T5 ?2 N$ }
1、便宜
& n  r! z% [0 p5 O) ~2、声音频率可控,可以做出“多来米发索拉西”的效果
" r2 Z0 C) H2 w0 g: t" W5 R( q3、在一些特例中,可以和LED复用一个控制口
5 q! p$ f0 k6 G) O( l4 D6 q
0 l8 e" j" Q0 Y8 `& U9 ]7 j+ x

% V2 e! v  |9 K5 R$ Z5 E# E有源蜂鸣器的优点是:程序控制方便。
3 _  {" P5 R/ g  i1 v2 m9 P4 |
  s, S( {/ S+ B) G& z5 Z1 t

7 @. A' g- W$ P% Z! ?' s因为无源蜂鸣器便宜,所以大部分人手里拿到的都是无源蜂鸣器,我这里使用的是也是无源蜂鸣器。2 S' \; {' i6 s7 K
& x/ F* h9 K/ Z0 x

8 ^7 ?; D4 Q8 T& P2、编程思路
  N- ~1 O' ~/ l& b这里思路很简单,就是做一个可变时间的延时开控制发声,关于延时可以使用定时器来做,但是这里为了简便,就是用了一个简单的延时来完成。
: M+ f3 y6 P- I' l  \0 y8 }0 R: D. c, p; {# i4 c2 ^) v
6 P, F7 M8 \8 V, \* U% V3 d
延时时间的长短可以改变声音的频率,这是本程序的关键。) h" Z; F3 d& {. `) k
) {0 p  `' B5 p/ D3 d) c8 w
3 K( k4 q" T. R1 T% m
但是这里有一个问题,如果要播放频率变化特别快的音乐,比如下面的《红尘情歌》,必须控制输出电流大小,直接连接单片机IO口是不行的,加一个100K欧姆的电阻可以听出来,但是声音特别小,不适合做提示。所以,定义了一个简单的提示音数组melody,来完成功能。, F- m+ d4 V, m& k; Y9 A
" g7 G$ u; y8 f7 ~9 g. L

% R! B9 a" a1 l+ u7 i7 z% w如下图所示,是蜂鸣器与STM32连接的电路图,程序中的“PBeep”就是图中的“BEEP”IO口的宏定义,一般在头文件中进行定义:; V8 d' M+ W# |4 \* ~5 h
#define PBeep PBout(8)
0 b4 g; n6 \/ I: c- ^. S4 |- A 2.png 2 C5 ]' O$ R5 p, J

8 [) @' U' c3 [, I* A4 ^8 Y二,移植,运行,查看代码,尽力理解代码) n7 h4 D5 J) c
说明一下,我使用的芯片类型是stm32F103C8,集成开发环境用的是Keil5 MDK-ARM,仿真器使用JLINK。
% Q5 |; y% n3 r& U% ?; s查看板子上的蜂鸣器接口,相应修改下代码中的引脚。这个和硬件相关,每个人只能根据自己的硬件来调整。7 N0 e& B" y" t0 T" c, l
然后是将这个引脚配置成推挽输出,默认是低电平,输出高电平可以驱动蜂鸣器响报警。
$ y" S. ~" \: m/ I0 L& j- Z然后是开关的操作,Buzzer_On(),Buzzer_Off()来替换;
5 C; K& ?/ b$ p1 p2 z然后就开始运行,能听到蜂鸣声响,但是刺刺啦啦的,不像音乐。3 R8 A# c1 z! h5 E: g4 Q
没有办法,偷懒不行啦,就只好开始看程序了,尽力去理解代码。毕竟代码在手,任我蹂躏,哈哈!
4 r8 ^3 k! M" a7 O; h) L大体理解了一遍程序,调整了几个延时相关的值,发现效果有所好转,比较像音乐了。
+ R$ A7 P" G6 R* Z! \$ L+ [  |但是,到底对不对,对于例程中的歌曲《红尘情歌》,我没听过啊,比较不出来对不对,好不好。) w2 D' X' H" r9 o; ?
如果能添加一首自己熟悉的歌曲,就能判断了。不过,先别着急,慢慢来。先来个最简单的,就是先听听“多瑞咪”吧。' c3 r! r0 T- E0 ~0 m) W
  1. u8 music[]={13,1,2,3,4,5,6,7,8};//音调 测试基础音4 C: Y3 b8 M8 E+ |1 n6 ^
  2.     u8 time[] ={4, 4,4,4,4,4,4,4,4};//持续时长
复制代码
例子中,歌曲由两个数组来表示,一个是音调,一个是这个音调的持续时长。2 `5 k, I+ F0 C2 H' `8 t& _1 |
音调数组music[],里面的数字看起来就是123,与曲谱上的一样,它是怎么实现那个声音的呢?
5 H; f! `: o  D. u1 }看代码,是这么调用的:   6 j7 _: h) O' h
  1. tone[music[i]]
复制代码
也就是说,是把它作为索引传给了tone[]数组,而tone[]的定义如下:    ' M- V8 B: G* A/ i" r
  1. //             低7  1   2   3   4   5   6   7  高1 高2  高3 高4 高5 不发音: ~& O! U8 u+ U' U
  2.         uc16 tone[] = {247,262,294,330,349,392,440,294,523,587,659,698,784,1000};//音频数据表, C6 H) X& u1 {8 s/ T. ^
复制代码
这个数组中的数值,又有什么用呢?
" t6 ]7 Z  f& y3 T, K+ m注释说是音频数据。根据蜂鸣器的原理,就是输出不同频率的PWM波,即可发出不同的声音(音符)。然后将不同的音符组合起来就是一个曲子。所以这里的数据应该是用于控制蜂鸣器的发声频率的。, O/ Y9 N. }2 c2 U' k/ S
看看调用的地方:- O7 L$ b+ X  g- V$ r! o! b
  1. for(e=0;e<((u16)time[i])*tone[music[i]]/yanshi;e++){* V+ S5 \5 F6 x' [* w% I% D0 ]; l& z8 ]5 u
  2.             Sound((u32)tone[music[i]]);
    0 c" s- s2 C: u. L. y' O6 g
  3.         }
复制代码
查看一下Sound()这个函数,这可是核心函数:$ p; j/ F8 b* ^$ q( A1 s; Q3 @
  1. void Sound(u16 frq)+ O6 m4 F5 W- j  s) X
  2. {
    ! u8 P% s" S/ o
  3.     u32 time;% s+ Y) g4 s$ l! ^# C
  4.     if(frq != 1000)
    , ]7 l9 ^5 j9 d; U  L2 D% [
  5.     {! p7 i* c. P8 U. X0 d
  6.         time = 500000/((u32)frq);+ f' G! b; n6 D8 L( a
  7.         PBeep = 1;8 _. Y, |" ?' P! v! L4 `
  8.         delay_us(time);3 Q" {, \& ?# M. v1 Q8 K. k
  9.         PBeep = 0;: G% F/ m8 @& V$ _# b! I. g
  10.         delay_us(time);" Q/ o) ?$ X; d  `
  11.     }else
    1 J9 |. `3 b! j! I- M: d% G8 p, [
  12.         delay_us(1000);& u9 X8 P3 |, t. X* u' l
  13. }
    9 S" F; q+ `% F) H. T. Z  L
复制代码
其实也很简单,就是打开蜂鸣器,持续一会,然后关闭蜂鸣器,持续同样时间。
9 N6 ^6 w5 E  |! N2 ]" N然后再看看调用的地方:0 |# |& d' y2 `# v% L
  1.      for(e=0;e<((u16)time[i])*tone[music[i]]/yanshi;e++){& _: H4 z) ^! J% X9 _) p3 R
  2.             Sound((u32)tone[music[i]]);
    9 ?0 v8 B) l6 l/ \; R+ T
  3.         }
复制代码
是用时长来做控制,重复调用了许多次Sound()播放声音。这样持续起来,听起来就是一个音符了。
# @: {" m5 y) l# [. A; t- r/ f+ p! O6 b- D7 q$ y' c! u$ q0 p; C
跑起来程序,让我们来听听“多瑞咪”!5 U1 W( n2 V& f2 L0 ~, g* G0 L
嗯~~有点问题,有个音听起来不对劲!仔细听,数了数,对应的应该是7(第8个音),再仔细看一遍:
! n, p3 m1 V! e. f
  1. //             低7  1   2   3   4   5   6   7  高1 高2  高3 高4 高5 不发音  I: d4 ]9 x9 b( p% _. N
  2.         uc16 tone[] = {247,262,294,330,349,392,440,294,523,587,659,698,784,1000};//音频数据表. A9 D( l, w: Z4 d/ Y9 `
复制代码
这个音对应的是294,在这个数组中有些不协调啊!
( M* r! C; F. M+ T2 _$ U: r其他的数据,都是按从小到大的顺序排列的,这个数怎么比左右的数都小啊?
; }* E2 p2 ]" q4 x4 n4 P难道写错了?如果也按是顺序来的话,我看改为494比较协调。7 d3 Y# g% T; [7 d8 r3 j
那就改改呗,代码在手嘛,哈哈!. W" [* m- P. w/ S3 d! d
改了下,一听,还真就顺耳了,我是不是天才,哈哈!(A:天才?我看是多了两横。B:我看不光是要去掉两横,还要去掉一个“才”字。)
# _* R) z! {$ N8 Y3 s! P$ |闲话少说,继续调试。    5 D1 T$ H2 M) H
% w9 C* a, S6 I: x9 Y; p4 ^

% Q8 i+ g% f4 d! ~三,调整测试参数. Z, P7 _) F1 G6 W, v
    到现在,大体能听出来是个音乐了,但是播放太快了(与板子上硬件相关),不太舒服,调调延时吧。
6 k- [6 s; P* k) _5 p    大体相关的有几个参数:
. J$ p! f+ [6 G8 M9 M6 w    1,持续时长,就是time[]数组,但是,一旦调整,需要每个参数都修改一遍,太累了。
  E2 J7 v, R9 g) b0 L# p    2,有没有修改一处,所有音符的播放时长都变的?, B* B4 i" U8 `# H/ o( w
    有!就是这个:
4 W, S2 V7 J( ]: n4 l! V    yanshi = 2;//10;
! B5 h% T3 T) w+ l: j  ~* y  H2 X    把它调小,循环播放的次数是增加的,也就是音符的持续时间变长了。
: v0 g2 t, e6 B7 N8 \2 p1 w+ M    3,还有一个值,就是前面Sound()函数中的time值,这个值影响的是音调准不准。具体怎么计算的,我也没搞明白,就是多试了几个值,挑了个听着舒服的。: M; G. \* J8 w/ Y6 y
7 D8 v( d3 S6 j& Z4 ^/ D
对这几个参数一顿调整后,“多瑞咪”听着就很顺畅了。
) [+ s! \6 I" w/ C) l7 q
0 D2 K# U0 K, D% O( g1 K+ B9 p6 q- ]8 y下面,最激动人心的时刻到了!
4 T7 J. Y: b5 h$ q2 ^5 K) L. s3 {: p* O8 B* l
* M1 \2 C0 T0 I6 _' o
四,添加一首歌曲
" T0 Z* X, {# g
, S) t$ T2 z; R2 u& C* p) ^早就想添加一首歌曲了,不过真要开始添加,也还有点忐忑,得弄首自己熟悉的吧,不能弄太复杂的吧,嗯,来首儿歌吧,就选《小燕子》吧。
* N# t* _$ g, L% u, ?先上网找个谱子,仔细看看,嗯,也就是搞定两个数组嘛,一个音调,一个音长。
' L1 b" ]( r; B; P8 m; h查看乐谱,如下:
# f& N- y5 D5 U* I0 |# i# x2 S) G
* e7 I& A1 k! U7 c9 h; k: V" t真到了转换简谱到数组的时候,这时就能发现选择儿歌的好处了,所有的音调都在tone[]数组中,也就是说,在低音7到高音5之间。让我们再看一遍这个数组:
" G: y; h4 [. M
  1. //             低7  1   2   3   4   5   6   7  高1 高2  高3 高4 高5 不发音
    : _  I7 }$ @( z# A5 E& ]
  2.         uc16 tone[] = {247,262,294,330,349,392,440,494,523,587,659,698,784,1000};//音频数据表$ C' q* r5 c% D! P; |6 P- u8 @
复制代码
如果搞了一个复杂的歌曲,音调的范围超过了这个范围,就需要自己研究下那些新音符对应的是什么频率值了。
& P. D' A5 [% O9 ^: R8 g至于时长,这里是用的4表示1拍的时长。这样,半拍(一个音符下有一道横线)就是2,四分之一拍(一个音符下有两道横线)就是1了,还有两拍的,就是8。" t5 R9 d1 r) S& |3 Q6 D; a
这样,音调与时长都搞定了,是不是就ok了?
; A8 k3 u. _" |. w- q: a2 N嗯,基本上可以这么说,不过,还有些小细节,例如,在两个音符中间的一个小点是什么?) |0 U) Y8 Y+ X' a' W
我在这里就犯过错,我还以为是休止符(不知道是从那里获取的错误信息!也许真的有必要将大脑中的知识点都梳理一遍),后来查了一下,这玩意叫附点,看介绍:
. }/ R& w8 r4 t% D附点的符号很简单,就是单纯音符后面的小圆点。附点的作用是:将前面挨着它的单纯音符的时值延长一半。! `3 e% u, ?7 A6 n0 p( B
还有一个小细节,就是在一句歌词唱完之后,需要停顿一会,这时候,就用上了音频数据表中一个特殊的值:1000,前面注意看的话,在Sound()中就有对它的特殊处理,就是不开启蜂鸣器,只是一个延时。
' x, b% f  P' ]3 w这时再听听,就有些感觉了哈!" L& @0 L: [; n5 y8 n
后面就是根据整体感觉,做点微调了,例如,我就调整了下尾部拖音的时长,本来是8(两拍),我改为6(一拍半),似乎听起来更好听了,哈哈!
8 j# G" a' J1 z6 n6 l0 I6 ]" f. S7 t# S2 E/ p0 t  A$ b
下面是我修改后的代码:
8 a: ^% w9 n9 Z* n4 r0 x7 e4 m; {
  1. #include "beep.h"  B: v, s3 a; D/ V
  2. #include "stm32f10x.h"" i9 ^: k5 ~  {6 M! N
  3. #include "gpioHandler.h"5 \( n! J  t$ `
  4. #include "timerHandler.h"! a8 p0 C$ z6 Q& o

  5. & M% _/ E) i; M& y& a, R) H
  6. void BEEP_Init(void)! C' E7 L  x% [' a
  7. {   
    0 b' Q' p0 O% z" e
  8.     GPIO_InitTypeDef  GPIO_InitStructure;
    : z; N$ P$ t' N/ W

  9. . P  V( ~& I$ B+ a9 Z8 B
  10.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);         //使能A端口时钟
    : y0 y) b6 N7 I# @9 h0 a3 W  u# e
  11.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;           i! K  D7 v. _7 z- s7 o* k4 U
  12.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;                  //推挽输出/ P; z; z% i. K+ i
  13.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度50MHz
    , M" s: H) V; E6 `) l
  14.     GPIO_Init(GPIOB, &GPIO_InitStructure);          //初始化GPIOD3,6& K7 o1 v* q) P# w+ A
  15.     GPIO_SetBits(GPIOB,GPIO_Pin_5);       
    * A6 c) ~7 C2 ?; q4 S5 Q+ m9 Q: I2 u
  16. }9 T% x( M8 L2 m/ R9 z* j

  17. ) ^3 j. n; a. q3 I
  18. void Sound(u16 frq)
    2 |& E! A) C  R# z4 A
  19. {! Y8 l4 j: f& B
  20.         u32 time;
    0 B, e; N& |& q' J( H9 a
  21.         if(frq != 1000); V1 O# H* x9 c) g
  22.         {
    ; [9 L- e; j6 Y% [+ V' O+ s2 {
  23. //                time = 500000/((u32)frq);7 A& X: ^3 d7 h# h5 Z
  24.                 time = 100000/((u32)frq);- U0 M; {! ~; [
  25. //                PBeep = 1;5 I7 t' M3 K% t& P5 L
  26.                 Buzzer_On();//打开蜂鸣器--根据自己的硬件情况调整,通常就是控制蜂鸣器的gpio引脚置1" N8 D9 {3 k. q. n

  27. ! ?3 ~  ]1 M  c6 T
  28.                 delay_us(time);/ h) F3 m( z8 q( }$ L( e1 I- O) R
  29. //                PBeep = 0;
    9 v; b3 y- E  W( P
  30.                 Buzzer_Off();//关闭蜂鸣器--根据自己的硬件情况调整,通常就是控制蜂鸣器的gpio引脚置0
    / Z9 f- G1 |7 |6 j! r. a
  31.                 delay_us(time);
    7 T  w( b: x  x5 h! a
  32.         }else
    ) }; {9 T( b* |" z: e9 B6 |4 O( B8 T
  33.                 delay_us(1000);( K$ z) B, G# {6 R) \8 U
  34. }' b; y6 O8 c, r" |' P: C  h
  35. void play_music(void)
    # F2 O2 Z9 z! S& R8 z, I6 |
  36. {
    4 d8 t& k0 T1 A0 I, [
  37.         //             低7  1   2   3   4   5   6   7  高1 高2  高3 高4 高5 不发音
    ! @4 W- Q1 w1 X& g
  38.         uc16 tone[] = {247,262,294,330,349,392,440,494,523,587,659,698,784,1000};//音频数据表, P6 A+ z( f; ~( [5 q1 H1 `( V

  39. " C, f3 N, s8 h) \( }% Y
  40.         //小燕子! i6 o$ B4 [! e: p
  41.                 u8 music[]={3,5,8,6,5,13,//音调
    7 P; S, r% S2 K' i3 I
  42.                         3,5,6,8,5,13,8 h; }1 N! ^# E% L
  43.                         8,10,9,8,9,8,6,8,5,13,
    ( N& F  x) S3 H% g+ o* w- U
  44.                                         3,5,6,5,6,8,9,5,6,13,
    ' X+ E/ g4 P* Z& d0 R! f
  45.                                         3,2,1,2,13,+ z* X3 O. }, W& \: K( D8 t% H! A
  46.                                         2,2,3,5,5,8,2,3,5,13};6 |% X- ]5 T% K/ P
  47.                 u8 time[] ={2,2,2,2,6,4,//时间  
    6 w; I9 o% @/ N/ c) i
  48.                                 2,2,2,2,6,4,
    + K3 \5 K5 k' r  ~& c
  49.                 6,2,4,4,2,2,2,2,6,4,/ u/ w! c0 p, j( U
  50.                                 6,2,4,2,2,4,2,2,6,4,
    6 [0 E' R/ r# |; H# [. a
  51.                                 2,2,4,6,4,
    2 \+ Y( x  |8 p8 b. ^# v  t
  52.                                 4,2,2,4,4,4,2,2,6,4};
    2 V' Z3 i" i5 R$ r& }0 H1 U
  53. //        u8 music[]={13,1,2,3,4,5,6,7,8};//测试基础音# b- S" S" E1 I$ x5 q
  54. //        u8 time[] ={4, 4,4,4,4,4,4,4,4};
    & b- ]$ ]# U+ R, v: y; ?! v$ G

  55. 2 @- ?% v  m) B$ R2 y! A; d
  56.         u32 yanshi;
    6 e7 }; ~2 e5 S1 a
  57.         u16 i,e;
    2 I' d, `6 _3 S
  58.         yanshi = 2;//10;  4;  2
    + W! H2 }) _* z
  59.         for(i=0;i<sizeof(music)/sizeof(music[0]);i++){! M+ ~$ l& \" j- Q( h. f
  60.                 for(e=0;e<((u16)time[i])*tone[music[i]]/yanshi;e++){
    3 M/ [" {( d) j; O4 X7 |
  61.                         Sound((u32)tone[music[i]]);% w4 c) y- h7 b& x
  62.                 }       
    - c; G: D$ r. h
  63.         }  ^* m* m# a  d4 p; b* F
  64. }& v: u3 I# Z/ ]5 ]6 ]& z4 ^3 T2 c
复制代码
调用的时候,就是两句代码:
+ R: Z2 J6 C' q9 v" g
  1. BEEP_Init();7 _, I' ~* e$ k0 Q: G$ ?5 [0 k
  2.         play_music();
复制代码
2 {7 N! c5 W9 S& x* [. E8 w! b; p
5 `; c3 v7 Y. z" `4 t7 e* K0 {

( f; _- [* `  y# l; X  v
9 |) s/ y( F" G  e( R9 S' k! U* f" H: `$ x) L2 Q: Y$ [# J

$ y! L& x0 O8 q7 W0 g. P* X/ _- c2 i
赞 收藏 评论0 发布时间:2021-6-1 15:26

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版