《超圖解ESP32應用實作》採用ESP32 Arduino開發平台2.0版(基於ESP-IDF 4.4),以Arduino IDE 1.8.19為例,請在「開發板管理員」中搜尋“esp32”,並且安裝2.x版,筆者安裝的是2.0.17版。

2024年五月底推出的ESP32 Arduino開發平台3.0版(基於ESP-IDF 5.1),修改了一些語法,相關說明請參閱樂鑫官方的“Migration from 2.x to 3.0”(從2.x版遷移到3.0版)文件,本文將重點說明其中與PWM訊號有關的部分以及程式範例。
ESP32 Arduino開發平台3.x版的PWM訊號輸出指令改動,主要是把「通道(channel)」設置變成自動,並且刪除原先的ledcSetup()和ledcAttachPin()函式,改成ledcAttach()或ledcAttachChannel()函式。

ledcAttach()函式用於設置PWM輸出的接腳、頻率和解析度,並自動選擇LEDC 通道,其原型如下。若設置成功,則傳回true;如果傳回false,代表發生錯誤且未配置LEDC 通道。
bool ledcAttach(uint8_t pin, uint32_t freq, uint8_t resolution);
函式參數說明:
- pin:PWM輸出的腳位
- freq:PWM頻率
- resolution:PWM的解析度,有效範圍為1~14位元(ESP32-S3為1~20 位元)。
ledcAttachChannel()函式的作用跟上面的函式相同,只是多了設置通道的“channel”參數,其原型如下:
bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, int8_t channel);
輸出PWM訊號的函式同樣是ledcWrite(),但第一個參數改成“pin”(接腳編號),其原型如下。若設定PWM成功,函式將傳回 true;如果傳回false,則代表發生錯誤且未設定工作週期(佔空比)。
bool ledcWrite(uint8_t pin, uint32_t duty);
為了提升跟其他非ESP32晶片的Arduino程式相容性,ESP32 Arduino開發平台也新增analogWrite()函式,不過,從函式的原始碼(位於esp32-hal-ledc.c)可看出,analogWrite()函式其實在內部呼叫了ledcWrite(),而且呼叫analogWrite()函式之前,仍要先執行ledcAttach()或ledcAttachChannel()設置PWM,所以在ESP32平台,沒有必要使用analogWrite()函式。
呼吸燈效果範例程式一
底下是採用ledcAttach()設置LED PWM值的呼吸燈效果程式,編譯執行此範例程式,需要把ESP32 Arduino開發平台升級到3.x版。
#define LED_PIN 5 // LED腳位
#define MAX_BRIGHTNESS 255 // 亮度上限
#define PWM_RES 12 // 12位元解析度
#define PWM_FREQ 5000 // PWM頻率500Hz
#define MAXDUTY ((1 << PWM_RES) - 1) // 工作週期上限
#define FADE_DELAY 30 // 變化間隔時間
unsigned long lastTime = 0;
// 淡入淡出LED效果(呼吸燈)
void fadeLED() {
static int brightness = 0; // 亮度值
static int fadeAmount = 5; // 每次的亮度變化量
int duty = (MAXDUTY / MAX_BRIGHTNESS) * min(brightness, MAX_BRIGHTNESS);
ledcWrite(LED_PIN, duty);
// 上面這一行可以改成:
// analogWrite(LED_PIN, duty);
brightness += fadeAmount; // 調整亮度
// 完成淡入或淡出效果後,反轉亮度變化量。
if (brightness <= 0 || brightness >= MAX_BRIGHTNESS) {
fadeAmount = -fadeAmount;
}
}
void setup() {
// 設定PWM輸出腳位、頻率和解析度
ledcAttach(LED_PIN, PWM_FREQ, PWM_RES);
}
void loop() {
unsigned long now = millis();
if (now - lastTime >= FADE_DELAY) {
lastTime = now;
fadeLED();
}
}
這個程式指定LEDC採用12位元解析度,因此工作週期(duty,佔空比)最大值為212 -1 = 4095;MAXDUTY透過左移運算子(<<)計算出指定解析度的最大工作週期值;亮度上限255,代表亮度有255個層次變化。

呼吸燈效果範例程式二
ESP32 Arduino開發平台3.0版內建呼吸燈效果的ledcFade()函式,傳入LED腳位、起始工作週期值、目標工作週期值和變化時間,即可產生淡入(fade in,從低亮度逐漸調升到高亮度)或淡出(fade out,從高亮度逐漸降低為低亮度)效果,此函式原型如下:
bool ledcFade(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms);
參數說明:
- pin:LED腳位
- start_duty:淡入淡出變化的起始工作週期。
- target_duty:淡入淡出的目標週期。
- max_fade_time_ms:淡入淡出的變化時間。
若配置成功,函式將傳回true;如果傳回 false,代表發生錯誤且未配置淡入淡出效果。
底下是採用ledcFade()函式編寫的呼吸燈效果,loop()將每隔3000毫秒交替「淡入」和「淡出」變化。
#define LED_PIN 5 // LED腳位
#define PWM_RES 12 // PWM解析度:12位元
#define PWM_FREQ 5000 // PWM頻率:5KHz
#define START_DUTY 0 // 起始工作週期
#define TARGET_DUTY 4095 // 目標工作週期
#define FADE_TIME 3000 // 變化時間
unsigned long lastTime = 0;
bool fadeIn = true;
void setup() {
// 設定LED腳位、頻率和解析度,自動選擇PWM通道。
ledcAttach(LED_PIN, PWM_FREQ, PWM_RES);
// 設定淡出淡入變化的LED腳位、起始值、結束值、變化時間
// 燈光的PWM值從0淡入變化到4095
ledcFade(LED_PIN, START_DUTY, TARGET_DUTY, FADE_TIME);
delay(FADE_TIME); // 等到淡入結束
fadeIn = false;
}
void loop() {
unsigned long now = millis();
if (now - lastTime >= FADE_TIME) {
lastTime = now;
if (fadeIn) {
// 設定淡出淡入變化的LED腳位、起始值、結束值、變化時間
// 燈光的PWM值從0淡入變化到4095
ledcFade(LED_PIN, START_DUTY, TARGET_DUTY, FADE_TIME);
fadeIn = false;
} else {
// 設定淡出淡入變化的LED腳位、起始值、結束值、變化時間
// 燈光的PWM值從0淡入變化到4095
ledcFade(LED_PIN, TARGET_DUTY, START_DUTY, FADE_TIME);
fadeIn = true;
}
}
}
使用中斷常式的呼吸燈效果
ledcFadeWithInterrupt()同樣是執行「淡入淡出效果」的函式,但是透過中斷告知「變化完成」,函式原型如下:
bool ledcFadeWithInterrupt(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void));
參數說明:
- pin:LED腳位
- start_duty:淡入淡出變化的起始工作週期。
- target_duty:淡入淡出的目標週期。
- max_fade_time_ms:淡入淡出的變化時間。
- 觸發中斷時呼叫的處理函式
若配置成功,函式將傳回true;如果傳回 false,代表發生錯誤且未配置淡入淡出效果。
底下是採用ledcFadeWithInterrupt ()函式編寫的呼吸燈效果,每隔3000毫秒交替「淡入」和「淡出」變化。
#define LED_PIN 5 // LED腳位
#define PWM_RES 12 // PWM解析度:12位元
#define PWM_FREQ 5000 // PWM頻率:5KHz
#define START_DUTY 0 // 起始工作週期
#define TARGET_DUTY 4095 // 目標工作週期
#define FADE_TIME 3000 // 變化時間
volatile bool fadeEnded = false; // 表示LED變化結束與否
bool fadeOut = false; // 是否為「淡出」狀態
// 中斷處理常式
void ARDUINO_ISR_ATTR FADE_ISR() {
fadeEnded = true; // 變化結束
}
void setup() {
// 設定LED腳位、頻率和解析度,自動選擇PWM通道。
ledcAttach(LED_PIN, PWM_FREQ, PWM_RES);
// 使用中斷設置LED淡入效果
// 燈光的PWM值從0淡出變化到4095
ledcFadeWithInterrupt(LED_PIN, START_DUTY, TARGET_DUTY, FADE_TIME, FADE_ISR);
}
void loop() {
if (fadeEnded) { // 若「LED變化效果」結束…
fadeEnded = false;
if (fadeOut) { // 若上次是「淡出」效果…
// 開始進行「淡入」效果
ledcFadeWithInterrupt(LED_PIN, START_DUTY, TARGET_DUTY, FADE_TIME, FADE_ISR);
fadeOut = false;
} else {
// 開始進行「淡出」效果
ledcFadeWithInterrupt(LED_PIN, TARGET_DUTY, START_DUTY, FADE_TIME, FADE_ISR);
fadeOut = true;
}
}
}
