今回はFunction Blockについて説明します。Function BlockになりますとまずObject Oriented ProgrammingとProcedural Programmingの2種類があります。Function Blockを使うことによってあなたのプログラムがもっとメンテナンスしやすくなり、質も上がったり、もっとクリアなCodeになることが可能です。
Function Blockの話になると何時間も喋れますので、とりあえず1,2,3のようにPostを分けて行きたいと思います。
Oriented ProgrammingとProcedural Programming
その前に、まずOriented ProgrammingとProcedural Programmingの違いを説明したいと思います。Procedural Programmingはちょっと巻物のような感覚で、Functionは自分のLocal Dataがあり、そしてGlobalアクセスするところが多いです。
一方、Oriented ProgrammingはObjectsが自分の”データ”をもっていて、動作などするときはMethodsを経由します。そしてObject間のデータやりとりは”Messages”でやりとります。
つまり、もの自体をObjectとして定義します。
他のObjectとやりとりするときはMessages-INPUT/OUTPUT/INOUTを使います。
他のプログラムからアクセスできないようにLocal変数で保護かけます。
Function Block内のプログラム実行はMethodから経由します。
Objectってなに?
そもそも、Objectってなんですか?WIKIなどはやまほどの”解釈”もありますが、自分は世の中にあるものはすべて”Object”だと思ってて、例えば猫、机、ネズミなどです。
もちろん、Industrialの世界ではサボモータ・シリンダー・カメラなどだと喩えます。
そしてすべてのObjectにも自分のState(データ)とBehavior(Methods)があります。
例えば、カメラであれば、正常・Busy・待機・エーラ・写真などの”State”があり、
リセット・撮影・Flash・画素削除などの”Behavior”があります。
IEC61131-1ではFunction BlockでこのOOPコンセプトを実現します。
Functionの中にMethodやDataの定義できます。
このように、FB_MotorsというFBがあり、中にMotorの電源入れ・速度と位置コマンド・アラームリセットのMethodsあります。
そしてMotor自分にもFaultCode、WarningCode、現在速度や位置のデータがあります。
Instance
このFB_MotorsのObject生成するには、”Instance”を作成する必要がありますね。
Instanceは自分の名前があります。例えば下図のようにMotor1,Motor2など宣言すればOKです。
そしてMotor1に速度コマンド発行したいのであれば、
Motor1.SetSpeed()
もしMotor2のアラームリセットしたいなら、
Motor1.AlarmReset()
Encapsulation
OOPには”Encapsulation”というコンセプトがあります。
たとえになりますと、下図のようにどんなMethodsが外から実行できるか、どんなDataが書き込み禁止なのか、それらの構成することできます。
でもなぜ一部のDataやMethodsをGlobal領域から隠す必要があるでしょうか。
それは各プログラムや変数は他の部分に関するの影響を最低限に抑えるというメリットがあります。プログラマーは変更すべき部分だけに集中し、あっちこっちに変数がプログラムの中に走り回ることなくなります。パスタコードは誰でも触りたくないでしょう?
そして実際プログラム内でFBを宣言するときは、このようにすればOKです。Motor1とMotor2は別々のObjectとしてCPU内に定義されていますので、メモリの被りなどの心配もいりません。(悪い意味ではありませんが、国産のPLCはよくDM領域を100つつ開けるとかの話がありますね。)
VAR Motor1:FB_Motors; Motor2:FB_Motors; END_VAR |
Method
次はMethodについて話しします。MethodはObjectの中にどんな動作・制御があるのかを定義するところです。以下はMethodの定義方法です。
METHOD PUBLIC SetPosition
VAR_INPUT
rPosition:real;
END_VAR
そして、先定義したFB_Motors ObjectのMotor1,Motor2に位置決めしたいなら、
Motor1.SetPosition(300);
Motor2.SetPosition(20);
注意したいのはMotor1.SetPosition(300)はできるのは位置決めすること”だけ”です。
それはOOPプログラミングの”Abstraction”です。Objectの中に様々なMethodが実際に実装され、何ラインのCodeもかかられたけど、私たちはそれを知る必要がないです。頭の覚えて置きたいのは位置決めしたいときはそのSetPosition()を呼び出すだけです。
Inheritance(承継)
これもOOPの中に、すごく大事なコンセプトです。Inheritanceというのは、下図のようにFB_MotorsのDataとMethodsをFB_Motors_SiemensやFB_Motors_Mels承継されていますね。承継されたFB_MotorsがParent Function Blocks(親FB)そして承継したFBをChild Function Block(子FB)と言います。
それでもまだうん?と思う人いるかもしれません。例えば、みんなさんよく使うRemote-IOに例えましょう。Remote-IOは色なメーカーも作っており、DI/DQ/AI/AQなどたくさんの種類があります。例えば、自分がいまProfinet用のRemote-IOモジュールを作成しました。
FB_PNDevicesがいまRemote-IOの状態を表すState:INTのデータが持っていて、そしていまの状態を判断するInError(),InWarning()とInNormalがあります。
そして、Remote-IOに16点の入力や4点のアナログ入力があります。その2つのFunction BlockがFB_PBDevicesのMethodsやDataを承継しながら、自分だけのMethodsがもっています。たとえばFB_PNDevices_4AIsなら長さ4のWord配列で、なかにRemote-ioの生値が格納してるとか。
Access specifies
Methodが4つのアクセス設定があります。
Public
どこでもアクセスできるです。つまりFunction Block内部自体だけではなく、外でもAccessできるようにイメージです。
Motor1.Method1()
Motor2.Method2()
Private
AcessできるのはFunction Block内部だけになります。
Motor1.Method1()
Motor2.Method2()
はできません。
Protected
Methodは親FBと子FBにもアクセスできます。
Internal
ライブラリ関連していますので、自分にもよくわかりません。
Properties
このコンセプトはいままでの国産PLCはほぼないだと思います。Function Blockの”中”にある内部変数は基本的にか勝手に読み書きすることができません。
この変数のアクセスするために:
”GET”、つまり読む
”SET”、つまり書き
の2種類があります。
もしPropertiesのなかに”GET”が消されたら、この変数は書きしかできません。
逆にPropertiesのなかに”SET”がなくなったら、この変数は読むしかできません。
Example
では話はここまで、簡単なFunction Blockを作ってみましょう。
まずPOUs>Add>POUします。
FB_Calという名前で、TypeはFunction Blockを選びましょう。
Implementation Languagesはなれてるものにしましょう。
まずはいらないのVAR_INPUTとVAR_OUTエリアを消します。
それはやらなくてもいいですが、Interfaceはこうしったほうがすっきりします。
次は内部変数iNumberを定義します。
Methodを追加します。
FB_Cal>右クリック>Add>Methodします。
Methodの名前あMethd_Addします。
Access specifierはPublicしましょう。
このMeth_Addが呼びされるたびにiNumberを+1にします。
Methodが終わったら、今度はPropertyを設定します。
FB_Cal>右クリック>Add>Propertyします。
NameはpiNumberします。
Return TypeはINTです。それはこのPropertyと通してiNumberを読み書きしたいです。
よし、作成しました。
GetのほうにはiNumberをReturn値として返しします。
SetのほうがもしpiNumber>0、つまりこのPropertyに値を与えるときに、0より大きなら、内部変数iNumberに入れます。
これでOK。注意するのはFB_Cal自体はなにもCode書いていません。
あ、最後はMAINにFBを呼び出します。
テスト用の変数も定義します。
最後はFB_CalをCallし、bsetがTrueでなければidという変数にPropertyのpiNumberから内部変数iNumberから転送します。bsetならivalの与えをPropertyのpiNumber経由で変数iNumberに書き込む。最後、pAddがTrueならMeth_Addを呼び出し+します。
Test
Idの値は常にc()の内部変数をpiNumberのGet Propertyを経由し読みます。
bsetをTrueにし、ival(現在値30)をpiNumberのSet Propertyを経由し書きます。
最後はbAddがTrueになると、内部変数iNumberが常に+の状態になります。
では、先Propertyにも説明しましたが、FBの変数を勝手に読み書きできません。例えば以下のCodeように、無理やり値を与えますと、Code自体がエラーになります。
コンパイルしたらiNumber is no input of “FB_CAL:つまりInputパラメータではないから、勝手にメモリを使う)になります。
以下は今回ProjectのLINKです:
https://github.com/soup01Threes/TwinCAT3/blob/main/TwinCAT%20Project_FunctionBlocks_1.tnzip