很早就想做這個實驗, 因為只需要 Arduino NANO 加上一個蜂鳴器就可以進行了, 頂多加一顆按鈕開關, 這跟物聯網無關, 因此不需要用到 ESP8266 上網. 趁著中秋連假, 就把這個簡單的小實驗做完吧!
以下實驗所用的程式是參考下列幾本書裡面的範例加以修改來的 :
- Arduino 輕鬆入門, 葉難, 博碩出版 (第 6 章)
- Arduino 互動設計入門 2, 趙英傑, 旗標 (第 13-1, 13-2 節)
- Arduino 最佳入門與應用第二版, 楊明峰, 碁峰 (第 11 章)
- Arduino Cookbook 錦囊妙計, 歐萊禮 (第 9 節)
我使用之前製作的 Arduino NANO+ESP8266 IOT 模組來進行實驗, 在設計此模組時有考量到如果偵測到異常狀況可能需要發出警告音, 所以有加裝了一個無源蜂鳴器 (Passive piezo speaker), 透過一個跳線針腳連接到 Arduino 的 D9 腳, 另一端接地即可. 由於無源蜂鳴器為高阻抗, 因此不需要串聯限流電阻. 如果專案中不需要用到蜂鳴器, 且又必須用到 D9 腳時, 可將跳線帽拔掉, 讓蜂鳴器與 D9 脫鉤即可, 如下圖左半部所示 (左上是跳線帽, 左下圓形物為蜂鳴器, 此為第一版的 IOT 模組) :
關於此模組佈線圖與製作方式, 參考 :
#
製作 Arduino Nano + ESP8266 物聯網模組 (四) : 完結篇
注意, 購買蜂鳴器要指名無源蜂鳴器, 這樣才能透過 PWM 方式控制其發聲頻率 (又稱為它激式), 不要買到有源蜂鳴器, 這種蜂鳴器內建震盪電路 (又稱為自激式), 只要一通電就會發出固定頻率的聲音, 無法利用 PWM 對其音頻進行控制. 另外還要注意其電壓, 要配合 Arduino 板是跑 3.3V 還是 5V 而定, 我都使用 5V 系統, 需要 3.3V 時再用 AMS1117 來降壓. 我買的無源蜂鳴器長相如下 :
剛買來上面有一張貼紙, 標示其接腳有分正負極, 長腳為正, 短腳為負.
首先說明一下蜂鳴器的發聲原理, 一般的無源蜂鳴器主要是由一片金屬片 (銅片) 與壓電感應材料構成, 通電後金屬片會移動 (稱為壓電效應), 無電時金屬片復位, 亦即壓電效應可將電能轉換成機械能, 跟一般喇叭的音盆震動發聲原理相似.
原本聲音是連續的類比信號, 數位系統無法輸出類比信號, 但是可以利用不同頻率的方波來發聲, 因為如果對蜂鳴器施予週期性方波, 金屬片就會來回震動, 只要方波週期在 20Hz 到20KHz 之間, 就會發出人耳聽得到的聲音.
利用數位方波來模擬類比效果的方法稱為脈寬調變 PWM (Pulse Width Modulation), 參考 :
#
[Arduino] 脈衝寬度調變 (PWM) 與 Arduino – Pulse Width Modulation
#
Make: Projects|技能培養系列:進階Arduino聲響合成技術
#
[Arduino] 脈衝寬度調變 (PWM) 與 Arduino – Pulse Width Modulation
PWM 有兩個參數, 一個是單位為 Hz 的頻率, 即週期的倒數, 它決定了聲音的音高 (Pitch); 另一個參數是 Duty Cycle (工作週期), 方波一個週期有 HIGH (峰值) 與 LOW (谷值) 兩個狀態, 峰值時間佔整個週期的百分比稱為 Duty Cycle, 它決定了聲音的音色.
Duty cycle=峰值時間/週期
Arduino 內建了 tone() 與 noTone() 兩個函式來對數位接腳輸出 Duty Cycle 為 50% 的方波 (即 HIGH, LOW 各佔週期之一半) :
其中 tone() 有三個參數 tone(pin, frequency, duration) 與兩個參數 tone(pin, frequency) 兩種, 前者有指定發音期間的時間到時就會停止; 而後者會一直發音直到呼叫 noTone(pin) 才會停止.
注意, Arduino 同時只能對一個數位接腳輸出一個音調, 它既不能同時讓兩個數位接腳發聲, 也不能對同一接腳發出兩種頻率的聲音. 這是因為 ATmega328 處理器只有三個硬體計時器, 其中 timer0 被 millis() 函式用掉了 (它也負責 D5 與 D6 的 PWM 輸出); 而 timer1 則被 Servo.h 函式庫用掉了 (它也負責 D9 與 D10 的 PWM 輸出); 所以 tone() 只能用剩下的 timer2 來產生音調. 注意,
因為 timer2 負責 D3 與 D11 的 PWM, 所以使用 tone() 函數時會干擾這兩個接腳的 PWM 輸出功能. ATmega328 硬體計時器使用情形摘要如下 :
硬體計時器 | 控制 PWM 接腳 | 預設使用者 |
timer0 | D5, D6 | millis() |
timer1 | D9, D10 | Servo.h |
timer2 | D3, D11 | tone() |
參考 "
Arduino 輕鬆入門" 第 6-4 節說明以及下列文章 :
#
Questions about millis()
#
http://playground.arduino.cc/ComponentLib/Servotimer1
下面我們就來測試一下 tone() 函數, 首先測試 1 是讓 Arduino 發出 1 KHz 的告警音, 這可以用在偵測到異常時發出訊號用 :
測試 1 :
int buzzerPin=9; //D9 conectted to a buzzer
void setup() {
pinMode(buzzerPin, OUTPUT);
}
void loop() {
alarmBeep(buzzerPin);
}
void
alarmBeep(int pin) {
tone(pin, 1000, 1000);
delay(2000);
}
此程式會先發出 1 秒 Bee 後停兩秒, 如此周而復始.
下面測試 2 則是模擬鬧鐘的鬧鈴音 :
測試 2 :
int buzzerPin=9;
void setup() {
pinMode(buzzerPin, OUTPUT);
}
void loop() {
alarmClockBeep(buzzerPin);
}
void
alarmClockBeep(int pin) {
tone(pin, 1000, 100);
delay(200);
tone(pin, 1000, 100);
delay(200);
tone(pin, 1000, 100);
delay(200);
tone(pin, 1000, 100);
delay(1000);
}
此程式是發出 4 聲短促的 Bee 聲後暫停 1 秒, 周而復始.
另外我在 "
Arduino 最佳入門與應用" 11-3 節看到一個模擬電話鈴聲的範例, 我將其改寫為如下測試 3 :
測試 3 :
int buzzerPin=9;
void setup() {
pinMode(buzzerPin, OUTPUT);
}
void loop() {
ringTone(buzzerPin);
}
void
ringTone(int pin) {
for (int i=0; i<10; i++) { //repeat 10 times
tone(pin, 1000);
delay(50);
tone(pin, 500);
delay(50);
}
noTone(pin);
delay(2000);
}
電話振鈴音是以 1000 Hz 與 500 Hz 間隔 50 毫秒重複 10 次模擬出來的, 然後用 noTone() 來暫停 2 秒. 我覺得這電話鈴聲跟真的一樣耶! 還蠻有趣的.
還有一個常作為告警鈴聲的是警車的笛聲, 我參考了下面這篇文章加以改編成測試 4 :
#
Arduino Police Light and Sound
測試 4 :
int buzzerPin=9;
void setup() {
pinMode(buzzerPin, OUTPUT);
}
void loop() {
policeSiren(buzzerPin);
}
void policeSiren(int pin) {
for (int i=150; i<1800; i++) { //upward tone
tone(pin, i, 10);
delay(1);
}
for (int i=1800; i>150; i--) { //downward tone
tone(pin, i, 10);
delay(1);
}
}
還有一個可以表示系統有問題的音訊是救護車 "歐伊歐伊" 的笛聲, 我參考下面這篇文章裡的範例, 將其修改為測試 5 :
#
Arduino Fun – Door Entry Alarm
測試 5 :
int buzzerPin=9;
void setup() {
pinMode(buzzerPin, OUTPUT);
}
void loop() {
ambulenceSiren(buzzerPin);
}
void
ambulenceSiren(int pin) {
tone(pin, 400);
delay(500);
tone(pin, 800);
delay(500);
noTone(pin);
}
其他參考 :
#
Arduino Siren Sound Generator Circuit - Customizable
除了產生機械音外, Arduino 也可以產生樂音, 即依據樂理發出特定頻率與節拍的聲音. 頻率部分, 聲音的頻率有時又被稱為音高 (Pitch), 但所謂的音高其實是指我們的聽覺對物理世界中的聲音頻率之主觀認知, 與機械性客觀的頻率意涵不同, 但在指涉頻率高低上兩者意思一樣, 所以兩個詞也就經常被混用了.
樂理上把一個音階分成了 8 個音度, 首尾兩個 Do 在頻率上剛好相差 2 倍. 而這 8 個音度加上半音又分成 12 個音符, 音符有唱名 (Do, Re, Mi, Fa, So, La, Si, Do) 與相對之音名 (A, B, C, D, E, F, G, A) 兩種標示. 其中的 C 大調低音 La 為 440 Hz, 被用來作為樂器調校的標準音. 一個音階的 12 個音符要差兩倍頻率的話, 每一個頻率之間的乘數就是 2^(1/12), 即 2 開 12 次方, 大約是 1.05946 倍. 根據此倍數可計算出各音符的近似頻率如下表 :
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
音名 | C | C# | D | D# | E | F | F# | G | G# | A | A# | B |
0 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 25 | 26 | 28 | 29 | 31 |
1 | 33 | 35 | 37 | 39 | 41 | 44 | 46 | 49 | 52 | 55 | 58 | 62 |
2 | 65 | 69 | 73 | 78 | 82 | 87 | 93 | 98 | 104 | 110 | 117 | 123 |
3 | 131 | 139 | 147 | 156 | 165 | 175 | 185 | 196 | 208 | 220 | 233 | 247 |
4 | 262 | 277 | 294 | 311 | 330 | 349 | 370 | 392 | 415 | 440 | 466 | 493 |
5 | 523 | 554 | 587 | 622 | 659 | 698 | 740 | 784 | 831 | 880 | 932 | 988 |
6 | 1046 | 1109 | 1175 | 1245 | 1319 | 1397 | 1480 | 1568 | 1661 | 1760 | 1864 | 1976 |
7 | 2093 | 2217 | 2349 | 2489 | 2637 | 2794 | 2960 | 3136 | 3322 | 3520 | 3729 | 3951 |
8 | 4186 | 4435 | 4699 | 4978 | 5274 | 5588 | 5920 | 6272 | 6645 | 7040 | 7459 | 7902 |
其中藍色字體部分為標準 88 鍵樂器的音域範圍, 而 C4 (262 Hz) 即為鋼琴鍵盤的中央 C (C 大調低音 Do). 依據這張樂音頻率分配表就可以利用 tone() 函數讓蜂鳴器發出各種音符了.
除了頻率之外還需要節拍 (tempo) 才能構成音樂的旋律, 這就需要利用 tone() 函數的持續時間以及 delay() 函數來模擬停頓效果了, 關於節拍參考 :
#
音符時值
下列測試 6 是我參考 "
Arduino 互動設計入門 2" 13-1 節瑪莉歐旋律的範例加以改編的 :
測試 6 :
int buzzerPin=9;
void setup() {
pinMode(buzzerPin, OUTPUT);
}
void loop() {
mario(buzzerPin);
}
void mario(int pin) {
tone(pin, 659, 150); //E5
delay(150); //pause for key transition
tone(pin, 659, 150); //E5
delay(150); //pause for key transition
tone(pin, 659, 150); //E5
delay(150); //pause for key transition
delay(150); //pause for a Quarter rest (1/4)
tone(pin, 523, 150); //C5
delay(150); //pause for key transition
tone(pin, 659, 150);
delay(150); //pause for key transition
delay(150); //pause for a Quarter rest (1/4)
tone(pin, 784, 150); //G5
delay(3000);
}
這裡每個音符之間要用 delay(150) 來模擬每個琴鍵轉換之空檔, 否則所有的音符就會黏在一起. 另外, 四分休止符的時間長度也剛好是停頓 150 毫秒. 如果要像上面的程式一樣, 每次都要查頻率表實在太麻煩了, 而且一堆數字看起來令人眼花撩亂, 其實每個音符可以用 define 或 const 將頻率以音名來代換, 這樣程式可讀性比較高, 如下測試 7 所示 :
測試 7 :
#define E5 659
#define C5 523
#define G5 784
int buzzerPin=9;
void setup() {
pinMode(buzzerPin, OUTPUT);
}
void loop() {
mario(buzzerPin);
}
void mario(int pin) {
tone(pin,
E5, 150); //E5
delay(150); //pause for key transition
tone(pin,
E5, 150); //E5
delay(150); //pause for key transition
tone(pin,
E5, 150); //E5
delay(150); //pause for key transition
delay(150); //pause for a Quarter rest (1/4)
tone(pin,
C5, 150); //C5
delay(150); //pause for key transition
tone(pin,
E5, 150);
delay(150); //pause for key transition
delay(150); //pause for a Quarter rest (1/4)
tone(pin,
G5, 150); //G5
delay(3000);
}
如果換另一首樂曲就要 define 所用到的音符的話很麻煩, 乾脆將上面的頻率分配表另外打成一個 notes.h 檔, 然後用 include 匯入即可. 此檔內容如下 :
#define C0 18
#define CS0 17
#define D0 18
#define DS0 19
#define E0 21
#define F0 22
#define FS0 23
#define G0 25
#define GS0 26
#define A0 28
#define AS0 29
#define B0 31
#define C1 33
#define CS1 35
#define D1 37
#define DS1 39
#define B0 31
#define C1 33
#define CS1 35
#define D1 37
#define DS1 39
#define E1 41
#define F1 44
#define FS1 46
#define G1 49
#define GS1 52
#define A1 55
#define AS1 58
#define B1 62
#define C2 65
#define CS2 69
#define D2 73
#define DS2 78
#define E2 82
#define F2 87
#define FS2 93
#define G2 98
#define GS2 104
#define A2 110
#define AS2 117
#define B2 123
#define C3 131
#define CS3 139
#define D3 147
#define DS3 156
#define E3 165
#define F3 175
#define FS3 185
#define G3 196
#define GS3 208
#define A3 220
#define AS3 233
#define B3 247
#define C4 262
#define CS4 277
#define D4 294
#define DS4 311
#define E4 330
#define F4 349
#define FS4 370
#define G4 392
#define GS4 415
#define A4 440
#define AS4 466
#define B4 494
#define C5 523
#define CS5 554
#define D5 587
#define DS5 622
#define E5 659
#define F5 698
#define FS5 740
#define G5 784
#define GS5 831
#define A5 880
#define AS5 932
#define B5 988
#define C6 1047
#define CS6 1109
#define D6 1175
#define DS6 1245
#define E6 1319
#define F6 1397
#define FS6 1480
#define G6 1568
#define GS6 1661
#define A6 1760
#define AS6 1865
#define B6 1976
#define C7 2093
#define CS7 2217
#define D7 2349
#define DS7 2489
#define E7 2637
#define F7 2794
#define FS7 2960
#define G7 3136
#define GS7 3322
#define A7 3520
#define AS7 3729
#define B7 3951
#define C8 4186
#define CS8 4435
#define D8 4699
#define DS8 4978
#define E8 5274
#define F8 5588
#define FS8 5920
#define G8 6272
#define GS8 6645
#define A8 7040
#define AS8 7459
#define B8 7902
由於係自訂的標頭檔, 因此要放在主檔案目錄內, 如下圖所示 :
而且
要用雙引號匯入 (內建函式庫才是用角括號匯入) :
# include "notes.h"
使用 notes.h 將上面程式改為如下測試 8 :
測試 8 :
#
include "notes.h"
int buzzerPin=9;
void setup() {
pinMode(buzzerPin, OUTPUT);
}
void loop() {
mario(buzzerPin);
}
void mario(int pin) {
tone(pin, E5, 150);
delay(150);
tone(pin, E5, 150);
delay(150);
tone(pin, E5, 150);
delay(300);
tone(pin, C5, 150);
delay(150);
tone(pin, E5, 150);
delay(300);
tone(pin, G5, 150);
delay(3000);
}
效果是一樣的. 在 Arduino IDE 中也有內建一個 toneMelody 範例, 可從 "檔案/範例/02.Digital/toneMelody" 開啟, 我將其改編為如下測試 9 :
測試 9 :
#include "notes.h"
int buzzerPin=9;
int note[]={C4, G3, G3, A3, G3,
0, B3, C4}; //note name
int duration[]={4, 8, 8, 4, 4, 4, 4, 4}; //note type (tempo)
void setup() {
pinMode(buzzerPin, OUTPUT);
}
void loop() {
melody(buzzerPin);
}
void melody(int pin) {
for (int i=0; i<8; i++) { //visit 8 notes
int d=1000/duration[i]; //transform tempo to ms
tone(pin, note[i], d);
int p=d * 1.3; //suitable pause=plus 30% duration
delay(p); //pause between notes
noTone(8);
}
delay(2000);
}
在這個程式中, 我分別為音符與節拍定義了 note 與 duration 兩個陣列, 其中 note 第 6 個音符 0 表示頻率為 0, 也就是休止符的意思. 而 duration 陣列之值, 4 表示 四分音符; 8 表示八分音符, 這樣可讀性比較高, 但在 melody() 函數中, 我們必須將其轉換成毫秒數, 在此以全音符為 1 秒, 則四分音符為 1000/4=250 毫秒, 亦即用 1000 毫秒除以 duration 陣列的元素值即可. 另外每個音符之間必須模擬一個琴鍵轉換的停頓, 否則每一個音符會黏在一起, 這裡以節拍的 1.3 倍時間為最適宜.
在 "
Arduino 最佳入門與應用" 的 11-3-4 節有一個演奏小蜜蜂的範例, 我把它改編成下列測試 10, 小蜜蜂的簡譜如下 :
|5 3 3 - |4 2 2 - |1 2 3 4 |5 5 5 - |
|5 3 3 - |4 2 2 - |1 3 5 5 |3 - - - |
|2 2 2 2 |2 3 4 - |3 3 3 3 |3 4 5 –|
|5 3 3 - |4 2 2 - |1 3 5 5 |1 - - - |
參考 :
#
小蜜蜂簡譜
其中 1, 2, 3, 4, 5 分別對應到 C5, D5, E5, F5, G5 五個音符, 將其轉換成 note[] 陣列, 每個音節有 4 拍, 以四分音符當作一拍.
測試 10 :
#include "notes.h"
int buzzerPin=9;
int note[]={G5, E5, E5, 0, F5, D5, D5, 0, C5, D5, E5, F5, G5, G5, G5, 0,
G5, E5, E5, 0, F5, D5, D5, 0, C5, E5, G5, G5, E5, 0, 0, 0,
D5, D5, D5, D5, D5, E5, F5, 0, E5, E5, E5, E5, E5, F5, G5, 0,
G5, E5, E5, 0, F5, D5, D5, 0, C5, E5, G5, G5, C5, 0, 0, 0};
int duration[]={4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4};
void setup() {
pinMode(buzzerPin, OUTPUT);
}
void loop() {
littleBee(buzzerPin,
sizeof(note)/sizeof(int));
}
void littleBee(int pin, int
count) {
for (int i=0; i<count; i++) {
int d=1000/duration[i];
tone(pin, note[i], d);
int p=d * 1.3;
delay(p);
noTone(pin);
}
delay(2000);
}
這裡比較特別的地方是要把 note[] 或 duration[] 陣列的長度傳進函數中, 以便 for 迴圈擷取樂譜中的全部音符與音長. 由於 sizeof() 事實上是傳回這個陣列所占的 byte 數, 並非元素個數, 因此必須除以此陣列元素之資料型別長度, 才會得到元素個數, 即陣列長度.
所以只要有簡譜, 我們可以很快地將其轉換成音符與音長陣列, 套用上面的程式架構讓 Arduino 透過蜂鳴器演奏音樂.
下面是小星星的簡譜 :
|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 -|
這裡簡譜的 1, 2, 3, 4, 5, 6 分別對應到 C5, D5, E5, F5, G5, A5 六個音符, 將其轉換成 note[] 陣列, 每個音節有 4 拍, 以四分音符當作一拍. 其演奏程式如下 :
測試 11 :
#include "notes.h"
int buzzerPin=9;
int note[]={C5, C5, G5, G5, A5, A5, G5, 0, F5, F5, E5, E5, D5, D5, C5, 0,
G5, G5, F5, F5, E5, E5, D5, 0, G5, G5, F5, F5, E5, E5, D5, 0,
C5, C5, G5, G5, A5, A5, G5, 0, F5, F5, E5, E5, D5, D5, C5, 0};
int duration[]={4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4};
void setup() {
pinMode(buzzerPin, OUTPUT);
}
void loop() {
littleStar(buzzerPin, sizeof(note)/sizeof(int));
}
void littleStar(int pin, int count) {
for (int i=0; i<count; i++) {
int d=1000/duration[i];
tone(pin, note[i], d);
int p=d * 1.3;
delay(p);
noTone(pin);
}
delay(2000);
}
很簡單齁! 其實凡事只要抓住了 pattern 或架構, 應用就很簡單, 就是把內容餵進去而已, 玩久了就是所謂 "熟能生巧" 的老狗把戲罷了.
最後我想把上面小蜜蜂與小星星兩首曲子做成音樂盒, 透過按鈕控制播放不同歌曲, 開機時預設靜音, 按一下播放小蜜蜂, 按一下播放小星星, 再按一下又變靜音, 如此周而復始. 此實驗參考 "Arduino 最佳入門與應用 (第二版)" 的 11-3-5 節範例修改.
測試 12 :
#include "notes.h"
int buzzerPin=9;
int littleStarNote[]={C5, C5, G5, G5, A5, A5, G5, 0, F5, F5, E5, E5, D5, D5, C5, 0,
G5, G5, F5, F5, E5, E5, D5, 0, G5, G5, F5, F5, E5, E5, D5, 0,
C5, C5, G5, G5, A5, A5, G5, 0, F5, F5, E5, E5, D5, D5, C5, 0};
int littleStarDuration[]={4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4};
int littleBeeNote[]={G5, E5, E5, 0, F5, D5, D5, 0, C5, D5, E5, F5, G5, G5, G5, 0,
G5, E5, E5, 0, F5, D5, D5, 0, C5, E5, G5, G5, E5, 0, 0, 0,
D5, D5, D5, D5, D5, E5, F5, 0, E5, E5, E5, E5, E5, F5, G5, 0,
G5, E5, E5, 0, F5, D5, D5, 0, C5, E5, G5, G5, C5, 0, 0, 0};
int littleBeeDuration[]={4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4};
const byte swPin=2; //switch pin for interrupt
volatile int swValue=0; //initial swValue:0=silent
int debounceDelay=200; //debounce delay (ms)
void setup() {
pinMode(buzzerPin, OUTPUT);
pinMode(swPin, INPUT_PULLUP); //enable pull-up resistor of input pin
attachInterrupt(0, int0, LOW); //assign int0
}
void loop() {
switch (swValue) {
case 1 :
playLittleBee(buzzerPin, sizeof(littleBeeNote)/sizeof(int));
break;
case 2 :
playLittleStar(buzzerPin, sizeof(littleStarNote)/sizeof(int));
break;
default :
noTone(buzzerPin); //silent buzzer
break;
}
}
void playLittleBee(int pin, int count) {
for (int i=0; i<count; i++) {
int d=1000/littleBeeDuration[i];
tone(pin, littleBeeNote[i], d);
int p=d * 1.3;
delay(p);
noTone(pin);
}
delay(2000);
}
void playLittleStar(int pin, int count) {
for (int i=0; i<count; i++) {
int d=1000/littleStarDuration[i];
tone(pin, littleStarNote[i], d);
int p=d * 1.3;
delay(p);
noTone(pin);
}
delay(2000);
}
void int0() { //interrupt handler
if (debounced()) { //debounced:
++swValue; //increment swValue
if (swValue > 2) {swValue=0;} //reset swValue
}
}
boolean debounced() { //check if debounced
static unsigned long lastMillis=0; //record last millis
unsigned long currentMillis=millis(); //get current elapsed time
if ((currentMillis-lastMillis) > debounceDelay) {
lastMillis=currentMillis; //update lastMillis with currentMillis
return true; //debounced
}
else {return false;} //not debounced
}
這個程式我使用了 D2 (中斷 0) 的硬體中斷來更改一個紀錄按鍵狀態的全域變數 swValue 之值, 每按一次就會將原本上拉至 HIGH 的 D2 準位拉到 LOW 而觸發中斷, 在中斷處理函數 int0() 裡面會將 swValue 增量, 但若超過 2 又會重置為 0, 藉此來讓 loop() 裡的 switch-case 判斷要執行哪一個演奏函數, 若為 1 執行 playLittleBee(); 若為 2 執行 playLittleStar(), 否則就靜音. 此程式使用了之前按鈕測試中所使用的去彈跳函數, 參考 :
#
Arduino 按鈕開關測試 (二) : 硬體中斷法 (Interrupt)
注意, 按鈕按下去只是改變 swValue 之值, 並非馬上切換所播放之歌曲, 要等到目前播放的曲子結束, 從函式回到 loop() 迴圈時才會判斷 swValue 之值決定接下來要播放哪一曲或靜音.
參考 :
#
Use tone() with Arduino for an Easy Way to Make Noise
#
Arduino Based Digital Clock with Alarm
#
Ardunio+蜂鳴器(Buzzer)播音樂
#
[Arduino] 會唱歌的蜂鳴器 – Controlling Piezo
#
Arduino 筆記 – Lab6 控制蜂鳴器發聲
#
Brett Hagman 的 Tone 函式庫 (播放多個頻率)