OpenPLCに入門してみた

OpenPLCに入門してみた

PLC未経験のWeb系エンジニアがOpenPLCに入門してみました。いわゆる「Lチカ」の動作をStructured Textによるプログラムとして書き、シミュレーターで実行しました。わからない用語が出てくるたびに調べながら進めたので、その過程も含めてお届けします。
2026.10.22

製造ビジネステクノロジー部・浅野です。

これまでの私の開発経験はWeb系が中心で、製造業の現場で利用されているPLCは全然触ったことがありませんでした。

そんな中、OpenPLCというものを知り、興味が湧いてきました。

今回はPLC未経験のWeb系エンジニアがOpenPLCに入門してみます。わからない用語が出てくるたびに調べながら進めたので、その過程も含めてお届けします。

PLCとは?

まずは、そもそもPLCとは何かを押さえておきます。以下の記事がとてもわかりやすいので、私もこちらを読んでから手を動かしました。

https://dev.classmethod.jp/articles/mfg-for-it-engineer-001/

PLCとは、工場の生産ラインやプラントにあるモーター・バルブ・センサーといった機器を、あらかじめ組んだプログラムに従って自動制御するための産業用コントローラです。ボタンが押されたら(入力)コンベアを動かす(出力)、といった条件分岐やタイミング制御を、ソフトウェアとして記述・変更できるのが特徴です。

OpenPLCとは?

OpenPLCは、Autonomy Logicが開発しているオープンソースのPLC環境です。次の3つの要素で構成されています。

  • OpenPLC Editor:PLCのプログラムを書くための開発環境(IDE)
  • StruC++:Structured TextをC++(C++17)に変換するコンパイラ
  • OpenPLC Runtime:コンパイルされたプログラムを実行するソフトウェア。Raspberry PiやLinuxマシンなどに入れることで、その機器がPLCとして動作する

Editorでプログラムを書き、StruC++がそれをコンパイルし、Runtimeが実行する、という流れです。

プログラムはIEC 61131-3という国際規格に沿った言語で記述します。Structured Text(ST)、Ladder Diagram(LD、ラダー図)、Function Block Diagram(FBD)、Instruction List(IL)などに対応しています。OpenPLC Editorにはシミュレーターが内蔵されているため、実機がなくてもPC上でプログラムを動かしてデバッグできます。

やってみる

前提

  • 動作確認した環境
    • macOS Tahoe 26.6.2(Apple Silicon)
    • OpenPLC Editor v4.3.2
  • Raspberry PiやArduinoなどの実機は使わず、Editor内蔵のシミュレーターのみで動かします

OpenPLC Editorをインストールする

OpenPLCにはクラウド上の開発環境からデバイスにデプロイして使う仕組みもあるのですが、まずはデスクトップで完結させたいと思い、OpenPLCのダウンロードページからOpenPLC Editorをダウンロードしました。

OpenPLC IDEのダウンロードダイアログ。v4.3.2、macOS (Apple Silicon)が選択されている

ダウンロードしたファイルを開き、アプリケーションフォルダに入れてインストールします。起動すると、次のような画面が表示されました。

OpenPLC Editorの起動画面。New Project、Open、Documentation、Sign inのメニューが並ぶ

上部に「Autonomy Edge Cloud Projects」というサインインを促す枠があります。今回は試しに作ってみたいだけなので、一旦スルーします。アカウントがなくてもローカルのプロジェクトは作成できます。

プロジェクトを作成する

公式ドキュメントのQuick Start Guideを開くと、出力を点滅させるいわゆる「Lチカ」のサンプルを作る手順が載っています。

ドキュメントではアカウント作成や実際のデバイスとの接続についての記載がありますが、今回は手軽に試してみたかったため、アカウントやデバイスまわりの手順は一旦置いておいて、プロジェクトの用意とプログラム作成の部分だけQuick Start Guideに沿って進めました。

New Projectをクリックすると、プロジェクト作成のウィザードが始まります。最初にPLC ProjectかLibraryかを聞かれます。今回はPLCで動かすプログラムを作りたいので、PLC Projectを選びます。

プロジェクトの種類を選ぶダイアログ。PLC ProjectとLibraryの2択

続いて、プロジェクト名と保存先を指定します。プロジェクト名はQuick Start GuideにならってBlinkingOutputとしました。

最後に、ベースとなるプログラムの言語と、周期タスクの時間を指定します。

言語にStructured Text、周期タスクの時間にT#20msが設定されたダイアログ

言語の選択肢を見ると、冒頭で紹介した別記事にもあったラダー図(Ladder Diagram)もあります。せっかくなのでラダー図にしようかなとも思ったのですが、Quick Start GuideではStructured Textで書かれていたので、今回はそれに従ってStructured Textを選びました。

もう一つの「Define the time for the cyclic task」は「何秒ごとに動作するか」を指定する設定のようです。この時点ではよくわかっていなかったので、デフォルトのT#20msのままにして進めました。これについては後ほど解説します。

シミュレーターが選ばれていることを確認する

プロジェクトが作成されると、左側にプロジェクトツリーが表示されます。Device > Configurationを開くと、DeviceにOpenPLC Simulatorが選択されていることが確認できます。

Board Settings画面。DeviceにOpenPLC Simulatorが選択され、「Built-in simulator — no configuration required」と表示されている

ちなみにResourceを開くと、task0という名前の周期(Cyclic)タスクがT#20ms間隔で設定され、mainプログラムのインスタンスがこのタスクに割り当てられていることが確認できます。T#20ms間隔というあたりは、先ほどウィザードで設定した内容ですね。

Resource画面。TasksにCyclic・T#20msのtask0、Instancesにmainプログラムをtask0で実行するinstance0が登録されている

変数を定義する

プログラムを書いていきます。まずはPrograms > mainを開き、上部の変数テーブルに2つの変数を追加します。

Name Class Type Location
blink_timer Local TON (なし)
output_state Local BOOL %QX0.0

blink_timerの型であるTONは、TypeのドロップダウンからSystemを選び、検索欄で「TON」と検索すると見つかります。

Typeのドロップダウンを開き、Systemを選択した状態。検索欄と型の一覧が表示されている

TONって何?

ここで早速「TONって何?」となりました。

「PLC TON」で検索してみると、オンディレイタイマーとのこと。つまり「入力がONになってから一定時間経ったら、出力をONにする」タイマーのようです。主な入出力は以下となっています。

  • IN(入力):TRUEの間、時間を計測する。FALSEになるとリセットされる
  • PT(入力):設定時間
  • Q(出力):INがTRUEのままPTの時間が経過するとTRUEになる
  • ET(出力):経過時間

コイルって何?

もう一つの変数output_stateには、Locationに%QX0.0を指定します。Quick Start Guideには、このアドレスが「デジタル出力コイル(digital output coil)」に対応する、と書かれていました。

急に「コイル」という用語が出て混乱したのですが、これはもともとPLCがリレー回路を置き換えるものだった名残で、リレーでいうところの「コイル」がPLCの出力を表す言葉として使われているということのようです。リレー回路では、条件が満たされるとコイルに電流が流れて接点が動き、それが次の回路への入力になります。それを踏まえて、「条件が満たされればビットがON、満たされなければOFF」という出力を「コイル」とここでは呼んでいるようです。

%QX0.0の読み方は以下のようになっています。

  • %Q:出力(Output)
  • X:ビット(BOOL)
  • 0.0:バイト0のビット0

入力の場合は%IXを使います。今回はシミュレーターなので実際にどこかの端子が動くわけではありませんが、実機であれば、この変数のTRUE/FALSEが出力端子のON/OFFに対応することになります。

プログラムを書く

変数テーブルの下にあるエディタに、Quick Start Guideのコードを入力します。最初にプロジェクトを設定した通り、ここではStructured Textでプログラムを作成します。

(* Hello World: Blink output every second *)
blink_timer(IN := NOT(blink_timer.Q), PT := T#500ms);

IF blink_timer.Q THEN
    output_state := NOT(output_state);
END_IF;

入力し終えたら、Cmd + Sで保存します。

変数テーブルに2つの変数が登録され、エディタにサンプルコードが入力された状態。TONの入出力(IN、PT、Q、ET)がツールチップで表示されている

blink_timerにカーソルを合わせると、TONの入出力がツールチップで表示されるのも親切ですね。

コードを読み解く

貼り付けてはみたものの、正直このコードが何をしているのかピンとこなかったので、1行ずつ調べながら読んでみました。

まず押さえておくべきなのが、PLCはプログラムを無限ループで実行し続けるという前提です。PLCは「入力を読む → プログラムを上から下まで1回実行する → 出力に書き込む」という1周(スキャン)を、短い周期でずっと繰り返します。このコードも「1回実行して終わり」ではなく、スキャンのたびに全体が実行される前提で読む必要があります。

文法面では、

  • (* ... *)はコメント
  • :=は代入、または名前付き引数の指定
  • T#500msは500ミリ秒を表す時間のリテラル

です。blink_timerはクラスのインスタンスのようなもので、blink_timer.Qはその出力です。

2行目のIN := NOT(blink_timer.Q)では、「自分の出力を反転させて自分の入力に入れる」ということを行なっています。何をしているのか最初よくわからなかったのですが、このコードが無限ループで実行されると以下のような動きをすることになります。

  • QがFALSEの間は、INはずっとTRUEなので、タイマーは時間を計り続ける
  • 500msが過ぎるとQがTRUEになる。そのスキャンでoutput_stateが反転する
  • 次のスキャンではINがNOT(TRUE)、つまりFALSEになるので、タイマーがリセットされてQもFALSEに戻る
  • するとINがまたTRUEになり、計測をやり直す

表にするとこうなります。

スキャン IN(= NOT 前回のQ) タイマーの状態 Q output_state
開始 TRUE 計測を始める FALSE 変化なし
〜500ms TRUE 計測中 FALSE 変化なし
500ms到達 TRUE 満了 TRUE 反転
その次 FALSE リセットされる FALSE 変化なし
その次 TRUE 計測をやり直す FALSE …

つまり、これでパルスを作っているんですね。Qは500msごとに1スキャンだけTRUEになるパルスになり、そのたびにoutput_stateが反転するので、「500ms ON → 500ms OFF」を繰り返す1秒周期の点滅になります。

JavaScriptで書くとしたら、雰囲気としては以下のような処理です。

setInterval(() => { output = !output; }, 500);

プログラムの中で「500msごとにこれを実行する」と書く仕組みはPLCの標準的な命令にはないので、「自分自身をリセットするタイマー」で周期的なパルスを作るのがよく行われます。なお、リセットにもスキャン1回分の時間がかかるため、厳密な周期は指定した時間よりも少し長くなります。

さらに気になったこと

続けて気になって調べてみたのが、次の2つです。

「TRUEって、つまりは1なの?」

TRUE/FALSEはBOOL型の値で、値としてはTRUE=1、FALSE=0に対応します。このあたりはC言語などと近いのですが、C言語のように勝手に整数として扱われることはなく、数値として使いたいときはBOOL_TO_INTなどで明示的に変換します。

「出力としてどのくらいの電圧が出るかは、コード上では決まってないの?」

これは決まっていません。

プログラムが決めるのはTRUEかFALSEかだけで、実際に何ボルト出るかは出力を担当するハードウェア側で決まります。たとえば、OpenPLCを動かせるボードだとArduino Unoは5V、Raspberry Piは3.3Vで、産業用PLCのトランジスタ出力ではDC24Vが主流です。同じoutput_state := TRUEでも、どのハードにつなぐかで出る電圧が変わるわけです。電圧などはハード側がもう決めていて、PLCはそれをONにする信号を送っているだけ、というイメージで良さそうです。

シミュレーターで動かす

コードの意味もわかったところで、いよいよ動かしてみます。左側のアクティビティバーにある再生ボタン(Start Simulator)をクリックすると、ビルドが始まります。

初回は、コンソールに「Installing Arduino core...」「Downloading packages...」といったログが流れ、Arduino関連のパッケージをいっぱい取ってきていました。私の環境では、ボタンを押してからシミュレーターが起動するまで1分弱でした。

初回ビルド時のコンソール。Arduino CLIとStruC++のバージョン確認の後、Arduino coreのインストールとパッケージのダウンロードが行われている

公式ドキュメントによれば、このシミュレーターの正体はavr8jsというJavaScript製のATmega2560(Arduino Megaに載っているマイコン)エミュレーターです。Start Simulatorを押すと、Structured TextをStruC++でC++に変換し、Arduino Mega向けのファームウェアとしてコンパイルして、それをエミュレーター上で実行する、という流れになっています。

ビルドが完了すると、コンソールに「Simulator is running.」と表示され、デバッガーが接続されます。

シミュレーター起動後のコンソール。Compilation complete、Simulator is running、Debugger connectedのログが表示されている。エディタ上ではコードの各変数の横に現在値が表示されている

この時点で、エディタ上のコードの各変数の横に= TRUEや= FALSEといった現在値が表示され、リアルタイムに切り替わっていきます。値がパタパタ切り替わっているだけではありますが、動きが見えるので楽しいです。

デバッガーで動作をグラフ表示する

シミュレーターが起動すると、下部のパネルがDebuggerタブに切り替わります。画面上部の各変数のDebugの虫マークをクリックするとDebuggerタブ左側のVariablesに変数の一覧と現在値が表示されます。変数名の左にある目のマークをクリックすると、その変数が右側のグラフに追加されます。

main.output_stateを追加してみましょう。

Debuggerパネル。main.output_stateの値がグラフに矩形波として表示されている

output_stateのON/OFFが切り替わって、パルスとなっているのが確認できました!実機につないでいれば、この波形に合わせてLEDがチカチカしているはずです。

続いて、タイマーの入力であるblink_timer.INもグラフに追加してみます。

Debuggerパネル。上段にmain.output_stateの矩形波、下段にmain:blink_timer.INのグラフが表示されている。INはほぼTRUEで、ときどき一瞬だけFALSEに落ちている

INはほとんどの時間TRUEで、ときどき一瞬だけFALSEに落ちています。先ほどの表でいう、QがTRUEになって反転が起きた次のスキャンでINがFALSEになり、タイマーがリセットされる瞬間ですね。

ただ、よく見るとINの落ち込みは、output_stateが切り替わるたびに毎回描かれているわけではありません。INがFALSEになるのはスキャン1回分(今回のタスク設定だと20ms程度)だけなので、デバッガーが値を取得するタイミングによっては取りこぼしているのかもしれません。

また、上段のグラフを見ると、10秒間の表示範囲に入っている周期は9回ほどで、1周期は1秒よりわずかに長くなっていました。前述の「リセットにスキャン1回分かかる」影響に加えて、公式ドキュメントにもエミュレーションのオーバーヘッドで実機より遅く動作する旨の記載があるので、そのあたりも効いていそうです。

さらに調べてみる:タスクとは?

グラフを眺めながらプロジェクト内を見て回っているうちに、プロジェクト作成時に出てきた「タスク」が気になったため、そちらも調べてみました。

タスクは、プログラムを「いつ」「どのくらいの間隔で」実行するかを決める設定のようです。プログラム自体は「何をするか」だけを記述したもので、実行のタイミングは持っていません。タスクに割り当てて初めて実行されます。つまり今回の場合は、「20msごとにこのプログラムを1回動かすよ」ということがタスクとして決まっていたわけです。

タスクでは主に以下を設定します。

  • 種類:決まった間隔で実行する周期(Cyclic)タスクのほか、ある信号が変化したときだけ1回実行するイベントタスクなどもある
  • 周期:何msごとに実行するか(今回はT#20ms)
  • 優先度:複数のタスクの実行タイミングが重なったときに、どちらを優先するか

「優先度?PLCって複数のタスクを処理できるの?」と思ったのですが、できます。たとえば「非常停止やモーター制御のように速く反応すべき処理は短い周期・高い優先度で」「画面表示の更新のように遅くてもよい処理は長い周期・低い優先度で」というように、処理の重要度に応じて実行頻度を振り分けられます。並列に動くというよりは、優先度に従って切り替えながら動かすのが一般的で、OSのプロセススケジューリングに近い考え方となっているようです。

実際のPLCや現場のことも気になってきた

ここまで来ると、そもそも実際のPLCってどんなものなんだろう、というのも気になってきました。少し調べてみましたので記載しておきます。

実物のPLCはどんな見た目?

実物のPLCは、小型のものは本1冊くらいの大きさの箱で、工場の制御盤の中にDINレールという金属レールで取り付けられているのが一般的です。

OpenPLCで書いたプログラムは、メーカー製PLCで動く?

メーカー製PLCには各メーカーの専用ソフトで作ったプログラムを書き込みます。OpenPLCで書いたプログラムをそのまま書き込むことは基本的にできません。

OpenPLCは「他社のPLCに書き込むためのツール」ではなく、Raspberry PiやArduinoといった汎用的なハードウェアにOpenPLC Runtimeを入れることで、PLCのように動かせるようにする仕組みです。ただし、Structured Textなどプログラムの書き方はIEC 61131-3という規格に沿ったものなので、OpenPLCで身につけた考え方はメーカー製PLCでもある程度通用します。

学習用だけ?現場でも使われている?

OpenPLCの主な用途は、調べてみた範囲では教育や研究、試作などが中心のようです。開発元のドキュメントでも、想定ユーザーとして「教育者・学生」や「Raspberry PiやLinux PCでOpenPLCを動かすホビイスト」などが挙げられています。

一方で、開発元のドキュメント上では、プラントエンジニアや装置メーカーといった産業向けのユーザーも想定されていると記載があります。一般に、PLCの制御プログラムを専用のPLC本体ではなく、汎用的なサーバーや仮想マシン、コンテナ上で動かす「仮想PLC」という考え方がありますが、開発元からはOpenPLC v4 RuntimeをDockerコンテナとして動かすvPLCが製品として提供されており、これらが産業向けの取り組みとなっているようです。

まとめ

PLCを触ったことのないWeb系エンジニアが、OpenPLCに入門してみました。

最初は慣れない用語もあって戸惑いましたが、調べながら進めていくとイメージが持てるようになりました。今回操作したOpenPLCのIDEもわかりやすいUIになっていて、私のような調べながら学習する人にとっても使いやすいものであったと感じました。何より、実機がなくてもシミュレーターで動かせて、変数の値の変化をグラフで眺められるのが楽しかったです。私のように「PLCって何?」という状態の人でも、実際に動かしてみて「ああ、そういうことか」と思える体験ができるのではないかなと思います。

今回はシミュレーターでの入門でしたが、実際にRaspberry Piなどにつなげばちゃんと「Lチカ」になるはずなので、実機でも動かしてみたくなりました。ボタンやセンサーを組み合わせた、なんちゃって装置を作ってみるのも楽しそうです。PLCが気になっている方は、ぜひ一度OpenPLCを試してみてください。

参考


製造業のクラウド活用とデジタル化を支援します

クラスメソッドの専門家による包括的なクラウド導入とデジタル化支援で、製造業の業務効率を最大化しましょう。AWSの導入から運用、最適化まで、最新技術と豊富な知見であらゆる課題に対応します。生産ラインのデジタル化やデータ活用、IoTの導入事例もございます。ぜひ、弊社の実績をご覧ください。

製造業界での支援内容を見る

この記事をシェアする

関連記事