精通 ESP32 呼吸燈 Breathing 設計|漸亮漸暗的 LEDC PWM 效果教學
在物聯網裝置設計中,漸亮漸暗的 LED 呼吸燈(Breathing Light)效果不僅常見,還能為裝置增添生命感。無論是待機顯示、睡眠模式提示,還是氛圍燈設計,呼吸燈都是一個實用又美觀的功能。
我將帶你從零開始,深入掌握如何使用 ESP32 與 LEDC 和 PWM 實現呼吸燈效果,並使用 ESP-IDF 開發環境,完整示範如何控制 LED 漸亮漸暗,達到視覺上平滑且穩定的呼吸感。

內容
什麼是「呼吸燈」?
呼吸燈(Breathing Light)是一種 LED 亮度隨時間緩慢變化、看起來像「呼吸」一樣的燈光效果。它不像一般閃爍那樣突兀,而是 漸亮→漸暗→重複循環,產生平滑而具有節奏感的視覺體驗。
這種燈光效果經常應用在:
- 裝置待機狀態顯示(如筆電電源燈)
- 睡眠輔助燈、夜燈
- 智慧家居氛圍照明
- 視覺 UI 回饋效果(UX)
呼吸燈不只能讓裝置看起來更有生命感,也能提供使用者更溫和的視覺提示。
PWM 與呼吸燈原理快速理解
呼吸燈的核心原理,是透過 PWM 技術改變 LED 的亮度。
PWM 是一種讓數位腳位「看起來像類比輸出」的方式,控制的是每個週期中高電位的時間佔比(即佔空比)。當我們讓佔空比在 0%~100% 間平滑變化時,LED 的亮度也會隨之逐漸變化,產生呼吸燈效果。
ESP32 的 LEDC(LED Control)模組支援高解析度的 PWM 輸出,最多支援 16 個通道,能同時控制多顆 LED 或馬達。
開發環境
在開始編程之前,請確保已完成以下準備工作:
- 安裝 ESP-IDF 開發環境 (至少版本 v4.4 或更高)。
- ESP32 開發板。
使用 ESP-IDF 撰寫呼吸燈控制程式
建立一個 ESP-IDF 專案,並將下列程式碼放入 main/main.c
中:
/**
* ESP32 Breathing LED Example using ESP-IDF
* This code demonstrates a smooth breathing effect on an LED using PWM hardware fading
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/ledc.h" // LED Control (PWM) driver
#include "esp_err.h" // ESP32 error codes
/* Hardware Configuration */
#define LED_GPIO GPIO_NUM_2 // GPIO pin connected to LED (adjust according to your setup)
#define LEDC_CHANNEL LEDC_CHANNEL_0 // PWM channel (ESP32 has 0-7 high-speed channels)
#define LEDC_TIMER LEDC_TIMER_0 // PWM timer (ESP32 has 0-3 timers)
#define LEDC_MODE LEDC_HIGH_SPEED_MODE // PWM operating mode
#define LEDC_FREQ_HZ 5000 // PWM frequency in Hz (5kHz works well for LEDs)
#define LEDC_RES LEDC_TIMER_13_BIT // PWM resolution (13-bit = 0-8191 duty values)
#define BREATHE_TIME_MS 3000 // Total time for one complete breathe cycle (in milliseconds)
/**
* @brief Main application entry point
*/
void app_main(void)
{
/* Step 1: Configure PWM Timer */
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_MODE, // PWM mode (high-speed or low-speed)
.timer_num = LEDC_TIMER, // Timer number (0-3)
.duty_resolution = LEDC_RES, // PWM resolution (bit depth)
.freq_hz = LEDC_FREQ_HZ, // PWM frequency
.clk_cfg = LEDC_AUTO_CLK // Automatic clock source selection
};
// Apply timer configuration - this sets up the base PWM signal characteristics
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
/* Step 2: Configure PWM Channel */
ledc_channel_config_t ledc_channel = {
.gpio_num = LED_GPIO, // GPIO pin connected to LED
.speed_mode = LEDC_MODE, // Must match timer mode
.channel = LEDC_CHANNEL, // PWM channel (0-7)
.intr_type = LEDC_INTR_DISABLE,// Disable interrupts (not needed for fading)
.timer_sel = LEDC_TIMER, // Timer to be attached to this channel
.duty = 0, // Initial duty cycle (0 = LED off)
.hpoint = 0 // Phase point (0 for standard PWM)
};
// Apply channel configuration - links the timer to a specific GPIO
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
/* Step 3: Initialize Hardware Fading Functionality */
// This enables the ESP32's built-in PWM fading hardware acceleration
// Parameter is interrupt flags (0 means no special interrupt handling)
ESP_ERROR_CHECK(ledc_fade_func_install(0));
/* Main loop to create breathing effect */
while (1) {
/* Fade IN (from 0% to 100% brightness) */
// Configure fade: target duty = max (8191 for 13-bit), time = half of total cycle
ESP_ERROR_CHECK(ledc_set_fade_with_time(LEDC_MODE, LEDC_CHANNEL,
8191, BREATHE_TIME_MS/2));
// Start fade (LEDC_FADE_NO_WAIT means don't block during fade)
ESP_ERROR_CHECK(ledc_fade_start(LEDC_MODE, LEDC_CHANNEL, LEDC_FADE_NO_WAIT));
// Delay for fade duration (while hardware handles the actual fading)
vTaskDelay(pdMS_TO_TICKS(BREATHE_TIME_MS/2));
/* Fade OUT (from 100% back to 0% brightness) */
ESP_ERROR_CHECK(ledc_set_fade_with_time(LEDC_MODE, LEDC_CHANNEL,
0, BREATHE_TIME_MS/2));
ESP_ERROR_CHECK(ledc_fade_start(LEDC_MODE, LEDC_CHANNEL, LEDC_FADE_NO_WAIT));
vTaskDelay(pdMS_TO_TICKS(BREATHE_TIME_MS/2));
/* Note: The actual fading happens in hardware while the CPU is free to do other tasks */
}
}
呼吸效果控制技巧
Fade IN (漸亮):
ledc_set_fade_with_time()
:這個函數用來設定漸變的目標佔空比以及時間。在這裡,目標佔空比是 8191,這對應於 100% 亮度(13 位解析度,範圍是 0~8191)。BREATHE_TIME_MS / 2
表示漸變的時間是總週期的一半。ledc_fade_start()
:開始執行設定的漸變,LEDC_FADE_NO_WAIT
表示這個操作不會阻塞程式執行,會讓硬體自動處理漸變,並且程式會繼續執行後續代碼。vTaskDelay()
:這個延遲函數讓程式在漸變時間內進行延遲,保證在漸變過程中不會進入下一步。這裡的延遲時間是BREATHE_TIME_MS / 2
,也就是漸變所需時間的一半。
Fade OUT (漸暗):
ledc_set_fade_with_time()
:與漸亮相似,這裡的目標佔空比設為 0,表示要從 100% 亮度漸變回 0% 亮度,漸變時間同樣是週期的一半。ledc_fade_start()
:開始漸變,LEDC_FADE_NO_WAIT
參數的使用方式與前面相同。vTaskDelay()
:這裡的延遲時間同樣是BREATHE_TIME_MS / 2
,保證漸變時間的同步。
關鍵概念:
- 漸變時間(
BREATHE_TIME_MS
):這個變數控制了整個呼吸燈週期的長度,包括從最暗到最亮的過渡,以及從最亮到最暗的過渡。你可以根據需求調整這個變數來改變呼吸燈的速度。 ledc_set_fade_with_time()
:這個函數會根據設定的時間,在指定的時間內完成漸變,讓亮度平滑過渡。BREATHE_TIME_MS / 2
的時間設定確保了漸變的順暢性和效果。LEDC_FADE_NO_WAIT
:表示ledc_fade_start()
不會等待漸變完成,而是直接讓硬體處理漸變。這樣程式不會被阻塞,可以繼續執行其他的邏輯。vTaskDelay()
:這個延遲確保了程式在等待漸變完成的同時,保持適當的時間同步,不會提前進入下一輪漸變。
編譯和燒錄
完成程式碼後,您可以使用 ESP-IDF 提供的命令進行編譯、燒錄和監控。
結論
呼吸燈看似簡單,其實涵蓋了 PWM 控制、時間延遲、輸出平滑調變 等多個關鍵概念,是非常實用的入門範例。當你能靈活運用這些基礎,未來在馬達控制、燈光特效或 UI 回饋設計中都能派上用場。
下一篇教學我會帶你做出 RGB 呼吸燈 或是 根據聲音或感測器數據動態變化的燈效,敬請期待!