2023年3月15日 星期三

樹莓派學習筆記 : GPIO 測試 (三) PWM 脈寬調變

本篇測試樹莓派 GPIO 的 PWM (Pulse Width Modulation) 功能, 這是利用調整數位方波的頻率與高準位寬度來模擬類比信號的技術, 常用於資料傳輸與通訊. 由於 PWM 實際上是指定頻率下高低位準交替的方波, 比使用真實的類比信號傳輸具有抗雜訊與增加傳輸距離之優點. 在物聯網專案中, PWM 常用來控制燈光的亮度或聲音的大小, 參考 :


本系列之前的文章參考 :


以下測試將分別使用 RPi.GPIO 與 gpiozero 模組之範例以茲對照. 為了實驗接線方便, 將 BCM 與 BOARD 兩種腳位對照圖重貼如下 : 




4. 使用 RPi.GPIO 的 PWM 類別 : 

RPi.GPIO 模組的 PWM 類別用來處理 GPIO 的 PWM 輸出功能, 呼叫其建構式 PWM(pin, freq) 並傳入輸出腳位編號與頻率即可建立一個 PWM 物件 :

pwm=PWM(pin, freq)   

但在呼叫 PWM() 之前必須先呼叫 setup() 函式將該腳位設為輸出模式, 例如 : 

>>> import RPi.GPIO as GPIO    
>>> GPIO.setmode(GPIO.BOARD)      # 使用板子位置編號
>>> GPIO.setup(3, GPIO.OUT)            # 必須設為輸出腳
>>> pwm=GPIO.PWM(3, 1000)            # 板子位置編號 3=GPIO2, 頻率為 1000 Hz
>>> type(pwm)    
<class 'RPi.GPIO.PWM'>

可見已建立一個 PWM 物件, 可用前一篇使用的自訂模組 members 來檢視其成員 :

>>> import members 
>>> members.list_members(pwm)         
ChangeDutyCycle <class 'builtin_function_or_method'>
ChangeFrequency <class 'builtin_function_or_method'>
start <class 'builtin_function_or_method'>
stop <class 'builtin_function_or_method'>

可見 PWM 有四個方法, 用法摘要如下表 : 


 PWM 物件方法 說明
 start(dc) 開始 PWM 輸出, 傳入 dc (duty cycle, 值 0~100) 
 stop()  停止 PWM 輸出
 ChangeDutyCycle(dc) 更新 duty cycle, 傳入新 dc (duty cycle, 值 0~100) 
 ChangeFrequency(freq) 更新頻率, 傳入新頻率 freq (Hz) 


例如 : 


測試 4-1 : 用 PWM 物件控制 LED 燈 (1) [看原始碼]

# rpi-gpio-led-test-4-1.py
import RPi.GPIO as GPIO
from time import sleep

GPIO.setmode(GPIO.BOARD) 
GPIO.setup(3, GPIO.OUT)
pwm=GPIO.PWM(3, 10)      # Pin3= GPIO2, 頻率 10 Hz

while True:
    for dc in range(5, 101, 5):   # dc=5~100 增幅為 5
        pwm.start(dc)
        sleep(0.5)

此例將 GPIO2 (pin3) 設為 PWM 輸出, 頻率為 10 Hz, 然後在迴圈中將 dc 從 5 增幅 5 增加到 100, 可以看到 LED 在 dc 值小時明顯會閃爍 (因為頻率才 10 Hz), 當 dc 變大時閃爍現象就越來越小 (亮的時間較多了). 如果將頻率提高為 1000 Hz, 則即使 dc 值小, 閃爍也不明顯, LED 將慢慢地增亮. 

下面是改編自官網的範例, 參考 : 



測試 4-2 : 用 PWM 物件控制 LED 燈 (2) [看原始碼]

# rpi-gpio-led-test-4-2.py
import RPi.GPIO as GPIO
form time import sleep

GPIO.setmode(GPIO.BOARD)
GPIO.setup(3, GPIO.OUT)

pwm=GPIO.PWM(3, 50)    # Pin3= GPIO2, 頻率 50 Hz
pwm.start(0)
try:
    while 1:
        for dc in range(0, 101, 5):           # 遞增 dc 由 0 至 100 增幅 5
            pwm.ChangeDutyCycle(dc)
            sleep(0.1)
        for dc in range(100, -1, -5):        # 遞減 dc 由 100 至 0 減幅 5
            pwm.ChangeDutyCycle(dc)
            sleep(0.1)
except KeyboardInterrupt:
    pass
pwm.stop()
GPIO.cleanup()

此例在無限迴圈中先將 dc 由 0 遞增 5 至 100, 然後由 100 遞減 5 至 0, 使用 try except 處理 Ctrl+C 鍵盤中斷. 


5. 使用 gpiozero 的 PWMLED 類別 : 

gpiozero 的 PWMLED 類別可用來控制 PWM 輸出腳位, 只要將 BCM 腳位編號傳入建構式 PWMLED() 就會傳回一個 PWMLED 物件 : 

pwm=PWMLED(pin [, initial_value=0, frequency=100])

若只傳入 pin 參數, 則預設 duty cycle 為 0, 頻率為 100 Hz, 可以用 value 屬性設定其 duty cycle (值 0~1), 或使用 frequency 屬性變更其頻率, 例如 : 

>>> from gpiozero import PWMLED    
>>> pwm=PWMLED(2)      # 預設為 BCM 編號=GPIO2
>>> type(pwm)       
<class 'gpiozero.output_devices.PWMLED'>      # 建立 PWMLED 物件
>>> pwm.frequency       # 預設頻率為 100 Hz
100
>>> pwm.value               # 預設 duty cycle 為 0
0.0 
>>> pwm.value=0.5        # 設定 duty cycle 為 0.5 (50%) LED 開始亮

用自訂模組 members 檢視 PWMLED 物件的成員 : 

>>> members.list_members(pwm)    
active_high <class 'bool'>
blink <class 'method'>
close <class 'method'>
closed <class 'bool'>
frequency <class 'int'>
is_active <class 'bool'>
is_lit <class 'bool'>
off <class 'method'>
on <class 'method'>
pin <class 'gpiozero.pins.rpigpio.RPiGPIOPin'>
pin_factory <class 'gpiozero.pins.rpigpio.RPiGPIOFactory'>
pulse <class 'method'>
source <class 'NoneType'>
source_delay <class 'float'>
toggle <class 'method'>
value <class 'float'>
values <class 'generator'>


常用屬性如下表 : 


 PWMLED 物件常用屬性 說明
 value 檢視或設定 duty cycle (值 0~1)
 frequency 檢視或設定頻率 (單位 Hz)
 is_active 若 PWM 有輸出脈波傳回 True, 否則 False
 is_lit 若 PWM 有輸出脈波傳回 True, 否則 False
 pin 傳回此 PWM 腳之 BCM 編號 (GPIOxx)
 closed PWM 功能是否已被關閉 (True/False)
 active_high 是否 active 狀態為高位準  (True/False)


常用方法如下表 : 


 PWMLED 物件常用方法 說明
 toggle() 呼叫後 duty cyle 會從 0 變 1 或 1 變 0 (亮滅交替)
 pulse(fade_in_time, fade_out_time) 呼吸燈效果 (參數為淡入淡出秒數)
 blink(on_time, off_time) 輸出脈波使 LED 閃爍 (參數單位為秒)
 off() 關掉脈波 (熄燈, duty ccycle=0)
 on() 開啟脈波 (亮燈, duty ccycle=1)
 close() 結束 PWM 功能 (清除物件)


用法參考 :


茲將上面 RPi.GPIO 範例改為如下 gpiozero 版 :


測試 5-1 : 用 PWMLED 物件控制 LED 燈 (1) [看原始碼]

# rpi-gpio-led-test-5-1.py
from gpiozero import PWMLED
from time import sleep

pwm=PWMLED(2, frequency=10)
dc=0.05
while True:
    if dc <= 1.0:
        pwm.value=dc
        dc += 0.05
    else:
        dc=0.05
    sleep(0.5)

此例由於 value 屬性的值為 0~1 的浮點數, 不能使用產生整數序列的 range(), 所以改用 if else 判斷, 結果與上面用 RPi.GPIO 的相同. 如果有安裝 Numpy 就可以用 np.arange() 來產生浮點數序列, 程式碼會更簡短. 

下面範例是上面 4-2 的 gpiozero 版 : 


測試 5-2 : 用 PWMLED 物件控制 LED 燈 (2) [看原始碼]

# rpi-gpio-led-test-5-2.py
from gpiozero import PWMLED
from time import sleep
import numpy as np

pwm=PWMLED(2, frequency=50)
try:
    while 1:
        for dc in np.arange(0, 1, 0.05):
            pwm.value=dc
            sleep(0.1)
        for dc in np.arange(1, 0, -0.05):
            pwm.value=dc
            sleep(0.1)
except KeyboardInterrupt:
    pass

此例使用 numpy 的 arange() 來產生 0~1 的 PWMLED 物件之 value 以控制 duty cycle, 因為 Python 本身的 range() 函式只能用在產生整數序列. 

沒有留言 :