Skip to main content
< All Topics
Print

【聲音】蜂鳴器 | buzzer

簡介


在嵌入式系統與機器人開發中,蜂鳴器是提供即時狀態回饋最直接的工具。dual2s 函式庫封裝了複雜的 PWM 頻率控制與計時邏輯,提供開發者一套直覺且高效的介面。本範例旨在示範如何從簡單的警告音到複雜的背景旋律播放,達成多樣化的聲響回饋。

硬體資訊

  • 控制晶片:ESP32 ( dual2s 控制板 )
  • 連接腳位:GPIO 15 (DUAL2S_HW::BUZZER)
  • 開發環境:Arduino Core v3.3.5 / ESP-IDF v5.1

核心功能說明

  • 阻塞式與非阻塞式設計
    • 阻塞式 (Blocking):如 alarm(),程式會在此處停留直到音效播放結束,適合用於系統初始化或發生嚴重錯誤須立即中斷任務的場景。
    • 非阻塞式 (Non-blocking):這是本類別的核心優勢。透過在 loop() 中持續呼叫 bz.update(),系統能自動處理音符的切換,讓開發者在播放「帝國進行曲」等長旋律時,仍能同時處理感測器數據或通訊任務。
  • 內建預設音效: 提供多種情境音效,簡化開發流程:
    • soundBoot():輕快的開機提示音。
    • soundConnect():連線成功確認音。
    • soundError():低沉的錯誤警告音。
    • soundNotify():簡短的通知提醒。
  • 自訂旋律播放: 透過 playMelody 函式搭配 Note 結構陣列,開發者可以自定義音高(Pitch)與節拍長度。範例中定義了詳細的半音階與高低音組合,展現了本類別處理複雜樂譜的能力。

使用建議

  1. 務必宣告 update():若使用背景播放功能,請確保 bz.update() 位於 loop() 的最頂端。
  2. 音量控制tone() 函式支援音量參數(0-255),可依環境需求調整輸出強度。

dual2s控制器與環境


AI學伴


互動(1):基於函數庫 – dual2s

互動(2):基於範例程式

👉 你也可以在dual2s函數庫(./example/basic)中找到此範例程式。

/*=====================================================================================
yesio.net / 2026.03.12 / by nick
# Filename:02_Buzzer.ino
# Function:蜂鳴器(Buzzer類別)使用範例
  Test Code of dual2S's Buzzer CLASS.
# Buzzer Pin G15 in dual2s HW  
# Toolchain & Libs:ESP32 Arduino Core v3.3.5 (ESP-IDF v5.1), dual2s
======================================================================================*/
#include <dual2s.h>

// 宣告 Buzzer 物件
// dual2s G15控制 蜂鳴器, DUAL2S_HW::BUZZER
// 參數:控制腳位
Buzzer bz(DUAL2S_HW::BUZZER);

// 測試流程控制變數
unsigned long lastActionTime = 0;
int testStep = 0;
unsigned long waitTime = 3000; // 新增:預設每個步驟等待 3 秒

#define Song_ImperialMarch //使用帝國進行曲或小蜜蜂測試
#ifdef Song_ImperialMarch 
  // 威風凜凜的帝國進行曲  
  constexpr uint16_t Eb4 = 311; // 降 Mi
  constexpr uint16_t Fs4 = 370; // 升 Fa / 降 Sol
  constexpr uint16_t Ab4 = 415; // 降 La
  constexpr uint16_t Bb4 = 466; // 降 Si
  constexpr uint16_t Db5 = 554; // 高音降 Re
  constexpr uint16_t Eb5 = 622; // 高音降 Mi
  constexpr uint16_t Fs5 = 740; // 高音升 Fa
  const Note mySong[] = {
    // 登、登、登、(登~登)
    {Pitch::Sol_4, 400}, {Pitch::Sol_4, 400}, {Pitch::Sol_4, 400}, {Eb4, 300}, {Bb4, 100}, 
    // 登、(登~登)、登——
    {Pitch::Sol_4, 400}, {Eb4, 300}, {Bb4, 100}, {Pitch::Sol_4, 800},
    // 叮、叮、叮、(叮~登)
    {Pitch::Re_5, 400}, {Pitch::Re_5, 400}, {Pitch::Re_5, 400}, {Eb5, 300}, {Bb4, 100},
    // 登、(登~登)、登—— 
    {Fs4, 400}, {Eb4, 300}, {Bb4, 100}, {Pitch::Sol_4, 800},

    // 登(高)、登(低)、登(高)
    {Pitch::Sol_5, 400}, {Pitch::Sol_4, 300}, {Pitch::Sol_4, 100}, {Pitch::Sol_5, 400},
    // 降登、登、(登~登~登) -> 經典的半音下行
    {Fs5, 300}, {Pitch::Fa_5, 100}, {Pitch::Mi_5, 150}, {Eb5, 150}, {Pitch::Mi_5, 400},
    // 休止符 + 降La、降Re、Do、Si
    {0, 200}, {Ab4, 200}, {Db5, 400}, {Pitch::Do_5, 300}, {Pitch::Si_4, 100},
    // 降Si、La、降Si
    {Bb4, 150}, {Pitch::La_4, 150}, {Bb4, 400},
    // 休止符 + 降Mi、降Sol、降Mi、降Sol
    {0, 200}, {Eb4, 200}, {Fs4, 400}, {Eb4, 300}, {Fs4, 100},
    // 降Si、Sol、降Si、Re(高音延伸結尾)
    {Bb4, 400}, {Pitch::Sol_4, 300}, {Bb4, 100}, {Pitch::Re_5, 800}
  };
#else
  //小蜜蜂
  const Note mySong[] = {
  // 嗡嗡嗡,大家一起勤做工 (5 3 3, 4 2 2)
    {Pitch::Sol_4, 300}, {Pitch::Mi_4, 300}, {Pitch::Mi_4, 600},
    {Pitch::Fa_4, 300}, {Pitch::Re_4, 300}, {Pitch::Re_4, 600},
    
    // 來匆匆,去匆匆,做工興味濃 (1 2 3 4, 5 5 5)
    {Pitch::Do_4, 300}, {Pitch::Re_4, 300}, {Pitch::Mi_4, 300}, {Pitch::Fa_4, 300},
    {Pitch::Sol_4, 300}, {Pitch::Sol_4, 300}, {Pitch::Sol_4, 600},
    
    // 天暖花好不做工,將來哪裡好過冬 (5 3 3, 4 2 2)
    {Pitch::Sol_4, 300}, {Pitch::Mi_4, 300}, {Pitch::Mi_4, 600},
    {Pitch::Fa_4, 300}, {Pitch::Re_4, 300}, {Pitch::Re_4, 600},
    
    // 嗡嗡嗡,大家一起勤做工 (1 3 5 5, 1)
    {Pitch::Do_4, 300}, {Pitch::Mi_4, 300}, {Pitch::Sol_4, 300}, {Pitch::Sol_4, 300},
    {Pitch::Do_4, 1200}
  };
#endif 

void setup() {
  Serial.begin(115200);
  Serial.println("=== Buzzer 音效測試開始 ===");

  // ==========================================
  // 測試 1:阻塞式警報 (適合開機或嚴重錯誤時使用)
  // ==========================================
  Serial.println("測試:阻塞式 alarm()");
  bz.alarm();       // 預設 800Hz 警報
  delay(500);
  bz.alarm(1200);   // 自訂 1200Hz 警報
  delay(1000);

  // ==========================================
  // 測試 2:基礎持續發聲 (tone 與 noTone)
  // ==========================================
  Serial.println("測試:基礎 tone() 與 noTone()");
  bz.tone(Pitch::Do_4, 127); // 發出中央 Do,音量一半 (127/255)
  delay(1000);               // 讓聲音持續 1 秒
  bz.noTone();               // 手動關閉聲音
  delay(1000);

  Serial.println("=== 進入非阻斷背景播放測試 ===");
  lastActionTime = millis();
}

void loop() {
  // 1. ================= 【背景音效更新區】 =================
  // 【必須】這行一定要放在 loop 最頂端,不可省略!
  // 它負責在背景檢查音符是否該切換了,不佔用 CPU 時間
  bz.update();
  uint16_t noteCount = sizeof(mySong) / sizeof(mySong[0]); 

  
  // 2. ================= 【狀態切換測試區】 =================
  // 利用 millis() 每隔 3 秒 (3000ms) 觸發下一個音效,證明主迴圈沒有被卡死
  if (millis() - lastActionTime > waitTime) {
    lastActionTime = millis();
    testStep++;
    
    waitTime = 3000; // 每次進入新步驟前,先把等待時間重置為 3 秒

    switch(testStep) {
      case 1:
        Serial.println("播放:開機音效 (soundBoot)");
        bz.soundBoot();
        break;
        
      case 2:
        Serial.println("播放:連線音效 (soundConnect)");
        bz.soundConnect();
        break;
        
      case 3:
        Serial.println("播放:錯誤警告 (soundError)");
        bz.soundError();
        break;
        
      case 4:
        Serial.println("播放:攻擊警報 (soundAttack):帝國進行曲");
        bz.soundAttack();
        break;

      case 5:
        Serial.println("播放:提示音 (soundNotify)");
        bz.soundNotify(); 
        break;

      case 6:
        Serial.println("播放:自訂旋律 (playMelody)");
        bz.playMelody(mySong, noteCount); 
         waitTime = 15000;
        break;
        
      case 7:
        Serial.println("=== 測試循環結束,重新開始 ===");
        testStep = 0; // 歸零重新測試
        break;
    }
  }
}
Table of Contents