
一、前言
上一篇講到了數位輸出的概念,讓 ESP32 透過輸出高電位和低電位點亮或是熄滅 LED 燈。但是數位輸出就只能做到打開或關閉嗎?其實數位輸出還能做出更多變化!讓我們來看看數位輸出的進階用法—PWM。
二、PWM 簡介
PWM(Pulse-width modulation,脈波寬度調變)是一種用數位訊號模擬類比訊號的技術,也是目前常見的數位功率控制方法。
一般來說,數位訊號只能輸出高電位(HIGH)和低電位(LOW)兩種狀態,並且當高、低電位在固定時間周期內有規律的變化時,會產生如下圖般的波形,像這樣的波形我們稱之為脈波(pulse)。

而 PWM 的原理就是在不改變時間周期的條件下,調整高電位在脈波中的寬度(亦即通電時間在周期中的比例),就能調整數位輸出的功率,因此稱做脈波寬度調變。舉凡 LED、直流馬達、RC 伺服馬達都可以用 PWM 來控制。
如何計算 PWM
PWM 的輸出實際上是一個 0~3.3v 的可變電壓訊號,這個訊號由頻率和工作周期(duty cycle)組成。我們知道,頻率是周期的倒數,周期是由高態時間加上低態時間組成;工作周期則是高態時間在整個周期中的所佔的比例。

用公式表示的話就會是:

有了工作周期的概念後,就能換算出 PWM 輸出的平均電壓。讓我們看一個例子(請參考下圖):假設有一個周期 10ms,工作周期 50%的脈波訊號,每個周期產生的平均電壓就是 1.65V。若工作周期是80%,平均電壓就會是 2.64V。工作周期越高,平均輸出電壓就越大,相對的平均輸出功率也會越大。
而當工作周期為 0% 或 100% 時,輸出的電壓就會是 0V 或 3.3V,等同於數位輸出的 LOW 或 HIGH。

三、在 ESP32 上面使用 LED PWM
💡 Tips
樂鑫為了讓 ESP32 也能使用 Arduino IDE 開發,另外開發了 Arduino-ESP32 這個核心函式庫,試圖做到像是在 Arduino 原生環境開發的體驗。 過去 Arduino-ESP32 只能用 ledc
才能產生 PWM 訊號,但在 2.0.6 版本以後,Arduino-ESP32 都能夠支援 analogWrite
。
ESP32 總共有 16 個 LED PWM 通道,每一個通道都可以獨立控制,除了 GPIO 34, GPIO 35, GPIO 36, GPIO 39(非輸出接腳)以及 GPIO 6~11,其他 I/O 接腳都能產生 PWM 訊號,可以任意進行配置。
(註:使用時請跳過 GPIO 6~11, GPIO 34, GPIO 35, GPIO 36, GPIO 39 這幾隻接腳。原因請見第三篇。)
LEDC 的配置比較複雜,需要手動配置多個參數,但是用途比較廣泛;analogWrite 函式的用法則與 Arduino 相同,使用時只要選擇 GPIO 接腳以及 PWM 數值即可。
下面將介紹 LEDC 以及 analogWrite 兩種 API 的使用方法。
LEDC API
💡 Tips
ledc 是 LED Control 的縮寫,它主要設計用來控制 LED,所以才會看到以 ledc 為開頭的語法。
LEDC API 的設定比較複雜,在配置時需要先設定三個參數:通道、頻率以及解析度,接著指定要使用的接腳,最後才是輸出 PWM 訊號。
步驟
1. 從 16 個通道中選擇一個 PWM 通道使用,編號從 0 到 15
2. 設定 PWM 頻率(LED使用5kHz即可)
3. 設定 Duty Cycle 的解析度 當我們把解析度設成 8 位元時,代表 PWM 的輸出數值範圍就會是 0~255。ESP32 的解析度最高可以到 20 位元,解析度越高,就代表能控制越微小的變化量。
通道、頻率與解析度都設定好之後,就可以將這些參數填入 ledSetup
ledcSetup(channel, freq, resolution_bits); // 設定通道、頻率與解析度
4. 指定要輸出的 GPIO 接腳與 PWM 通道
ledcAttechPin(pin, channel); // 設定接腳與通道
5. 在指定的PWM通道輸出 duty cycle
ledcWrite(channel, dutycycle); // 輸出PWM訊號驅動LED
analogWrite API
相對於 LEDC,analogWrite() 的設定就簡單許多。它使用的方式與 Arduino 語言相同,只需要設定 GPIO 接腳以及 PWM 數值(預設範圍從 0~255)就能在指定的接腳輸出對應的 PWM訊號。
analogWrite(pin, value); // value的預設範圍是0~255
如果想改變 PWM 輸出 0~255 以外的數值,可以使用 analogWriteResolution
來調整解析度,最高可以到 20 位元。
analogWriteResolution(bits); // 選擇PWM通道的解析度
要修改頻率的話則是用 analogWriteFrequency
。
analogWriteFrequency(freq);
四、範例 1: LED 呼吸燈
💡 Tips
所有能設定為輸出的接腳都可以產生 PWM 訊號。
材料清單
- ESP32 開發板 x1
- micro USB 傳輸線 x1
- 麵包板 x1
- 杜邦線(公-公) x2
- 5mm LED 燈 x1
- 220Ω ~ 1kΩ 電阻 x1
連接示意圖

程式碼
下面兩種版本的程式碼都有相同的輸出結果,為了方便讓讀者比較差異,程式碼使用相同的結構,並且分別使用 LEDC 和 analogWrite 產生 PWM 訊號。
說明
- 兩種範例的輸出結果都相同。在 LEDC 的範例中,我們使用通道 0,5kHz 的頻率及 8 位元的解析度。而 analogWrite 的預設解析度就是 8 位元(0~255),因此不需要額外設定。
- 注意 ledcWrite 的輸出的參數是通道,analogWrite 輸出的參數是接腳。
- 如果想加快呼吸燈的變化速度,可以增加 fade_amount 的數值。
- 如果覺得呼吸燈的效果不夠明顯,可以試著加大電阻值。
輸出結果

五、進階功能: 讓蜂鳴器發出聲音
雖然前面提到 ledc 是專門設計用來控制 LED 燈的語法,但事實上它還有讓蜂鳴器發出聲音的功能。在 Arduino-ESP32 中,我們可以用 ledcWriteTone
和 ledcWriteNote
這兩個語法來發出聲音。
語法說明
ledcWriteTone
這個函式會在接腳上產生指定頻率,工作週期為 50% 的方波,它與 Arduino Tone() 相同。
ledcWriteTone(channel, freq); //選擇PWM通道和頻率
ledcWriteNote
這個函式用來設定通道發出特定的音符(note)與音符的八度(可以設定0~8)
ledcWriteNote(channel, note, octave);
note 參數可以填入的內容有:
NOTE_C | NOTE_Cs | NOTE_D | NOTE_Eb | NOTE_E | NOTE_F |
NOTE_Fs | NOTE_G | NOTE_Gs | NOTE_A | NOTE_Bb | NOTE_B |
下表是音符(note)與八度(octave)的對應頻率,其中,標示為黃色區塊的是標準音高 440Hz。

設定流程
要使用 ledcWriteTone
和 ledcWriteNote
讓蜂鳴器順利發出聲音,設定的方法與範例1的 LEDC非常類似。
- 選擇通道與 GPIO 接腳
- 使用
ledcAttachPin
設定接腳與通道 - 選擇
ledcWriteTone
或ledcWriteNote
輸出。
💡 Tips
將 ledcWrite
搭配上面兩個語法使用,調整 PWM 工作周期就能改變輸出音量。
六、範例 2: 讓蜂鳴器產生音階
材料清單
- ESP32開發板 x1
- micro USB 傳輸線 x1
- 麵包板 x1
- 杜邦線(公-公) x2
- 無源蜂鳴器
連接示意圖

程式碼
雖然下面的程式碼用陣列來寫會更簡潔,但我們還是一項一項列出來,讓程式碼可以更容易的被閱讀。
七、範例3: toneMelody
在 Arduino 內建的範例中,有一項名為 toneMelody 的程式範例。

下面的程式是用 ledcWriteNote
和 ledcWriteTone
重寫過的版本,不過在實際測試後,筆者發現 ESP32 現在也支援 tone() 函式,可以直接執行內建的 toneMelody 範例,下面的程式就提供給有興趣的讀者參考。
這個程式的接線與範例 2 相同,因此不必修改硬體部分。這部分的程式較複雜,可以參考內建範例中的說明。
程式碼
八、小結
在這篇文章中,我們說明了如何配置 ESP32 的 PWM 參數,以及在 Arduino IDE 產生 LED PWM 訊號,還有讓無源蜂鳴器播放音調的方法。下篇將說明如何使用內建的 ADC 讀取類比數值。