【前編】TwinCAT でQueue システムを使ったサイクル動作を実装する
はじめに
ベッコフオートメーション社のTwinCAT 上で、Queue システムを使ったサイクル動作を実装してみたので、備忘録として記載します。
そもそも話、ベッコフオートメーション社のオーバルリニア XTS を動かそうとしたときに、可動子の管理が大変だなーと思ってアイディアを探していました。
スーパーのレジに並んでいる時に思いついたQueue システムを使って、実装を試みています。
今回はただの仮想軸を使った実装と、XTS を使った実装をそれぞれご紹介します。(中身はそんなに変わりません)
実際使い物になるかはハテナですが、何かの参考になれば幸いです。
Queue とは
言葉の定義
Queue (キュー) とは、基本的なデータ構造の一つで、データを先入れ先出しのリスト構造で保持するものである。 (Wikipedia より引用)
つまり、何らかのデータを保持できるリストで、かつデータを入れた順番にしか取り出せないという制約付きのリスト、と簡単に解釈しています。
(他にも色々な記事が出回っているので、ぜひ参考にしてみてください。)

この制約があるが故に、データハンドリングプロセスを単純化できるというわけですね。今回はこの特性を活用していきます。
Queue でできること
Enqueue (エンキュー)
Queue にデータを入れること。

今回は、軸構造体をデータとしてEnqueue して管理します。
Dequeue (デキュー)
Queue からデータを取り出すこと。

今回は、Enqueue した軸構造体を取り出して、どの軸を動かすかをユーザが判断せずにサイクル動作できるようにしています。
Queue ができることとしては、この2つだけです。(たぶん)
TwinCAT の実装では、この2つに加えてユーザを助けるいくつかの機能を持たせています。(一般的なQueue プログラムにもよく実装されていますね)
Full Check
あと1回Enqueue をしてデータを格納すると、Queue が満杯になることをユーザに伝える機能です。

データとして軸構造体を扱う上では満杯になることはないですが(リストを最大数より多く用意すれば良いので)、今後別のデータを扱う際に、Enqueue 時にデータを入れられないという事態を防ぎます。
また、エラーの予防にFull の状態でEnqueue しようとすると、Enqueue は実行せず、後述のError を発生させるようにします。
Empty Check
Queue に何のデータも入っていないことをユーザに伝える機能です。

Queue のデータを使って何かの処理をするプログラムが、Null をDequeue してError になることを防ぎます。
また、エラーの予防にEmpty の状態でDequeue しようとすると、後述のError を発生させるようにします。
Error Handling
Queue でエラーが発生していることをユーザに伝える機能です。
基本的には、運用時に使うものではないと想定していますが、設計時や保全時に活用できると思っています。
Queue の実装方法
Queue をただのリストで用意しても良いのですが、その場合、以下のような問題が生じます。

(分かりにくい図で申し訳ないですが…)
データをDequeue するたびに、次にDequeue されるデータのIndex が右から左へずれてきています。(一番右 → 右から2番目 → 右から3番目 ...)
これでは、いつか用意していたリスト長を使い切ってしまい、Enqueue できなくなってしまいます。(最後の図がまさにそれで、次のEnqueue で入れる場所がありません!)
Dequeue のたびにリストのシフトをすれば良いと思うかもしれませんが、システムによっては重すぎて泣いちゃう可能性があります。
また、可変長のリストを使えば良いと思うかもしれませんが、使い終わったIndex が無駄になるので長期運用には向きません。
そこで、Queue のリストとして広く用いられているのは、"リングバッファ"と呼ばれるリストです。
リングバッファとは
リングバッファとは、リング状に配置されたバッファである。
一時的にデータを貯めておくバッファ領域のうち、終端と先端が論理的に連結され、循環的に利用されるようになっている。 (Wikipedia より引用)
つまり、何らかのデータを保持する固定長のリストであることに変わりはないが、リストの終端と先端を連結させて循環リストとして考えるというわけですね。(もちろん物理的に連結させることはできないので、論理的に連結させています)

今回もリストをリングバッファとして考え、Queue システムを実装しています。
head と tail について
Queue の中のデータ格納状態を表す変数として、"head" と"tail" というものを扱うため、紹介しておきます。(Queue システムを扱う上では知らなくても良い概念ですが、一応…)
head
現在のQueue の中で最初に追加されたデータのIndex を表します。
つまり、次のDequeue で取り出されるデータのIndex を指しています。
tail
現在のQueue の中で最後に追加されたデータの次のIndex を表します。
つまり、次のEnqueue でデータが格納されるIndex を指しています。

先ほど使っていた画像にhead, tail の情報を書き加えると上のようになります。
head と tail を扱えると嬉しいこと
以下のような嬉しさがあります。
- Enqueue する時にリストを見る必要がない
- tail のindex にデータを格納するだけ。
(正確には、次のEnqueue のためにtail のインクリメント処理を行いますが) - tail が無い場合: リスト内を検索して、順方向に充 → 空 となっているIndex Search が必要
- tail のindex にデータを格納するだけ。
- Dequeue する時にリストを見る必要がない
- head のindex のデータを取り出すだけ。
(こちらも正確には、次のDequeue のためにhead をインクリメント処理をします) - head が無い場合: リスト内を検索して、順方向に空 → 充 となっているIndex Search が必要
- head のindex のデータを取り出すだけ。
- Full 状態、Empty 状態がリストを見なくても分かる
- Full状態: head と(tail+1) MOD (Queueのサイズ) が同じ値
(head = (tail + 1) MOD MaxQueueSize) - Empty状態: head とtail が同じ値
(head = tail)
- Full状態: head と(tail+1) MOD (Queueのサイズ) が同じ値
Full 状態の補足
機能のFull Check のところでも書きましたが、今回はQueue のFull 状態を
"あと1回Enqueue をしてデータを格納すると、Queue が満杯になる状態"
としています。
これは、上記のhead, tail を扱ってQueue 管理をする際に、感覚的にFull 状態を"Queue が満杯になった状態"としてしまうと、"head とtail が同じ値の時、Full でもあり、Empty でもある…"という事態に陥ってしまいます。
- Full状態: head とtail が同じ値
(head = tail) - Empty状態: head とtail が同じ値
(head = tail)
Full のみhead とtail を使わずに確認しようとすると、リストの全Index が埋まっていることを確認するしかないので、あまりにも悲しい事態になってしまいます。
なので、このような定義にしている…と私は解釈しました。(違かったらごめんなさい)
TwinCAT でQueue を実装
Queue の説明が長くなってしまいましたが、ここからは実際にTwinCAT 上でQueue を実装していきます。
プログラム構造
TwinCAT にはIEC61131-3 に第3版 で追加された、オブジェクト指向の機能が備えられています。
詳細は以下のInfoSys やテクニカルノートをご参照ください。(ベッコフさんありがとうございます。)
今回はこちらの機能を使って実装を進めていきます。
さっそく、今回のプログラム構造です。以下のようになっています。

2つのファンクションブロックで構成されます。
(OOP 的にはクラスと呼びますが、TwinCAT ではファンクションブロックを拡張してクラスの機能を追加しているので、ここではファンクションブロックと呼びます。抽象クラスは抽象クラスと呼びますが…)
FB_QueueAbstract
(画像と順番逆になっていてごめんなさい)
Queue ファンクションブロックの抽象クラスを定義しています。
- メソッド
| メソッド名 | 機能 |
|---|---|
| init | 【抽象メソッド】子クラスで初期化処理を実装します。 |
| isEmpty | Queue がEmpty 状態か確認するメソッドです。Empty 状態なら返り値にTRUEが入ります。 |
| isFull | Queue がFull 状態か確認するメソッドです。Full 状態なら返り値にTRUEが入ります。 |
- プロパティ
無し
-
【ご参考】内部変数

定数として、QueueのMaxSize を定義しています。
また、内部変数で上にて説明したhead, tail を用意しています。 -
【ご参考】メソッドの中身


とても簡単なLogic になっています。
FB_AxisQueue
軸構造体 AXIS_REF をデータとして格納するQueue のファンクションブロックを定義しています。
- メソッド
| メソッド名 | 機能 |
|---|---|
| init | 抽象メソッドを実装しています。head と tail、Error状態、Queueの中身を初期化します。 |
| enqueue | Queue へ引数のAXIS_REF をenqueue するメソッドです。データ格納完了すると、返り値にTRUEが入ります。 |
| dequeue | Queue から先頭のAXIS_REF をdequeue するメソッドです。返り値にdequeue したAXIS_REF が入ります。 |
- プロパティ
| プロパティ名 | 機能 |
|---|---|
| isErrorState | Queue でエラーが発生していることをユーザへ知らせます。Get のみ |
- 【ご参考】内部変数

Queue 本体を内部変数として定義しています。(これも抽象クラスに置けなかったのはデータ型問題…)
また、isErrorState で使うError の内部変数を用意してenqueue, dequeue 時に値を書き換えるようにしています。
Queue で管理するデータ型はAXIS_REF のPOINTER としています。REFERENCE を使っても良かったのですが、個人的な好みでPOINTER にしました。
POINTER? REFERENCE? な方は下記テクニカルノートをご参照ください。(ベッコフさんいつもありがとうございます。)
- 【ご参考】メソッドの中身



特筆する内容は無いですが、head とtail のインクリメントの際にリングバッファであることに注意して実装しています。
その他、エラー発生時には早期リターンでQueue への処理が発生しないようにしています。
(個人的に、if文のネスト構造苦手でして…)
Github へのリンク
下記で公開していますので、良かったらご参照・ご活用ください。
TwinCAT でQueue を使ってみる
FB_AxisQueue を"AxisQueue" という変数で宣言して、4軸のAXIS_REF(配列: stAxis) をenqueue, dequeue してみます。
また、dequeue した際に"AxisPointer" というAXIS_REF のPOINTER 変数へ書き込みを行い、想定通りの軸がdequeue されているか確認します。
(Github からプログラムを落とされた方はMAIN POU の中にトライ用のプログラムを書いていますので、ご活用ください。ここでは実施していないですが、エラー状態に遷移するかも確認できるようにしてあります。Full、Empty メソッドも是非ご確認ください。)
enqueue してみる
enqueue の処理は以下のように書いています。

4軸あるためFOR文で回していますが、xEnqueue の任意のIndex をTRUE にすることで該当のIndex の軸をAxisQueue にenqueue します。
早速 Axis1 をenqueue してみます。

AxisQueue の_Queue[0] にAxis1 のPointer が格納されました!tail もインクリメントされています。

続いて、Axis2,3,4 もenqueue します。同時にTRUE を書き込むと、FOR文により順番にenqueue されます。

それぞれ違うPOINTER が格納され、tail が4にインクリメントされていますね!
dequeue してみる
dequeue の処理は以下のように書いています。

xDequeue をTRUE にすることで、先頭のAXIS_REF のPOINTER を"AxisPointer" へ格納します。
早速 1回だけdequeue してみます。

AxisQueue の_Queue[0] に格納されていたAxis1 のPointer → 16#000001BFA4555378 がAxisPointer へ格納されました!head もインクリメントされています。

残りの軸もdequeue してみましょう。xDequeue を3回 TRUE にします。

Axis4 のPointer → 16#000001BFA45560C8 がAxisPointer へ格納されている状態になっています。
head もインクリメントされ、tail と同じ値になっていますね!(= Empty 状態になっています)
おわりに
長々と文章を書いてしまいましたが、Queue システムの説明とTwinCAT で実装したQueue の確認で終わってしまいました。
後編という形で実際に仮想軸をQueue を使って動かすのと、元々やりたかったXTS の管理システムの実装を後々公開しようと思っています。
Queue システムは概念的には簡単なものになっていて、意外と刺さるところが多いイメージがあります。
軸の動作以外にも活用できるところがあれば、ぜひご活用ください。
Discussion