今回の記事はBeckhoff TwinCAT3のANY Typeについて説明します。ANY TYPEのコンセント・使用方法を理解することにより、可変メモリのプログラム、そしてFlexibleなプログラムを目指すのが第1 Stepではないかな?と思っています。
さ、始めましょう。
ANY?
Beckhoff TwinCATは関数、メソッド、ファンクションブロック(Build4026以降)を実装する場合、入力(VAR_INPUT)を汎用IEC日付型ANYまたはANY_<type>の変数として宣言できます。なので、呼び出しされたパラメータがData Typeによって異なる制御を実装できるようになりました。Runtime中にANYの構造体を介して、転送された値・Data Type・サイズを求めることが可能です。
実際、コンパイラは内部で入力変数の型を構造体に置き換え、値は直接転送なく、
代わりに実際の値へのポインタが転送されます。。したがって、パラメータのData Tyoeによってそれぞれ異なるデータ型を持つ制御ができます。
Internal data structure
TwinCATプログラムをコンパイルする時、ANYデータ型の入力変数は、内部で以下の構造体に置き換えられます。構造体の要素は、実行時に実際の呼び出しパラメータに割り当てられる。
TYPE AnyType : STRUCT // the type of the actual parameter typeclass : __SYSTEM.TYPE_CLASS ; // the pointer to the actual parameter pvalue : POINTER TO BYTE; // the size of the data, to which the pointer points diSize : DINT; END_STRUCT END_TYPE |
Variables | Data Type | Description |
typeclass | __SYSTEM.TYPE_CLASS | パラメータのData Type |
pvalue | POINTER TO BYTE | パラメータのPointer |
diSize | DINT | パラメータのメモリサイズ |
Example1
最初のExampleではANY Typeを使用し、異なるData Typeのパラメータを関数に渡し、そのパラメータのData TypeをUDINTの形でReturnします。
Program
FC_GetVarType
注目するのはmyAnyDataのData TypeはAnyです。
FUNCTION FC_GetVarType : UDINT VAR_INPUT myAnyData :ANY; END_VAR FC_GetVarType:=myAnyData.TypeClass; |
MAIN
Functionを何回も繰り返して呼び出して、異なる変数をパラメータとして渡します。
VAR wStringVar:STRING; StringVar:WSTRING; BoolVar:BOOL; ByteVar:BYTE; WordVar:WORD; DWordVar:DWORD; LWordVar:LWORD; USIntVar:USINT; IntVar:INT; RealVar:REAL; myReturnValues:ARRAY[0..99]OF UDINT; END_VAR myReturnValues[0]:=FC_GetVarType(myAnyData:=wStringVar); myReturnValues[1]:=FC_GetVarType(myAnyData:=StringVar); myReturnValues[2]:=FC_GetVarType(myAnyData:=BoolVar); myReturnValues[3]:=FC_GetVarType(myAnyData:=ByteVar); myReturnValues[4]:=FC_GetVarType(myAnyData:=WordVar); myReturnValues[5]:=FC_GetVarType(myAnyData:=DWordVar); myReturnValues[6]:=FC_GetVarType(myAnyData:=LWordVar); myReturnValues[7]:=FC_GetVarType(myAnyData:=USIntVar); myReturnValues[8]:=FC_GetVarType(myAnyData:=IntVar); myReturnValues[9]:=FC_GetVarType(myAnyData:=RealVar); |
Result
FC_GetVarTypeの内部をみてみましょう。myAnyDataの構造体では先程書いてた通り、TYPE_CLASS・diSize・pValueもあります。
Done!パラメータのData TypeによってReturn値が違います。
この数字の意味を調べるため、ReferencesからBase_InterfacesのLibraryをImportします。
TypeClassには各Data Typeを分別するためのENUMがあります。次のExampleはこのENUMを使ってみます。
Example2
Example2では先程紹介したTypeClassとCASE文を使用しパラメータのData Typeを文字列としてReturnします。
Program
FC_GetVarTpeAsString
新しいFunctionを作成し、パラメータはANY Typeします。
FUNCTION FC_GetVarTpeAsString : String VAR_INPUT myAnyData :ANY; END_VAR CASE UDINT_TO_INT(myAnyData.TypeClass) OF IBaseLibrary.TypeClass.TYPE_BOOL: FC_GetVarTpeAsString:=’Bool’; IBaseLibrary.TypeClass.TYPE_BYTE: FC_GetVarTpeAsString:=’Byte’; IBaseLibrary.TypeClass.TYPE_REAL: FC_GetVarTpeAsString:=’Real’; IBaseLibrary.TypeClass.TYPE_INT: FC_GetVarTpeAsString:=’Int’; IBaseLibrary.TypeClass.TYPE_WORD: FC_GetVarTpeAsString:=’Word’; IBaseLibrary.TypeClass.TYPE_DWORD: FC_GetVarTpeAsString:=’DWord’; IBaseLibrary.TypeClass.TYPE_LWORD: FC_GetVarTpeAsString:=’LWord’; IBaseLibrary.TypeClass.TYPE_STRING: FC_GetVarTpeAsString:=’String’; IBaseLibrary.TypeClass.TYPE_WSTRING: FC_GetVarTpeAsString:=’WString’; ELSE FC_GetVarTpeAsString:=’Unknown’; END_CASE |
MAIN
Example1とあまり変わりなく、呼び出しされた関数が違うだけです。
PROGRAM MAIN VAR wStringVar:STRING; StringVar:WSTRING; BoolVar:BOOL; ByteVar:BYTE; WordVar:WORD; DWordVar:DWORD; LWordVar:LWORD; USIntVar:USINT; IntVar:INT; RealVar:REAL; myReturnValues:ARRAY[0..99]OF UDINT; myReturnStrings:ARRAY[0..99]OF STRING; END_VAR myReturnStrings[0]:=FC_GetVarTpeAsString(myAnyData:=BoolVar); myReturnStrings[1]:=FC_GetVarTpeAsString(myAnyData:=StringVar); myReturnStrings[2]:=FC_GetVarTpeAsString(myAnyData:=BoolVar); myReturnStrings[3]:=FC_GetVarTpeAsString(myAnyData:=ByteVar); myReturnStrings[4]:=FC_GetVarTpeAsString(myAnyData:=WordVar); myReturnStrings[5]:=FC_GetVarTpeAsString(myAnyData:=DWordVar); myReturnStrings[6]:=FC_GetVarTpeAsString(myAnyData:=LWordVar); myReturnStrings[7]:=FC_GetVarTpeAsString(myAnyData:=USIntVar); myReturnStrings[8]:=FC_GetVarTpeAsString(myAnyData:=IntVar); myReturnStrings[9]:=FC_GetVarTpeAsString(myAnyData:=RealVar); |
Result
Done!Data Typeに沿って文字列がReturnされました。
Example3
ANY TYPEは実際すごく大きな枠で、ANY_BIT・ANY_DATE・ANY_NUM・ANY_STRINGに分かれ、ANY_NUMは更にANY_REALとANY_INTがあります。
例えばANYの代わりにANY_NUMを宣言すると、Userが関数などに渡せるパラメータを実数・整数に制限できます。
Program
実際にプログラムを作ってみましょう。
FC_GetVarType_ANYNUM
関数のプログラムより、myAnyDataはANY_NUMとして定義されています。つまりこの関数は関数に渡せるパラメータは実数・整数だけになります。
FUNCTION FC_GetVarType_ANYNUM : bool VAR_INPUT myAnyData :ANY_NUM; END_VAR //Do something |
MAIN
MAINプログラムでは整数だけではなく、文字列とBoolの変数も関数に渡してみます。
myReturnBool[0]:=FC_GetVarType_ANYNUM(myAnyData:=USIntVar); myReturnBool[1]:=FC_GetVarType_ANYNUM(myAnyData:=BoolVar); myReturnBool[2]:=FC_GetVarType_ANYNUM(myAnyData:=DWordVar); myReturnBool[3]:=FC_GetVarType_ANYNUM(myAnyData:=StringVar); |
Result
プログラムをコンパイルするとエラーが出で、BoolVarとStringVarが赤い線がついています。
Errro Listを見てみましょう。Cannot convert type ‘BOOL’ to type “ANY_NUM’のエラーメッセージがあり、コンパイラはパラメータのData TypeをANY_NUMに宣言することによって、ある程度はCheckしてくれます。
Example4
今度は関数に2つのANY TYPEパラメータを渡し、Data Size・Data Typeと現在値を比べてみます。
Program
FC_CompareDataInAnyType
こちらの関数は2つのANYパラメータとして定義します。
- もしData Sizeが異なるなら、Bit0=Trueします。
- もしData Typeが異なるなら、Bit1=Trueします。
- もし現在値が異なるなら、比較のFor Loopから離れ、Bit2=Trueします。
FUNCTION FC_CompareDataInAnyType : WORD VAR_INPUT myAnyData1 :ANY; MyAnyData2 :ANY; END_VAR VAR i:DINT; END_VAR FC_CompareDataInAnyType:=0; IF myAnyData1.diSize <> MyAnyData2.diSize THEN FC_CompareDataInAnyType.0:=TRUE; END_IF IF myAnyData1.TypeClass <> MyAnyData2.TypeClass THEN FC_CompareDataInAnyType.1:=TRUE; END_IF FOR i:=0 TO myAnyData1.diSize-1 DO IF myAnyData1.pValue[i] <> MyAnyData2.pValue[i] THEN FC_CompareDataInAnyType.2:=TRUE; RETURN; END_IF END_FOR VAR compareInt1,compareInt2:INT; compareReal1,compareReal2:REAL; compareString1:STRING(50); compareString2:STRING(20); myReturnWord:ARRAY[0..99]OF WORD; END_VAR |
Result
もし二つのパラメータが同じData Type、なおかつ現在値が同等であれば、Retrun値が0になります。
二つのパラメータは同じData Typeですが、現在値が異なりますので、Return値が4(Bit2=True)になります。
2つのパラメータは異なるData Typeであれば、Return値が3(Bit0,Bit1=True)になります。
Example5
最後のExampleは関数が異なるData Typeによって制御を分けることを試してみましょう。
Program
今回の関数は結果としてはパラメータの現在値をDINT Data Typeとして変換するだけですが、理論さえ分かれば、複雑な制御を作れます。
FC_TransferDataFromAnyToDINT
注目するのはVARエリアで3つのPOINTER変数が定義され、CASE文からANY TYPEのData Typeを分別し異なるPointerにアクセスし、現在値をDINT Formatに変換します。
もし該当するData TypeがReal・LReal・DWORD以外のものであれば、DataTypeInValidがTrueになります。
FUNCTION FC_TransferDataFromAnyToDINT : DINT VAR_INPUT myAnyData :ANY; END_VAR VAR_OUTPUT DataTypeInValid:BOOL; END_VAR VAR pReal :POINTER TO REAL; pLReal :POINTER TO LREAL; pDWORD :POINTER TO DWORD; END_VAR DataTypeInValid:=False; CASE UDINT_TO_INT(myAnyData.TypeClass) OF IBaseLibrary.TypeClass.TYPE_REAL: pReal:=myAnyData.pValue; FC_TransferDataFromAnyToDINT:=REAL_TO_DINT(pReal^); IBaseLibrary.TypeClass.TYPE_LREAL: pLReal:=myAnyData.pValue; FC_TransferDataFromAnyToDINT:=LREAL_TO_DINT(pLReal^); IBaseLibrary.TypeClass.TYPE_DWORD: pDWORD:=myAnyData.pValue; FC_TransferDataFromAnyToDINT:=REAL_TO_DINT(pDWORD^); ELSE DataTypeInValid:=TRUE; END_CASE |
Result
32Bit実数、64Bit実数、32BitWordをパラメータとして関数に渡しても正常に変換されます。
ですが、Bool Typeの変数を渡してみるとエラーに代わります。