💭

【後編】TwinCAT でQueue システムを使ったサイクル動作を実装する

に公開

はじめに

ベッコフオートメーション社のTwinCAT 上で、Queue システムを使ったサイクル動作を実装してみたので、備忘録として記載します。

あまりに記事が長くなったので、前編・後編に分けています。今回は後編です。
前編の記事はこちらからご確認ください。
https://zenn.dev/fredec/articles/twincatqueue

後編では、前編で実装したQueue システムを使って

  1. 仮想軸でサイクル動作を実装
  2. オーバルリニアXTS でサイクル動作を実装

を試みます。
何かの参考になれば幸いです。


前編の補足

前編の"TwinCAT でQueue を実装"の項で、

抽象クラスの方にenqueue とdequeue の抽象メソッドが無くて良いの?と思われると思いますが、私もそう思っています。
最初は抽象メソッドにして、FB_AxisQueue で実装、という形にしようとしていましたが、抽象メソッドで引数・返り値のデータ型を指定しないとなので、引数・返り値AXIS_REF を指定した抽象メソッドという悲しいモノが生まれてしまいました。
なんかうまいやり方は無いか…探しています。(実はできる、とかあるのかもですが)

という説明をしていましたが、調べたら"PVOID" というデータ型が存在していることを知りました。
(ベッコフ様のテクニカルノートに書いてありました。ありがとうございます。)
https://beckhoff-jp.github.io/TwinCATHowTo/plc_design_pattern/abstruct_array.html#id2

PVOID 型を使うことで、データ型に縛られないポインタアドレスの受け渡しを行うことができます。
型安全ではないですが、PVOID 型であれば抽象クラスを用意する必要がなくなるので、補足としてPVOID 型を使った実装を記載します。


プログラム構造

PVOID 型を活用することで、プログラム構造は以下のような形になりました。

元々、抽象クラスとAXIS_REF 用のクラスの2つのファンクションブロックを用意していましたが、1つにまとめることができています。
前回、OOP の継承・メソッド・プロパティ・抽象クラス・抽象メソッド あたりまで使っていたので、せっかくなので今回はインターフェイスの活用をしてみました。
型安全が気になる方は、このファンクションブロックを継承してAXIS_REF 等の型安全なデータ型で実装してください。(前編の方を参考に。。。)

I_Queue

(前回同様、画像と順番逆になっていてごめんなさい)
Queue の機能を持ったインターフェイスを定義しています。
PVOID 型にした時点で今後さまざまなQueue ファンクションブロックのバリエーションが増えていくか、と言われると微妙かもですが。。。

インターフェイスに関する説明は、ベッコフ様のテクニカルノートをご参照ください。
https://beckhoff-jp.github.io/TwinCATHowTo/plc_object_oriented_programming/object_interface.html

  1. メソッド

    メソッド名 機能
    init Queue の初期化メソッド。戻り値 → BOOL型
    isEmpty Queue のEmpty 状態を確認するメソッド。戻り値 → BOOL型
    isFull Queue のFull 状態を確認するメソッド。戻り値 → BOOL型
    enqueue Queue へデータを入れるメソッド。戻り値 → BOOL型。引数 → Data(PVOID型)
    dequeue Queue からデータを取り出すメソッド。戻り値 → PVOID型
  2. プロパティ

    プロパティ名 機能
    isErrorState Queue でエラーが発生していることを通知するプロパティ。Get のみ

FB_Queue

何かしらのポインタ型の変数をデータとして格納するQueue のファンクションブロックを定義しています。こちらで上で定義したインターフェイス"I_Queue" を実装しています。

  1. メソッド

    メソッド名 機能
    init head と tail、Error状態、Queueの中身を0 に初期化します。
    isEmpty Empty 状態なら戻り値にTRUEが入ります。
    isFull Full 状態なら戻り値にTRUEが入ります。
    enqueue Queue へポインタ型のデータをenqueue します。データ格納完了すると、戻り値にTRUEが入ります。
    dequeue Queue からポインタ型のデータをdequeue します。戻り値にdequeue したポインタ型のデータが入ります。
  2. プロパティ

    プロパティ名 機能
    isErrorState Queue でエラーが発生するとTRUE が入ります。Get のみ

プログラムの中身については、前編に貼ったものとほとんど変わりません。気になる方は以下のGithub をご参照ください。
https://github.com/g-fredec/TwinCAT_Queue.git


サイクル動作を実装するために

この記事はQueue システムを使ったサイクル動作を実装したかったので書いていますが、Queue だけでは軸は動いてくれません。
Queue を使ってデータを上手いこと管理してくれて、Queue から取り出したデータ(今回の場合は軸構造体) に対して何らかの処理をするようなオブジェクトを用意します。

これ以降はこのオブジェクトのことを"Queue Processing Unit (略してQPU)" と呼んでいくことにします。
(訳すとキュー処理装置になりますね。あまり深い理由は無くて、気に入ったからQPU と呼んでいます。量子コンピュータ(Quantum Processing Unit)のことではありません。)


Queue Processing Unit

QPU の機能整理

まずはポンチ絵でQPU の機能を整理していきます。

今回は、何かしらの加工機をイメージして抽象化を実施しました。
QPU には3つの機能『Pull → Process → Push』 を持たせており、これは加工機で言うところの『搬入 → 加工 → 搬出』と対応付けていただくと想像しやすいかと思います。

それぞれの機能の中に1つずつQueue を用意しており、各機能はQueue にデータが入ってきたら処理を実行します。
処理の流れは以下の通りです。

  1. Pull 機能実行
    1. Previous Queue にデータが入ってくるのを待つ (= Empty チェック)
    2. 入ってきたデータをdequeue
    3. Internal Queue にenqueue してデータ格納しておく
    4. 該当データに対して、MoveAbsolute で加工点への搬入動作を行う。
    5. PullCheck する場合、位置決め完了を確認する。(後述します)
  2. Process 機能実行
    1. ユーザ定義の処理を実行する (今回の例では、加工点を上下させています)
  3. Push 機能実行
    1. Internal Queue からデータをdequeue
    2. 該当データに対して、MoveAbsolute で加工点からの搬出動作を行う。
    3. PushCheck する場合、位置決め完了を確認する。(後述します)
    4. Next Queue にenqueue してデータを受け渡す

(今回はPull → Process → Push は順次実行として実装したので、並列処理は成されないですが、QPU の組み方次第で1つのQPU の中でも並列処理が実現できると思います。1つのQPU 内での並列処理への拡張のため、わざわざInternal Queue なるものを用意してenqueue, dequeue しています。)

QPU の連結

今回は、上記のQPU を複数連結させて、設備のサイクル動作を実現しようと思います。
イメージは下のような形で、前のQPU のNext Queue = 次のQPU のPrevious Queue とすることで、QPU 間のデータの受け渡しを実現できます。

かつ、一連の連結の最後のQPU のNext Queue = 最初のQPU のPrevious Queue とすることで、データの受け渡しをループ状にすることができます。
(分かりづらくて申し訳ないですが、下の図では3つのQPU を使ってループ状のシステムを構築しています。後述する、今回の例でも同じような構成になっています。)

更に、1つのQPU から複数のQPU に対して条件分岐したり、合流したりも可能なシステムにしています。
加工点内のOK/NG 判定により次の処理を切り替えるのに使ったり、複数搬送機から1つの搬送機への合流等での使用を想定しています。
(今回はそこまでやっていないですが…すぐに拡張はできると思います!)

QPU の実装

今回は以下のようにプログラム構成をしました。

1つ目の"仮想軸でサイクル動作をする"例では、搬送機としてただの仮想軸を設定しているので、Pull, Push ではMoveAbsolute ファンクションを使用しています。(XTS にした場合は、MoveAbsoluteCA にする必要があるので注意してください。拡張については後述します。)
また、Process の内容の個数分、抽象メソッドであるprocess メソッドを実装した具象クラスを2つ用意しています。
(今回は、加工点を上下させるProcess と搬送リターン用の何もしないProcess を用意しています。)

FB_QueueProcessingUnit

QPU の抽象クラスを定義しています。Queue の設定やPull, Push の指令位置の設定を行うメソッドを定義し、run メソッドを実行することでQPU として動かすことができるようにしています。

  1. メソッド

    メソッド名 機能
    configure QPU の設定を行うメソッドです。Previous Queue, Next Queue, PullPosition, PushPosition, PullCheck, PushCheck の引数でQPU に設定します。(引数の詳細は後述)
    init 初期化処理です。今回は特になかったので何も書いてませんが、必要に応じて子クラスでオーバーライドしてください。
    process 【抽象メソッド】 子クラスでProcess の中身を実装します。
    run QPU のPull -> Process -> Push の処理を実行するメソッドです。今回は順次処理で簡単に書いていますが、必要に応じて子クラスでオーバーライドしてください。
  2. プロパティ

    プロパティ名 機能
    State run メソッド内の状態をユーザに通知します。現時点では使っていませんが、デバッグ目的等でご活用ください。(将来的にレポートシステムに渡すために用意しています)
  3. 設定項目の詳細
    configure メソッドで設定する各種項目について、説明します。

    1. Previous Queue
      Pull 処理で使用するQueue を設定します。データ型をインターフェイス"I_Queue" としているので、前述のFB_Queue 以外でも同じインターフェイスを実装しているファンクションブロックなら何でも入ります。

    2. Next Queue
      Push 処理で使用するQueue を設定します。Previous Queue と同じく、例えばFB_Queue を拡張したファンクションブロックを入れることも想定しています。

    3. PullPosition
      Pull した際の指令位置を格納します。データ型としてユーザ定義構造体である"ST_PositionData" という型を用意しています。(Position, Velocity 別々に入れても良いですが、まとめておきたかったので。将来加減速度も書き込みたくなるかもですし)

    4. PushPosition
      Push した際の指令位置を格納します。PullPosition と同じく、ST_PositionData 型で定義しています。

    5. PullCheck
      Pull 処理の挙動を設定しています。Pull のMoveAbsolute を実行した後、位置決め完了するまでProcess 処理に状態遷移しないか、位置決め完了を待たずにProcess 処理に状態遷移するかをQPU に設定します。
      データ型はBOOL型で、
      TRUE → 位置決め確認を行ってから、状態遷移する。
      FALSE → 位置決め確認は行わず、動作開始のみを確認して状態遷移する
      としています。
      ユースケースとしては、加工点の動作開始が加工点直下に搬送機が止まった時のみであればPullCheck する、ながら加工で搬送機の動作中に加工を開始するのであればPullCheck しないようなイメージです。

    6. PushCheck
      PullCheck と同じです。
      ユースケースとしては、加工点に搬入する前に何らかの理由で停止する必要があるならPushCheck する、何も考えず次のQPU にPull されても問題なければPushCheck しないようなイメージです。
      後ほど書くXTS では、システム側で干渉回避機能を持っているため、基本的にはFALSE にしています。

FB_MoveAbsProcess

抽象クラスであるFB_QueueProcessUnit を実装している具象クラスです。
具象クラスにはProcess の中身だけを記述すれば良いため、このクラスでは加工点を上下動作させるProcess を定義しています。

  1. メソッド

    メソッド名 機能
    process 抽象メソッドを実装してProcess 処理を記述しています。今回は加工点を下降 → 2秒待機 → 上昇 という動作をするようにしています。
    setParameter Process 処理の中で使用する変数の設定を行うメソッドです。今回は加工点として動かす軸の軸構造体、各指令位置を設定しています。
  2. プロパティ

    プロパティ名 機能
    inState process メソッド内の状態をユーザに通知します。State プロパティと同じく、現時点では使っていませんが、デバッグ目的等でご活用ください。

FB_ReturnProcess

こちらも具象クラスです。このクラスでは搬送機を初期位置に戻したかったので、Process としては何の処理もしないクラスになっています。(Pull, Push だけで搬送できるので)

  1. メソッド

    メソッド名 機能
    process 抽象メソッドを実装してProcess 処理を記述しています。今回は何もしていません。
  2. プロパティ

    プロパティ名 機能
    inState process メソッド内の状態をユーザに通知します。Process が何もしていないので、こちらもずっとINITIALIZE の状態を返しています。

プログラムの中身も記載するととても長くなることを前編で学んだので、割愛します。
Github をご参照ください。
https://github.com/g-fredec/TwinCAT_Queue.git


仮想軸でサイクル動作を実装

ここからはQPU を使ってサイクル動作を実装します。

システムイメージ

今回は、下のような搬送機をイメージしてサイクル動作を組むことにします。

Axis1~4 を並列の搬送機に見立て、Axis5,6 を加工点と見立てています。

搬送機のサイクル動作

Axis1~4 は、以下のようなサイクル動作を実行します。

  1. Axis5 へ搬入 → 加工 → 搬出
  2. Axis6 へ搬入 → 加工 → 搬出
  3. 初期位置へ戻る

ただし、Axis5,6 には1つの搬送機しか入れることができないという制約があり、ここをQPU を使って簡単に実装しています。
(普段であれば、搬送機と加工点間のインターロックだけでなく、搬送機同士の相互インターロックも必要になるのですが、このロジックをQueue が肩代わりしてくれています。)

加工点のサイクル動作

Axis5, 6 は、以下のようなサイクル動作を実行します。

  1. 搬送機が搬入されたら下降 (画像上の各搬送機の位置まで下降します)
  2. 2秒待機(ホットプレスでもしてると思ってください)
  3. 初期位置へ戻る

今回はProcess の簡素化のため、搬送機は1から順番に搬入される、という制約を付けています。ただ、ちゃんと組めば搬送機の順番を気にせずに処理をすることができると思います。
(また、今回は1軸のみの動作なので簡単ですが、複数軸で加工を行う…とかになるとProcess の中身の抽象化も要るかもしれないですね。備忘録として残しておきます)

プログラム実装

上記サイクル動作実装のため、以図のように3つのQueue と3つのQPU を用意しています。

※ Queue の変数名が0,1,2 と連番にしてますが、本来であれば良い記法ではありません!(反省)

システムのInitialize 処理として、最初のQueue へ搬送機をenqueue する処理とQPU の設定処理をします。

指令位置はグローバル変数に"ST_PositionData" データ型の2次元配列を用意しており、TwinCAT のレシピ機能を使って値の書き込みをしています。
レシピ機能については、ベッコフさんのテクニカルノートをご参照ください。
https://beckhoff-jp.github.io/TwinCATHowTo/recipe/index.html

配列の要素は、それぞれ以下のポジションに対応しています。
[1,1] -> 搬送機 初期位置
[1,3] -> 搬送機 Axis5 の直下
[1,4] -> 搬送機 Axis6 の手前
[1,5] -> 搬送機 Axis6 の直下
[1,6] -> 搬送機 EndPoint
[5,1] -> 加工点 初期位置
[5,2] -> 加工点 Axis1 の加工位置
[5,3] -> 加工点 Axis2 の加工位置
[5,4] -> 加工点 Axis3 の加工位置
[5,5] -> 加工点 Axis4 の加工位置

また、今回の例では、搬送機は
初期位置、Axis5 の直下、Axis6 の直下、EndPoint
で停止することとするので、PullCheck, PushCheck をそのように設定しています。
(Axi5 のPull, Axis6 のPull, Return のPull とPush)

最後に、起動Bit によって各QPU のrun メソッドがCall されるようにしてプログラム実装は終了です。

サイクル動作

実際に動かしてみましょう。今回はTwinCAT のVisualization で動きを確認しています。

(もしかして、私のfps 低すぎ…!? Snipping Tool で録画してgif に変換したらこんなんになっちゃいました。(Visualization 上でもカクカクしてはいたんですが) 心の目で見ていただけると。。。)

Google Drive にも保存してあるので、こちらでご参照いただくのが良いかもしれません。
https://drive.google.com/file/d/1EUmf8tWVSsJsEw8CKlouxyzatXJeaSzA/view?usp=sharing

なにはともあれ、いい感じに動いてますね!
ミソはどの搬送機をどのタイミングでどこに動かす…という指示はしていない点と、加工点の直前で止まる必要がある or ない をQPU が自分で判断してやってくれている点ですかね。
実際の設備設計で苦労する部分をシステム化できているように感じました。(自己満足)


オーバルリニアXTS でサイクル動作を実装

ここからは上記システムをオーバルリニア XTSを使ったものに拡張していきます。

そもそもXTS って何?という方は、ベッコフ様のTwinCAT Howto にまとめられていますので、こちらをご参照ください。
https://sites.google.com/site/twincathowto/xts/xts概要/xtsの概要説明

また、XTS を動かすための各種設定や各種プログラムについては、今回省略しています。その辺りもTwinCAT Howto に書いてあるのでこちらをご参照いただくか、Github から実際のプログラムを覗いてもらえたらと思います。(機会があれば記事にしますが…)

TwinCAT 3.1 4026 ユーザモードランタイム機能でXTS を動かす

最初に環境設定からです。
"TwinCAT 3.1 4026 のUsermode Runtime で仮想軸を動かす" の記事で、ユーザモードランタイムで仮想軸を動かすために、"TwinCAT.XARUM.NCPTP" をインストールしたのですが、XTS を動かすためにも、同じようなパッケージ(TwinCAT.XARUM.AdvancedMotion)のインストールが必要です。
https://zenn.dev/fredec/articles/twincat-4026userrunmotion

前回同様、CUI でインストールを行いました。

  • ターミナルを管理者で起動する
  • tcpkg コマンドでインストールコマンドの実行
    • 起動したターミナル上に以下のコマンドを入力します。Installed と出たら完了です。
      tcpkg install TwinCAT.XARUM.AdvancedMotion
      

ちなみに、私の環境はHyper-V の仮想マシン上でTwinCAT を動かしていますが、メモリ容量的にXTS のループ1つのシミュレーションが限界のようでした。
(ログインしようとしたときにメモリ足りないよエラーが出ます! 仮想マシンへのメモリ割付増やせば行けそうですが…今回は1ループだけなのでヨシとしました)

QPU の拡張

XTS を動かすために、QPU を拡張します。

といっても、拡張した内容は下の2つだけです。

  1. 抽象クラス FB_QueueProcessUnit_CA をFB_QueueProcessUnit を継承して作成。
    QPU の中で搬送機を動かしていたファンクションブロックがMoveAbsolute だったので、XTS を動かすためにMoveAbsoluteCA に変更した抽象クラスを用意しました。
    (オーバーロードができれば上手いことまとめられたと思いますが、今回は抽象クラスを分けることにしました)
  2. FB_QueueProcessUnit_CA を継承した子クラス FB_MoveAbsProcess_CA, FB_ReturnProcess_CA を作成。
    中身は仮想軸の項のものとほとんど同じです。継承元の抽象クラスがFB_QueueProcessUnit_CA に切り替わっているのと、setParameter に変更が加えられています。
    (XTS にしたことで加工点のポジション数が減っているので)

システムイメージ

今回は、下のようなシステムでサイクル動作を組むことにします。

仮想軸の項で使っていたAxis1~4 の搬送機をXTS に置き換えただけです。
(そのため、Visualization はそのまま使って、その下にXTS Viewer を表示させているというかっこ悪い感じになってます。)

XTS のサイクル動作

XTS は、以下のようなサイクル動作を実行します。

  1. Axis5 へ搬入 → 加工 → 搬出
  2. Axis6 へ搬入 → 加工 → 搬出
  3. 初期位置へ戻る

ここは仮想軸の項の置き換えになっています。

加工点のサイクル動作

Axis5, 6 は、仮想軸の項と同じサイクル動作を実行します。

  1. 搬送機が搬入されたら下降
  2. 2秒待機
  3. 初期位置へ戻る

プログラム実装

システム構成は仮想軸のシステムと全く同じで、3つのQueue と3つのQPU で構成します。

システムのInitialize 処理もほとんど同じです。

異なる点としては、

  1. オーバルなので、EndPoint = 初期位置としています。それに伴って、Return Process後、少し加工点に近付けるように変更しました。
    (ただ、PushCheck はしない設定にしているので、Axis5 が空いていればすぐに搬入されます)
  2. Axis5,6 の指令位置の数が減りました。
    (元々、Axis1~4 それぞれのポジションが用意されていましたが、搬送機がオーバルなので1点のみになりました)

サイクル動作

実際に動かしてみましょう。
先ほどはgif を貼っていましたが、3MB までの制限があり、どう頑張っても貼れなかったのでGoogle Drive のショートカットだけを貼ることにしました。
https://drive.google.com/file/d/1U0IXcMQDqrFk306lJ4K-SQSyKXp1iOZS/view?usp=sharing
(もしリンク切れていたら教えてください…)

イメージ通り動いていますね!
普段であれば、FOR文とCASE文、IF文のネストを作ってサイクル動作をつくることが多いと思いますが、その辺りの処理をQueue とQPU にやってもらって、簡単にサイクル動作が実現できているかと思います。

おわりに

かなりの長文になってしまいましたが、やりたかったQueue システムを使ったサイクル動作を実装はできたのかなと思っています。
(本当はもっと抽象化して…とかやりたかったんですが、そこはまた別の機会にしようと思ってます)

個人的には普段よくやるような、FOR 文・CASE 文・IF 文を多用する書き方よりもまとまっている気がしている…ですが、好みの問題かもしれません。

QPU の具象クラスをProcess の数だけ増やす必要はありますが、生産設備という目線に立つと、この部分の数が無限になるようなことはないんじゃないかと思っていて、人手でも設計保守管理ができる気はしています。(Process も抽象化したりすればもっと簡単になると思いますし…)

Github で公開していますので、もし一部分でも刺さる部分があれば、ぜひご活用ください。

Discussion