zoukankan      html  css  js  c++  java
  • 详解树莓派Model B+控制蜂鸣器演奏乐曲

    步进电机以及无源蜂鸣器这些都需要脉冲信号才能够驱动,这里将用GPIO的PWM接口驱动无源蜂鸣器弹奏乐曲,本文基于树莓派Mode B+,其他版本树莓派实现时需参照相关资料进行修改!

    1 预备知识

    1.1 无源蜂鸣器和有源蜂鸣器

    无源蜂鸣器:内部没有震荡源,直流信号无法让它鸣叫。必须用去震荡的电流驱动它,2K-5KHZ的方波PWM (Pulse Width Modulation脉冲宽度调制)。5KHZ的电流方波就是每秒震动5K次,每一个完整的周期占用200us的时间,高点平占一部分时间,低电平占一部分时间。声音频率可控,可以做出不同的音效。

    有源蜂鸣器:内部带震荡电路,一通电就鸣叫,所以可以跟前面LED一样,给个高电平就能响,编程比无源的更方便。

    本文利用无源蜂鸣器弹奏乐曲,用的就是淘宝上普通的电磁式阻抗16欧交流/2KHz 3V 5V 12V通用无源蜂鸣器,如果手边没有无源蜂鸣器,用普通的耳机也可以来代替无源蜂鸣器。

    1.2 PWM

    PWM(Pulse Width Modulation)即脉冲宽度调制,是一种利用微处理器的数字输出来控制模拟电路的控制技术。可以用下面的一幅图来形象地说明PWM:

    图中tpwm就是一个周期的时间长度。对于2KHz频率来说,那么周期就是1s/2K=500us。图中的D叫做占空比,指的是高电平的时间占用整个周期时间的百分比。第一个周期D=50%,那么就是高电平低电平的时间各占一半。接下来的D为33%,那就是通电时间为33%,剩余的不通电时间占用67%。树莓派Model B+有4个PIN脚支持PWM输出,如下图最右侧:

    但是,需要注意的是BCM2835芯片只支持两路PWM输出,所以以上12 Pin脚和32 Pin脚对应的都是channel 1的PWM输出,即如果这两个Pin的功能都选择的是PWM输出,则它们输出的PWM是完全相同的,同理33 Pin脚和35 Pin脚对应芯片channel 2的PWM输出。

    博通公司公布的BCM2835芯片资料BCM2835 ARM Peripherals中第9章比较详细的介绍了PWM相关内容,此外还可参考网上整理好的寄存器介绍资料rip-registers,通过阅读可以得知树莓派Model B+支持两种模式的PWM输出:一种是Balanced mode(平衡模式),一种是Mark-Space mode(MS模式)。另外树莓派的PWM输出基础频率是19.2MHz,PWM输出频率受这个基础频率的限制。

    1.3 树莓派PWM分析

    进行分析前先看一下实验的物理电路连接:

    图中,红色杜邦线一头连接树莓派的32 Pin脚(PWM0),一头连接示波器的探针;绿色杜邦线一头连接树莓派的12 Pin脚(PWM0),一头连接无源蜂鸣器的正极;黄色杜邦线一头连接树莓派的6 Pin脚(ground),一头连接无源蜂鸣器的负极,此外示波器探针的ground也连接到黄色杜邦线,结合bcm2835 C library来进行分析:

    (1)下载bcm2835库:wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.50.tar.gz

    (2)解压:tar -zxvf bcm2835-1.50.tar.gz

    (3)进入目录:cd bcm2835-1.35

    (4)编译:./configure && make

    (5)安装:sudo make install

    修改examples/pwm/pwm.c的内容如下:

     1 // pwm.c
     2 //
     3 // Example program for bcm2835 library
     4 // Shows how to use PWM to control GPIO pins
     5 //
     6 // After installing bcm2835, you can build this 
     7 // with something like:
     8 // gcc -o pwm pwm.c -l bcm2835 
     9 // sudo ./pwm
    10 //
    11 // Or you can test it before installing with:
    12 // gcc -o pwm -I ../../src ../../src/bcm2835.c pwm.c
    13 // sudo ./pwm
    14 //
    15 // Author: Mike McCauley
    16 // Copyright (C) 2013 Mike McCauley
    17 // $Id: RF22.h,v 1.21 2012/05/30 01:51:25 mikem Exp $
    18 
    19 #include <bcm2835.h>
    20 #include <stdio.h>
    21 
    22 // PWM output on RPi Plug P1 pin 12 (which is GPIO pin 18)
    23 // in alt fun 5.
    24 // Note that this is the _only_ PWM pin available on the RPi IO headers
    25 #define PIN RPI_GPIO_P1_12
    26 // and it is controlled by PWM channel 0
    27 #define PWM_CHANNEL 0
    28 // This controls the max range of the PWM signal
    29 #define RANGE 1024
    30 
    31 #define PIN2 RPI_BPLUS_GPIO_J8_32
    32 
    33 int main(int argc, char **argv)
    34 {
    35     if (!bcm2835_init())
    36         return 1;
    37 
    38     // Set the output pin to Alt Fun 5, to allow PWM channel 0 to be output there
    39     bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_ALT5);
    40     
    41     bcm2835_gpio_fsel(PIN2, BCM2835_GPIO_FSEL_ALT0);    // 打开PI 32 Pin脚的PWM0输出功能
    42 
    43     // Clock divider is set to 16.
    44     // With a divider of 16 and a RANGE of 1024, in MARKSPACE mode,
    45     // the pulse repetition frequency will be
    46     // 1.2MHz/1024 = 1171.875Hz, suitable for driving a DC motor with PWM
    47     bcm2835_pwm_set_clock(BCM2835_PWM_CLOCK_DIVIDER_16);
    48     bcm2835_pwm_set_mode(PWM_CHANNEL, 0, 1);
    49     bcm2835_pwm_set_range(PWM_CHANNEL, RANGE);
    50     
    51     printf("this is banlance mode, anykey will change to markspace mode
    ");
    52     bcm2835_pwm_set_data(PWM_CHANNEL, RANGE/4);
    53     getchar();
    54     
    55     printf("change to markspace mode, anykey to exit
    ");
    56     bcm2835_pwm_set_mode(PWM_CHANNEL, 1, 1);
    57     bcm2835_pwm_set_range(PWM_CHANNEL, RANGE);
    58     bcm2835_pwm_set_data(PWM_CHANNEL, RANGE/4);
    59     getchar();
    60 
    61     bcm2835_close();
    62     return 0;
    63 }
    pwm.c

     代码中首先设置PWM输出为平衡模式,之后按任意键切换为MS模式,编译:gcc -o pwm pwm.c -lbcm2835,运行:sudo ./pwm,示波器分别捕获到如下波形图:

    balance modemarkspace mode

    代码第47行用divider=16对19.2MHz的基础频率进行调整,调整后的pwm频率为19.2MHz/16=1.2MHz,根据BCM2835芯片资料及代码49行和52行内容可知占空比应为N/M=(RANGE/4)/RANGE=256/1024,平衡模式力求任意一段时间占空比都最接近N/M=1/4,即把256个高电平时钟周期平均的分配到1024个之中周期中,可以这样进行处理,每4个时钟周期为一组,其中的一个周期内为高电平,这样即可实现“平衡”,这时真实的PWM输出帧率为1.2MHz/4=300KHz,如以上左图所示;对于MarkSpace模式来说,占空比为M/S=(RANGE/4)/RANGE=256/1024,这种模式不需要进行平衡,即可以认为1024个时钟周期的前256个为高电平,其余的为低电平,这时真实的PWM输出帧率为1.2MHz/1024=1171.875Hz,如以上右图所示。

    2 树莓派播放音乐

    2.1 乐理知识

    一首乐曲有若干音符组成,每个音符由音调和演奏时间组成。不同的音调在物理上就对应不同频率的音波。所以我们只要控制输出的频率和时长就能输出一首音乐了。当然实际的音乐很复杂,又有连接,还有重音什么的,这个就先不在讨论范围内了。

    每个音符都会播放一定的时间,这样就能构成一首歌曲。在音乐上,音符节奏分为1拍、1/2拍、1/4拍、1/8拍,假设一拍音符的时间为1;半拍为0.5;1/4拍为0.25;1/8拍为0.125……,所以我们可以为每个音符赋予这样的拍子播放出来,音乐就成了。

    Arduino官方网站给出了不同音符对应的不同频率的头文件pitches.h,相关内容可以参考博文,在本文我们把pitches.h文件直接应用到树莓派,该文件内容如下:

     1 /*************************************************
     2  * Public Constants
     3  *************************************************/
     4 
     5 #define NOTE_B0  31
     6 #define NOTE_C1  33
     7 #define NOTE_CS1 35
     8 #define NOTE_D1  37
     9 #define NOTE_DS1 39
    10 #define NOTE_E1  41
    11 #define NOTE_F1  44
    12 #define NOTE_FS1 46
    13 #define NOTE_G1  49
    14 #define NOTE_GS1 52
    15 #define NOTE_A1  55
    16 #define NOTE_AS1 58
    17 #define NOTE_B1  62
    18 #define NOTE_C2  65
    19 #define NOTE_CS2 69
    20 #define NOTE_D2  73
    21 #define NOTE_DS2 78
    22 #define NOTE_E2  82
    23 #define NOTE_F2  87
    24 #define NOTE_FS2 93
    25 #define NOTE_G2  98
    26 #define NOTE_GS2 104
    27 #define NOTE_A2  110
    28 #define NOTE_AS2 117
    29 #define NOTE_B2  123
    30 #define NOTE_C3  131
    31 #define NOTE_CS3 139
    32 #define NOTE_D3  147
    33 #define NOTE_DS3 156
    34 #define NOTE_E3  165
    35 #define NOTE_F3  175
    36 #define NOTE_FS3 185
    37 #define NOTE_G3  196
    38 #define NOTE_GS3 208
    39 #define NOTE_A3  220
    40 #define NOTE_AS3 233
    41 #define NOTE_B3  247
    42 #define NOTE_C4  262
    43 #define NOTE_CS4 277
    44 #define NOTE_D4  294
    45 #define NOTE_DS4 311
    46 #define NOTE_E4  330
    47 #define NOTE_F4  349
    48 #define NOTE_FS4 370
    49 #define NOTE_G4  392
    50 #define NOTE_GS4 415
    51 #define NOTE_A4  440
    52 #define NOTE_AS4 466
    53 #define NOTE_B4  494
    54 #define NOTE_C5  523
    55 #define NOTE_CS5 554
    56 #define NOTE_D5  587
    57 #define NOTE_DS5 622
    58 #define NOTE_E5  659
    59 #define NOTE_F5  698
    60 #define NOTE_FS5 740
    61 #define NOTE_G5  784
    62 #define NOTE_GS5 831
    63 #define NOTE_A5  880
    64 #define NOTE_AS5 932
    65 #define NOTE_B5  988
    66 #define NOTE_C6  1047
    67 #define NOTE_CS6 1109
    68 #define NOTE_D6  1175
    69 #define NOTE_DS6 1245
    70 #define NOTE_E6  1319
    71 #define NOTE_F6  1397
    72 #define NOTE_FS6 1480
    73 #define NOTE_G6  1568
    74 #define NOTE_GS6 1661
    75 #define NOTE_A6  1760
    76 #define NOTE_AS6 1865
    77 #define NOTE_B6  1976
    78 #define NOTE_C7  2093
    79 #define NOTE_CS7 2217
    80 #define NOTE_D7  2349
    81 #define NOTE_DS7 2489
    82 #define NOTE_E7  2637
    83 #define NOTE_F7  2794
    84 #define NOTE_FS7 2960
    85 #define NOTE_G7  3136
    86 #define NOTE_GS7 3322
    87 #define NOTE_A7  3520
    88 #define NOTE_AS7 3729
    89 #define NOTE_B7  3951
    90 #define NOTE_C8  4186
    91 #define NOTE_CS8 4435
    92 #define NOTE_D8  4699
    93 #define NOTE_DS8 4978
    pitches.h

    可以看到,这是一张类似表格的东西,里面是定义的大量的宏,即用宏名代替了频率名,对应到键盘的各个按键上。我们需要对应相应的音符到宏名上,为了实现这个首先看看钢琴大谱表与钢琴琴键的对照表:

    为了将个音符的音名直观的看出来,给出以下表格:

    2.2 播放音乐

    对照以上表格及射雕英雄传主题曲铁血丹心简谱实现树莓派播放,铁血丹心简谱如下:

    上面的简谱中缺少前奏,程序中增加了从其他版本中摘录的前奏部分,主程序tiexuedanxin.c代码如下:

      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <stdint.h>
      4 
      5 #include <bcm2835.h>
      6 #include "pitches.h"
      7 
      8 #define PWM_CHANNEL 0
      9 typedef struct _TONE{
     10   int freq;
     11   int t_ms;
     12 } TONE,*PTONE;
     13 
     14 int pin = RPI_GPIO_P1_12;
     15 int baseFreq = 600000;          // BCM2835_PWM_CLOCK_DIVIDER_32 对应600KHz
     16 
     17 typedef struct _melodyNode{ 
     18   int note;
     19   float fDuration;
     20 }melodyNode;
     21 
     22 melodyNode melody[]= {
     23   // 1
     24   {NOTE_A4, 1.5},      // 6
     25   {NOTE_G4, 0.5},      // 5
     26   {NOTE_A4, 1},        // 6
     27   {NOTE_G4, 0.5},      // 5
     28   {NOTE_E4, 0.5},      // 3
     29   
     30   // 2
     31   {NOTE_G4, 1},        // 5
     32   {NOTE_D4, 3},        // 2
     33   
     34   // 3
     35   {NOTE_C4, 1.5},      // 1
     36   {NOTE_A3, 0.5},      // .6
     37   {NOTE_D4, 0.5},      // 2
     38   {NOTE_E4, 0.5},      // 3
     39   {NOTE_G4, 0.5},      // 5
     40   {NOTE_F4, 0.5},      // 4
     41   
     42   // 4
     43   {NOTE_E4, 3},        // 3
     44   {NOTE_E4, 0.5},      // 3
     45   {NOTE_G4, 0.5},      // 5
     46   
     47   // 5
     48   {NOTE_A4, 1.5},      // 6
     49   {NOTE_G4, 0.5},      // 5
     50   {NOTE_A4, 1},        // 6
     51   {NOTE_G4, 0.5},      // 5
     52   {NOTE_E4, 0.5},      // 5
     53   
     54   // 6
     55   {NOTE_G4, 1},        // 5
     56   {NOTE_D4, 3},        // 2
     57   
     58   // 7
     59   {NOTE_C4, 1.5},      // 1
     60   {NOTE_A3, 0.5},      // .6
     61   {NOTE_D4, 0.5},      // 2
     62   {NOTE_E4, 0.5},      // 3
     63   {NOTE_G3, 0.5},      // .5
     64   {NOTE_B3, 0.5},      // .7
     65   
     66   // 8
     67   {NOTE_A3, 4},        // .6
     68   
     69   {0, 1},              // 0
     70   {NOTE_E4, 0.5},      // 3
     71   {NOTE_D4, 0.5},      // 2
     72   {NOTE_C4, 1.5},      // 1
     73   {NOTE_B3, 0.5},      // .7
     74   
     75   //
     76   {NOTE_A3, 1.5},      // .6
     77   {NOTE_E3, 0.5},      // .3
     78   {NOTE_A3, 2},        // .6
     79   
     80   //{NOTE_A3, 1},        // .6
     81   {NOTE_A4, 0.5},      // 6
     82   {NOTE_G4, 0.5},      // 5
     83   {NOTE_E4, 1},        // 3
     84   {NOTE_G4, 0.5},      // 5
     85   {NOTE_D4, 0.5},      // 2
     86   
     87   {NOTE_E4, 3},        // 3
     88   
     89   {NOTE_E4, 0.5},      // 3
     90   {NOTE_D4, 0.5},      // 2
     91   {NOTE_C4, 1.5},      // 1
     92   {NOTE_B3, 0.5},      // .7
     93   
     94   {NOTE_A3, 1.5},        // .6
     95   {NOTE_E3, 0.5},        // .6
     96   {NOTE_A3, 2},          // .6
     97   
     98   {0, 1},              // 0
     99   {NOTE_D4, 0.5},      // 2
    100   {NOTE_C4, 0.5},      // 1
    101   {NOTE_A3, 1},        // .6
    102   {NOTE_C4, 0.5},      // 1
    103   {NOTE_D4, 0.5},      // 1
    104   
    105   {NOTE_E4, 3},        // 3*/
    106   {NOTE_E4, 1},        // 3 逐草四方
    107   
    108   {NOTE_A4, 1.5},      // 6
    109   {NOTE_G4, 0.5},      // 5
    110   {NOTE_A4, 1},        // 6
    111   {NOTE_G4, 0.5},      // 5
    112   {NOTE_E4, 0.5},      // 3
    113   
    114   {NOTE_G4, 1},        // 5
    115   {NOTE_D4, 3},        // 2
    116   
    117   {NOTE_C4, 1.5},      // 1
    118   {NOTE_A3, 0.5},      // .6
    119   {NOTE_D4, 0.5},      // 2
    120   {NOTE_E4, 0.5},      // 3
    121   {NOTE_G4, 0.5},      // 5
    122   {NOTE_FS4, 0.5},     // #4
    123   
    124   {NOTE_E4, 3},        // 3
    125   {NOTE_E4, 0.5},      // 3
    126   {NOTE_G4, 0.5},      // 5
    127   
    128   {NOTE_A4, 1.5},      // 6
    129   {NOTE_G4, 0.5},      // 5
    130   {NOTE_A4, 1.0},      // 6
    131   {NOTE_G4, 0.5},      // 5
    132   {NOTE_E4, 0.5},      // 3
    133   
    134   {NOTE_G4, 1.0},      // 5
    135   {NOTE_D4, 3},        // 2
    136   
    137   {NOTE_C4, 1.5},      // 1
    138   {NOTE_A3, 0.5},      // .6
    139   {NOTE_D4, 0.5},      // 2
    140   {NOTE_E4, 0.5},      // 3
    141   {NOTE_G3, 0.5},      // .5
    142   {NOTE_B3, 0.5},      // .7
    143   
    144   {NOTE_A3, 3},         // .6
    145   
    146   {0, 1},              // 0
    147   {NOTE_E4, 0.5},      // 3 应知爱意似
    148   {NOTE_D4, 0.5},      // 2
    149   {NOTE_C4, 1.0},      // 1
    150   {NOTE_C4, 0.5},      // 1
    151   {NOTE_B3, 0.5},      // .7
    152   
    153   {NOTE_A3, 1.5},      // .6
    154   {NOTE_E3, 0.5},      // .3
    155   {NOTE_A3, 2.0},      // .6
    156   
    157   {0, 1},              // 0
    158   {NOTE_A3, 0.5},      // .6
    159   {NOTE_G3, 0.5},      // .5
    160   {NOTE_E3, 1.0},      // .3
    161   {NOTE_G3, 0.5},      // .5
    162   {NOTE_D3, 0.5},      // .2
    163   
    164   {NOTE_E3, 3.0},      // .3
    165   
    166   {0, 1},              // 0
    167   {NOTE_E4, 0.5},      // 3 身经百劫也
    168   {NOTE_D4, 0.5},      // 2
    169   {NOTE_C4, 1.0},      // 1
    170   {NOTE_C4, 0.5},      // 1
    171   {NOTE_B3, 0.5},      // .7
    172   
    173   {NOTE_A3, 1.5},      // .6
    174   {NOTE_E4, 0.5},      // 3
    175   {NOTE_D4, 2.0},      // 2
    176   
    177   {0, 1},              // 0
    178   {NOTE_D4, 0.5},      // 2
    179   {NOTE_C4, 0.5},      // 1
    180   {NOTE_A3, 1.0},      // .6
    181   {NOTE_B3, 0.5},      // .7
    182   {NOTE_G3, 0.5},      // .5
    183   
    184   {NOTE_A3, 3.0},      // .6
    185 };
    186 
    187 void beep(int freq, int t_ms)
    188 {
    189     int range;
    190     /*if(freq<2000||freq>5000)
    191     {
    192         printf("invalid freq
    ");
    193         return;
    194     }*/
    195     if(freq == 0)
    196         range=1;
    197     else
    198         range=baseFreq/freq;
    199     printf("will call bcm2835_pwm_set_range freq: %d range: %d
    ", freq, range);
    200     bcm2835_pwm_set_range(PWM_CHANNEL, range);
    201     bcm2835_pwm_set_data(PWM_CHANNEL, range/2);
    202     if(t_ms>0)
    203     {
    204         delay(t_ms);
    205     }
    206 }
    207 
    208 void init()
    209 {
    210     if (!bcm2835_init())
    211         exit (1) ;
    212     
    213     printf("will init pin %d
    ", pin);
    214     // Set the output pin to Alt Fun 5, to allow PWM channel 0 to be output there
    215     bcm2835_gpio_fsel(pin, BCM2835_GPIO_FSEL_ALT5);
    216     bcm2835_pwm_set_clock(BCM2835_PWM_CLOCK_DIVIDER_32);
    217     bcm2835_pwm_set_mode(PWM_CHANNEL, 1, 1);
    218 }
    219 
    220 int main (void)
    221 {
    222     int index=0;
    223     int nLen = sizeof(melody)/sizeof(melody[0]);
    224     init();
    225 
    226     for ( ; index<nLen; index++) 
    227     {
    228         int noteDuration = 600*melody[index].fDuration;
    229         beep(melody[index].note, noteDuration);
    230         printf("will call bcm2835_pwm_set_data 0 after beep
    ");
    231         bcm2835_pwm_set_data(PWM_CHANNEL, 0);
    232         printf("index: %d nLen: %d@@@@@@@@@@@@
    ", index, nLen);
    233         //delay(100);
    234     }
    235 
    236     bcm2835_pwm_set_data(PWM_CHANNEL, 0);
    237     bcm2835_close();
    238     
    239     return 0 ;
    240 }
    tiexuedanxin

    注意代码中195行做了特殊处理,这时候频率并不是为0,只是让树莓派不再发声。

  • 相关阅读:
    hdu 4578 线段树 ****
    hdu 4576 概率dp **
    hdu 4622 **
    vue中保存和获取cookie,读写cookie以及设置有效时间等,使用js-cookie
    go语言 strconv.ParseInt 的例子
    【golang】unsafe.Sizeof浅析
    Golang 漫谈之channel妙法
    总结了才知道,原来channel有这么多用法!
    字符集之在UTF-8中,一个汉字为什么需要三个字节?
    什么是Bitmap
  • 原文地址:https://www.cnblogs.com/zhaoweiwei/p/RaspberryMusic.html
Copyright © 2011-2022 走看看