蜂鳴器、ledcWriteTone()基本練習

本單元利用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秒的完整音樂(非兒歌)到蜂鳴器。

實作結果: