前言: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:
最亮
所以可以簡單理解:
| Duty Cycle | LED 效果 |
|---|---|
| 0% | 熄滅 |
| 25% | 暗 |
| 50% | 中等 |
| 75% | 亮 |
| 100% | 最亮 |
五、什麼是 PWM Resolution?
Resolution 就是:
PWM 解析度
假設設定:
8-bit
代表 PWM 可以分成:
2^8 = 256
個等級。
因此 Duty 值就是:
0 ~ 255
例如:
| 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 開發板 | 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 | 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
利用三個顏色不同的亮度比例,就能產生大量不同顏色。
例如概念上:
| 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 |
|---|---|---|
| 內建振盪 | 通常有 | 通常沒有 |
| 接固定電壓 | 可發固定音 | 不適合作為多音階控制方式 |
| 改變音高 | 通常有限 | 可透過輸入 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 | 元件 |
|---|---|
| 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 警報器
的重要基礎。