こちらの記事について、内容・翻訳・視点・長さなど、皆様のご意見をお送りください。今後の記事製作の参考にしたいと思います。
Thank you! Your feedback has been received.
There was a problem submitting your feedback, please try again later.
こちらの記事の感想をお聞かせください。
このシリーズをご覧いただいている方であれば、おそらくパート1でハードウェアセットアップの構築に成功し、パート2での一連のインストールも終了していることとだろう。今回はやっとPythonのコーディングに入る。
私はテストとデバッグをインタラクティブに行うのが好きなので、RevPi付属のPython IDE、「IDLE」を使用する。まず、VNC接続を使用して、RevPiデスクトップからIDLEを起動する。
赤いラズベリーのアイコンをクリックして[Programming]を開き、[Python 3 (IDLE)]をクリックする。Python 2とPython 3のどちらを使うかという議論に興味がある場合は、Googleやネットサーフィンでこの「宗教戦争」に飛び込んでみるとよいでしょう。ここでは、深く考えずにPython 3を使用する。
ここまでの操作で「Python Shell」のウィンドウが開く。
プロンプト「>>>」は、システムがPythonの式の実行を待っていることを示している。入力したものがすべて実行される。式「4+5 <Enter>」を入力すると、システムの回答は「9」になる。
>>> 4+5
9
>>>
私は正しい構文や結果の型が分からないときに、この環境を使用して1行のコードを実行している。今回は、2の8乗を得るために2^8と入力してみる。
>>> 2^10
10
これでは構文が間違っている。2**8にすると求めていた答えが得られる。
>>> 2**8
256
ここでは、Pythonについてくわしく解説しない。インターネットにはいくらでも教材がありるので参照してほしい。ちなみに、私が調べるのによく利用するのは、Pythonの公式ドキュメントだ。
ほとんどすべてのPythonスクリプトには、最初に「import」セクションがある。このセクションは、インタプリタにどのライブラリをスクリプトで使用するのかを伝える。Pythonではよくライブラリを利用する。私は、最小セットの共通言語要素をだけを用意して、残りは世界規模の巨大なコミュニティが作成するライブラリにまかせるというコンセプトこそ、この言語の成功の秘密だと確信してる。つまり「群体知性」なのだ。特定の問題があれば、Pythonライブラリを検索するだけで、見つかる可能性がある。これはPID制御アルゴリズムにも当てはまる。私の場合は、「simple-pid」などが見つかった。ライブラリを使用できるようにするには、システムにインストールする必要がある。ここではSSHセッション(PuTTY)を使用して、シェルコマンド「sudo pip install simple-pid」を入力する(次の画面ダンプではライブラリはすでにインストールされている)。
なお、RevPiデスクトップでターミナルウィンドウを使用してインストールすることもできる。
「pip」はすばらしいツールだ。Python団体のセントラルリポジトリからライブラリを取得し、システムにインストールしてくれる。前回インストールした、KUNBUSリポジトリの「python3-revpimodio2」と「revpipyload」のときは、Linuxのapt-getコマンドを使用した。
インストールが成功すると、これらのライブラリをインポートする式を実行してもエラーメッセージは出ない。
>>> from simple_pid import PID
>>> import revpimodio2
では、ここからスクリプトを書き始める。Pythonシェルのメニュー[File] -> [New File]を使用して、新しい空のエディタウィンドウを開き、最初の2行に上のインポート式を入力する。このエディタではPythonキーワードを直ちに別の色でマークすることで、入力をサポートする。
システムクラッシュで時間を無駄にするのは嫌なので、このファイルを時々保存した方がいいだろう。コマンドは[File] -> [Save]にある。この手順に従う場合、「/home/pi」ディレクトリに「myplc.py」という名前で保存することをお勧めする。
GitHubには沢山のライブラリがある。私たちが使うライブラリについては、こちらを参照してほしい。使い方を簡単に理解できるようにサンプルも用意されている。ここではサンプルを利用して、PIDオブジェクトクラスのインスタンス「TempController」を作成できる。式は次のとおりだ。
TempController = PID(1,0.1,0.0, setpoint=250, output_limits=(0,100))
クラスの初期化呼び出しのドキュメントで以下の関数パラメータを指定:
- Kp=1.0 - 比例ゲイン(「コントローラゲイン」と呼ぶ方がふさわしい)
- Ki=0.1 - 積分ゲイン
- Kd=0.0 - 微分ゲイン
- setpoint=250 - PIDが達成しようとする初期目標(今回は温度25 °C *10)
- sample_time=0.1 - コントローラが新しい出力値を生成する前に待機する時間(秒単位)。PIDは定期的に呼び出されるときに(ループ中など)最も良く機能するが、各更新間の時間差がほぼ一定になるようにサンプル時間を設定している。Noneに設定すると、PIDは呼び出されるたびに新しい出力値を計算する。ここで100msを使用しているのは、10Wヒーターでは温度変化が非常に遅いためだ。
- output_limits=(0, 100) - 使用する初期出力制限値だ。たとえば(<下限>, <上限>)のような2つの要素のイテラブルとして指定される。出力が下限値より下、上限値より上になることはない。
rpi.io.PWM_heater1.value = int(pwm)
rpi.io.PWM_heater2.value = int(pwm)
制限値のいずれかをNoneに設定して、その方向に無制限にすることもできる。また、出力制限値を設定すると、積分値が制限外まで大きくなることがないので、積分ワインドアップを避けることができる。私たちの例では、0%~100%のPWM値を使用する。
- auto_mode=True - PIDコントローラの有効(True)、無効(False)。
- proportional_on_measurement=False - 比例値をエラー時(従来の方法)ではなく、入力時に直接計算するかどうか。proportional-on-measurementを使用すると、ある種のシステムでオーバーシュートを回避できる。
ここではどちらもデフォルトを使用しているため、特にプログラムに書く必要はない。
PID制御アルゴリズムに馴染みのない方は、こちらのページを参照してほしい。チューニングパラメータKp、Ki、Kdについての詳しい解説を参照できる。これらのチューニング値を予測することはできないので、制御動作をテストすることで設定する必要がある(この記事で後述)。
revpimodioクラスのインスタンスも必要だ。revpimodioのWebページのドキュメントから初期化呼び出しの方法を探す。
rpi = revpimodio2.RevPiModIO(autorefresh=True)
PIDコントローラを動作させるため、周期的に呼び出す必要がある3つの関数が存在する。
最初の関数はプロセスイメージから現在の温度を取得する。
Temp = rpi.io.Temp10.value
2つ目の関数はPID出力値を計算する。
pwm = TempController(Temp)
3つ目の関数は、PWM出力をプロセスイメージに書き込むことで、PWM出力を計算値に設定する。
プロセスイメージのPWM値は符号なし整数なので、PID出力を関数「int()」で変換する必要がある。Pythonのwhileループの引数を「True」にして、この4行をエンドレスループに入れる。Pythonに馴染みのない方は、Pythonでは、コードのグループ化に{}で囲まない点に注意してほしい。コードは「ホワイトスペース」(インデント)でグループ化される。whileループの4つのステートメントの前にインデントが入っているのが分かる。
私たちはマルチタスクのLinuxシステムを使用しているので、タスクの厳格なエンドレスループでCPUをブロックしたくない。そこでライブラリ「time」と関数「sleep(n)」を使用して、ループに0.05秒の一時停止を入れる。1秒ごとにデバッグ出力が得られるように、ループを20回カウントし、入力と出力の値を印字した後に0にリセットされるカウンタ変数も追加した。
完成したプログラムはこちらだ。
from simple_pid import PID
import revpimodio2
import time
TempController = PID(5,0.13,10.5, sample_time=0.1, setpoint=400, output_limits=(0,100))
rpi = revpimodio2.RevPiModIO(autorefresh=True)
n=0
while True:
Temp = rpi.io.Temp10.value
pwm = TempController(Temp)
rpi.io.PWM_heater1.value = int(pwm)
rpi.io.PWM_heater2.value = int(pwm)
n +=1
if n==20:
n=0
print(Temp, int(pwm))
time.sleep(0.05)
このスクリプトを<F5>又はメニューの[Run] -> [Run Module]で実行すると、Pythonシェルウィンドウに周期的に値が印字される。実行を中止するには、Ctrl + Cキーを押す。
次はチューニング値の実験だ。チューニング値は確かに熱源と温度センサの間の物理フィードバックに依存する。このフィードバックを近付けたいので、センサと電球の距離を小さくする。
熱を閉じ込めるケース又はアルミホイルに入れてもいいかもしれない。
しかし、このケースでは、10Wヒーター x 2と40°C前後のセットポイント温度なので、3つのチューニングパラメータを調整して、ほとんど完璧に制御することができた。最初にKiとKdを0に設定した。Kpの値を1、5、10、20ととったとき、10で最終温度が見られた。これは、セットポイントの下に0.1°Cずれるごとに、制御関数はすぐに10%大きなPWMを返すということを意味する。値を20にすると、1°C以上の振動が発生した。次に安定値の半分の5をKpに設定しました。非常に安定した最終温度になりましたが、セットポイントから負のオフセットが発生し、40°Cまで上がらず、約39°Cまでしか上がらなかった。次に非常にゆっくりとKiを上げていきました。Kiはオフセットを補償するが、振動も大きくなった。値が0.13のとき、セットポイント40°C前後で、ピーク間振動1°Cで安定した。Kdを上げると、コントローラは高め又は低めのPWM値に一種の短パルスを「挿入」する。これにより振動が減った。最終的にKdを10前後にすると、+/- 0.1°C(温度測定の全体的な精度)の振動に安定した。チューニングの結果は次のとおりだ。
15時57分、コントローラのスイッチが周囲温度34°Cでオンになった(私のオフィスは気候変動の影響を受けている!)。15時59分(2分後)には、温度が安定温度の40°C (セットポイント)に達した。2つ目の画像は、セットポイント前後の非常に小さな振動を示している。
テキスト出力が期待に沿った制御ループを示したら、次のステップに進む。MQTTとNode-Redを使用して、ヒーターコントローラの簡単なGUIを構築し、見栄えの良い時系列グラフを表示できるようにする。これについては、パート4でご紹介しよう。