Learning Objectives
- 說明 PWM 的頻率與工作週期,以及它們怎麼讓只有 0 與 1 的腳位「調出」中間值
- 用 machine.PWM 與 duty_u16 調整 LED 亮度
- 改變 PWM 頻率,讓無源蜂鳴器發出不同音高
- 算出伺服馬達角度對應的脈波寬度與 duty_u16,控制伺服轉到指定角度
只有 0 與 1,怎麼調出一半的亮度
第 03 課的 LED 只有亮與暗兩種狀態,因為數位輸出只有 3.3V 與 0V。可是生活中的裝置常常需要「中間值」: 檯燈要調亮度、喇叭要發出不同的音、伺服馬達要轉到某個角度。
第 03 課的互動練習最後一題提過:把 sleep 拿掉,LED 亮暗切換得非常快,人眼看起來就是半亮。
這個想法做成硬體功能,就是脈波寬度調變(PWM,Pulse Width Modulation):
腳位以固定的節奏重複「高一段時間、低一段時間」,由硬體負責切換,程式只要設定兩個數字。
頻率與工作週期
┌────┐ ┌────┐ ┌────┐
3.3V │ │ │ │ │ │
0V ───┘ └──────┘ └──────┘ └────
|←高→|
|←── 週期 ──→|
| 名稱 | 意思 | 例子 |
|---|---|---|
| 週期(period) | 重複一次「高+低」花的時間 | 1 ms |
| 頻率(frequency) | 一秒重複幾次,等於 1 ÷ 週期 | 1 ÷ 0.001 秒 = 1000 Hz |
| 工作週期(duty cycle) | 一個週期裡高電位占的比例 | 高 0.25 ms ÷ 週期 1 ms = 25% |
工作週期 25% 時,腳位的平均電壓大約是 3.3V × 25% ≈ 0.8V。LED、馬達這類反應比較慢的元件, 感受到的就是這個平均值,所以工作週期越大,LED 越亮。
三種用途,看的數字不一樣
| 用途 | 頻率 | 工作週期 |
|---|---|---|
| LED 調光 | 夠快就好(例如 1000 Hz),人眼不會察覺閃爍 | 決定亮度 |
| 無源蜂鳴器 | 決定音高(例如 262 Hz 是 Do) | 常用 50%;0 就安靜 |
| 伺服馬達 | 固定 50 Hz | 高電位的時間長短決定角度 |
MicroPython 的 machine.PWM
from machine import Pin, PWM
led = PWM(Pin(23), freq=1000, duty_u16=32768) # 1000 Hz、工作週期 50%
led.duty_u16(16384) # 改成 25%
led.freq(2000) # 改頻率
led.deinit() # 關閉 PWM,腳位停止輸出
duty_u16(數值):工作週期用 0~65535 表示,0 是 0%,65535 是 100%。這個寫法在 ESP32、Raspberry Pi Pico 等不同開發板上都一樣,建議使用。- 舊程式常見
duty(數值),範圍是 0~1023(512 約是 50%)。ESP32 的 MicroPython 還能用,看到時知道是同一件事。 - 建立 PWM 時沒有指定工作週期,ESP32 預設是 50%;接伺服馬達時,最好一開始就給一個合理的值。
ESP32 的 PWM 頻率可以從 1 Hz 設到 40 MHz,但頻率越高,工作週期能分得越細的程度(解析度)就越低。 原版 ESP32 有 16 個 PWM 通道,可以同時輸出 16 個不同的工作週期,但只有 8 種不同的頻率; 大部分專題用不到這麼多,知道有上限就好。以上數字以 MicroPython 官方文件的 ESP32 快速參考為準。
逐步看:調整 LED 亮度
每執行一次 led.duty_u16(duty),上方的 LED 亮度就跟著改變。看 duty 怎麼從百分比算出來。
呼吸燈與亮度的感覺
把上面的迴圈改成每次只差一點點,例如每 20 ms 增加 1%,亮度就會平順地變亮再變暗,像在呼吸:
while True:
for p in range(0, 101): # 漸亮
led.duty_u16(p * 65535 // 100)
time.sleep_ms(20)
for p in range(100, -1, -1): # 漸暗
led.duty_u16(p * 65535 // 100)
time.sleep_ms(20)
這樣的迴圈步數很多,不適合逐步播放;在下面的互動練習裡可以試試看。 你可能會發現:工作週期從 0% 到 25% 時亮度變化很明顯,75% 到 100% 卻感覺差不多。 人眼對亮度的感覺不是線性的,商業產品常把數值先經過一條曲線再送出,讓變化看起來比較均勻。
逐步看:蜂鳴器的音階
蜂鳴器有兩種:有源蜂鳴器裡面有振盪電路,接上高電位就響,音高固定; 無源蜂鳴器需要外面送進一定頻率的訊號,頻率就是音高。這個範例接的是無源蜂鳴器,看上方蜂鳴器顯示的頻率。
伺服馬達:用脈波寬度決定角度
伺服馬達(servo)裡面有馬達、齒輪、位置感測與控制電路,你只要告訴它「轉到幾度」,它會自己轉過去並停住。 告訴它的方式就是 PWM:
- 頻率固定 50 Hz,也就是每 20 ms 送一個脈波
- 脈波高電位維持多久決定角度。常見的小型伺服大約是 0.5 ms 對應 0°、2.5 ms 對應 180°(有些型號是 1~2 ms,實際範圍要看規格)
換算成 duty_u16:
脈波寬度(微秒)= 500 + 角度 × 2000 ÷ 180
duty_u16 = 脈波寬度 ÷ 20000 × 65535
90 度 → 1500 μs → 1500 ÷ 20000 × 65535 ≈ 4915
伺服馬達轉動時電流比較大,最好用獨立的 5V 電源供電,並把電源的 GND 和 ESP32 的 GND 接在一起;ESP32 只負責送訊號。馬達與電源的細節在「自造者入門」軌第 09 課。
逐步看:伺服轉到指定角度
看 set_angle 怎麼把角度換成脈波寬度,再換成 duty_u16;上方的伺服臂會轉到對應的角度。
小測驗:頻率與工作週期
PWM 的頻率是 500 Hz,一個週期是多少?
下面的程式會印出什麼?
percent = 40
print(percent * 65535 // 100)
想讓無源蜂鳴器的聲音變高,要改哪一個?
小測驗:伺服的換算
用本課的公式(0° = 500 μs、180° = 2500 μs、週期 20000 μs),下面的程式會印出什麼?
angle = 135
pulse_us = 500 + angle * 2000 // 180
print(pulse_us, pulse_us * 65535 // 20000)
舊程式寫 servo.duty(77),這是用哪一種範圍表示工作週期?
重點整理
- PWM 讓腳位快速重複高、低電位:頻率是一秒重複幾次,工作週期是高電位占的比例。
PWM(Pin(編號), freq=頻率, duty_u16=數值)建立;duty_u16()範圍 0~65535,freq()改頻率,deinit()關閉。舊的duty()範圍是 0~1023。- LED 調光看工作週期;無源蜂鳴器的音高看頻率;伺服馬達用 50 Hz,脈波寬度(約 0.5~2.5 ms,依型號)決定角度。
- ESP32 的 PWM 頻率可設 1 Hz~40 MHz,頻率越高解析度越低;原版 ESP32 有 16 個通道、8 種頻率。
- 伺服馬達用獨立電源供電,GND 要和 ESP32 共地。
下一課反過來讀「中間值」:用 ADC 讀取可變電阻與光敏電阻的電壓。