ESP32 教學系列(五):數位脈衝寬度調變(PWM)

ESP32教學系列(五):數位脈衝寬度調變(PWM)

一、前言

上一篇講到了數位輸出的概念,讓 ESP32 透過輸出高電位和低電位點亮或是熄滅 LED 燈。但是數位輸出就只能做到打開或關閉嗎?其實數位輸出還能做出更多變化!讓我們來看看數位輸出的進階用法—PWM。

二、PWM 簡介

PWM(Pulse-width modulation,脈波寬度調變)是一種用數位訊號模擬類比訊號的技術,也是目前常見的數位功率控制方法。

一般來說,數位訊號只能輸出高電位(HIGH)和低電位(LOW)兩種狀態,並且當高、低電位在固定時間周期內有規律的變化時,會產生如下圖般的波形,像這樣的波形我們稱之為脈波(pulse)。

ESP32教學系列(五):數位脈衝寬度調變(PWM)
脈波訊號

而 PWM 的原理就是在不改變時間周期的條件下,調整高電位在脈波中的寬度(亦即通電時間在周期中的比例),就能調整數位輸出的功率,因此稱做脈波寬度調變。舉凡 LED、直流馬達、RC 伺服馬達都可以用 PWM 來控制。

如何計算 PWM

PWM 的輸出實際上是一個 0~3.3v 的可變電壓訊號,這個訊號由頻率和工作周期(duty cycle)組成。我們知道,頻率是周期的倒數,周期是由高態時間加上低態時間組成;工作周期則是高態時間在整個周期中的所佔的比例。

ESP32教學系列(五):數位脈衝寬度調變(PWM)
周期、頻率與工作周期的關係

用公式表示的話就會是:

ESP32教學系列(五):數位脈衝寬度調變(PWM)

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

而當工作周期為 0% 或 100% 時,輸出的電壓就會是 0V 或 3.3V,等同於數位輸出的 LOW 或 HIGH。

ESP32教學系列(五):數位脈衝寬度調變(PWM)
相同週期中,改變高態和低態的占比就能控制LED發出不同的亮度。

三、在 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教學系列(五):數位脈衝寬度調變(PWM)

程式碼

下面兩種版本的程式碼都有相同的輸出結果,為了方便讓讀者比較差異,程式碼使用相同的結構,並且分別使用 LEDC 和 analogWrite 產生 PWM 訊號。

說明

  • 兩種範例的輸出結果都相同。在 LEDC 的範例中,我們使用通道 0,5kHz 的頻率及 8 位元的解析度。而 analogWrite 的預設解析度就是 8 位元(0~255),因此不需要額外設定。
  • 注意 ledcWrite 的輸出的參數是通道,analogWrite 輸出的參數是接腳。
  • 如果想加快呼吸燈的變化速度,可以增加 fade_amount 的數值。
  • 如果覺得呼吸燈的效果不夠明顯,可以試著加大電阻值。

輸出結果

五、進階功能: 讓蜂鳴器發出聲音

雖然前面提到 ledc 是專門設計用來控制 LED 燈的語法,但事實上它還有讓蜂鳴器發出聲音的功能。在 Arduino-ESP32 中,我們可以用 ledcWriteToneledcWriteNote 這兩個語法來發出聲音。

語法說明

ledcWriteTone

這個函式會在接腳上產生指定頻率,工作週期為 50% 的方波,它與 Arduino Tone() 相同。

ledcWriteTone(channel, freq); //選擇PWM通道和頻率

ledcWriteNote

這個函式用來設定通道發出特定的音符(note)與音符的八度(可以設定0~8)

ledcWriteNote(channel, note, octave);

note 參數可以填入的內容有:

NOTE_CNOTE_CsNOTE_DNOTE_EbNOTE_ENOTE_F
NOTE_FsNOTE_GNOTE_GsNOTE_ANOTE_BbNOTE_B

下表是音符(note)與八度(octave)的對應頻率,其中,標示為黃色區塊的是標準音高 440Hz。

音符與聲音頻率的對應關係表

設定流程

要使用 ledcWriteTone ledcWriteNote 讓蜂鳴器順利發出聲音,設定的方法與範例1的 LEDC非常類似。

  1. 選擇通道與 GPIO 接腳
  2. 使用 ledcAttachPin 設定接腳與通道
  3. 選擇 ledcWriteToneledcWriteNote 輸出。

💡 Tips

ledcWrite 搭配上面兩個語法使用,調整 PWM 工作周期就能改變輸出音量。

六、範例 2: 讓蜂鳴器產生音階

材料清單

  • ESP32開發板 x1
  • micro USB 傳輸線 x1
  • 麵包板 x1
  • 杜邦線(公-公) x2
  • 無源蜂鳴器

連接示意圖

程式碼

雖然下面的程式碼用陣列來寫會更簡潔,但我們還是一項一項列出來,讓程式碼可以更容易的被閱讀。

七、範例3: toneMelody

在 Arduino 內建的範例中,有一項名為 toneMelody 的程式範例。

內建範例程式的位置

下面的程式是用 ledcWriteNoteledcWriteTone 重寫過的版本,不過在實際測試後,筆者發現 ESP32 現在也支援 tone() 函式,可以直接執行內建的 toneMelody 範例,下面的程式就提供給有興趣的讀者參考。

這個程式的接線與範例 2 相同,因此不必修改硬體部分。這部分的程式較複雜,可以參考內建範例中的說明。

程式碼

八、小結

在這篇文章中,我們說明了如何配置 ESP32 的 PWM 參數,以及在 Arduino IDE 產生 LED PWM 訊號,還有讓無源蜂鳴器播放音調的方法。下篇將說明如何使用內建的 ADC 讀取類比數值。