第5章 ESP32 PWM 教學:LED 調光、RGB LED 與蜂鳴器

發布日期: |更新日期:
文章主圖:第5章 ESP32 PWM 教學:LED 調光、RGB LED 與蜂鳴器

前言:ESP32 為什麼需要 PWM?

前面的 GPIO 數位輸出只能讓 ESP32 輸出兩種狀態:

HIGH
LOW

因此控制 LED 時,最基本只能做到:

HIGH → LED 亮
LOW  → LED 滅

如果我們想讓 LED:

25% 亮度
50% 亮度
75% 亮度
慢慢變亮
慢慢變暗

或者控制:

RGB LED 顏色
蜂鳴器音調
馬達速度
風扇速度

就需要使用:

PWM

PWM 全名:

Pulse Width Modulation

中文通常稱為:

脈衝寬度調變

ESP32 具有專門的 LEDC(LED Control)PWM 周邊。Espressif 官方說明 LEDC 主要設計用途是 LED 亮度控制,但同樣可以產生其他用途的 PWM 訊號,而且多個 Channel 可以產生獨立波形,例如用於 RGB LED。

這一章將完整學習:

PWM
↓
Frequency
↓
Resolution
↓
Duty Cycle
↓
LEDC
↓
LED 調光
↓
呼吸燈
↓
RGB LED
↓
Passive Buzzer

最後完成:

ESP32 呼吸燈 + RGB LED 漸變

完整實作。

一、PWM 是什麼?

ESP32 的 GPIO 是數位輸出,因此基本狀態仍然是:

HIGH

或者:

LOW

PWM 並不是讓 GPIO 真的輸出:

1.2V
1.8V
2.5V

這種連續類比電壓。

它實際上是讓 GPIO 非常快速地:

HIGH
LOW
HIGH
LOW
HIGH
LOW

切換。

例如:

HIGH ────    ────    ────
LOW      ────    ────

當切換速度很快時,LED 會呈現出不同的平均亮度。

因此:

PWM 是利用快速切換 HIGH / LOW 的時間比例,控制平均輸出效果。

二、PWM 的三個核心參數

學習 ESP32 PWM 時,最重要的是理解三個參數:

Frequency
Resolution
Duty Cycle

也就是:

  • PWM Frequency
  • PWM Resolution
  • PWM Duty Cycle

這三個參數會直接影響 PWM 的使用方式。

三、什麼是 PWM Frequency?

Frequency 就是:

PWM 頻率

單位:

Hz

例如:

100 Hz

代表 PWM 每秒執行:

100 個週期

如果:

5000 Hz

就是:

每秒 5000 個 PWM 週期

本章 LED 與 RGB LED 實驗使用:

const uint32_t PWM_FREQ = 5000;

也就是:

5 kHz

這是一組適合拿來進行 LED PWM 教學的設定。

四、什麼是 PWM Duty Cycle?

Duty Cycle 中文通常叫:

工作週期
占空比

它代表在一個 PWM 週期中:

HIGH 所占的時間比例。

例如:

0% Duty Cycle

LOW ─────────────────────

LED:

熄滅

25% Duty Cycle

HIGH ──      ──      ──
LOW    ──────  ──────

LED:

較暗

50% Duty Cycle

HIGH ────    ────
LOW      ────    ────

LED 大約處於中間亮度範圍。

100% Duty Cycle

HIGH ───────────────────

LED:

最亮

所以可以簡單理解:

PWM Duty Cycle 與 LED 亮度效果
Duty Cycle LED 效果
0% 熄滅
25% 暗
50% 中等
75% 亮
100% 最亮

五、什麼是 PWM Resolution?

Resolution 就是:

PWM 解析度

假設設定:

8-bit

代表 PWM 可以分成:

2^8 = 256

個等級。

因此 Duty 值就是:

0 ~ 255

例如:

8-bit PWM Duty 數值對照
Duty 約略比例
0 0%
64 25%
128 50%
192 75%
255 100%

如果改成:

10-bit

則有:

2^10 = 1024

個階級:

0 ~ 1023

Arduino-ESP32 官方目前的 ledcAttach() 文件指出,LEDC Resolution 一般依 SoC 支援 1~14 bit,而原始 ESP32 可達 1~20 bit。

本章為了讓程式容易理解,統一採用:

8-bit
0 ~ 255

六、Frequency 與 Resolution 不是完全獨立

這是一個很重要的 ESP32 PWM 觀念。

ESP32 的:

Frequency

與:

Resolution

存在取捨關係。

Espressif 官方說明:

PWM Frequency 越高
↓
可使用的最高 Resolution 通常越低

反過來:

Resolution 越高
↓
可設定的最高 PWM Frequency 通常越低

因此不能任意要求「最高頻率 + 最高解析度」。實際可用範圍還會受到 ESP32 型號與 LEDC Clock Source 等因素影響。

七、ESP32 LEDC 是什麼?

LEDC 全名:

LED Control

它是 ESP32 用來產生 PWM 的重要硬體周邊。

LEDC 可以控制:

PWM Frequency
PWM Resolution
Duty Cycle
Fade
Tone

因此不只可以控制:

LED

還能應用在:

RGB LED
Passive Buzzer
照明
馬達控制訊號
其他 PWM 裝置

Espressif 的 ESP-IDF 文件也明確指出 LEDC 可以控制 LED 強度以及產生其他 PWM 訊號。

八、Arduino-ESP32 3.x LEDC 新語法

這一點非常重要。

很多舊 ESP32 教學使用:

ledcSetup(channel, frequency, resolution);
ledcAttachPin(pin, channel);
ledcWrite(channel, duty);

但是:

Arduino-ESP32 3.x 已經改版

官方 Migration Guide 已列出:

ledcSetup()
ledcAttachPin()

已移除。

現在改成:

ledcAttach(
  pin,
  frequency,
  resolution
);

而 ledcWrite() 也可以直接以 GPIO Pin 控制。

所以本章全部採用:

Arduino-ESP32 3.x 新版 API

九、ledcAttach()

新版基本格式:

ledcAttach(
  pin,
  frequency,
  resolution
);

例如:

ledcAttach(
  18,
  5000,
  8
);

代表:

GPIO = 18
Frequency = 5000 Hz
Resolution = 8-bit

而且:

LEDC Channel 會自動選擇。

這是目前 Arduino-ESP32 官方 API 的正式行為。

十、ledcWrite()

設定完成後,可以使用:

ledcWrite(
  pin,
  duty
);

例如:

ledcWrite(
  18,
  128
);

在本章的 8-bit 設定中:

128 / 255

大約相當於:

50% Duty

Arduino-ESP32 官方 API 定義 ledcWrite(pin, duty) 用於設定指定 LEDC Pin 的 Duty。

十一、本章準備材料

ESP32 PWM 實作材料清單
材料 數量
ESP32 開發板 1
單色 LED 1
RGB LED 1
220Ω~330Ω 電阻 4
Passive Buzzer 1
麵包板 1
杜邦線 若干
USB 線 1

本章範例 GPIO:

單色 LED      GPIO18
RGB Red       GPIO25
RGB Green     GPIO26
RGB Blue      GPIO27
Passive Buzzer GPIO23

若使用不同型號 ESP32 開發板,GPIO 配置應依該開發板實際 Pinout 調整。

十二、ESP32 LED 調光接線

單色 LED:

ESP32 GPIO18
     │
   220Ω
     │
     ▼
   LED 正極
     │
   LED 負極
     │
     ▼
    GND

接線表:

ESP32 PWM LED 調光接線表
ESP32 LED
GPIO18 220Ω~330Ω → LED 正極
GND LED 負極

限流電阻不可省略。

十三、第一個 ESP32 PWM LED 調光程式

先讓 LED 固定在約 50% Duty。

const uint8_t LED_PIN = 18;

const uint32_t PWM_FREQ = 5000;
const uint8_t PWM_RESOLUTION = 8;

void setup()
{
  Serial.begin(115200);

  if (!ledcAttach(
        LED_PIN,
        PWM_FREQ,
        PWM_RESOLUTION))
  {
    Serial.println(
      "LEDC initialization failed!"
    );

    while (true)
    {
      delay(1000);
    }
  }

  ledcWrite(
    LED_PIN,
    128
  );
}

void loop()
{
}

這裡:

ledcWrite(
  LED_PIN,
  128
);

就是控制 LED Duty。

目前官方 ledcAttach() 成功會回傳 true,失敗回傳 false;ledcWrite() 則負責設定 Pin 的 Duty。

十四、ESP32 LED 四段亮度

接著做:

0%
25%
50%
75%
100%

完整程式:

const uint8_t LED_PIN = 18;

const uint32_t PWM_FREQ = 5000;
const uint8_t PWM_RESOLUTION = 8;

void setup()
{
  Serial.begin(115200);

  ledcAttach(
    LED_PIN,
    PWM_FREQ,
    PWM_RESOLUTION
  );
}

void loop()
{
  // 0%
  ledcWrite(
    LED_PIN,
    0
  );

  delay(1000);

  // 25%
  ledcWrite(
    LED_PIN,
    64
  );

  delay(1000);

  // 50%
  ledcWrite(
    LED_PIN,
    128
  );

  delay(1000);

  // 75%
  ledcWrite(
    LED_PIN,
    192
  );

  delay(1000);

  // 100%
  ledcWrite(
    LED_PIN,
    255
  );

  delay(1000);
}

透過這個實驗,就能清楚看到:

Duty 越小 → LED 越暗
Duty 越大 → LED 越亮

十五、ESP32 呼吸燈

接著讓 Duty:

0
↓
1
↓
2
↓
...
↓
255

再:

255
↓
254
↓
...
↓
0

LED 就會產生:

慢慢變亮
↓
慢慢變暗

的呼吸效果。

十六、ESP32 呼吸燈完整程式

const uint8_t LED_PIN = 18;

const uint32_t PWM_FREQ = 5000;
const uint8_t PWM_RESOLUTION = 8;

const int MAX_DUTY = 255;
const int FADE_DELAY = 8;

void setup()
{
  Serial.begin(115200);

  if (!ledcAttach(
        LED_PIN,
        PWM_FREQ,
        PWM_RESOLUTION))
  {
    Serial.println(
      "LEDC initialization failed!"
    );

    while (true)
    {
      delay(1000);
    }
  }
}

void loop()
{
  // 漸亮
  for (
    int duty = 0;
    duty <= MAX_DUTY;
    duty++
  )
  {
    ledcWrite(
      LED_PIN,
      duty
    );

    delay(
      FADE_DELAY
    );
  }

  // 漸暗
  for (
    int duty = MAX_DUTY;
    duty >= 0;
    duty--
  )
  {
    ledcWrite(
      LED_PIN,
      duty
    );

    delay(
      FADE_DELAY
    );
  }
}

修改:

FADE_DELAY

就可以調整呼吸速度。

十七、ESP32 還有硬體 Fade

除了自己使用:

for()

改變 Duty,目前 Arduino-ESP32 也提供:

ledcFade()

格式:

ledcFade(
  pin,
  startDuty,
  targetDuty,
  fadeTime
);

例如:

ledcFade(
  LED_PIN,
  0,
  255,
  2000
);

可以讓 LED 在約 2000 ms 內由設定的起始 Duty 漸變到目標 Duty。Espressif 官方 LEDC 文件同時提供 ledcFade()、ledcFadeWithInterrupt() 等 API。

對初學者而言,本章先使用:

for()
+
ledcWrite()

因為更容易理解 PWM Duty 如何變化。

十八、RGB LED 是什麼?

RGB LED 可以把三個 LED 顏色整合在同一顆元件裡:

R = Red
G = Green
B = Blue

利用三個顏色不同的亮度比例,就能產生大量不同顏色。

例如概念上:

RGB LED 基本顏色對照表
R G B 顏色
255 0 0 紅
0 255 0 綠
0 0 255 藍
255 255 0 黃
0 255 255 青
255 0 255 紫紅
255 255 255 白

因此只要讓 ESP32 同時產生:

3 組 PWM

就可以分別控制:

Red
Green
Blue

的亮度。

ESP32 LEDC 本身就是設計來提供多路 PWM 波形,官方文件也直接以 RGB LED 作為多 Channel PWM 的應用例子。

十九、RGB LED:共陰極與共陽極

常見 RGB LED 有兩種:

Common Cathode

共陰極:

R ──電阻── GPIO
G ──電阻── GPIO
B ──電阻── GPIO

Common
  │
 GND

這種最直觀:

Duty 越大
↓
越亮

本章程式主要以:

Common Cathode 共陰極 RGB LED

為例。

Common Anode

共陽極:

Common
  │
 3.3V

這種控制邏輯相反。

概念上:

PWM Duty 越大

並不直接等於 LED 越亮,因此程式通常需要做反相,例如在 8-bit 控制下:

255 - value

購買 RGB LED 時應先確認是:

Common Cathode

還是:

Common Anode

二十、RGB LED 接線

本章使用:

Red   → GPIO25
Green → GPIO26
Blue  → GPIO27

共陰極:

ESP32 GPIO25
     │
   220Ω
     │
     ▼
    RGB R

ESP32 GPIO26
     │
   220Ω
     │
     ▼
    RGB G

ESP32 GPIO27
     │
   220Ω
     │
     ▼
    RGB B

RGB Common Cathode
     │
     ▼
    GND

三個顏色最好各自配置限流電阻。

二十一、RGB LED 初始化

需要三個 PWM 輸出:

const uint8_t RED_PIN   = 25;
const uint8_t GREEN_PIN = 26;
const uint8_t BLUE_PIN  = 27;

初始化:

ledcAttach(
  RED_PIN,
  5000,
  8
);

ledcAttach(
  GREEN_PIN,
  5000,
  8
);

ledcAttach(
  BLUE_PIN,
  5000,
  8
);

新版 ledcAttach() 會自動選擇 LEDC Channel;多個符合相同 Frequency / Resolution 的通道也可以共用適合的 Timer,而各 Channel 仍可設定各自 Duty。

二十二、建立 setRGB() 函式

為了讓程式更容易閱讀,可以建立:

void setRGB(
  uint8_t red,
  uint8_t green,
  uint8_t blue
)
{
  ledcWrite(
    RED_PIN,
    red
  );

  ledcWrite(
    GREEN_PIN,
    green
  );

  ledcWrite(
    BLUE_PIN,
    blue
  );
}

這樣就能直接:

setRGB(
  255,
  0,
  0
);

顯示紅色。

二十三、ESP32 RGB LED 基本顏色測試

完整程式:

const uint8_t RED_PIN = 25;
const uint8_t GREEN_PIN = 26;
const uint8_t BLUE_PIN = 27;

const uint32_t PWM_FREQ = 5000;
const uint8_t PWM_RESOLUTION = 8;

void setRGB(
  uint8_t red,
  uint8_t green,
  uint8_t blue
)
{
  ledcWrite(
    RED_PIN,
    red
  );

  ledcWrite(
    GREEN_PIN,
    green
  );

  ledcWrite(
    BLUE_PIN,
    blue
  );
}

void setup()
{
  ledcAttach(
    RED_PIN,
    PWM_FREQ,
    PWM_RESOLUTION
  );

  ledcAttach(
    GREEN_PIN,
    PWM_FREQ,
    PWM_RESOLUTION
  );

  ledcAttach(
    BLUE_PIN,
    PWM_FREQ,
    PWM_RESOLUTION
  );
}

void loop()
{
  // Red
  setRGB(
    255,
    0,
    0
  );

  delay(1000);

  // Green
  setRGB(
    0,
    255,
    0
  );

  delay(1000);

  // Blue
  setRGB(
    0,
    0,
    255
  );

  delay(1000);

  // Yellow
  setRGB(
    255,
    255,
    0
  );

  delay(1000);

  // Cyan
  setRGB(
    0,
    255,
    255
  );

  delay(1000);

  // Magenta
  setRGB(
    255,
    0,
    255
  );

  delay(1000);

  // White
  setRGB(
    255,
    255,
    255
  );

  delay(1000);

  // OFF
  setRGB(
    0,
    0,
    0
  );

  delay(1000);
}

二十四、RGB LED 漸變原理

如果直接:

Red
↓
Green
↓
Blue

會變成突然切換。

要做出平滑漸變,可以讓兩個顏色同時改變。

例如:

Red → Green

Red   255 → 0
Green   0 → 255
Blue    0

程式:

for (
  int i = 0;
  i <= 255;
  i++
)
{
  setRGB(
    255 - i,
    i,
    0
  );

  delay(10);
}

這樣就會產生:

紅
↓
橙
↓
黃
↓
黃綠
↓
綠

的平滑變化。

二十五、RGB LED 三色循環漸變

我們可以設計:

Red
↓
Green
↓
Blue
↓
Red

三個階段。

Red → Green(補充說明)

for (
  int i = 0;
  i <= 255;
  i++
)
{
  setRGB(
    255 - i,
    i,
    0
  );

  delay(10);
}

Green → Blue

for (
  int i = 0;
  i <= 255;
  i++
)
{
  setRGB(
    0,
    255 - i,
    i
  );

  delay(10);
}

Blue → Red

for (
  int i = 0;
  i <= 255;
  i++
)
{
  setRGB(
    i,
    0,
    255 - i
  );

  delay(10);
}

三段接起來,就形成一個循環 RGB 漸變效果。

二十六、ESP32 RGB LED 漸變完整程式

const uint8_t RED_PIN = 25;
const uint8_t GREEN_PIN = 26;
const uint8_t BLUE_PIN = 27;

const uint32_t PWM_FREQ = 5000;
const uint8_t PWM_RESOLUTION = 8;

const uint16_t COLOR_DELAY = 10;

void setRGB(
  uint8_t red,
  uint8_t green,
  uint8_t blue
)
{
  ledcWrite(
    RED_PIN,
    red
  );

  ledcWrite(
    GREEN_PIN,
    green
  );

  ledcWrite(
    BLUE_PIN,
    blue
  );
}

void setup()
{
  Serial.begin(115200);

  ledcAttach(
    RED_PIN,
    PWM_FREQ,
    PWM_RESOLUTION
  );

  ledcAttach(
    GREEN_PIN,
    PWM_FREQ,
    PWM_RESOLUTION
  );

  ledcAttach(
    BLUE_PIN,
    PWM_FREQ,
    PWM_RESOLUTION
  );
}

void loop()
{
  // Red -> Green
  for (
    int i = 0;
    i <= 255;
    i++
  )
  {
    setRGB(
      255 - i,
      i,
      0
    );

    delay(
      COLOR_DELAY
    );
  }

  // Green -> Blue
  for (
    int i = 0;
    i <= 255;
    i++
  )
  {
    setRGB(
      0,
      255 - i,
      i
    );

    delay(
      COLOR_DELAY
    );
  }

  // Blue -> Red
  for (
    int i = 0;
    i <= 255;
    i++
  )
  {
    setRGB(
      i,
      0,
      255 - i
    );

    delay(
      COLOR_DELAY
    );
  }
}

二十七、Passive Buzzer 是什麼?

除了 LED,PWM 還能拿來產生聲音。

本章使用:

Passive Buzzer

在常見教學用語中,Passive Buzzer 通常是指需要外部週期性訊號來驅動的發聲元件,而不是只接固定 DC 電壓就以內建振盪器產生固定音調。

例如壓電 Sounder 本身沒有內建驅動電路時,需要外部訊號驅動,而且輸入訊號 Frequency 改變時,產生的音調也會改變。Murata 對 Piezoelectric Sounder 與帶振盪電路的 Buzzer 也有類似區分。

因此:

Frequency
↓
決定音高

這正好可以利用 ESP32 LEDC。

二十八、Active Buzzer 與 Passive Buzzer 差異

簡化理解:

Active Buzzer 與 Passive Buzzer 比較
項目 Active Buzzer Passive Buzzer
內建振盪 通常有 通常沒有
接固定電壓 可發固定音 不適合作為多音階控制方式
改變音高 通常有限 可透過輸入 Frequency
ESP32 PWM 不一定需要 很適合
音階實驗 不適合 適合

實際模組仍應以手上的 Buzzer / Sounder 規格書為準。

二十九、ESP32 ledcWriteTone()

Arduino-ESP32 LEDC 提供非常方便的:

ledcWriteTone()

格式:

ledcWriteTone(
  pin,
  frequency
);

例如:

ledcWriteTone(
  BUZZER_PIN,
  1000
);

代表讓指定 LEDC Pin 產生:

1000 Hz

的 50% Duty Tone。

如果 Frequency 設為:

0

則停止 Tone 輸出。這是目前 Arduino-ESP32 官方 ledcWriteTone() 的定義。

三十、Passive Buzzer 接線

如果使用適合由 GPIO 訊號驅動的小型教學用壓電發聲元件,可依元件規格設計:

GPIO23
  │
  ▼
Passive Buzzer
  │
 GND

但實際 Buzzer 的:

工作電壓
輸入電流
驅動方式
額定頻率

必須查閱所使用元件的 Datasheet。

如果負載需求超過 GPIO 適合直接驅動的範圍,應使用:

ESP32 GPIO
↓
Transistor / Driver
↓
Buzzer

而不是直接由 GPIO 承擔負載。

三十一、ESP32 Passive Buzzer 基本程式

const uint8_t BUZZER_PIN = 23;

void setup()
{
  Serial.begin(115200);

  ledcAttach(
    BUZZER_PIN,
    2000,
    8
  );
}

void loop()
{
  // 1000 Hz
  ledcWriteTone(
    BUZZER_PIN,
    1000
  );

  delay(500);

  // Stop
  ledcWriteTone(
    BUZZER_PIN,
    0
  );

  delay(500);

  // 2000 Hz
  ledcWriteTone(
    BUZZER_PIN,
    2000
  );

  delay(500);

  // Stop
  ledcWriteTone(
    BUZZER_PIN,
    0
  );

  delay(1000);
}

ledcWriteTone() 會設定 LEDC Pin 輸出指定 Frequency 的 50% Duty Tone。

三十二、使用不同 Frequency 改變音高

例如:

ledcWriteTone(
  BUZZER_PIN,
  500
);
ledcWriteTone(
  BUZZER_PIN,
  1000
);
ledcWriteTone(
  BUZZER_PIN,
  2000
);

外部驅動型 Piezo Sounder 的音調會跟著輸入訊號 Frequency 改變,但實際音量通常也會受到元件共振頻率與輸入電壓等因素影響。

三十三、本章實作:ESP32 呼吸燈 + RGB LED 漸變

現在把本章最重要的內容整合起來。

硬體:

ESP32
├── GPIO18 → 單色 LED
├── GPIO25 → RGB Red
├── GPIO26 → RGB Green
├── GPIO27 → RGB Blue
└── GPIO23 → Passive Buzzer

執行流程:

ESP32 啟動
↓
蜂鳴器提示音
↓
LED 呼吸一次
↓
RGB Red → Green
↓
RGB Green → Blue
↓
RGB Blue → Red
↓
重新循環

這個實驗一次練習:

PWM Frequency
Resolution
Duty Cycle
LEDC
LED 調光
RGB LED
Passive Buzzer

三十四、完整接線表

ESP32 呼吸燈與 RGB LED 完整接線表
ESP32 元件
GPIO18 220Ω → 單色 LED 正極
GND 單色 LED 負極
GPIO25 電阻 → RGB Red
GPIO26 電阻 → RGB Green
GPIO27 電阻 → RGB Blue
GND RGB Common Cathode
GPIO23 Passive Buzzer Signal
GND Passive Buzzer GND

本例以:

Common Cathode RGB LED

為基準。

如果使用 Common Anode RGB LED,需要修改輸出邏輯。

三十五、ESP32 呼吸燈 + RGB LED 漸變完整程式

/*
  第 5 章
  ESP32 PWM 完整教學

  功能:
  1. ESP32 LEDC PWM
  2. 單色 LED 呼吸燈
  3. RGB LED 三色漸變
  4. Passive Buzzer 提示音

  適用:
  Arduino-ESP32 3.x

  RGB LED:
  Common Cathode
*/


// -----------------------------
// GPIO
// -----------------------------
const uint8_t LED_PIN = 18;

const uint8_t RED_PIN = 25;
const uint8_t GREEN_PIN = 26;
const uint8_t BLUE_PIN = 27;

const uint8_t BUZZER_PIN = 23;


// -----------------------------
// PWM
// -----------------------------
const uint32_t PWM_FREQ = 5000;

const uint8_t PWM_RESOLUTION = 8;

const int MAX_DUTY = 255;


// -----------------------------
// Speed
// -----------------------------
const uint8_t BREATH_DELAY = 6;

const uint8_t COLOR_DELAY = 8;


// -----------------------------
// RGB Function
// -----------------------------
void setRGB(
  uint8_t red,
  uint8_t green,
  uint8_t blue
)
{
  ledcWrite(
    RED_PIN,
    red
  );

  ledcWrite(
    GREEN_PIN,
    green
  );

  ledcWrite(
    BLUE_PIN,
    blue
  );
}


// -----------------------------
// Buzzer Beep
// -----------------------------
void beep(
  uint32_t frequency,
  uint16_t duration
)
{
  ledcWriteTone(
    BUZZER_PIN,
    frequency
  );

  delay(
    duration
  );

  ledcWriteTone(
    BUZZER_PIN,
    0
  );
}


// -----------------------------
// Breathing LED
// -----------------------------
void breathingLED()
{
  // Fade In
  for (
    int duty = 0;
    duty <= MAX_DUTY;
    duty++
  )
  {
    ledcWrite(
      LED_PIN,
      duty
    );

    delay(
      BREATH_DELAY
    );
  }

  // Fade Out
  for (
    int duty = MAX_DUTY;
    duty >= 0;
    duty--
  )
  {
    ledcWrite(
      LED_PIN,
      duty
    );

    delay(
      BREATH_DELAY
    );
  }
}


// -----------------------------
// RGB Gradient
// -----------------------------
void rgbGradient()
{
  // -------------------------
  // Red -> Green
  // -------------------------
  for (
    int i = 0;
    i <= 255;
    i++
  )
  {
    setRGB(
      255 - i,
      i,
      0
    );

    delay(
      COLOR_DELAY
    );
  }


  // -------------------------
  // Green -> Blue
  // -------------------------
  for (
    int i = 0;
    i <= 255;
    i++
  )
  {
    setRGB(
      0,
      255 - i,
      i
    );

    delay(
      COLOR_DELAY
    );
  }


  // -------------------------
  // Blue -> Red
  // -------------------------
  for (
    int i = 0;
    i <= 255;
    i++
  )
  {
    setRGB(
      i,
      0,
      255 - i
    );

    delay(
      COLOR_DELAY
    );
  }
}


// -----------------------------
// setup()
// -----------------------------
void setup()
{
  Serial.begin(115200);

  delay(500);

  Serial.println();
  Serial.println(
    "ESP32 PWM Demo"
  );


  // -------------------------
  // Single LED
  // -------------------------
  if (!ledcAttach(
        LED_PIN,
        PWM_FREQ,
        PWM_RESOLUTION))
  {
    Serial.println(
      "LED PWM initialization failed!"
    );

    while (true)
    {
      delay(1000);
    }
  }


  // -------------------------
  // RGB Red
  // -------------------------
  if (!ledcAttach(
        RED_PIN,
        PWM_FREQ,
        PWM_RESOLUTION))
  {
    Serial.println(
      "RED PWM initialization failed!"
    );
  }


  // -------------------------
  // RGB Green
  // -------------------------
  if (!ledcAttach(
        GREEN_PIN,
        PWM_FREQ,
        PWM_RESOLUTION))
  {
    Serial.println(
      "GREEN PWM initialization failed!"
    );
  }


  // -------------------------
  // RGB Blue
  // -------------------------
  if (!ledcAttach(
        BLUE_PIN,
        PWM_FREQ,
        PWM_RESOLUTION))
  {
    Serial.println(
      "BLUE PWM initialization failed!"
    );
  }


  // -------------------------
  // Passive Buzzer
  // 使用不同初始 Frequency
  // -------------------------
  if (!ledcAttach(
        BUZZER_PIN,
        2000,
        PWM_RESOLUTION))
  {
    Serial.println(
      "Buzzer LEDC initialization failed!"
    );
  }


  // Turn everything off
  ledcWrite(
    LED_PIN,
    0
  );

  setRGB(
    0,
    0,
    0
  );


  Serial.println(
    "PWM Ready"
  );


  // Startup Beep
  beep(
    1500,
    150
  );

  delay(100);

  beep(
    2200,
    150
  );
}


// -----------------------------
// loop()
// -----------------------------
void loop()
{
  Serial.println(
    "Breathing LED"
  );

  breathingLED();


  delay(300);


  Serial.println(
    "RGB Gradient"
  );

  rgbGradient();


  delay(300);


  // End Beep
  beep(
    1800,
    100
  );


  delay(500);
}

這支程式使用的是目前 Arduino-ESP32 的 Pin-based LEDC API:ledcAttach() 自動分配 Channel,ledcWrite() 設定 Duty,ledcWriteTone() 則可以產生指定 Frequency 的 50% Duty Tone。

三十六、完整程式流程解析

開機:

setup()
↓
初始化 Serial
↓
初始化單色 LED PWM
↓
初始化 RGB R / G / B PWM
↓
初始化 Passive Buzzer
↓
蜂鳴兩聲

接著:

loop()

執行:

breathingLED()

產生:

LED 漸亮
↓
LED 最亮
↓
LED 漸暗
↓
LED 熄滅

然後:

rgbGradient()

執行:

Red
↓
Green
↓
Blue
↓
Red

最後蜂鳴器發出提示音,再重新循環。

三十七、為什麼 RGB LED 需要三路 PWM?

因為:

R
G
B

必須分別控制亮度。

例如:

R = 255
G = 128
B = 0

與:

R = 128
G = 0
B = 255

會產生完全不同的混色結果。

因此架構就是:

ESP32 LEDC
├── PWM 1 → Red
├── PWM 2 → Green
└── PWM 3 → Blue

LEDC 的多 Channel 設計正適合這類 RGB LED 控制。

三十八、如何控制 RGB 漸變速度?

本章設定:

const uint8_t COLOR_DELAY = 8;

如果改:

COLOR_DELAY = 2;

顏色變化會更快。

如果:

COLOR_DELAY = 20;

漸變會變慢。

呼吸燈則由:

BREATH_DELAY

控制。

因此可以分別調整:

BREATH_DELAY
↓
呼吸速度

COLOR_DELAY
↓
RGB 漸變速度

三十九、如果 RGB LED 是 Common Anode 怎麼辦?

如果你的 RGB LED 是:

Common Anode(補充說明)

可以把 setRGB() 改成:

void setRGB(
  uint8_t red,
  uint8_t green,
  uint8_t blue
)
{
  ledcWrite(
    RED_PIN,
    255 - red
  );

  ledcWrite(
    GREEN_PIN,
    255 - green
  );

  ledcWrite(
    BLUE_PIN,
    255 - blue
  );
}

也就是將:

0 ~ 255

反相成:

255 ~ 0

其他 RGB 漸變程式就不需要大幅修改。

四十、PWM Frequency 越高越好嗎?

不是。

ESP32 PWM 應依用途選擇:

Frequency
+
Resolution

兩者存在硬體上的取捨。

Espressif 官方文件明確指出 Frequency 與 Duty Resolution 是相互關聯的;要求更高 Frequency 時,通常可用的 Resolution 會降低。

因此本章:

5000 Hz
8-bit

只是方便 LED 與 RGB LED 教學的一組設定,並不是所有 PWM 應用的唯一最佳設定。

四十一、ESP32 analogWrite() 可以使用嗎?

可以。

目前 Arduino-ESP32 官方也提供:

analogWrite(
  pin,
  value
);

而且預設可使用:

0 ~ 255

另外還有:

analogWriteResolution()

以及:

analogWriteFrequency()

可以調整 Resolution 與 Frequency。

所以簡單 LED 調光可以寫:

analogWrite(
  LED_PIN,
  128
);

但是本章仍建議學會:

LEDC

因為後續要理解:

Frequency
Resolution
Duty
RGB
Tone
Fade

時,LEDC 會更加完整。

四十二、常見錯誤:ledcSetup 找不到

如果看到:

'ledcSetup' was not declared in this scope

或:

'ledcAttachPin' was not declared in this scope

而目前安裝的是 Arduino-ESP32 3.x,原因通常是教學使用了舊 API。

舊版:

ledcSetup(
  channel,
  frequency,
  resolution
);

ledcAttachPin(
  pin,
  channel
);

新版:

ledcAttach(
  pin,
  frequency,
  resolution
);

Espressif 的 2.x → 3.0 Migration Guide 已明確列出 ledcSetup()、ledcAttachPin() 被移除,並由 ledcAttach() 整合設定。

四十三、常見錯誤:RGB LED 顏色完全不對

先檢查:

RGB Pin 腳排列

不要假設每一顆 RGB LED 都是:

R
Common
G
B

實際排列應依產品 Datasheet 或實際量測確認。

另外還要確認:

Common Cathode

還是:

Common Anode

如果類型弄反,PWM 控制結果也會反過來。

四十四、常見錯誤:RGB 三色需要幾顆電阻?

建議:

Red   → 一顆限流電阻
Green → 一顆限流電阻
Blue  → 一顆限流電阻

也就是:

三顆

而不是只在 RGB LED Common 端共用一顆電阻。

這樣才能比較合理地分別控制三個 LED Die 的電流。

四十五、常見錯誤:Passive Buzzer 沒有聲音

先確認買到的是:

Passive / External-drive

還是:

Active / Built-in oscillator

兩者驅動概念不同。

也要檢查:

Pin 接線
GND
工作電壓
驅動需求
Frequency

外部驅動型 Piezo Sounder 需要輸入週期性訊號,而實際最響的 Frequency 通常與元件本身的共振特性有關。

四十六、常見錯誤:蜂鳴器直接接 GPIO

不能只看它叫:

Buzzer

就假設一定可以直接由 ESP32 GPIO 驅動。

應先查看:

Datasheet

確認:

工作電壓
電流
輸入方式

需要較大驅動能力時,使用:

GPIO
↓
Transistor / MOSFET / Driver
↓
Buzzer

會比較合理。

四十七、ESP32 PWM 可以應用在哪裡?

掌握本章後,可以繼續發展:

LED 調光
RGB Mood Light
RGB 狀態燈
呼吸燈
蜂鳴器警報
電子門鈴
音階
DC Motor Speed
Fan Speed
MOSFET Lighting Control
IoT Smart Light

所以:

PWM 是 ESP32 從基本 GPIO 控制進入實際控制系統的重要技術。

四十八、FAQ:ESP32 PWM 常見問題

Q1:ESP32 支援 PWM 嗎?

支援。

ESP32 提供 LEDC 周邊來產生 PWM,官方定位除了 LED 亮度控制,也可用於其他 PWM 應用。

Q2:ESP32 LEDC 是什麼?

LEDC:

LED Control

是 ESP32 用來產生 PWM 的硬體周邊之一,可設定 Frequency、Resolution 與 Duty。

Q3:ESP32 LED 亮度如何控制?

可以改變:

PWM Duty Cycle

例如在 8-bit PWM:

0 ~ 255

中:

0

代表最低輸出,

而:

255

代表最高 Duty。

Q4:ESP32 RGB LED 為什麼需要 PWM?

因為 RGB LED 必須分別控制:

Red
Green
Blue

三個亮度,才能進行混色與漸變。

Q5:ESP32 可以做呼吸燈嗎?

可以。

最基本的方法是使用:

ledcWrite()

逐步改變:

Duty

也可以使用目前官方提供的:

ledcFade()

進行 Fade。

Q6:ESP32 可以控制 Passive Buzzer 嗎?

可以利用 PWM / Tone 訊號驅動適合的外部驅動型發聲元件。

Arduino-ESP32 提供:

ledcWriteTone(
  pin,
  frequency
);

可產生指定 Frequency、50% Duty 的 Tone。

Q7:如何停止 ledcWriteTone()?

使用:

ledcWriteTone(
  BUZZER_PIN,
  0
);

Frequency 為 0 時,官方 API 會將 Duty 設為 0。

Q8:為什麼 ledcSetup() 無法編譯?

因為 Arduino-ESP32 3.x 已移除:

ledcSetup()
ledcAttachPin()

新版改用:

ledcAttach()

而且 LEDC Channel 可以自動配置。

四十九、本章學習重點

本章最重要的第一個觀念:

PWM

不是直接輸出真正連續變化的類比電壓,而是:

HIGH / LOW
快速切換
↓
改變 Duty
↓
產生平均輸出效果

第二個重點:

PWM
├── Frequency
├── Resolution
└── Duty Cycle

第三個重點:

ESP32 透過:

LEDC

來進行完整 PWM 控制。

目前 Arduino-ESP32 3.x 的基本寫法:

ledcAttach(
  pin,
  frequency,
  resolution
);

ledcWrite(
  pin,
  duty
);

ledcAttach() 會自動配置 Channel。

第四個重點:

RGB LED

其實就是:

Red PWM
+
Green PWM
+
Blue PWM

三路亮度組合。

第五個重點:

Passive Buzzer

可以透過改變輸入 Frequency 產生不同音高,而 Arduino-ESP32 可以利用:

ledcWriteTone()

方便地產生 Tone。

五十、本章總結

在第 5 章中,我們從 ESP32 最基本的:

PWM

一路完成:

PWM 原理
↓
Frequency
↓
Resolution
↓
Duty Cycle
↓
LEDC
↓
LED 調光
↓
呼吸燈
↓
RGB LED
↓
RGB 漸變
↓
Passive Buzzer

並完成完整實作:

ESP32 呼吸燈 + RGB LED 漸變(補充說明)

其中最重要的 Arduino-ESP32 3.x API 包括:

ledcAttach()
ledcWrite()
ledcWriteTone()

另外還可以進一步使用:

ledcRead()
ledcReadFreq()
ledcChangeFrequency()
ledcFade()

目前 Arduino-ESP32 官方已經將 LEDC 改為較直觀的 Pin-based API,並由 ledcAttach() 自動配置 Channel;舊版 ledcSetup() 與 ledcAttachPin() 已在 3.x API 移除。

學完本章後,就已經不只是會讓 ESP32 的 LED:

ON
OFF

而是能夠控制:

亮度
顏色
漸變
呼吸效果
聲音頻率

這也是後續學習:

RGB 智慧燈
蜂鳴器音階
馬達速度控制
風扇調速
MOSFET 功率控制
智慧家庭照明
IoT 警報器

的重要基礎。

下一步學習

不要停在這一篇,接著把內容做成自己的實作

目前主題:ESP32 / IoT。從完整學習路線、免費資源、會員下載,再接到實作所需材料。

同系列:ESP32 |  已是會員?直接前往 Getfile
相關資源與會員文章下載
文章可免費閱讀;下載完整 DOCX 或 PDF 需登入免費基本會員。
查看相關資源 會員免費下載 DOCX 會員免費 PDF/列印

← 回文章列表