Python可以這樣玩(15):蜂鳴器與音樂

蜂鳴器是一種一體化結構的電子發聲器,採用直流電壓供電,廣泛應用於電腦、印表機、影印機、報警器、電子玩具、汽車電子設備、電話機、計時器等電子產品中作發聲器件。




按其驅動方式的不同,可分為:有源蜂鳴器(內含驅動線路)和無源蜂鳴器(外部驅動)。教你區分有源蜂鳴器和無源蜂鳴器,現在市場上出售的一種小型蜂鳴器,因其體積小(直徑只有 11 mm)、重量輕、價格低、結構牢靠,而廣泛地應用在各種需要發聲的電器設備、電子製作和單片機等電路中。有源蜂鳴器和無源蜂鳴器的外觀好像一樣,但仔細看,兩者的高度略有區別,有源蜂鳴器,高度為 9 mm,而無源蜂鳴器的高度為 8mm。如將兩種蜂鳴器的引腳朝上放置時,可以看出有綠色電路板的一種是無源蜂鳴器,沒有電路板而用黑膠封閉的一種是有源蜂鳴器。

工作原理

蜂鳴器發聲原理是電流通過電磁線圈,使電磁線圈產生磁場來驅動振動膜發聲的,因此需要一定的電流才能驅動它,本實驗用的蜂鳴器內部帶有驅動電路,所以可以直接使用。當蜂鳴器連接的引腳為高電壓時,內部驅動電路導通,蜂鳴器發出聲音;當蜂鳴器連接的引腳為低電壓,內部驅動電路截止,蜂鳴器不發出聲音。

蜂鳴器的連線

本實驗用的蜂鳴器內部帶有驅動電路,所以可以直接將蜂鳴器的正極連接到數位口,蜂鳴器的負極連接到 GND 插口中。如下圖 (實際接線的時候,我並沒有把上個範例中的LED拆除,因此我把蜂鳴器的正極接到 PIN 10):


實際連線的時候,建議使用編號 ZK-1205S 的蜂鳴器,它會麵包板的格子剛好契合。注意上面的正負極。

蜂鳴器模擬救護車警笛聲音實驗

實驗原理:蜂鳴器發出聲音的時間間隔不同,頻率就不同,所以發出的聲音就不同。根據返一原理我們通過改發蜂鳴器發出聲音的時間間隔,來發出不同種聲音,來模擬各種聲音。本程式首先讓蜂鳴器間隔 1ms 發出一種頻率的聲音,迴圈 80 次;接著讓蜂鳴器間隔 2ms 發出另一種頻率的聲音,迴圈 100 次。程式我改寫如下,原本蜂鳴器執行的動作是放在 loop() 裡面的,但是由於執行起來停不了太吵了,所以我把主程式放在 setup() 裡面,只會執行三次。當然,如果你要保持原來的程式,讓它一直在loop()裡面執行,建議加一個開關,按下的時候才會響,開關的線路要如何加?思考一下。

int buzzer=10;
void setup()
{
  pinMode(buzzer,OUTPUT);
    unsigned char i,j,times=0;
  while(times<3)
  {
    for(i=0;i<80;i++)
    {
      digitalWrite(buzzer,HIGH);
      delay(1);
      digitalWrite(buzzer,LOW);
      delay(1);
    }
    for(i=0;i<100;i++)
    {
      digitalWrite(buzzer,HIGH);
      delay(2);
      digitalWrite(buzzer,LOW);
      delay(2);
    }
    times+=1;
  }
}
void loop()
{

}


接下來請直接按上傳。不過這裡位遇到問題,如果之前正在使用 Python 控制 Arduino,可能就會報錯,COM3被占用,請關閉 Python並按下UNO上面的RESET按鈕,或移除USB重新插入一次。

讓常見的蜂鳴器唱歌

讓蜂鳴器亂叫的程式很簡單,如果能讓它唱歌就更好了。還記得我們在前一個章節中實做出讓多個LED燈按照音階閃爍,如果我們把這兩種電路結合在一起,效果勢必不錯。

要讓蜂鳴器唱歌就是讓蜂鳴器可以產生不同頻率的聲音, Arduino 上要用兩個指令:tone() / noTone() 來達到,我修改了網路上其他網友分享的範例,搭配我們特別設計的電路板,如下圖,做出可以放音樂並且同時亮起各個音階上的LED燈的功能:



如果對線路不清,可以放大此照片。因為蜂鳴器真的很吵,我特別多加了一個開關按鈕在蜂鳴器旁邊,想聽歌的時候,按下去即可。

指令的用法如下:

tone(pin, frequency, duration)

pin:輸出方波訊號的 digital pin
frequency (unsigned int):讓蜂鳴器發出的聲音的對應頻率
duration (unsigned long):發出聲音的長短
使用 tone() 的時候後面依然要搭配 delay (duration) 才能夠讓執行續等待聲音放完。

noTone (pin):

將指定 pin 的聲音停止,蜂鳴器一次只能撥放一個音階,如果要做和絃的效果,必須使用兩個蜂鳴器。先不要想那麼多,我們先做單音。

接下來要了解每個音階的頻率,請參考下面的表格,我們直接把數字用在我們的程式裡面就好:




最後,我們要來編寫程式了,C程式內容如下:

int speakerPin = 10;
// 依照數字簡譜1-7,高音1用8代替,0代表休止符
// 每個音階的拍子,寫在一起方便對照
int notes[] = {1,1,5,5,6,6,5,4,4,3,3,2,2,1,5,5,4,4,3,3,2,5,5,4,4,3,3,2,1,1,5,5,6,6,5,4,4,3,3,2,2,1,0};
int beats[] = {1,1,1,1,1,1,2,1,1,1,1,1,1,2,1,1,1,1,1,1,2,1,1,1,1,1,1,2,1,1,1,1,1,1,2,1,1,1,1,1,1,2,8};
// 利用 sizeof(),算出總共要多少音符
int length = sizeof(notes)/sizeof(notes[0]);
// 決定一拍多長,這邊一拍 300 ms
int tempo = 300;

void setup() {
  pinMode(speakerPin, OUTPUT);
  pinMode(2,OUTPUT);  //燈號Do
  pinMode(3,OUTPUT);  //燈號Re
  pinMode(4,OUTPUT);  //燈號Mi
  pinMode(5,OUTPUT);  //燈號Fa
  pinMode(6,OUTPUT);  //燈號So
  pinMode(7,OUTPUT);  //燈號La
  pinMode(8,OUTPUT);  //燈號Si
  pinMode(9,OUTPUT);  //燈號Do
}

void loop() {
  // 利用 for 來播放我們設定的歌曲,一個音一個音撥放
  for (int i = 0; i < length; i++) {
    // 如果是0的話,不撥放音樂
    if (notes[i] == 0) {
      delay(beats[i] * tempo); // rest
    } else {
      // 呼叫 palyNote() 這個 function,將音符轉換成訊號讓蜂鳴器發聲
      playNote(speakerPin,notes[i], beats[i] * tempo);
    }
    // 讓每個音符之間停頓一下
    delay(tempo/10);
  }
}

void playNote(int OutputPin, int note, int duration) {
   // 音符字元與對應的頻率由兩個矩陣表示
  int names[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
  int tones[] = { 261, 294, 330, 349, 392, 440, 494, 523 };
  int leds[] = { 2, 3, 4, 5, 6, 7, 8, 9 };
  // 播放音符對應的頻率
  for (int i = 0; i < 8; i++) {
    if (names[i] == note) {
      tone(OutputPin,tones[i], duration);
      //讓燈號亮起來
      digitalWrite(leds[i], HIGH);
      //下方的 delay() 及 noTone (),一定要有這兩行
      delay(duration);
      noTone(OutputPin);
      digitalWrite(leds[i], LOW);
    }
  }
}

C程式碼說明

首先,為了同時控制音階與節拍,我用了兩個陣列來儲存,請看下面兩行:
int notes[] = {1,1,5,5,6,6,5,4,4,3,3,2,2,1,5,5,4,4,3,3,2,5,5,4,4,3,3,2,1,1,5,5,6,6,5,4,4,3,3,2,2,1,0};
int beats[] = {1,1,1,1,1,1,2,1,1,1,1,1,1,2,1,1,1,1,1,1,2,1,1,1,1,1,1,2,1,1,1,1,1,1,2,1,1,1,1,1,1,2,8};

這種方式有一個好處,就是你可以上下比對,一眼看出音階對應的節拍,比較直觀。接著,我們利用 playNote(speakerPin,notes[i], beats[i] * tempo) 這個副程式來撥放音樂,把 notes 與 beats 傳入,就知道每一個音要響多久。最後,在副程式的 for 迴圈裡面利用下面幾個動作來完成控制:

    if (names[i] == note) {
      tone(OutputPin,tones[i], duration);
      //讓燈號亮起來
      digitalWrite(leds[i], HIGH);
      //下方的 delay() 及 noTone (),一定要有這兩行
      delay(duration);
      noTone(OutputPin);
      digitalWrite(leds[i], LOW);

所有動作都是相對應的,亮燈、響聲、停頓、滅燈、止聲。這樣音樂就會一直循環的撥放。如果要樣它停止,請拔掉USB。

使用 Python 演奏音樂

現在讓我們用 Python 來改寫這段C語言程式,同樣的,請先上傳 Standard Firmata,你可以從 [檔案] [開啟最近] 裡面找到之前開啟過的 Standard Firmata 草稿,然後請按下 [上傳]。

最後你會看到紅色的訊息 “avardude done: Thank you.”,就代表上傳成功。

開啟 Python IDLE 馬上就會發現,Firmata 並不支援 tone() 與 noTone() 這兩個函數,也就是說,我們必須自己設計 tone() 的功能,好讓蜂鳴器發出音樂。所以,一切必須從頭開始。

我們回到一開始的救護車警示聲的程式:

    for(i=0;i<80;i++)
    {
      digitalWrite(buzzer,HIGH);
      delay(1);
      digitalWrite(buzzer,LOW);
      delay(1);
    }
    for(i=0;i<100;i++)
    {
      digitalWrite(buzzer,HIGH);
      delay(2);
      digitalWrite(buzzer,LOW);
      delay(2);
    }
要讓蜂鳴器發出聲音很簡單,輸入高電壓1ms 然後再輸入低電壓1ms,就是一個音頻,輸入高電壓2ms 再輸入低電壓2ms,又是另一個音頻。而頻率 Hz的定義,是指每秒鐘震動的次數。以第一個音頻為例,兩個 1ms 組合就等於震動一次需要 2ms,而 1 sec = 1000 ms,因此,1000 ms/2 ms = 500 Hz 就是第一個音的頻率,1000 ms/4 ms = 250 Hz 就是第二個音的頻率,這兩個音就組成救護車警笛聲。

有了這樣的概念就好辦了,我們知道 Do 到高音都的頻率如下:
from pyfirmata import Arduino
from time import sleep

# 定義八個音
notes = [ 261, 294, 330, 349, 392, 440, 494, 523 ]

把上面的公式反過來運算,1000 ms / 2 X ms = 261,X = 500 ms / 261 = 1.915 ms,也就是我們只要把 HIGH 和 LOW 的 delay 時間都設定成 1.915 ms ,在 Python 用 sleep(0.001915) 就可以發出 Do 的聲音了。

以上的理論是沒有問題的,但是卻遇到精度的問題,還記得在C語言程式裡面使用的 delay(1) 嗎?在這裡 delay() 的最小單位是毫秒(ms),並不支援 1.915 或是 1.7 毫秒,而在 Python 中所使用的 sleep() 的單位是秒,就更不可能達成,所以聽到的聲音都是一樣的。

結論是,如果想要用蜂鳴器撥放音樂,還是要回到 Arduino IDE 使用 tone()、noTone()來寫程式才能達成,至於 Python 如何玩音樂,我們會放到後面討論 Pymata 以及 Pygame 的章節再行討論。

影片展示如下:


留言

這個網誌中的熱門文章

LINE 開發工具

Line Manager 使用說明