
本單元利用ESP32的PWM (脈衝寬度調變) 技術控制蜂鳴器來播放音樂,說明如下:
1、定義音符頻率:
// 音符範圍:C3~C7(涵蓋低音到高音),包含所有升降音(♯、♭)
// 單位:赫茲(Hz)
// 低音區(C3 ~ B3)
#define C3 131
#define Cs3 139 // C#3 / Db3
#define D3 147
#define Ds3 156 // D#3 / Eb3
#define E3 165
#define F3 175
#define Fs3 185 // F#3 / Gb3
#define G3 196
#define Gs3 208 // G#3 / Ab3
#define A3 220
#define As3 233 // A#3 / Bb3
#define B3 247
// 中音區(C4 ~ B4,包含標準 C4)
#define C4 262
#define Cs4 277 // C#4 / Db4
#define D4 294
#define Ds4 311 // D#4 / Eb4
#define E4 330
#define F4 349
#define Fs4 370 // F#4 / Gb4
#define G4 392
#define Gs4 415 // G#4 / Ab4
#define A4 440
#define As4 466 // A#4 / Bb4
#define B4 494
// 高音區(C5 ~ B5)
#define C5 523
#define Cs5 554 // C#5 / Db5
#define D5 587
#define Ds5 622 // D#5 / Eb5
#define E5 659
#define F5 698
#define Fs5 740 // F#5 / Gb5
#define G5 784
#define Gs5 831 // G#5 / Ab5
#define A5 880
#define As5 932 // A#5 / Bb5
#define B5 988
// 超高音區(C6 ~ B6)
#define C6 1047
#define Cs6 1109 // C#6 / Db6
#define D6 1175
#define Ds6 1245 // D#6 / Eb6
#define E6 1319
#define F6 1397
#define Fs6 1480 // F#6 / Gb6
#define G6 1568
#define Gs6 1661 // G#6 / Ab6
#define A6 1760
#define As6 1865 // A#6 / Bb6
#define B6 1976
// 超高音區(C7)
#define C7 2093
#define Cs7 2217 // C#7 / Db7
#define D7 2349
#define Ds7 2489 // D#7 / Eb7
#define E7 2637
#define F7 2794
#define Fs7 2960 // F#7 / Gb7
#define G7 3136
#define Gs7 3322 // G#7 / Ab7
#define A7 3520
#define As7 3729 // A#7 / Bb7
#define B7 3951
// 休止符
#define REST 0 // 無聲音
2、設定PWM接腳與初始化
ledcAttach(pin, 2000, 10); // 設定pin腳位使用LEDC(PWM 控制器),初始頻率2000Hz,解析度10位元
3、ledcWriteTone()指令:
ledcWriteTone(pin, 發音頻率); // 發出聲音
ledcWriteTone(pin, 0); // 發音頻率0,代表靜音
基本播放範例:
// ---------------------------------------------------------------------
// 定義音符頻率 (Hz)
#define C4 262 // Do
#define D4 294 // Re
#define E4 330 // Mi
#define F4 349 // Fa
#define G4 393 // So
#define A4 440 // La
#define B4 494 // Si
#define REST 0 // 休止符 (頻率 0 即無聲)
// ---------------------------------------------------------------------
// 音符序列(此陣列的元素必須對應length陣列的元素)
int melody[] = {
G4, E4, E4, F4, D4, D4, C4, D4, E4, F4, G4, G4, G4,
G4, E4, E4, F4, D4, D4, C4, E4, G4, G4, E4,
D4, D4, D4, D4, D4, E4, F4, E4, E4, E4, E4, E4, F4, G4,
G4, E4, E4, F4, D4, D4, C4, E4, G4, G4, C4
};
// ---------------------------------------------------------------------
// 節拍長度序列(單位為 base_duration 的倍數)
int length[] = {
1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2,
1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 2,
1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2,
1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 4
};
// ---------------------------------------------------------------------
// 音符總數 = melody 陣列總位元組數 / 單一整數位元組數
int total = sizeof(melody) / sizeof(int);
// ---------------------------------------------------------------------
int pin = 15; // 蜂鳴器連接腳位 (GPIO 15)
int base_duration = 200; // 音符基準時間 (單位: ms,決定節奏快慢)
int note_interval = 100; // 音符間的短暫間隔 (單位: ms,避免聲音黏在一起)
// ---------------------------------------------------------------------
void setup() {
// ESP32 LEDC 設定
ledcAttach(pin, 2000, 10); // 設定腳位, 初始頻率 2000Hz, 解析度 10 bits
ledcWriteTone(pin, 0); // 初始狀態為靜音
}
void loop() {
// 依序播放所有音符
for (int i = 0; i < total; i++)
{
int note_duration = base_duration * length[i]; // 計算當前音符持續時間
ledcWriteTone(pin, melody[i]); // 發出聲音
delay(note_duration); // 音符播放時間
ledcWriteTone(pin, 0); // 靜音
delay(note_interval); // 間隔時間
}
delay(2000); // 播放結束後暫停2秒後重新開始
}
使用 struct (結構) 將音符(音頻)與節拍(音長)綁定在一起,可以讓兩個陣列更容易對齊,程式碼的可讀性也更高。
// 定義音符結構(Struct),名稱為Note
struct Note {
int frequency; // 音頻 (Hz)
int length; // 音長 (為 base_duration 的倍數)
};
// 建立一個 Note 物件陣列,名稱為 song
Note song[] = {
{G4, 1}, {E4, 1}, {E4, 2}, {F4, 1}, {D4, 1}, {D4, 2}, {C4, 1}, {D4, 1}, {E4, 1}, {F4, 1}, {G4, 1}, {G4, 1}, {G4, 2},
{G4, 1}, {E4, 1}, {E4, 2}, {F4, 1}, {D4, 1}, {D4, 2}, {C4, 1}, {E4, 1}, {G4, 1}, {G4, 1}, {E4, 2},
{D4, 1}, {D4, 1}, {D4, 1}, {D4, 1}, {D4, 1}, {E4, 1}, {F4, 2}, {E4, 1}, {E4, 1}, {E4, 1}, {E4, 1}, {E4, 1}, {F4, 1}, {G4, 2},
{G4, 1}, {E4, 1}, {E4, 2}, {F4, 1}, {D4, 1}, {D4, 2}, {C4, 1}, {E4, 1}, {G4, 1}, {G4, 1}, {C4, 4}
};
原程式碼可改寫如下:
// ---------------------------------------------------------------------
// 定義音符頻率 (Hz)
#define C4 262 // Do
#define D4 294 // Re
#define E4 330 // Mi
#define F4 349 // Fa
#define G4 393 // So
#define A4 440 // La
#define B4 494 // Si
#define REST 0 // 休止符 (頻率 0 即無聲)
// ---------------------------------------------------------------------
// 定義音符結構(Struct),名稱為Note
struct Note {
int frequency; // 音頻 (Hz)
int length; // 音長 (為 base_duration 的倍數)
};
// ---------------------------------------------------------------------
// 建立一個 Note 物件陣列,名稱為 song
Note song[] = {
{G4, 1}, {E4, 1}, {E4, 2}, {F4, 1}, {D4, 1}, {D4, 2}, {C4, 1}, {D4, 1}, {E4, 1}, {F4, 1}, {G4, 1}, {G4, 1}, {G4, 2},
{G4, 1}, {E4, 1}, {E4, 2}, {F4, 1}, {D4, 1}, {D4, 2}, {C4, 1}, {E4, 1}, {G4, 1}, {G4, 1}, {E4, 2},
{D4, 1}, {D4, 1}, {D4, 1}, {D4, 1}, {D4, 1}, {E4, 1}, {F4, 2}, {E4, 1}, {E4, 1}, {E4, 1}, {E4, 1}, {E4, 1}, {F4, 1}, {G4, 2},
{G4, 1}, {E4, 1}, {E4, 2}, {F4, 1}, {D4, 1}, {D4, 2}, {C4, 1}, {E4, 1}, {G4, 1}, {G4, 1}, {C4, 4}
};
// ---------------------------------------------------------------------
// 音符總數 = melody 陣列總位元組數 / 單一整數位元組數
int total = sizeof(song) / sizeof(Note);
// ---------------------------------------------------------------------
int pin = 15; // 蜂鳴器連接腳位 (GPIO 15)
int base_duration = 200; // 音符基準時間 (單位: ms,決定節奏快慢)
int note_interval = 100; // 音符間的短暫間隔 (單位: ms,避免聲音黏在一起)
// ---------------------------------------------------------------------
void setup() {
// ESP32 LEDC 設定
ledcAttach(pin, 2000, 10); // 設定腳位, 初始頻率 2000Hz, 解析度 10 bits
ledcWriteTone(pin, 0); // 初始狀態為靜音
}
void loop() {
// 依序播放所有音符
for (int i = 0; i < total; i++)
{
int note_duration = base_duration * song[i].length; // 計算當前音符持續時間
ledcWriteTone(pin, song[i].frequency); // 發出聲音
delay(note_duration); // 音符播放時間
ledcWriteTone(pin, 0); // 靜音
delay(note_interval); // 間隔時間
}
delay(2000); // 播放結束後暫停2秒後重新開始
}
狀態機的寫法:
// ---------------------------------------------------------------------
// 定義音符頻率 (Hz)
#define C4 262 // Do
#define D4 294 // Re
#define E4 330 // Mi
#define F4 349 // Fa
#define G4 393 // So
#define A4 440 // La
#define B4 494 // Si
#define REST 0 // 休止符 (頻率 0 即無聲)
// ---------------------------------------------------------------------
// 定義音符結構(Struct),名稱為Note
struct Note {
int frequency; // 音頻 (Hz)
int length; // 音長 (為 base_duration 的倍數)
};
// ---------------------------------------------------------------------
// 建立一個 Note 物件陣列,名稱為 song
Note song[] = {
{G4, 1}, {E4, 1}, {E4, 2}, {F4, 1}, {D4, 1}, {D4, 2}, {C4, 1}, {D4, 1}, {E4, 1}, {F4, 1}, {G4, 1}, {G4, 1}, {G4, 2},
{G4, 1}, {E4, 1}, {E4, 2}, {F4, 1}, {D4, 1}, {D4, 2}, {C4, 1}, {E4, 1}, {G4, 1}, {G4, 1}, {E4, 2},
{D4, 1}, {D4, 1}, {D4, 1}, {D4, 1}, {D4, 1}, {E4, 1}, {F4, 2}, {E4, 1}, {E4, 1}, {E4, 1}, {E4, 1}, {E4, 1}, {F4, 1}, {G4, 2},
{G4, 1}, {E4, 1}, {E4, 2}, {F4, 1}, {D4, 1}, {D4, 2}, {C4, 1}, {E4, 1}, {G4, 1}, {G4, 1}, {C4, 4}
};
// ---------------------------------------------------------------------
// 音符總數 = melody 陣列總位元組數 / 單一整數位元組數
int total = sizeof(song) / sizeof(Note);
// ---------------------------------------------------------------------
int pin = 15; // 蜂鳴器連接腳位 (GPIO 15)
int base_duration = 200; // 音符基準時間 (單位: ms,決定節奏快慢)
int note_interval = 100; // 音符間的短暫間隔 (單位: ms,避免聲音黏在一起)
// ---------------------------------------------------------------------
int i = 0; // 當前播放的音符索引
int state = 1; // 狀態機控制旗標:
// 1:播放音符
// 2:音符間隔
// 3:切換至下個音符
// 4:播放結束
// ---------------------------------------------------------------------
void setup() {
// ESP32 LEDC 設定
ledcAttach(pin, 2000, 10); // 設定腳位, 初始頻率 2000Hz, 解析度 10 bits
ledcWriteTone(pin, 0); // 初始狀態為靜音
}
void loop() {
switch(state)
{
case 1: // 播放音符
if (i < total) // 還有音符沒播完
{
int note_duration = base_duration * song[i].length; // 計算當前音符持續時間
ledcWriteTone(pin, song[i].frequency); // 發出聲音
delay(note_duration); // 音符播放時間
state = 2; // 進入狀態2
}
else // 所有音符已經播完
{
state = 4; // 進入狀態4
}
break;
case 2: // 音符間隔
ledcWriteTone(pin, 0); // 靜音
delay(note_interval); // 間隔時間
state = 3; // 進入狀態3
break;
case 3: // 切換至下一個音符
i++; // 索引值+1
state = 1; // 進入狀態1
break;
case 4: // 播放結束
ledcWriteTone(pin, 0); // 靜音
delay(2000); // 播放結束後暫停2秒
i = 0; // 索引值歸零(重新開始)
state = 1; // 進入狀態1(重新開始)
break;
}
}
作業說明:利用ledcWriteTone()指令、struct結構、狀態機的寫法,輸出約30秒~40秒的完整音樂(非兒歌)到蜂鳴器。
實作結果: