ラズパイPicoシーケンサ、ラダーをPythonで書く方法

シーケンサーのラダーをPythonで書く

複数の入出力を制御するために、産業用途や自動化システムではシーケンサ(PLCProgrammable Logic Controller)を使った「ラダー図」による制御が広く普及しています。

「ラダー図」を使用することで、工場の生産ラインの機械や、モーター、バルブ、ポンプなどの駆動装置をスイッチやセンサーと連携させ、必要なタイミングでON/OFF制御することが容易になります。

「ラダー図」を個人で学習するためには、ラズパイを利用した「CODESYS」が有名ですが、無償の個人利用でも使用時間に制限(2時間ごとに再接続)があり、環境の構築にも少々手間がかかります。

今回は、より手軽に「ラダー図」の動作確認や、学習をする方法として、安価で小型なマイコンボード「Raspberry Pi Pico」で、プログラム言語に「Python(Micropython)」を使用した方法を詳しく紹介します。

動作確認を行うための「ラズパイPico」を使用したシーケンサ基板については以下のリンクで詳しく紹介しています。

ラズパイPicoでシーケンサを作ろう(基板設計編)簡単基板見積り
Raspbrry Pi Picoを使用したシーケンサ基板の回路設計から基板の見積もり発注方法まで詳しく紹介。入力12点,出力6点,アナログ入力2点,I2C,UART,OLED表示付です。
ラズパイPicoでシーケンサを作ろう(動作確認編)基板製造、部品実装
Raspbrry Pi Picoで製作したシーケンサ(PLC)基板の動作確認(入出力、ラダー、I2C/UART通信等)を行います。基板の発注方法も詳しく紹介していきます。
スポンサーリンク

1.シーケンサ(ラダー図)とは

シーケンサの「ラダー図」とは下図のようなものです。

シーケンサーラダー図、基本回路

右側に配置されているものが「コイル」と呼ばれ、左側に入力(X000〜)や各コイルの「接点」と呼ばれるものが配置されています。
各「接点」のON/OFF状態に応じて各「コイル」のON/OFFが変化します。

この「ラダー図」の動作は以下のようになります。

「X001:起動ボタン」が押されると「M1:起動指令」がONし、同時に「T0:起動時間タイマ」のカウント(1.0s)が開始されます。
「M1」がONしているので「Y000:起動」がONします。
「M1」がONしたことで「X001」がOFFしても「M1」はONしたまま(自己保持)になり「Y000」はONし続けます。

「X000:停止ボタン」が押されるか、「T0」のカウントが終了して「T0」の接点がONすると「M1」がOFFし「Y000」もOFFします。

ラダー動作の基本は以下です。

入力状態の取得 → 左から右へ、上から下へラダー処理 → 内部コイルの状態を更新 → 出力実行→ 繰り返し(更新された内部コイル状態で入力状態を取得して処理)
※この1連の動作をスキャンサイクルと言います。

同様の動作を実現するにはいろいろな方法がありますが「ラダー図」を使用することで、視覚的に動作を確認しながらプログラムの作成をすることができます。

動作の追加や変更も容易に行うことができるため、入出力点数の多い「産業機械」や「工場の生産ライン」のプログラムの作成や動作確認、メンテナンスを行う時には特に有効です。
ラダー内で各種「接点」は複数使用することができますが、右側に配置する「コイル」は、1度しか記入してはいけません。
シーケンサでこれは「2重コイル」としてエラーになります。
エラーにならない場合でも、想定した動作はできないため注意が必要です。
スポンサーリンク

2.Raspberry Pi Picoとは

Raspberry Pi Pico(ラズパイ Pico)とは、Raspberry Pi財団が開発したマイコンボードです。「Python」や「C言語」でプログラミングすることができ、学習やホビー、組込み用途などに最適です。

本体基板上の入出力端子に、スイッチやセンサ、各種制御モジュール、通信モジュールを接続することで、それらをプログラミングによって制御して、動作確認を行うことができます。


「ラズパイ Pico」の端子配列は以下のようになります。
(Wi-Fi通信機能搭載のRaspberry Pi Pico Wも基本的な端子配列は同じです。)

ラズパイ(Raspberry Pi) Pico 端子配列

「ラズパイ Pico」の使い方や端子機能、開発環境の準備については、以下のリンク先で詳しく紹介しています。

ラズパイPico/PicoWの使い方を3つの開発環境Python、ArduinoIDE、PlatformIOで紹介
Raspberry Pi Pico/Pico Wの使い方を端子配列からPython(MicroPython)とC言語の開発環境、Lチカ方法まで紹介。PythonはTonny、C言語はArduinoIDEとPlatformIOの3種類で詳しく紹介します。
ラズパイPicoの使い方 MicroPython&開発環境Thonny、SSLエラーの対処方法も紹介
Raspberry Pi Picoので開発環境Thonnyを使用した「Python(MicroPython)」でのプログラミング方法について初期設定からパッケージ(ライブラリ)の追加、動作確認の方法まで詳しく紹介します。
スポンサーリンク

3.動作確認方法、配線図

動作確認は下画像のような配線図で、ブレッドボードにスイッチとLEDを配線して行いました。

シーケンサとしての入出力点数は、入力(X)4点、出力(Y)4点、補助コイル(M)20点、タイマー(T)とカウンター(C)はそれぞれ10点を想定しています。(拡張可能)

ラズパイPicoシーケンサ動作確認、配線図
ラズパイPicoシーケンサ動作確認、配線
配線図のLEDには抵抗を使用していますが実際にブレッドボードに配線したものは抵抗内蔵LEDを使用しています。
抵抗内蔵LEDの入手は「秋月電子通商」が安くて、少量でも購入できるのでおすすめです。
サイトの検索窓から「抵抗内蔵LED」で検索するとたくさん出てきます。(10個入りで¥120〜¥200程)
プログラムについては、基本的なラダー図の動作を、a接点、b接点、から紹介し、その後にそれらを「Python(Micropython)」に置き換えて作成する方法を紹介していきます。
サンプルプログラムは全て上の回路で動作可能なので、その都度動作確認してみてください。

「ラズパイ Pico」は端子が実装されていないものもあります。
自分で半田付けできる方はいいですが、半田付けが苦手な方は端子が半田付けされたものを準備しましょう。

ブレッドボードは安いものはありますが、1列5穴のものがほとんどです。1列6穴あると色々応用が効くので1列6穴のサンハヤト製がおすすめです。

LEDの使い方については、以下のリンク先で詳しく紹介しています。

LED(発光ダイオード)の使い方。Lチカでの動作確認方法
LEDが登場するまで光源としては白熱電球が主流でした。白熱電球は電気を熱に変えて光りますがLEDは+と-の電気が結合する時のエネルギーで光り、電気を直接光に変換するため発光効率が非常に良いです。

4.ラダー図の基本

まずは「ラダー図」に馴染みのない方のためにも、基本的な「ラダー図」について紹介します。

・a接点、b接点

スイッチやセンサのON/OFF状態を表すために「a接点」「b接点」という表現が使われます。

  • a接点:ONした時に接点が繋がる。
  • b接点:OFFしている時に接点が繋がっていて、ONすると離れる。
シーケンサーラダー、a接点、b接点

上の「ラダー図」では、接点「X0」がONすると、出力コイル「Y0」がONし「Y1」はOFFします。


・AND回路 と OR回路

AND回路

AND回路は、全ての接点が全て繋がった状態の時に、右側コイルをONさせる回路です。

シーケンサーラダー、AND

上のラダー図では、「X0」がON かつ「X1」がONの時に、出力コイル「Y0」がONします。

OR回路

OR回路は、いずれかの接点が繋がった状態の時に、右側コイルをONさせる回路です。

シーケンサーラダー、OR

上のラダー図では、「X0」がON または「X1」がON または両方がONの時に、 出力コイル「Y0」がONします。


・インタロック回路

「インタロック回路」とは、ある動作が実行中に、他の動作を実行させないようにするためのものです。
例えば、機械の扉が開いている時に動作しないようにしたり、モーターが正転中に逆転が実行されないようにすることで、機械の安全性や、制御順序を最適に実行させるために使用されます。

シーケンサーラダー、インタロック

上のラダー図では、補助コイル「M0」の接点がONすると「Y0」がONします。
「Y0」がONしている時に「M1」の接点がONしても、インタロック「Y0(b接点)」がONして接点が離れているため「Y1」はONしません。

「M0」がOFFし「Y0」がOFFすると、インタロック「Y0(b接点)」がOFFして接点が繋がるため「Y1」がONできるようになります。


・自己保持回路

「自己保持回路」とは接点がONした後にOFFしても、その接点がONしたことを保持(記憶)しておくための回路です。
ON状態を保持するために、ONしたコイル自身の接点で自分のON状態を保持するため「自己保持回路」と呼ばれます。

シーケンサーラダー、自己保持

上のラダー図では、接点「X0」がONすると、補助コイル「M0」がONして、接点「M0」がONすることで「M0」のON状態が保持されます。
この状態で「X0」がOFFしても「M0」はONのままです。
「X1」がONすることで自己保持が解除され「M0」はOFFします。


・タイマ と カウンタ

タイマ

「タイマ」は動作のタイミングを制御したいときに使用します。
ある処理を実行する前に一定時間待機させたり、ある動作を一定時間継続させたりすることができます。

シーケンサーラダー、タイマ

上のラダー図では、「自己保持回路」と組み合わせて、「X0」がONすると「M0」がONし「T0」のカウントが開始されます。(カウント数はK10[三菱シーケンサの場合]のように設定され、0.1sタイマの場合は0.1秒×10で1.0秒のカウントになります。)
「X0」がOFFしても「T0」のカウントは継続されます。
カウントが終了して「T0」の接点がONすると「M0」がOFFし「T0」もOFFしてカウントもリセットされます。

以上のように「X0」をONするごとに「M0」を1秒間だけONさせる動作が実現できます。

カウンタ

「カウンタ」は接点がONした回数をカウントし、設定した回数ONした時にある動作を実行、または終了させたいときに使用されます。

シーケンサーラダー、カウンタ

上のラダー図では、「X0」が1回ONすると「C0」のカウント数が+1します。(カウント数はK5[三菱シーケンサの場合]のように設定され、K5は5回のカウント数になります。)
「X0」が5回ONすると「C0」の接点がONして「Y0」がONします。
カウンタは一度ONするとON状態を保持するので「Y0」はONしたままになります。

「C0」をOFFするためにはカウンタをリセットする必要があるため、リセットコマンドを別の接点条件で実行します。
上のラダーでは「X1」をONすることで「C0」のカウント数をリセットし「C0」の接点がOFFすることで「Y0」もOFFします。

基板製造ならPCBgogoバナー2_1

5.ラダー図からPythonへの置き換え方法

ラダー図から「Python」への置き換えは、基本的には条件分岐の「if〜else 文」を使用しますが、普通に書くと長くなり、可読性も悪いので省略表記で簡潔に書きます。

タイマは「タイマー割り込み」で常時カウントできるようにし、同時に複数のタイマを正確にカウントしています。

カウンタは「カウンタクラス」を作成して必要な時に呼び出して回数をカウントします。

「ラダー図」は事前に作成しておく必要があり、それを元に「Python」に置き換えていきます。
サンプルプログラムでは「ラダー図」のサンプルも準備してあるので、「ラダー図」と照らし合わせて「Python」での記述方法を確認してみましょう。

・スキャン動作(リフレッシュ方式)の実装

まずはシーケンサとしての基本動作を実装します。

シーケンサの最大の特徴は「最初に入力状態を取得し、作成した「ラダー」条件を、左から右へ、上から下へ処理し、最後に出力を実行するという処理を繰り返す」ことです。

この動作を「リフレッシュ方式」と言い、この1連の動作サイクルを「スキャンサイクル」と言います。

「スキャンサイクル」ごとに、最初に入力状態を取得してから「ラダー」を実行します。
実行後に内部コイルの状態を更新し出力を実行し、次の「スキャンサイクル」では取得した入力条件と更新した内部コイルの条件で再び「ラダー」が実行されます。

この動作を実装するために、プログラム内のメイン処理ループ内では以下のようにプログラムを作成します。

# メイン処理 ---------------------------------------------------------------
while True:  # ずっと繰り返し
    # 入力状態スキャン
    x0, x1, x2, x3 = not inputs[0].value(), not inputs[1].value(), not inputs[2].value(), not inputs[3].value()

    # ************* シーケンス処理(ラダーから変換) *************
    '''
    ここにラダー処理を記入
    '''
    # ************* シーケンス処理 ここまで *************

    # 出力実行
    outputs[0].value(y0)
    outputs[1].value(y1)
    outputs[2].value(y2)
    outputs[3].value(y3)

メイン処理のループを実行する前に「4行目」で「ラズパイ Pico」の入力端子に接続した「ボタンスイッチ」等のON/OFF状態を取得します。

その下からラダー処理を記入していきます。

メイン処理ループの最後にラダー処理の結果に応じて「13〜16行目」のように「ラズパイ Pico」の出力端子に接続したLED等のON/OFFを実行します。


・条件分岐(if〜else 文)の省略表記

「ラダー図」を「Python」へ変換するための基本は「if〜else 文」です。

しかし、一般的な「if文」の表記ではプログラム全体が長文となり、また可読性も悪いため、省略表記で記述することで簡潔に見やすく書くことができます。

以下のラダー図を例に紹介します。

このラダー図の動作は以下になります。

「X0」がONで「X2」がOFFなら「M0」がONして自己保持され「Y0」がONします。
「X0」がOFFしても「M0」は自己保持されてONのままなので「Y0」はONし続けます。
「X1」がONすると「M0」の自己保持が解除され「M0」はOFFし「Y0」もOFFします。

この動作を「if〜else 文」で書くために以下のように言い変えてみます。
ON状態を「True」とし、OFF状態を「False」とします。

もし(「X0」が True かつ 「X2」が Falseなら)または 「M0」が Trueなら「M0」を True
もし「X1」が Trueなら「M0」を False
もし「M0」が Trueなら「Y0」を True、そうでなければ「Y0」をFalse

これを実際に「if〜else 文」で書くと以下になります。

if (x0 == True and x2 == False) or m0 == True:
    m0 = True
if x1 == True:
    m0 = False

if m0 == True:
    y0 = True
else:
    y0 = False

これでもラダー図の動作を再現できていますが、毎回これを書くのは大変ですし、読みにくいです。

これは以下のように省略して書くことができ、簡潔で見やすくすることができます。

m0 = (x0 and not x2) or m0 if not x1 else False
y0 = m0

上記の書き方を、もう一度以下のラダー図と見比べると、1行づつがラダー図そのものを表現していることが確認できると思います。

慣れないうちは大変に感じるかもしれませんが、パターンは決まっているので、大規模な「ラダー図」でも、簡単に「Python」で置き換えることができます。

シーケンサで開発した制御プログラムを「ラズパイ Pico」のようなマイコンボードに簡単に置き換えることができるため、試作から量産基板への移行も容易になります。
さらに、プログラムに変更があった場合でも「ラダー図」の修正から置き換えるだけで、細かいタイミング調整を気にする必要がないため、デバッグも容易になります。

・タイマー割り込みによる常時タイマカウント

「ラズパイ Pico」のプログラムで遅延時間を簡単に作る時は「time.sleep(1.0)」のように書きますが、この場合、タイマカウント中は割り込み処理を設定しない限り他の処理を受け付けなくなります。

入力端子ごとに割り込み処理を設定しても良いですが、「タイマー割り込み」を使用して、シーケンサの「リフレッシュ方式」を実装することで、全ての入出力端子で並列処理しているような動作を実現することができます。

最小の処理速度は「1スキャンタイム」に依存するため、ラダー処理が長くなると遅くなります。
大規模なラダーの場合は「1スキャンタイム」が「タイマー割り込み」の「割り込み時間間隔」を超えないように注意する必要があります。

サンプルプログラム内では以下のようにタイマー割り込みを実装しています。

#タイマON/OFF状態格納変数
t0 = t1 = t2 = t3 = t4 = t5 = t6 = t7 = t8 = t9 = False  # タイマ(10点)

# タイマ割り込み間隔設定(ms)
PERIOD = 100

# 汎用タイマ初期設定
timer_start = [False] * 10          # タイマカウント開始信号
TIM_SET = [0] * len(timer_start)    # タイマカウント値設定用初期値0(x100ms)
time_value = [0] * len(timer_start) # タイマカウント現在値格納用

# インターバルタイマ初期設定
TIM_INTERVAL0 = 5  # インターバルタイマON/OFF間隔(x0.1s)
tim_interval0 = 0  # インターバルタイマカウント現在値格納用
flicker = False    # フリッカON/OFF出力用

# タイマー割り込み発生時実行関数 ----------------------------------------------
def timer_callback(timer):
    # グローバル変数を宣言
    global TIM_INTERVAL0, tim_interval0, flicker
    global timer_start, time_value

    # 汎用タイマカウント処理
    t_values = []  # タイマのON/OFF結果を保持するリストを準備

    # 汎用タイマカウント(汎用タイマの数だけループ)
    for i in range(len(timer_start)):
        if timer_start[i] and time_value[i] < TIM_SET[i]:  # カウント条件ONでカウント値が設定値より小さければ
            time_value[i] += 1                             # タイマカウント+1
            print("T{} = {}".format(i, time_value[i]))
        t_values.append(time_value[i] == TIM_SET[i])       # タイマカウントが完了したかを確認しON/OFF結果を格納
        time_value[i] = 0 if not timer_start[i] else time_value[i]  # カウント条件OFFなら初期化

    # 汎用タイマのON/OFF結果をタイマ変数に設定(汎用タイマの数だけループ)
    for i, value in enumerate(t_values):
        globals()[f"t{i}"] = value

    # インターバルタイマ(フリッカ)
    if not flicker:  # インターバルOFFならカウントアップ
        tim_interval0 += 1
        flicker = (tim_interval0 == TIM_INTERVAL0)  # カウントMAXでインターバルOFF
    else:            # インターバルONならカウントダウン
        tim_interval0 -= 1
        flicker = not (tim_interval0 == 0)  # カウントMAXでインターバルOFF

# タイマー割り込み設定(間隔, モード, コールバック関数)
timer = Timer()  # タイマーオブジェクトを作成
timer.init(period=PERIOD, mode=Timer.PERIODIC, callback=timer_callback)

2行目」で「タイマON/OFF状態格納変数」を準備しています。
タイマはラダー処理の条件式内で複数回記述することになるので、簡潔に書くためにリストではなく変数で個別に宣言しています。

5行目」でタイマ割り込み時間間隔を「ms」単位で設定します。
この時間がタイマカウントの最小値になります。
時間を短くする場合はラダー処理の「1スキャンタイム」を超えないように注意する必要があります。

8行目」で使用するタイマの数を指定しています。初期値は10点です。
タイマを増やす場合は「タイマON/OFF状態格納変数」を増やして、ここの要素数も増やします。

13〜15行目」では汎用的に使用できる「0.5秒のフリッカ(ON/OFF繰り返し)タイマ」を準備しています。必要に応じて使用してください。
サンプルプログラムでは「ラズパイ Pico」本体のLEDを点滅させるために使用しています。

18行目」からタイマー割り込み発生時に呼び出す関数を定義しています。
ラダー処理内でタイマのカウント条件が成立している時に、各タイマに設定された時間カウント処理が行われます。フリッカタイマのカウントもここで行います。

47,48行目」でタイマー割り込みを使用するための設定を行います。


タイマーを使用するにはメインループ内の「シーケンス処理記入部」に以下のように記入します。
以下は「T0:タイマ」を使用する例です。

    # T0 タイマ例 ****************************
        x2     t0
      --| |-┬-|/|-┬----------( m1 )--
        m1  |     | 
      --| |-┘     └--------(t0 K10)--
        m1
      --| |------------------( y1 )--
    '''
    # Pythonへ変換(T0 タイマ例)
    m1 = x2 or m1 if not t0 else False

    TIM_SET[0] = 10     # カウント時間設定(x0.1s)
    timer_start[0] = m1 # タイマ0 のカウント条件として m1 を指定

    y1 = m1

上コードの「12行目」でタイマのカウント時間を設定しています。
タイマ「T0」のカウント時間を設定するには「TIM_SET」リストの要素「0」に数値を指定します。

13行目」でタイマカウントを開始する条件を設定しています。
タイマ「T0」のカウント条件は「timer_start」リストの要素「0」に指定し、この条件が「True」になるとカウントが開始されます。
タイマカウントが終了するとタイマ変数「t0」が「True」になります。
カウント条件が「False」になると「t0」は「False」になり、カウント時間もリセットされます。


・カウンタークラス作成によるカウンタ

カウンタの回数カウントは「カウンタークラス」を作成して、必要な時に呼び出してカウントを行います。

#カウンタON/OFF状態格納変数
c0 = c1 = c2 = c3 = c4 = c5 = c6 = c7 = c8 = c9 = False  # カウンタ(10点)

# カウンタ処理クラス ---------------------------------------------------------
class Counter:
    # インスタンス変数
    def __init__(self):
        self.c_state = [False] * 10  # カウンタ状態格納用(10点)
        self.cnt = [0] * 10  # カウンタカウント数保持用(10点)
    
    # カウント実行(self, カウント条件, カウンタ番号, カウント値)
    def counter(self, coil, num, value):
        """
        カウンタの値を1つ増やします。
        Args:
            coil (bool): カウント条件が満たされたかどうかを表すブール値
            num (int): カウンタの番号
            value (int): カウントする最大値
        Returns:
            bool: カウンタが指定の最大値に達した場合はTrue、それ以外の場合はFalse
        """
        if coil and not self.c_state[num] and self.cnt[0] < value:
            self.cnt[num] += 1
            self.c_state[num] = True
            print("C = ", self.cnt[num])
            if self.cnt[num] == value:
                print("C{} ON!".format(num))
                return True
        if not coil:  # カウント条件がOFFなら カウンタ状態をFalseへ
            self.c_state[num] = False
        return False
    
    # カウント数リセット
    def reset_counter(self, num):
        self.cnt[num] = 0

count = Counter()  # カウンタのインスタンスを作成

2行目」で「カウンタのON/OFF状態格納変数」を準備しています。
ラダー処理の条件式内で複数回記述することになるので、簡潔に書くためにリストではなく変数で個別に宣言しています。

「カウンタークラス」内の「8,9行目」で使用するカウンタの数を指定しています。初期値は10点です。
カウンタを増やす場合は「カウンタON/OFF状態格納変数」を増やして、ここの要素数も増やします。

12行目」の「count.counter()」関数を呼び出すことでカウンタの回数カウントを実行します。
この関数は以下のように呼び出します。

カウンタ変数 = count.counter(カウント条件, カウンタ番号, カウント数
・カウンタ変数:c0 やc1 のように、カウンタ変数を指定します。カウント終了で「True」が格納されます。
カウント条件
:カウントする条件を設定します。x1 やm1 のように接点変数を指定してもいいですし、「x1 and not m1(x1がON かつ m1がOFF なら)」のように条件式を設定してもいいです。
カウンタ番号
:使用するカウンタ番号を、1 や2 のように数値で設定します。
カウント数
:カウント回数の設定値を、3 や10 のように数値で設定します。

実際は以下のように指定します。
以下の例ではカウンタ「c0」を使用して「x1」がONした回数を3回カウントします。

if not c0:  # カウンタC0がOFFなら
    c0 = count.counter(x1, 0, 3)  # カウント実行関数呼び出し(カウント条件, カウンタ番号, カウント数)
カウンタのカウント数が設定値に達したら「c0」が「True」になります。
「c0」のカウントが終了したらその後のカウントが実行されないように、上コード「1行目」 のように「if not c0:」の中でカウント関数を呼び出す必要があります。

34行目」でカウンタをリセットする「count.reset_counter()」関数を定義しています。
カウンタをリセットするときは、リセットしたいカウンタ番号を指定して以下のように「count.reset_counter()」呼び出します。

if x1:  # X1がONなら
    c0 = False  # カウンタC0をOFF
    count.reset_counter(0)  # カウンタC0のカウント数を0リセット(カウンタ番号)

上コードでは「x1」がONしたらカウンタ「c0」を「False」に設定し、リセット関数を呼び出し、カウント数をリセットしています。

6.サンプルプログラム

実際にサンプルプログラムを使用して「ラダー図」を「Python」に変換して動作確認してみましょう。
各サンプルプログラムを開発環境(Thonny等)にコピペで貼り付けて実行してください。

開発環境「Thonny」のインストール方法や使い方は以下のリンクで詳しく紹介しています。

ラズパイPicoの使い方 MicroPython&開発環境Thonny、SSLエラーの対処方法も紹介
Raspberry Pi Picoので開発環境Thonnyを使用した「Python(MicroPython)」でのプログラミング方法について初期設定からパッケージ(ライブラリ)の追加、動作確認の方法まで詳しく紹介します。

・サンプル1:a接点、b接点、AND回路、OR回路

基本的なラダープログラム「a接点」「b接点」「AND回路」「OR回路」の「Python」での記述方法を確認し、サンプルプログラムで動作確認を行います。
ラダー図はサンプルプログラム内のコメントで書いてあります。

「ラズパイ Pico」を使用した動作は以下のようになります。

ラダーをPythonで書くサンプル1

[LED赤(Y0)・青(Y1)]で「a接点」「b接点」の動作確認を行います。
[LED青(Y1)]は「b接点」に繋がっているので最初から点灯しています。

ラダーをPythonで書くサンプル1

[スイッチ赤(X0)]を押すと[LED青(Y1)]が消灯し、[LED赤(Y0)]が点灯します。[本体LED緑]も[LED赤(Y0)]と一緒に点灯します。

ラダーをPythonで書くサンプル1

[LED緑(Y2)]で「AND回路」の動作確認を行います。
[スイッチ赤(X0)と青(X1)]を同時に押すと、[LED緑(Y2)]が点灯します。

ラダーをPythonで書くサンプル1

[LED黄(Y3)]で「OR回路」の確認を行います。
[スイッチ白(X2)]を押すと[LED黄(Y3)]が点灯します。

ラダーをPythonで書くサンプル1

[スイッチ黄(X3)]を押しても[LED黄(Y3)]が点灯します。

ラダーをPythonで書くサンプル1

[スイッチ白(X2)と黄(X3)]両方を押しても[LED黄(Y3)]が点灯します。


サンプルプログラムは以下になります。コピペで貼り付けて実行してください。
黒枠内の右上アイコンのクリックでもコピーできます。

from machine import *  # 制御用モジュールを準備

# 本体LED(GP25)を出力設定
led = Pin(25, Pin.OUT)

# INPUT:入力端子(x0〜x3)設定(プルアップ)
input_pins = [3, 7, 11, 15]  # 使用する入力端子を設定(リスト作成)
inputs = [Pin(pin, Pin.IN, Pin.PULL_UP) for pin in input_pins]

# OUTPUT:出力端子(y0〜y3)設定
output_pins = [28, 26, 21, 16]  # 使用する出力端子を設定(リスト作成)
outputs = [Pin(pin, Pin.OUT) for pin in output_pins]

# 初期化
x0 = x1 = x2 = x3 = False  # 入力接点(4点)
y0 = y1 = y2 = y3 = False  # 出力リレー(4点)
m0 = m1 = m2 = m3 = m4 = m5 = m6 = m7 = m8 = m9 = \
m10 = m11 = m12 = m13 = m14 = m15 = m16 = m17 = m18 = m19 = False  # 補助リレー(20点)

# メイン処理 ---------------------------------------------------------------
while True:  # ずっと繰り返し
    # 入力状態スキャン
    x0, x1, x2, x3 = not inputs[0].value(), not inputs[1].value(), not inputs[2].value(), not inputs[3].value()

    # ************* シーケンス処理(ラダーから変換) *************
    '''
    # a接点、b接点 *********************
        x0
      --| |------------------( y0 )--
        x0
      --|/|------------------( y1 )--
    '''
    # Pythonへ変換(基本ラダー例)
    y0 = x0
    y1 = not x0

    '''
    # AND、OR *************************
        x0    x1
      --| |---| |------------( y2 )--
        x2
      --| |-┬----------------( y3 )--
        x3  |
      --| |-┘
    '''
    y2 = x0 and x1
    y3 = x2 or x3

    # 本体LED動作(y0がONなら点灯)
    led_state = y0
    led.value(led_state)

    # ************* シーケンス処理 ここまで *************

    # 出力実行
    outputs[0].value(y0)
    outputs[1].value(y1)
    outputs[2].value(y2)
    outputs[3].value(y3)
基板製造ならPCBgogoバナー2_1

・サンプル2:自己保持回路、出力方法の確認

「自己保持回路」の「Python」での記述方法を確認し、サンプルプログラムで動作確認を行います。
出力コイルを使用した、各条件成立時の出力方法についても確認します。
ラダー図はサンプルプログラム内のコメントで書いてあります。

ラダー内で各種「接点」は複数使用することができますが、出力の「コイル」は、1度しか記入してはいけません。
シーケンサでこれは「2重コイル」としてエラーになります。
エラーにならない場合でも、想定した動作はできないため注意が必要です。

「ラズパイ Pico」を使用した動作は以下のようになります。

ラダーをPythonで書くサンプルプログラム2

[LED赤(Y0)]で「自己保持回路」の確認を行います。
[スイッチ赤(X0)]を押すと[LED赤(Y0)]が点灯します。
[LED黄(Y3)]は出力部で[LED赤(Y0)]とOR条件で出力設定しているため、一緒に点灯します。

ラダーをPythonで書くサンプルプログラム2

[スイッチ赤(X0)]を離しても「自己保持」されているため、[LED]は点灯したままです。

ラダーをPythonで書くサンプルプログラム2

[スイッチ青(X1)]を押すと「自己保持」が解除されて[LED]が消灯します。

ラダーをPythonで書くサンプルプログラム2

[LED青(Y1)]で複合条件での「自己保持」動作の確認を行います。
[スイッチ赤(X0)と青(X1)]を同時に押した時に[LED青(Y1)]が点灯し「自己保持」されます。

ラダーをPythonで書くサンプルプログラム2

[LED青(Y1)]の「自己保持」は、[スイッチ白(X2)]を押しても、[スイッチ黄(X3)]を押しても解除されて消灯します。

ラダーをPythonで書くサンプルプログラム2

[スイッチ赤(X0)]と[スイッチ青(X1)]の押し方と順番によっては、上写真のように全ての[LED]を点灯させることができます。
出力部の「出力条件AND/OR」を確認しながら、どのような押し方をしたら全点灯できるか考えてみましょう。


サンプルプログラムは以下になります。コピペで貼り付けて実行してください。
黒枠内の右上アイコンのクリックでもコピーできます。

from machine import *  # 制御用モジュールを準備

# 本体LED(GP25)を出力設定
led = Pin(25, Pin.OUT)

# INPUT:入力端子(x0〜x3)設定(プルアップ)
input_pins = [3, 7, 11, 15]  # 使用する入力端子を設定(リスト作成)
inputs = [Pin(pin, Pin.IN, Pin.PULL_UP) for pin in input_pins]

# OUTPUT:出力端子(y0〜y3)設定
output_pins = [28, 26, 21, 16]  # 使用する出力端子を設定(リスト作成)
outputs = [Pin(pin, Pin.OUT) for pin in output_pins]

# 初期化
x0 = x1 = x2 = x3 = False  # 入力接点(4点)
y0 = y1 = y2 = y3 = False  # 出力リレー(4点)
m0 = m1 = m2 = m3 = m4 = m5 = m6 = m7 = m8 = m9 = \
m10 = m11 = m12 = m13 = m14 = m15 = m16 = m17 = m18 = m19 = False  # 補助リレー(20点)

# メイン処理 ---------------------------------------------------------------
while True:  # ずっと繰り返し
    # 入力状態スキャン
    x0, x1, x2, x3 = not inputs[0].value(), not inputs[1].value(), not inputs[2].value(), not inputs[3].value()

    # ************* シーケンス処理(ラダーから変換) *************
    '''
    # 自己保持1 *************************
        x0    x1
      --| |-┬-|/|---------------( m0 )--
        m0  |
      --| |-┘
    '''
    m0 = x0 or m0 if not x1 else False

    '''
    # 自己保持2 *************************
        x0    x1    x2    x3
      --| |---| |-┬-|/|---|/|---( m1 )--
        m1        |
      --| |-------┘
    '''
    m1 = (x0 and x1) or m1 if (not x2 and not x3) else False

    '''
    # 出力 *****************************
        x0
      --| |---------------------( y0 )--
        m1        
      --| |---------------------( y1 )--
        m0    m1
      --| |---| |---------------( y2 )--
        m0
      --| |-┬-------------------( y3 )--
        m1  |
      --| |-┘
    '''
    y0 = m0
    y1 = m1
    y2 = m0 and m1
    y3 = m0 or m1

    # 本体LED動作(y0がONなら点灯)
    led_state = y0
    led.value(led_state)

    # ************* シーケンス処理 ここまで *************

    # 出力実行
    outputs[0].value(y0)
    outputs[1].value(y1)
    outputs[2].value(y2)
    outputs[3].value(y3)

・サンプル3:タイマ、カウンタ

「タイマ」「カウンタ」を使用したラダーの「Python」での記述方法を確認し、サンプルプログラムで動作確認を行います。
ラダー図はサンプルプログラム内のコメントで書いてあります。

サンプルプログラムには「タイマー割り込み」と「カウンタクラス」の処理が長々と書かれてますが、この辺りは特に気にせず、メイン処理の「シーケンス処理」部でプログラムの確認を行なってください。

「ラズパイ Pico」を使用した動作は以下のようになります。

ラダーをPythonで書くサンプルプログラム3

サンプルプログラムを実行すると[本体LED緑]が点滅します。

ラダーをPythonで書くサンプルプログラム3

「タイマ」カウントには「自己保持回路」使用するので[LED赤(Y0)]でおさらいです。
[スイッチ赤(X0)]を押すと[LED赤(Y0)]が点灯し「自己保持」されます。

ラダーをPythonで書くサンプルプログラム3

[スイッチ青(X1)]を押すと[LED赤(Y0)]は消灯します。

ラダーをPythonで書くサンプルプログラム3

「タイマ」の動作確認をします。
[スイッチ白(X2)]を押すと[LED青(Y1)]が点灯します。

ラダーをPythonで書くサンプルプログラム3

[スイッチ白(X2)]を押してから1秒後に[LED青(Y1)]は消灯します。
[スイッチ白(X2)]をどんなタイミングで押しても正確に1秒後に消灯し、[本体LED緑]は一定間隔で点滅を繰り返し続けることが確認できます。

ラダーをPythonで書くサンプルプログラム3

「カウンタ」の動作確認を行います。
「カウント数」の設定値は3です。
[スイッチ黄(X3)]を押すと[LED緑(Y2)]が点灯しカウンタ「C0」のカウント数が+1されます。(シリアルモニタで確認できます)

ラダーをPythonで書くサンプルプログラム3

[スイッチ黄(X3)]を合計3回押すとカウンタ「C0」がONし、[LED黄(Y3)]が点灯します。

ラダーをPythonで書くサンプルプログラム3

[スイッチ青(X1)]を押すと[LED黄(Y3)]が消灯し、カウンタ「C0」がOFFしてカウント数もリセットされます。


サンプルプログラムは以下になります。コピペで貼り付けて実行してください。
黒枠内の右上アイコンのクリックでもコピーできます。

from machine import *  # 制御用モジュールを準備

# 本体LED(GP25)を出力設定
led = Pin(25, Pin.OUT)

# INPUT:入力端子(x0〜x3)設定(プルアップ)
input_pins = [3, 7, 11, 15]  # 使用する入力端子を設定(リスト作成)
inputs = [Pin(pin, Pin.IN, Pin.PULL_UP) for pin in input_pins]

# OUTPUT:出力端子(y0〜y3)設定
output_pins = [28, 26, 21, 16]  # 使用する出力端子を設定(リスト作成)
outputs = [Pin(pin, Pin.OUT) for pin in output_pins]

# 初期化
x0 = x1 = x2 = x3 = False  # 入力接点(4点)
y0 = y1 = y2 = y3 = False  # 出力リレー(4点)
m0 = m1 = m2 = m3 = m4 = m5 = m6 = m7 = m8 = m9 = \
m10 = m11 = m12 = m13 = m14 = m15 = m16 = m17 = m18 = m19 = False  # 補助リレー(20点)
t0 = t1 = t2 = t3 = t4 = t5 = t6 = t7 = t8 = t9 = False  # タイマ(10点)
c0 = c1 = c2 = c3 = c4 = c5 = c6 = c7 = c8 = c9 = False  # カウンタ(10点)

# タイマ割り込み間隔設定(ms)
PERIOD = 100

# 汎用タイマ初期設定
timer_start = [False] * 10          # タイマカウント開始信号
TIM_SET = [0] * len(timer_start)    # タイマカウント値設定用初期値0(x100ms)
time_value = [0] * len(timer_start) # タイマカウント現在値格納用

# インターバルタイマ初期設定
TIM_INTERVAL0 = 5  # インターバルタイマON/OFF間隔(x0.1s)
tim_interval0 = 0  # インターバルタイマカウント現在値格納用
flicker = False    # フリッカON/OFF出力用

# タイマー割り込み発生時実行関数 ----------------------------------------------
def timer_callback(timer):
    # グローバル変数を宣言
    global TIM_INTERVAL0, tim_interval0, flicker
    global timer_start, time_value

    # 汎用タイマカウント処理
    t_values = []  # タイマのON/OFF結果を保持するリストを準備

    # 汎用タイマカウント(汎用タイマの数だけループ)
    for i in range(len(timer_start)):
        if timer_start[i] and time_value[i] < TIM_SET[i]:  # カウント条件ONでカウント値が設定値より小さければ
            time_value[i] += 1                             # タイマカウント+1
            print("T{} = {}".format(i, time_value[i]))
        t_values.append(time_value[i] == TIM_SET[i])       # タイマカウントが完了したかを確認しON/OFF結果を格納
        time_value[i] = 0 if not timer_start[i] else time_value[i]  # カウント条件OFFなら初期化

    # 汎用タイマのON/OFF結果をタイマ変数に設定(汎用タイマの数だけループ)
    for i, value in enumerate(t_values):
        globals()[f"t{i}"] = value

    # インターバルタイマ(フリッカ)
    if not flicker:  # インターバルOFFならカウントアップ
        tim_interval0 += 1
        flicker = (tim_interval0 == TIM_INTERVAL0)  # カウントMAXでインターバルOFF
    else:            # インターバルONならカウントダウン
        tim_interval0 -= 1
        flicker = not (tim_interval0 == 0)  # カウントMAXでインターバルOFF

# タイマー割り込み設定(間隔, モード, コールバック関数)
timer = Timer()  # タイマーオブジェクトを作成
timer.init(period=PERIOD, mode=Timer.PERIODIC, callback=timer_callback)

# カウンタ処理クラス ---------------------------------------------------------
class Counter:
    # インスタンス変数
    def __init__(self):
        self.c_state = [False] * 10  # カウンタ状態格納用(10点)
        self.cnt = [0] * 10  # カウンタカウント数保持用(10点)
    
    # カウント実行(self, カウント条件, カウンタ番号, カウント値)
    def counter(self, coil, num, value):
        """
        カウンタの値を1つ増やします。
        Args:
            coil (bool): カウント条件が満たされたかどうかを表すブール値
            num (int): カウンタの番号
            value (int): カウントする最大値
        Returns:
            bool: カウンタが指定の最大値に達した場合はTrue、それ以外の場合はFalse
        """
        if coil and not self.c_state[num] and self.cnt[0] < value:
            self.cnt[num] += 1
            self.c_state[num] = True
            print("C = ", self.cnt[num])
            if self.cnt[num] == value:
                print("C{} ON!".format(num))
                return True
        if not coil:  # カウント条件がOFFなら カウンタ状態をFalseへ
            self.c_state[num] = False
        return False
    
    # カウント数リセット
    def reset_counter(self, num):
        self.cnt[num] = 0

count = Counter()  # カウンタのインスタンスを作成

# メイン処理 ---------------------------------------------------------------
while True:  # ずっと繰り返し
    # 入力状態スキャン
    x0, x1, x2, x3 = not inputs[0].value(), not inputs[1].value(), not inputs[2].value(), not inputs[3].value()

    # ************* シーケンス処理(ラダーから変換) *************
    '''
    # 自己保持動作確認 *************************
        x0    x1
      --| |-┬-|/|------------( m0 )--
        m0  |
      --| |-┘
        m0
      --| |------------------( y0 )--
    '''
    # Pythonへ変換(基本ラダー例)
    m0 = x0 or m0 if not x1 else False
    y0 = m0

    '''
    # T0 タイマ例 ****************************
        x2     t0
      --| |-┬-|/|-┬----------( m1 )--
        m1  |     | 
      --| |-┘     └--------(t0 K10)--
        m1
      --| |------------------( y1 )--
    '''
    # Pythonへ変換(T0 タイマ例)
    m1 = x2 or m1 if not t0 else False
    TIM_SET[0] = 10     # カウント時間設定(x0.1s)
    timer_start[0] = m1 # タイマ0 のカウント条件として m1 を指定
    y1 = m1

    '''
    # C0 カウンタ例 ****************************
        x3
      --| |---------------( c0 K3 )--
        x1
      --| |--------------( RST c0 )--
        x3    c0
      --| |---|/|------------( y2 )--
        c0  
      --| |------------------( y3 )--
    '''
    # Pythonへ変換(C0 カウンタ例)
    if not c0:  # 動作サイクルカウンタ
        c0 = count.counter(x3, 0, 3)  # (カウント条件, カウンタ番号, カウント数)
    if x1:  # C0カウンタリセット
        c0 = False
        count.reset_counter(0)  # カウンタリセット(カウンタ番号)
    y2 = x3 and not c0
    y3 = c0

    # 本体LED動作(常時点滅、m0がONなら点灯)
    led_state = flicker or m0
    led.value(led_state)

    # ************* シーケンス処理 ここまで *************

    # 出力実行
    outputs[0].value(y0)
    outputs[1].value(y1)
    outputs[2].value(y2)
    outputs[3].value(y3)
基板製造ならPCBgogoバナー2_1

・サンプル4:モーターの正逆繰り返し例

最後に応用編として実用的な「モーターの正逆繰り返し」動作を想定した「ラダー図」を「Python」に置き換えて動作確認していきます。

動作は以下のようになります。

「自動ボタン」が押されると2秒間「正転」動作し停止、1秒停止後2秒間「逆転」動作し停止、1秒停止後に再度「正転」動作を3回繰り返し停止します。

今回は20行(62ステップ)あるラダーをそのまま「Python」に置き換えて記述しています。
これには「AND、OR、インタロック、自己保持、タイマ、カウンタ」の全てを含みます。
ラダー全体画像も載せていますので、見比べながら確認してみてください。

サンプルプログラムには「タイマー割り込み」と「カウンタクラス」の処理が長々と書かれてますが、この辺りは特に気にせず、メイン処理の「シーケンス処理」部でプログラムの確認を行なってください。

「ラズパイ Pico」を使用した動作は以下のようになります。

LED赤が「正転」、LED青が「逆転」
LED緑が「逆転起動待ち状態」、LED黄が「正転起動待ち状態」を表します。
スイッチ赤は「自動運転ボタン」、スイッチ青は「停止ボタン」
スイッチ白は「手動正転ボタン」、スイッチ黄は「手動逆転ボタン」として使用します。

ラダーをPythonで書くサンプルプログラム4

プログラムを実行すると[本体LED緑]が点灯します。

ラダーをPythonで書くサンプルプログラム4

[スイッチ赤(自動運転ボタン)]を押すと自動運転が開始され、[LED赤(正転)]が点灯します。
自動運転中は[本体LED緑]が点滅し続けます。
[スイッチ青(停止ボタン)]が押されると自動運転は終了します。

ラダーをPythonで書くサンプルプログラム4

2秒後に[LED赤(正転)]が消灯し、[LED緑(逆転起動待ち状態)]が点灯します。

ラダーをPythonで書くサンプルプログラム4

1秒後に[LED青(逆転)]が点灯します

ラダーをPythonで書くサンプルプログラム4

2秒後に[LED青(逆転)]が消灯し、[LED黄(正転起動待ち状態)]が点灯します。

ラダーをPythonで書くサンプルプログラム4

1秒後に[LED緑・黄]が消灯し再度[LED赤(正転)]点灯から繰り返されます。
この動作を3回繰り返すと自動停止します。

[スイッチ白(X2)]は手動正転ボタンで、[スイッチ黄(X3)]は手動逆転ボタンです。
自動運転中でなければ、個別に[LED赤(正転)][LED青(逆転)]の動作確認ができ、「インタロック」で両方が同時にONすることはありません。

使用したラダー図(モーターの正逆繰り返し)は下画像のようになります。

サンプルラダー図、モーターの正逆回転

サンプルプログラムは以下になります。コピペで貼り付けて実行してください。
黒枠内の右上アイコンのクリックでもコピーできます。

from machine import *  # 制御用モジュールを準備

# 本体LED(GP25)を出力設定
led = Pin(25, Pin.OUT)

# INPUT:入力端子(x0〜x3)設定(プルアップ)
input_pins = [3, 7, 11, 15]  # 使用する入力端子を設定(リスト作成)
inputs = [Pin(pin, Pin.IN, Pin.PULL_UP) for pin in input_pins]

# OUTPUT:出力端子(y0〜y3)設定
output_pins = [28, 26, 21, 16]  # 使用する出力端子を設定(リスト作成)
outputs = [Pin(pin, Pin.OUT) for pin in output_pins]

# 初期化
x0 = x1 = x2 = x3 = False  # 入力接点(4点)
y0 = y1 = y2 = y3 = False  # 出力リレー(4点)
m0 = m1 = m2 = m3 = m4 = m5 = m6 = m7 = m8 = m9 = \
m10 = m11 = m12 = m13 = m14 = m15 = m16 = m17 = m18 = m19 = False  # 補助リレー(20点)
t0 = t1 = t2 = t3 = t4 = t5 = t6 = t7 = t8 = t9 = False  # タイマ(10点)
c0 = c1 = c2 = c3 = c4 = c5 = c6 = c7 = c8 = c9 = False  # カウンタ(10点)

# タイマ割り込み間隔設定(ms)
PERIOD = 100

# 汎用タイマ初期設定
timer_start = [False] * 10          # タイマカウント開始信号
TIM_SET = [0] * len(timer_start)    # タイマカウント値設定用初期値0(x100ms)
time_value = [0] * len(timer_start) # タイマカウント現在値格納用

# インターバルタイマ初期設定
TIM_INTERVAL0 = 5  # インターバルタイマON/OFF間隔(x0.1s)
tim_interval0 = 0  # インターバルタイマカウント現在値格納用
flicker = False    # フリッカON/OFF出力用

# タイマー割り込み発生時実行関数 ----------------------------------------------
def timer_callback(timer):
    # グローバル変数を宣言
    global TIM_INTERVAL0, tim_interval0, flicker
    global timer_start, time_value

    # 汎用タイマカウント処理
    t_values = []  # タイマのON/OFF結果を保持するリストを準備

    # 汎用タイマカウント(汎用タイマの数だけループ)
    for i in range(len(timer_start)):
        if timer_start[i] and time_value[i] < TIM_SET[i]:  # カウント条件ONでカウント値が設定値より小さければ
            time_value[i] += 1                             # タイマカウント+1
            print("T{} = {}".format(i, time_value[i]))
        t_values.append(time_value[i] == TIM_SET[i])       # タイマカウントが完了したかを確認しON/OFF結果を格納
        time_value[i] = 0 if not timer_start[i] else time_value[i]  # カウント条件OFFなら初期化

    # 汎用タイマのON/OFF結果をタイマ変数に設定(汎用タイマの数だけループ)
    for i, value in enumerate(t_values):
        globals()[f"t{i}"] = value

    # インターバルタイマ(フリッカ)
    if not flicker:  # インターバルOFFならカウントアップ
        tim_interval0 += 1
        flicker = (tim_interval0 == TIM_INTERVAL0)  # カウントMAXでインターバルOFF
    else:            # インターバルONならカウントダウン
        tim_interval0 -= 1
        flicker = not (tim_interval0 == 0)  # カウントMAXでインターバルOFF

# タイマー割り込み設定(間隔, モード, コールバック関数)
timer = Timer()  # タイマーオブジェクトを作成
timer.init(period=PERIOD, mode=Timer.PERIODIC, callback=timer_callback)

# カウンタ処理クラス ---------------------------------------------------------
class Counter:
    # インスタンス変数
    def __init__(self):
        self.c_state = [False] * 10  # カウンタ状態格納用(10点)
        self.cnt = [0] * 10  # カウンタカウント数保持用(10点)
    
    # カウント実行(self, カウント条件, カウンタ番号, カウント値)
    def counter(self, coil, num, value):
        """
        カウンタの値を1つ増やします。
        Args:
            coil (bool): カウント条件が満たされたかどうかを表すブール値
            num (int): カウンタの番号
            value (int): カウントする最大値
        Returns:
            bool: カウンタが指定の最大値に達した場合はTrue、それ以外の場合はFalse
        """
        if coil and not self.c_state[num] and self.cnt[0] < value:
            self.cnt[num] += 1
            self.c_state[num] = True
            print("C = ", self.cnt[num])
            if self.cnt[num] == value:
                print("C{} ON!".format(num))
                return True
        if not coil:  # カウント条件がOFFなら カウンタ状態をFalseへ
            self.c_state[num] = False
        return False
    
    # カウント数リセット
    def reset_counter(self, num):
        self.cnt[num] = 0

count = Counter()  # カウンタのインスタンスを作成

# メイン処理 ---------------------------------------------------------------
while True:  # ずっと繰り返し
    # 入力状態スキャン
    x0, x1, x2, x3 = not inputs[0].value(), not inputs[1].value(), not inputs[2].value(), not inputs[3].value()

    # ************* シーケンス処理(ラダーから変換) *************
    # M0:自動運転中
    m0 = (x0 and not x2 and not x3) or m0 if m1 else False
    
    # M1:停止指令
    m1 = not x1 and not c0
    
    # M2:自動正転指令
    m2 = m0 and not m3
    TIM_SET[0] = 20
    timer_start[0] = m2  # 正転時間タイマ
    
    # M3:自動逆転停止信号
    m3 = t0 or m3 if m0 and not t3 else False
    TIM_SET[1] = 10
    timer_start[1] = m3  # 逆転起動待ちタイマ
    
    # M4:自動逆転指令
    m4 = t1 or m4 if m0 and not t3 and not m5 else False
    TIM_SET[2] = 20
    timer_start[2] = m4  # 逆転時間タイマ
    
    # M5:自動逆転停止信号
    m5 = t2 or m5 if m0 and not t3 else False
    TIM_SET[3] = 10
    timer_start[3] = m5  # 正転起動待ちタイマ
    if not c0:  # 動作サイクルカウンタ
        c0 = count.counter(m5, 0, 3)  # (カウント条件, カウンタ番号, カウント数)
    if x1:  # C0カウンタリセット
        c0 = False
        count.reset_counter(0)  # カウンタリセット(カウンタ番号)

    # M6::手動正転指令
    m6 = x2 and not m0 and not m7

    # M7:手動逆転指令
    m7 = x3 and not m0 and not m6

    # 出力処理
    y0 = m2 or m6 if not y1 else False  # 正転
    y1 = m4 or m7 if not y0 else False  # 逆転
    y2 = m3  # 逆転待ち(内部補助リレー確認用)
    y3 = m5  # 正転待ち(内部補助リレー確認用)
    
    # 本体LED動作(自動運転ONで点滅、OFFで点灯)
    led_state = (m0 and flicker) or not m0
    led.value(led_state)

    # ************* シーケンス処理 ここまで *************

    # 出力実行
    outputs[0].value(y0)
    outputs[1].value(y1)
    outputs[2].value(y2)
    outputs[3].value(y3)

7.まとめ

シーケンサのラダーを「Python」で書く方法について詳しく紹介しました。

シーケンサは、工場の生産ラインの機械や、モーター、バルブ、ポンプなどの駆動装置をスイッチやセンサーと連携させ、必要なタイミングでON/OFF制御することが容易なため、産業用途で広く普及していますが、学習コストは比較的高く、学習できる環境も多くはありません。

そんなシーケンサのラダーは「Python」で表現することができ、「ラズパイ Pico」のようなマイコンボードを使用することで簡単に動作確認できます。

今回は「Python」を使用しましたが、基本は条件分岐の「if〜else 文」を使用するだけなので「C言語」等の他の言語でも置き換えられます。

シーケンサのラダーを理解し、プログラム言語へ置き換える方法を理解することで、シーケンサで開発した制御プログラムを「ラズパイ Pico」のようなマイコンボードに簡単に置き換えることもできます。

シーケンサで開発したものを量産のために基板化することも容易になり、プログラムに変更があった場合でも「ラダー図」の修正から置き換えるだけで、細かいタイミング調整を気にする必要がないため、デバッグも容易になります。


今回で「Python」を使用したシーケンサの基本的な動作を実装することができました。
もっと効率の良い実装方法はあると思いますが・・・ひとまず満足です♪

次回は、もっと実用的に「ラズパイ Pico」をシーケンサとして使用できるように、周辺回路を実装した基板設計について紹介しています。

基板設計は何年振りでしょう^^; まずは思い出すところからですが、今は個人で基板を設計するのも、かなり安くできるようなので、プログラミングはちょっとお休みして、ひさしぶりに基板製作を楽しみたいと思います♪
基板製作でもやっぱり「Let’s 路地からThinking〜!!」でいきます。ぜひご覧ください!

ラズパイPicoでシーケンサを作ろう(基板設計編)簡単基板見積り
Raspbrry Pi Picoを使用したシーケンサ基板の回路設計から基板の見積もり発注方法まで詳しく紹介。入力12点,出力6点,アナログ入力2点,I2C,UART,OLED表示付です。
ラズパイPicoでシーケンサを作ろう(動作確認編)基板製造、部品実装
Raspbrry Pi Picoで製作したシーケンサ(PLC)基板の動作確認(入出力、ラダー、I2C/UART通信等)を行います。基板の発注方法も詳しく紹介していきます。
PCBGOGOバナー600_1
PCBGOGOバナー600_2

コメント

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