以下實驗所用的程式是參考下列幾本書裡面的範例加以修改來的 :
- Arduino 輕鬆入門, 葉難, 博碩出版 (第 6 章)
- Arduino 互動設計入門 2, 趙英傑, 旗標 (第 13-1, 13-2 節)
- Arduino 最佳入門與應用第二版, 楊明峰, 碁峰 (第 11 章)
- Arduino Cookbook 錦囊妙計, 歐萊禮 (第 9 節)
關於此模組佈線圖與製作方式, 參考 :
# 製作 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(pin, frequency, duration) | 對指定數位接腳 pin 發出 duration 毫秒, 頻率為 frequency 之方波 |
tone(pin, frequency) | 對指定數位接腳 pin 持續發出頻率為 frequency 之方波 |
noTone(pin) | 停止對指定數位接腳 pin 發出方波 |
其中 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 分別對應到
測試 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 或架構, 應用就很簡單, 就是把內容餵進去而已, 玩久了就是所謂 "熟能生巧" 的老狗把戲罷了.
測試 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
}
# Arduino 按鈕開關測試 (二) : 硬體中斷法 (Interrupt)
參考 :
# 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 函式庫 (播放多個頻率)
作者已經移除這則留言。
回覆刪除不好意思 小弟剛接觸這一塊 有很多東西不懂 想請教一下
回覆刪除請問測試1中alarmBeep(buzzerPin)這句是甚麼意思呢?為什麼只把它放進void loop裡 而其他指令要另外宣告呢?
如果要將測試一中的程式與while的語法搭配做開關控制又該如何寫呢?
alarmBeep(buzzerPin) 是呼叫定義在後面的函數 alarmBeep(), 並且將全域變數 buzzerPin=9 傳遞給它. Arduino 程式主要架構由引入函式庫+巨集+全域變數, 初始設定函數 setup() 與無窮迴圈 loop() 函數構成, 只需要執行一次的例如初始值放在 setup() 裡, 需重複執行的邏輯放在 loop() 中. 有 loop 就不需要用 while 了, 只要在 loop() 中用 DigitalWrite() 函數輸出 HIGH/LOW 即可 :
回覆刪除void loop() {
digitalWrite(swPin, HIGH);
delay(5000);
digitalWrite(swPin, LOW);
delay(5000);
}
請問使用nodemcu接蜂鳴器,因為nodemcu工作電壓3.3V。那麼蜂鳴器要選用哪種電壓呢?3V?還是5V?
回覆刪除Hi! 5V 蜂鳴器也可以給 3.3V 用喔! 只是聲音較小, 因為驅動電流變小了.
回覆刪除老師晚安:
回覆刪除我是一名小六生,想請教您我初學習Arduinod課程,所使用的語言是C語言,您可推薦有關C語言的書嗎?謝謝您
您好, 小六就對這有興趣, 太好了! 其實網路上有系統的 C 語言教學資料很多, 不一定要買書, 我大部分的程式能力都是上網學來的, 只有找不到時才買書. 像下面這個網站就很好 :
回覆刪除https://openhome.cc/Gossip/CGossip/
買書的話, 下面這本應該適合你 :
1.全民學程式設計:從插畫學C語言 (旗標, 林克鴻)
https://www.books.com.tw/products/0010777399?sloc=main
2. 最新C語言程式設計實例入門(第四版) (博碩, 高橋麻奈)
https://www.books.com.tw/products/0010576829?sloc=main
圖書館借得到就不用買了, 我大部分的技術書籍都是從圖書館借來的.
我有整理一篇 Arduino 速成的 C 語言語法 :
回覆刪除https://yhhuang1966.blogspot.com/2015/09/arduino_14.html
我的 Arduino 筆記總索引 :
https://yhhuang1966.blogspot.com/2020/05/blog-post_15.html
為甚麼我用tinkercad上的模擬電路裡的蜂鳴器,再加上鮑率後就不會響了
回覆刪除Hi, 線上模擬器可能有一些限制吧? 何不買實體來測試比較有臨場感?
回覆刪除請問要如何使一個開發版同時控制兩個蜂鳴器,並且兩個蜂鳴器要演奏出不同的旋律,我試過使用millis(),但只能做到讓兩個蜂鳴器同時發出同樣旋律,並且兩首旋律輪流撥放,另外我使用的開發版為ESP32不是UNO所以裡面的城市也會稍微跟UNO用的有點不同,試問有沒辦法修改,以下為程式碼:
回覆刪除#include "notes.h"
int note[]={0,0,0,0,0,0,F6,G6,F6,0,0,0,0,0,0,F6,G6,F6};
int duration[]={4,4,4,4,4,4,12,12,12,4,4,4,4,4,4,12,12,12};
int dutyCycle[]= {0,0,0,0,0,0,300,300,300,0,0,0,0,0,0,300,300,300};
int notes[]={G4,D4,G4,D4,G4,D4,G5,A5,G5,D5,G4,D4,G4,D4,G4,D4,G5,A5,G5,D5};
int durations[]={4,4,4,4,4,4,16,16,16,16,4,4,4,4,4,4,16,16,16,16,};
int dutyCycles[]= {300,300,300,300,300,300,100,100,100,100,300,300,300,300,300,300,300,300,300,300,100,100,100,100};
//宣告 Declare 區塊-----------------
unsigned long intervals[] = {250,2000}; //this defines the interval for each task in milliseconds
unsigned long last[] = {0,0}; //this records the last executed time for each task
int buzzerPin=14;
int Pin=27;//蜂鳴器控制腳位
int freq = 2000;
int Buzzer_Channel = 0; //channel提供0-15共16個通道。
int resolution = 10; //duty cycle解析度(2^10=1024解析度,變數dutyCycle最大值為1024)
//控制PWM脈寬,即可控制音量大小。脈寬越大、音量越大。
void setup() {
Serial.begin(115200);
pinMode(buzzerPin, OUTPUT);
pinMode(Pin, OUTPUT);
ledcSetup(Buzzer_Channel, freq, resolution); //設定通道0的頻率與duty cycle解析度。
ledcAttachPin(buzzerPin, Buzzer_Channel);
ledcAttachPin(Pin, Buzzer_Channel);//控制蜂鳴器的G15腳位綁定於通道0
}
void loop() {
unsigned long now = millis();
if(now-last[0]>=intervals[0]){ last[0]=now; firstTask(buzzerPin, sizeof(note)/sizeof(int)); }
if(now-last[1]>=intervals[1]){ last[1]=now; secondTask(Pin, sizeof(notes)/sizeof(int)); }
//do other things here
}
void firstTask(int pin, int count) {
for (int i=0; i<count; i++) {
int d=1000/duration[i];
ledcWriteTone(Buzzer_Channel, note[i]);
ledcWrite(Buzzer_Channel, dutyCycle[i]);
int p=d * 2.08;
delay(p);
}
delay(1);
}
void secondTask(int pin, int count) {
for (int i=0; i<count; i++) {
int d=1000/durations[i];
ledcWriteTone(Buzzer_Channel, notes[i]);
ledcWrite(Buzzer_Channel, dutyCycles[i]);
int p=d * 2.08;
delay(p);
}
delay(1);
}
抱歉, 我之前 ESP32 都以 MicroPython 做實驗, 很少用 Arduino C. 同時播放兩個蜂鳴器似乎要用多執行緒, 且 ESP32 有雙核, 這方面我沒試過, 您可參考這篇做法 :
回覆刪除https://randomnerdtutorials.com/esp32-dual-core-arduino-ide/