【转】详解树莓派Model B+控制蜂鸣器演奏乐曲
0赞步进电机以及无源蜂鸣器这些都需要脉冲信号才能够驱动,这里将用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库:wgethttp://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的内容如下:
pwm.c
代码中首先设置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文件直接应用到树莓派,该文件内容如下:
pitches.h
可以看到,这是一张类似表格的东西,里面是定义的大量的宏,即用宏名代替了频率名,对应到键盘的各个按键上。我们需要对应相应的音符到宏名上,为了实现这个首先看看钢琴大谱表与钢琴琴键的对照表:
为了将个音符的音名直观的看出来,给出以下表格:
2.2 播放音乐
对照以上表格及射雕英雄传主题曲铁血丹心简谱实现树莓派播放,铁血丹心简谱如下:
上面的简谱中缺少前奏,程序中增加了从其他版本中摘录的前奏部分,主程序tiexuedanxin.c代码如下:
1 #include2 #include 3 #include 4 5 #include 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\n"); 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\n", 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\n", 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