详解树莓派控制蜂鸣器演奏乐曲

步进电机以及无源蜂鸣器这些都需要脉冲信号才能够驱动,这里将用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相关内容,此外还可参考网上整理好的寄存器介绍资料rpi-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来进行分析:


#下载bcm2835库

wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.50.tar.gz

#解压

tar -zxvf bcm2835-1.50.tar.gz

#进入目录

cd bcm2835-1.35

#编译

./configure && make

#安装

sudo make install

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


// pwm.c

//

// Example program for bcm2835 library

// Shows how to use PWM to control GPIO pins

//

// After installing bcm2835, you can build this 

// with something like:

// gcc -o pwm pwm.c -l bcm2835 

// sudo ./pwm

//

// Or you can test it before installing with:

// gcc -o pwm -I ../../src ../../src/bcm2835.c pwm.c

// sudo ./pwm

//

// Author: Mike McCauley

// Copyright (C) 2013 Mike McCauley

// $Id: RF22.h,v 1.21 2012/05/30 01:51:25 mikem Exp $



#include <bcm2835.h>

#include <stdio.h>



// PWM output on RPi Plug P1 pin 12 (which is GPIO pin 18)

// in alt fun 5.

// Note that this is the _only_ PWM pin available on the RPi IO headers

#define PIN RPI_GPIO_P1_12

// and it is controlled by PWM channel 0

#define PWM_CHANNEL 0

// This controls the max range of the PWM signal

#define RANGE 1024



#define PIN2 RPI_BPLUS_GPIO_J8_32



int main(int argc, char **argv)

{

    if (!bcm2835_init())

        return 1;



    // Set the output pin to Alt Fun 5, to allow PWM channel 0 to be output there

    bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_ALT5);

    

    bcm2835_gpio_fsel(PIN2, BCM2835_GPIO_FSEL_ALT0);    // 打开PI 32 Pin脚的PWM0输出功能



    // Clock divider is set to 16.

    // With a divider of 16 and a RANGE of 1024, in MARKSPACE mode,

    // the pulse repetition frequency will be

    // 1.2MHz/1024 = 1171.875Hz, suitable for driving a DC motor with PWM

    bcm2835_pwm_set_clock(BCM2835_PWM_CLOCK_DIVIDER_16);

    bcm2835_pwm_set_mode(PWM_CHANNEL, 0, 1);

    bcm2835_pwm_set_range(PWM_CHANNEL, RANGE);

    

    printf("this is banlance mode, anykey will change to markspace mode\n");

    bcm2835_pwm_set_data(PWM_CHANNEL, RANGE/4);

    getchar();

    

    printf("change to markspace mode, anykey to exit\n");

    bcm2835_pwm_set_mode(PWM_CHANNEL, 1, 1);

    bcm2835_pwm_set_range(PWM_CHANNEL, RANGE);

    bcm2835_pwm_set_data(PWM_CHANNEL, RANGE/4);

    getchar();



    bcm2835_close();

    return 0;

}

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


代码第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文件直接应用到树莓派,该文件内容如下:


/*************************************************

 * Public Constants

 *************************************************/

#define NOTE_B0  31

#define NOTE_C1  33

#define NOTE_CS1 35

#define NOTE_D1  37

#define NOTE_DS1 39

#define NOTE_E1  41

#define NOTE_F1  44

#define NOTE_FS1 46

#define NOTE_G1  49

#define NOTE_GS1 52

#define NOTE_A1  55

#define NOTE_AS1 58

#define NOTE_B1  62

#define NOTE_C2  65

#define NOTE_CS2 69

#define NOTE_D2  73

#define NOTE_DS2 78

#define NOTE_E2  82

#define NOTE_F2  87

#define NOTE_FS2 93

#define NOTE_G2  98

#define NOTE_GS2 104

#define NOTE_A2  110

#define NOTE_AS2 117

#define NOTE_B2  123

#define NOTE_C3  131

#define NOTE_CS3 139

#define NOTE_D3  147

#define NOTE_DS3 156

#define NOTE_E3  165

#define NOTE_F3  175

#define NOTE_FS3 185

#define NOTE_G3  196

#define NOTE_GS3 208

#define NOTE_A3  220

#define NOTE_AS3 233

#define NOTE_B3  247

#define NOTE_C4  262

#define NOTE_CS4 277

#define NOTE_D4  294

#define NOTE_DS4 311

#define NOTE_E4  330

#define NOTE_F4  349

#define NOTE_FS4 370

#define NOTE_G4  392

#define NOTE_GS4 415

#define NOTE_A4  440

#define NOTE_AS4 466

#define NOTE_B4  494

#define NOTE_C5  523

#define NOTE_CS5 554

#define NOTE_D5  587

#define NOTE_DS5 622

#define NOTE_E5  659

#define NOTE_F5  698

#define NOTE_FS5 740

#define NOTE_G5  784

#define NOTE_GS5 831

#define NOTE_A5  880

#define NOTE_AS5 932

#define NOTE_B5  988

#define NOTE_C6  1047

#define NOTE_CS6 1109

#define NOTE_D6  1175

#define NOTE_DS6 1245

#define NOTE_E6  1319

#define NOTE_F6  1397

#define NOTE_FS6 1480

#define NOTE_G6  1568

#define NOTE_GS6 1661

#define NOTE_A6  1760

#define NOTE_AS6 1865

#define NOTE_B6  1976

#define NOTE_C7  2093

#define NOTE_CS7 2217

#define NOTE_D7  2349

#define NOTE_DS7 2489

#define NOTE_E7  2637

#define NOTE_F7  2794

#define NOTE_FS7 2960

#define NOTE_G7  3136

#define NOTE_GS7 3322

#define NOTE_A7  3520

#define NOTE_AS7 3729

#define NOTE_B7  3951

#define NOTE_C8  4186

#define NOTE_CS8 4435

#define NOTE_D8  4699

#define NOTE_DS8 4978

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

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

2.2 播放音乐

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

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


#include <stdio.h>

#include <stdlib.h>

#include <stdint.h>



#include <bcm2835.h>

#include "pitches.h"



#define PWM_CHANNEL 0

typedef struct _TONE{

  int freq;

  int t_ms;

} TONE,*PTONE;



int pin = RPI_GPIO_P1_12;

int baseFreq = 600000;          // BCM2835_PWM_CLOCK_DIVIDER_32 对应600KHz



typedef struct _melodyNode{ 

  int note;

  float fDuration;

}melodyNode;



melodyNode melody[]= {

  // 1

  {NOTE_A4, 1.5},      // 6

  {NOTE_G4, 0.5},      // 5

  {NOTE_A4, 1},        // 6

  {NOTE_G4, 0.5},      // 5

  {NOTE_E4, 0.5},      // 3

  

  // 2

  {NOTE_G4, 1},        // 5

  {NOTE_D4, 3},        // 2

  

  // 3

  {NOTE_C4, 1.5},      // 1

  {NOTE_A3, 0.5},      // .6

  {NOTE_D4, 0.5},      // 2

  {NOTE_E4, 0.5},      // 3

  {NOTE_G4, 0.5},      // 5

  {NOTE_F4, 0.5},      // 4

  

  // 4

  {NOTE_E4, 3},        // 3

  {NOTE_E4, 0.5},      // 3

  {NOTE_G4, 0.5},      // 5

  

  // 5

  {NOTE_A4, 1.5},      // 6

  {NOTE_G4, 0.5},      // 5

  {NOTE_A4, 1},        // 6

  {NOTE_G4, 0.5},      // 5

  {NOTE_E4, 0.5},      // 5

  

  // 6

  {NOTE_G4, 1},        // 5

  {NOTE_D4, 3},        // 2

  

  // 7

  {NOTE_C4, 1.5},      // 1

  {NOTE_A3, 0.5},      // .6

  {NOTE_D4, 0.5},      // 2

  {NOTE_E4, 0.5},      // 3

  {NOTE_G3, 0.5},      // .5

  {NOTE_B3, 0.5},      // .7

  

  // 8

  {NOTE_A3, 4},        // .6

  

  {0, 1},              // 0

  {NOTE_E4, 0.5},      // 3

  {NOTE_D4, 0.5},      // 2

  {NOTE_C4, 1.5},      // 1

  {NOTE_B3, 0.5},      // .7

  

  //

  {NOTE_A3, 1.5},      // .6

  {NOTE_E3, 0.5},      // .3

  {NOTE_A3, 2},        // .6

  

  //{NOTE_A3, 1},        // .6

  {NOTE_A4, 0.5},      // 6

  {NOTE_G4, 0.5},      // 5

  {NOTE_E4, 1},        // 3

  {NOTE_G4, 0.5},      // 5

  {NOTE_D4, 0.5},      // 2

  

  {NOTE_E4, 3},        // 3

  

  {NOTE_E4, 0.5},      // 3

  {NOTE_D4, 0.5},      // 2

  {NOTE_C4, 1.5},      // 1

  {NOTE_B3, 0.5},      // .7

  

  {NOTE_A3, 1.5},        // .6

  {NOTE_E3, 0.5},        // .6

  {NOTE_A3, 2},          // .6

  

  {0, 1},              // 0

  {NOTE_D4, 0.5},      // 2

  {NOTE_C4, 0.5},      // 1

  {NOTE_A3, 1},        // .6

  {NOTE_C4, 0.5},      // 1

  {NOTE_D4, 0.5},      // 1

  

  {NOTE_E4, 3},        // 3*/

  {NOTE_E4, 1},        // 3 逐草四方

  

  {NOTE_A4, 1.5},      // 6

  {NOTE_G4, 0.5},      // 5

  {NOTE_A4, 1},        // 6

  {NOTE_G4, 0.5},      // 5

  {NOTE_E4, 0.5},      // 3

  

  {NOTE_G4, 1},        // 5

  {NOTE_D4, 3},        // 2

  

  {NOTE_C4, 1.5},      // 1

  {NOTE_A3, 0.5},      // .6

  {NOTE_D4, 0.5},      // 2

  {NOTE_E4, 0.5},      // 3

  {NOTE_G4, 0.5},      // 5

  {NOTE_FS4, 0.5},     // #4

  

  {NOTE_E4, 3},        // 3

  {NOTE_E4, 0.5},      // 3

  {NOTE_G4, 0.5},      // 5

  

  {NOTE_A4, 1.5},      // 6

  {NOTE_G4, 0.5},      // 5

  {NOTE_A4, 1.0},      // 6

  {NOTE_G4, 0.5},      // 5

  {NOTE_E4, 0.5},      // 3

  

  {NOTE_G4, 1.0},      // 5

  {NOTE_D4, 3},        // 2

  

  {NOTE_C4, 1.5},      // 1

  {NOTE_A3, 0.5},      // .6

  {NOTE_D4, 0.5},      // 2

  {NOTE_E4, 0.5},      // 3

  {NOTE_G3, 0.5},      // .5

  {NOTE_B3, 0.5},      // .7

  

  {NOTE_A3, 3},         // .6

  

  {0, 1},              // 0

  {NOTE_E4, 0.5},      // 3 应知爱意似

  {NOTE_D4, 0.5},      // 2

  {NOTE_C4, 1.0},      // 1

  {NOTE_C4, 0.5},      // 1

  {NOTE_B3, 0.5},      // .7

  

  {NOTE_A3, 1.5},      // .6

  {NOTE_E3, 0.5},      // .3

  {NOTE_A3, 2.0},      // .6

  

  {0, 1},              // 0

  {NOTE_A3, 0.5},      // .6

  {NOTE_G3, 0.5},      // .5

  {NOTE_E3, 1.0},      // .3

  {NOTE_G3, 0.5},      // .5

  {NOTE_D3, 0.5},      // .2

  

  {NOTE_E3, 3.0},      // .3

  

  {0, 1},              // 0

  {NOTE_E4, 0.5},      // 3 身经百劫也

  {NOTE_D4, 0.5},      // 2

  {NOTE_C4, 1.0},      // 1

  {NOTE_C4, 0.5},      // 1

  {NOTE_B3, 0.5},      // .7

  

  {NOTE_A3, 1.5},      // .6

  {NOTE_E4, 0.5},      // 3

  {NOTE_D4, 2.0},      // 2

  

  {0, 1},              // 0

  {NOTE_D4, 0.5},      // 2

  {NOTE_C4, 0.5},      // 1

  {NOTE_A3, 1.0},      // .6

  {NOTE_B3, 0.5},      // .7

  {NOTE_G3, 0.5},      // .5

  

  {NOTE_A3, 3.0},      // .6

};



void beep(int freq, int t_ms)

{

    int range;

    /*if(freq<2000||freq>5000)

    {

        printf("invalid freq\n");

        return;

    }*/

    if(freq == 0)

        range=1;

    else

        range=baseFreq/freq;

    printf("will call bcm2835_pwm_set_range freq: %d range: %d\n", freq, range);

    bcm2835_pwm_set_range(PWM_CHANNEL, range);

    bcm2835_pwm_set_data(PWM_CHANNEL, range/2);

    if(t_ms>0)

    {

        delay(t_ms);

    }

}



void init()

{

    if (!bcm2835_init())

        exit (1) ;

    

    printf("will init pin %d\n", pin);

    // Set the output pin to Alt Fun 5, to allow PWM channel 0 to be output there

    bcm2835_gpio_fsel(pin, BCM2835_GPIO_FSEL_ALT5);

    bcm2835_pwm_set_clock(BCM2835_PWM_CLOCK_DIVIDER_32);

    bcm2835_pwm_set_mode(PWM_CHANNEL, 1, 1);

}



int main (void)

{

    int index=0;

    int nLen = sizeof(melody)/sizeof(melody[0]);

    init();



    for ( ; index<nLen; index++) 

    {

        int noteDuration = 600*melody[index].fDuration;

        beep(melody[index].note, noteDuration);

        printf("will call bcm2835_pwm_set_data 0 after beep\n");

        bcm2835_pwm_set_data(PWM_CHANNEL, 0);

        printf("index: %d nLen: %d@@@@@@@@@@@@\n", index, nLen);

        //delay(100);

    }



    bcm2835_pwm_set_data(PWM_CHANNEL, 0);

    bcm2835_close();

    

    return 0 ;

}

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

转自 https://www.cnblogs.com/zhaoweiwei/p/RaspberryMusic.html

这是一篇发布于 4年 前的文章,其中的信息可能已经有所发展或是发生改变,请了解。


3 评论

发表评论

你的邮件地址不会公开


*