Raspberry Pi で PWM制御 【Python】

Raspberry Pi

Raspberry Pi (bookworm 以降)で PWM (pulse width modulation) 制御を行う Python のスクリプトについてまとめておくことにします。
以下の環境で動作確認をしています。
環境:
・ Raspberry Pi 5 (bookworm 以降)
・ Windows パソコン(リモートデスクトップの設定済み)
・ LAN ネットワーク

背景 ~ GPIO の仕様が変更になった!

Raspberry Pi 5 (bookworm) 以降では、パルス幅変調(PWM、pulse width modulation)に関する仕様が変更になっています。
そこで、パルス幅変調を行う Python のスクリプトをまとめ、公開しておくことにします。

PWM 制御を行うことで、たとえば、以下が可能になります。
・ LED の明るさの微調整
・ DC モーターの回転速度、力量・トルクの微調整
・ ヒーター・熱源温度の微調整
・ サーボモーターの制御
・ フィードバック制御を行う
・ Raspberry Pi のプログラム上から上記の制御を行う
・ その他

手順

① PWM を行うためのフォルダ(例:”gpio1″)とファイル(”gpio_pwm1.py”)を作成し、以下のスクリプトをコピー&ペーストして保存します。
例: home/pi/gpio1/gpio_pwm1.py

② Raspberry Pi の GPIO に 動作確認のための回路を接続します。
上記の写真では、以下の接続としています。
・ GPIO03 (GND)、GPIO14 (PWM信号、3.3V)に、それぞれ、黒色、赤色のジャンパケーブルを接続し、ブレッドボードに配線を引き出します。
・ 赤色のジャンパケーブルから電流が、LED、330Ωの抵抗(橙橙茶金)を経由し、黒色のジャンパケーブルに流れるよう接続します。
※ Raspberry Pi のターミナルで pinout コマンドを実行することで、ピン番号・ピン配列を確認できます。
※ LED は足の長い側が +側(赤色ケーブル側)となるよう接続します。

③ ①のスクリプトを実行します。
例: python home/pi/gpio1/gpio_pwm1.py
※ スクリプトを実行すると、スクリプトで指定した時間(60秒)の間、キーボードの入力を受けつける状態となります。
④ スクリプト実行中に、”c” キーを押すとプログラムを終了します。”u” キーを押すと HIGH となっている期間のパルス幅が増加します。”d” キーを押すとパルス幅が狭くなります。

うまく動いたら

うまく動いたら、スクリプトをアレンジしてみてください。
PWM 出力のピン番号、周波数などを自由に変更できます。
また、GPIO 端子に接続した回路側も変更し、応用してみてください。

詳細は省略しますが、私の場合、同スクリプトを用いて以下が動いています。
・ トランジスタと外部バッテリーを使ってDCモーターの回転速度を Raspberry Pi 側から微調整する
・ ヒーターの温度の微調整を行う

※ なお、冒頭の写真は、LED の明るさを PWM で少しずつ変更したものです。わかりにくいですけれども。。

スクリプトの説明

・ スクリプトの冒頭で import PWMLED、atexit としているところでパルス幅制御に必要なモジュールをインポートしています。
・ import sys、tty、…、select としているところでは、キーボードのキー入力を受けつけるためのモジュールをインポートしています。キー入力により、パルス幅を最小値 0% から最大値 100% まで変えられるようにします。
・ def keypress1() 関数は、キー入力を受けつけて処理をするための関数です。プログラムを停止するコマンド(イベント)が発行されていなければ、キー入力を調べて、”u” (up) であれば、パルス幅を指定した値だけ増加させます。”d” (down) が入力されていれば、パルス幅を指定した値だけ減らします。
また、”c” が入力されていれば、プログラムを停止するコマンドを発行します。
もしコマンド処理がうまくいかない場合は、内部の状態を初期の状態に戻しておきます。
・ スクリプトを実行すると、関数を定義したのち、pin1 = 14 とした行以降が実行されます。
スクリプトでは、GPIO04 の 14 番ピンを使っています。他のピンを使いたい場合、出力を複数にしたい場合はこの部分を修正してください。
・ out1 = PWMLED() としたところで、ピン番号と PWM 制御の周波数 frequency, Hz を設定しています。スクリプトでは 100Hz、周期でいえば T1 = 1/100 = 10ms としています。周期、周波数を変更する場合はこの部分を修正してください。
・ stop1 = … とした行以降で、キー入力を受けつけるためのイベント処理をしています。
・ ch1 は、キー入力で入力された文字です。
・ val1 = 0.02 は、初期状態でのパルス幅です。この例では、起動時には 2% だけ 14番ピンを HIGH (3.3V) とし、残りの 98% を LOW (0V) とする設定としています。
・ del1 = 0.01 は、”d” または “u” キーを押したときのパルス幅の変化量です。
・ sec1 = 60 は、スクリプトの実行時間(秒)です。長時間、実行したい等の場合はこの値を調整してください。
・ 上記のパラメータの設定後、for ループでパルス幅制御のコマンドを繰り返し実行しています。この間、キーボードの入力を監視し、パルス幅を変更できるようにしています。
・ 末尾でパルス幅を0%としています。for ループの途中で抜けた場合であっても、終了時の外部機器の状態を初期状態に戻すようにします。モーターなどの機器が接続されているとき、制御対象を停止し特定の状態とするようにします。

まとめ

Raspberry Pi (bookworm 以降)でパルス幅変調を行うためのスクリプトについてまとめました。
今回の LED の回路図などは、以下の関連リンクに記載したものとほぼ同等です。
関心のある方は、関連リンクなども参照してみてください。

関連リンク
・ サーボモーターを動かす 【Raspberry Pi】
・ LED を ON/OFF する 【Raspberry Pi & Python】

スクリプト

PWM 制御を行うスクリプト GPIO_PWM1.py


import time as tm1 
from gpiozero import PWMLED 
import atexit as at1 

import sys 
import tty 
import threading as th1 
import termios as te1 
import select as sl1 

def keypress1(stop1): 
    global val1 
    global del1 
    global ch1 
    fi1 = sys.stdin.fileno() 
    old1 = te1.tcgetattr(fi1) 
    try: 
        tty.setcbreak(fi1) 
        while not stop1.is_set(): 
            if sl1.select([sys.stdin], [], [], 0.1)[0]: 
                ch1 = sys.stdin.read(1) 
                if ch1 == 'c': 
                    stop1.set() 
                elif ch1 == 'u': 
                    val1 = val1 + del1 
                    if val1 > 1.0: 
                        val1 = 1.0 
                elif ch1 == 'd': 
                    val1 = val1 - del1 
                    if val1 < 0.0: 
                        val1 = 0.0 
    finally: 
        restore1(fi1, old1) 

def restore1(fi1, settings1): 
    try: 
        te1.tcsetattr(fi1, te1.TCSADRAIN, settings1) 
    except Exception as e1: 
        print(e1) 

pin1 = 14                                             # GPIO14, pinout 
out1 = PWMLED(pin1, frequency=100)                    # PWM, 10 ms 
at1.register(lambda: out1.value == 0.0) 

stop1 = th1.Event()                                   # for key events 
thread1 = th1.Thread(target=keypress1, args=(stop1,), daemon=True) 
thread1.start() 
fi1 = sys.stdin.fileno() 
ini1 = te1.tcgetattr(fi1) 

ch1 = ""                                              # key 
val1 = 0.02                                           # duty: 2 % 
del1 = 0.01         

out1.value = val1 

sec1 = 60                                              # 60 seconds 

try: 
    for i1 in range(sec1): 
        if stop1.is_set(): 
            break 
        out1.value = val1 
        str1 = str(i1) + " " + str(val1) + " " + ch1 
        print(str1) 
        tm1.sleep(1) 
finally: 
    restore1(fi1, ini1) 
    
out1.value = 0.0 
tm1.sleep(1) 


タイトルとURLをコピーしました