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,高音18代替,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 = 261X = 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 的章節再行討論。

影片展示如下:


留言

這個網誌中的熱門文章

Python可以這樣玩(16):共陰/共陽七段顯示器

Python可以這樣玩(11):數學繪圖