詳解樹莓派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\n");
52     bcm2835_pwm_set_data(PWM_CHANNEL, RANGE/4);
53     getchar();
54     
55     printf("change to markspace mode, anykey to exit\n");
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\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<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\n");
231         bcm2835_pwm_set_data(PWM_CHANNEL, 0);
232         printf("index: %d nLen: %d@@@@@@@@@@@@\n", 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,只是讓樹莓派不再發聲。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章