IEC 61131-3は、FA(Factory Automation)のプログラミング言語に関する国際規格です。次の特徴があります。
一方、安全性を重視しているため、関数の再帰呼出しを制限していたり、関数ポインタを含むポインタ型を代替する機能が制約されていたりと機能的な自由度は高くありません。「IEC 61131-3にないもの」も参照ください。
日本のFAにおけるIEC 61131-3の浸透は十分とは言えません。従来のアドレスプログラミングやラダーによる巻物スタイルのプログラミングは根強く残っており、IEC 61131-3が良いだろうことは感じていても、約20年もの間、IEC 61131-3への移行は進んでいません。
これには、IEC 61131-3規格側の問題、PLCメーカー側の問題、およびユーザー側の様々な事情があります。
これらの問題が解消されることで、IEC 61131-3の導入障壁が下がり、IEC 61131-3のレベルが全体的に底上げされていくものと考えます。
本サイトは、上記4.の一助にするために作りました。IEC 61131-3の正しい詳細情報であったり、何ができるのか・できないのかといった情報を提供します。
IEC 61131-3 3rd Edition.を範囲とします。
プログラミング言語入門で最初の例として示されるHello worldのプログラムです。
PROGRAM Main VAR text: STRING[128]; END_VAR text := 'Hello, world!'; END_PROGRAM
IEC 61131-3に標準入出力の定義はないため、文字列型変数に代入しているだけにしています。文字列型変数をモニタすることでHello, world!を確認してください。このプログラムを実行するためには、実行するプログラム(例では、MainプログラムPOU)をタスクに割り付け、ビルドし、PLCにダウンロードする必要があります。
特定の関数から始まるといったエントリ関数のようなものはありません。複数のプログラムインスタンスがタスクの優先度に従い並列実行されます。。。これが、IEC 61131-3の規定ですが、各PLCメーカーの実装はそうではありません。優先度の高い一つのタスク、もしくはそのPLCメーカーにとって"メインのタスク"があり、そのタスクに割り付けたプログラムインスタンスがタスクごとに上から順番に実行されます。
IEC 61131-3 | PLCメーカーの実装 | |
---|---|---|
タスク割付 | プログラム1 WITH 定周期タスク1(優先度高) プログラム2 WITH 定周期タスク1(優先度高) プログラム3 WITH 定周期タスク2(優先度低) プログラム4 WITH 定周期タスク2(優先度低) プログラム5 WITH 定周期タスク3(優先度低) プログラム6 WITH 定周期タスク3(優先度低) プログラム7 WITH イベントタスク1 プログラム8 WITH イベントタスク2 |
定周期タスク1(優先度高): - プログラム1 - プログラム2 定周期タスク2(優先度低): - プログラム3 - プログラム4 定周期タスク3(優先度低): - プログラム5 - プログラム6 イベントタスク1: - プログラム7 イベントタスク2: - プログラム8 |
動作 | どの定周期タスクのプログラムが最初に実行されるかは不確定。 | プログラム1、3、5のいずれかが最初に実行される。 |
プログラムインスタンスには、プログラム間でデータをやりとりするための引数があります。。。これが、IEC 61131-3の規定ですが、各PLCメーカーの実装はそうではありません。プログラム間でデータをやりとりするには、グローバル変数を使用します。
ST言語には、1つの行コメントと、2つの複数行コメントがあります。
RightsideIsLinecomment := TRUE; // これは行コメントです。 (* これは 複数行コメント です。 *) AbovesideIsBlockcomment := TRUE; RightsideIsAnotherBlockcomment := TRUE; /* これは もう一つの 複数行コメント です。 */
プログラムやコメントで使用できる文字集合は、ISO/IEC 10646に従います。エンコーディングは、各メーカーの実装依存です。
※ISO/IEC 10646を符号化文字集合と解釈するか、文字符号化方式と解釈するかで違ってきますが、規格において、"Character set"の項に記載あるため、ここでは前者と解釈します。
識別子は、変数やデータ型などを識別するための名前であり、次のすべてを満たさなければいけません。
例えば、`abc`、`D2E_F3`、`_hi_jk_lmn`は識別子になりえますが、`5op`、`__qr`、`s__t`、`uvw_`は識別子ではありません。
また、識別子の大文字・小文字を区別しません。`Abc`、`abc`、`ABC`は同じ識別子と解釈されます。
プラグマは、{ ~ }で囲われた要素であり、IEC 61131-3で規定しない要素のために使用します。個々のプラグマの仕様は、IEC 61131-3規格では定義しておらず、IEC 61131-3実装者が定義します。
プラグマは、コメントや文字列リテラルを除く様々な場所に登場できると規定されており、以下の例のように「警告を無視する」「要素のドキュメントを埋め込む」「変数に追加属性を与える」「コンパイラの設定を変更する」など様々な用途での活用が想定されます。
{disable-warning: *} {document: mainプログラムは装置全体を制御します。} PROGRAM main VAR v: LINT {register}; END_VAR {line: 1} : END_PROGRAM
プラグマに関するIEC 61131-3の規定については、上述した程度の言及しかなく次に示すことは実装依存です。
※1 文字列リテラルと同じく$でエスケープするのが受け入れられやすいと考えます。
※2 未知のプラグマについては、無視してもコンパイルエラーとならず、プログラムの意味が変わらない範囲での活用が望ましいと考えます。
下表に示す通り、整数値、実数値、ビット列値、真偽値、時間、時間軸上の点、文字、文字列など多くの基本データ型があります。
データ型 | キーワード | ビット幅 | 範囲 | デフォルト初期値 | リテラル※の例 | 説明 |
---|---|---|---|---|---|---|
符号あり整数型 | SINT | 8 | -128~127 | 0 | -59 ,13 ,1_234_567 ,2#11_01 ,-16#2f
| |
INT | 16 | -32768~32767 | ||||
DINT | 32 | -2147483648~2147483647 | ||||
LINT | 64 | -9223372036854775808~9223372036854775807 | ||||
符号なし整数型 | USINT | 8 | 0~255 | |||
UINT | 16 | 0~65535 | ||||
UDINT | 32 | 0~4294967295 | ||||
ULINT | 64 | 0~18446744073709551615 | ||||
浮動小数点型 | REAL | 32 | IEC 60559単精度浮動小数点規格の範囲に従う | 0 | 0.1 ,1.0e-6 ,-1e3 ,-5 ,16#ff | |
LREAL | 64 | IEC 60559倍精度浮動小数点規格の範囲に従う | ||||
ビット列型 | BYTE | 8 | 16#00~16#ff | 16#00 | BYTE#16#ff ,BYTE#2#1111_1111_1111_1111 ,DWORD#16#cafe_babe | |
WORD | 16 | 16#0000~16#ffff | 16#0000 | |||
DWORD | 32 | 16#0000_0000~16#ffff_ffff | 16#0000_0000 | |||
LWORD | 64 | 16#0000_0000_0000_0000~16#ffff_ffff_ffff_ffff | 16#0000_0000_0000_0000 | |||
ブール型 | BOOL | 1 | FALSE(偽)、又はTRUE(真) | FALSE | TRUE ,BOOL#0 | |
時間型 | TIME | 実装依存 | 実装依存 | 0秒 | TIME#1s ,T#1s ,LTIME#1s ,LT#1s ,LT#1d2h3m4s5ms6us7ns ,LT#1.234567ms ,LT#1m234us567ns | 最小単位も含めて実装依存のため、TIME型ではなく、LTIME型の使用を推奨します。 |
LTIME | 64 | -9223372036854775808ナノ秒~9223372036854775807ナノ秒 最小単位はナノ秒 | ||||
日付型 | DATE | 実装依存 | 実装依存 | 実装依存 e.g. 1970年1月1日 | DATE#1978-9-7 ,D#2022-11-07 ,LDATE#1978-9-7 ,LD#2554-07-21 | |
LDATE | 64 | 1970年1月1日~2554年7月21日 最小単位は1日 | 1970年1月1日 | [範囲]に示す上限は、規格に示される最小単位ナノ秒という言及をベースに64ビット幅で計算したものです。 | ||
時刻型 | TIME_OF_DAY | 実装依存 | 実装依存 | 実装依存 e.g. 0時0分0秒 | TIME_OF_DAY#23:59:59 ,TOD#23:59:59 ,LTIME_OF_DAY#23:59:59.999_999_999 ,LTOD#23:59:59.999999999 | |
TOD | ||||||
LTIME_OF_DAY | 64 | 0時0分0秒~23時59分59.999999999秒 最小単位はナノ秒 | 0時0分0秒 | |||
LTOD | ||||||
日付時刻型 | DATE_AND_TIME | 実装依存 | 実装依存 | 実装依存 e.g. 1970年1月1日0時0分0秒 | DATE_AND_TIME#1970-1-1-23:59:59 ,DT#1970-01-01-23:59:59 ,LDATE_AND_TIME#2022-11-07-23:59:59.999_999_999 ,LDT#2554-07-21-23:34:33.709551615 | |
DT | ||||||
LDATE_AND_TIME | 64 | 1970年1月1日0時0分0秒~2554年7月21日23時34時33.709551615s 最小単位はナノ秒 | 1970年1月1日0時0分0秒 | |||
LDT | ||||||
文字型 | CHAR | 8 | ISO/IEC 10646規格に従う一文字。 | NULL文字('$00' ) | 'a' ,'→' ,'$00' // NULL文字 , | 文字符号化方式は、UTF-8。 |
WCHAR | 16 | ISO/IEC 10646規格に従う一文字。 | NULL文字("$0000" ) | "a" ,"→" ,"$0000" // NULL文字 | 文字符号化方式は、UTF-16。 | |
文字列型 | STRING | 8*N | ISO/IEC 10646規格に従う0文字以上の文字の列。 | 空文字列('' ) | 'This is a STRING literal.' ,'これは文字列リテラルです。' ,'abc' ,'$61$62$63' // = 'abc' ,'' // 空文字列 | 文字符号化方式は、UTF-8。 |
WSTRING | 16*N | ISO/IEC 10646規格に従う0文字以上の文字の列。 | 空文字列("" ) | "This is a STRING literal." ,"これはWSTRING型のリテラルです。" ,"abc" ,"$0061$0062$0063" // = 'abc' ,"" // 空文字列 | 文字符号化方式は、UTF-16。 |
※リテラルは、値のプログラミング言語における表現です。例えば、IEC 61131-3の真偽値である「真」の値の表現は、「TRUE
」や「BOOL#1
」があります。
BOOL型と宣言した変数は真と偽の2つの値しか取れないことからも分かる通り、データ型は変数がとり得る値の集合を制約する特性があります。列挙型は、その値の集合をプログラマ自身が定義できるデータ型です。例えば、次のプログラムは、eRed, eGreen, eBlue
の3つの値しか取れないColor
列挙型、eSun, eMon, eTue, eWed, eThu, eFri, eSat
の7つの値しか取れないDay
列挙型を宣言しています。列挙型を使用することにより、マジックナンバーを使用するよりも可読性が高くなり、変数がとり得る値をシステムが保証することができるなどのメリットがあります。
TYPE // 列挙型Colorの宣言、デフォルト初期値: eRed。 Color: (eRed, eGreen, eBlue); // 列挙型Day(曜日)の宣言、デフォルト初期値: eMon。 Day: (eSun, eMon, eTue, eWed, eThu, eFri, eSat) := eMon; END_TYPE; PROGRAM Main VAR colorv: Color; // 初期値 = Color#eRed dayv: Day := Day#eMon; END_VAR colorv := Color#eGreen; colorv := 2; // Error! colorv := Day#eSun; // Error! END_PROGRAM
上記Day
の宣言において、eMon
をデフォルト初期値として宣言しています。デフォルト初期値を指定しないとき、デフォルト初期値は、列挙型宣言において最初に登場する値です。上記プログラムにおけるColor
列挙型のデフォルト初期値は、Color#eRed
となります。
Color#eRed
、Day#eSat
などの列挙型の値を列挙子といいます。列挙子のプログラム上の表現は、データ型名#列挙子
です。ただし、列挙子のスコープは、その列挙子のデータ型と同じスコープをもつため、列挙型#を省略してもエラーとはなりません。一方、列挙子のこの広いスコープは、他の要素のスコープと重複する可能性を高めます。本書のプログラム例でも示す通り、列挙子名は接頭辞eを付加するなど、他と重複し難い命名規則にすることを推奨します。また、列挙型同士の重複も回避するため、列挙型#を省略しないことを推奨します。
TYPE Color: (eRed, eGreen, eBlue); Signal: (eBlue, eYellow, eRed); END_TYPE; PROGRAM Main VAR colorv: Color; END_VAR colorv := eGreen; // OK. colorv = Color#eGreen colorv := eRed; // Error! Color#eRedなのか、Signal#eRedなのか区別できない。 END_PROGRAM
コンパイラや実行エンジンは、列挙型の値の実体を整数値で実装することも多く、IEC 61131-3においても列挙子に対し、整数値を設定する構文が存在します。実体の値を指定する列挙型は、単なる列挙型とは区別され、値付き列挙型といいます。
構造体型変数の初期化は、変数宣言時に以下のように書くことができます。
TYPE s_t: STRUCT m0: DINT; m1: ARRAY[0..2] OF DINT; END_STRUCT; t_t: STRUCT n0: DINT; n1: s_t; END_STRUCT; END_TYPE; PROGRAM Main VAR sv1: s_t := (m0 := 2, m1 := [3, 5, 7]); sv2: s_t := (m0 := 11); // 部分的な指定 (m0 := 2, m1 := [0, 0, 0]) と同義です。 tv: t_t := ( n0 := 13, n1 := ( m0 := 17, m1 := [19, 23, 29] ) ); END_VAR END_PROGRAM
文法的に構造体初期化子といいます。
(<メンバ名1> := <初期値1>, <メンバ名2> := <初期値2>, ...)
構造体初期化子をコード部で書くことはできません。以下は不正なプログラムです。
: PROGRAM Main VAR sv: s_t; END_VAR sv := (m0 := 2, m1 := [3, 5, 7]); // Compile-time error. END_PROGRAM
配列変数の初期化は、変数宣言時に以下のように書くことができます。
PROGRAM Main VAR // a[3] = 2 // a[4] = 3 // a[5] = 5 a1: ARRAY[3..5] OF DINT := [2, 3, 5]; // a[0,0] = 16#00 // a[0,1] = 16#01 // a[0,2] = 16#02 // a[1,0] = 16#10 // a[1,1] = 16#11 // a[1,2] = 16#12 a2: ARRAY[0..1, 0..2] OF DINT := [ 16#00, 16#01, 16#02, 16#10, 16#11, 16#12 ]; // ※多次元配列における以下のネスト記法は、規格には示されいません。 a3: ARRAY[0..1, 0..2] OF DINT := [ [16#00, 16#01, 16#02], [16#10, 16#11, 16#12] ]; END_VAR END_PROGRAM
文法的に配列初期化子といいます。
[<初期値1>, <初期値2>, ...]
配列初期化子をコード部で書くことはできません。以下は不正なプログラムです。
PROGRAM Main VAR av: ARRAY[3..5] OF DINT; END_VAR av := [2, 3, 5]; // Compile-time error. END_PROGRAM
以下のように連続指定することができます。
TYPE s_t: STRUCT m0: DINT; m1: DINT; END_STRUCT; END_TYPE PROGRAM Main VAR // v1[0]=0 // : // v1[7]=0 v1: ARRAY[0..7] OF DINT := [8(0)]; // v2[0]=0 // : // v2[4]=1 // : v2: ARRAY[0..7] OF DINT := [4(0), 4(1)]; // v3[0].m0=2 // v3[0].m1=3 // v3[1].m0=2 // v3[1].m1=3 // v3[2].m0=2 // v3[2].m1=3 v3: ARRAY[0..2] OF s_t := [3((m0 := 2, m1 := 3))]; // 括弧の数に注意。 // v4[0,0]=0 // : v4: ARRAY[0..1, 0..2] OF DINT := [6(0)]; END_VAR END_PROGRAM
可変長配列は、配列長が実行時に定まる変数の型です。入出力変数の型として使用できます。(※規格上は、入力変数にも適用可能と規定されていますが、規定が不完全です)。
可変長配列を使用することで、あらゆる配列要素数にも対応できる汎用的なライブラリを作成することができます。
// LREAL型配列要素の総和を求めます。 FUNCTION sum: LREAL VAR_IN_OUT values: ARRAY[*] OF LREAL; // 1次元可変長配列変数の定義 END_VAR VAR i: DINT; END_VAR sum := 0.0; // 配列添字の始点・終点は、標準ファンクションのLOWER_BOUD、UPPER_BOUNDで取得します。 // 実引数の1は、1次元目の始点・終点を取得することを意味します。 FOR i := LOWER_BOUND(values, 1) TO UPPER_BOUND(values, 1) BY 1 DO sum := sum + values[i]; END_FOR; END_FUNCTION PROGRAM Main VAR v1: ARRAY[0..4] OF LREAL := [10, 20, 30, 40, 50]; v2: ARRAY[1..2] OF LREAL := [100, 200]; s1, s2: LREAL; END_VAR s1 := sum(v1); // 150 (=10+20+30+40+50) s2 := sum(v2); // 300 (=100+200) END_PROGRAM
1次元の可変長配列の定義は、次のように書きます。
<変数名>: ARRAY[*] OF <基底型>
多次元の可変長配列の定義は、カンマで区切って次のように書きます。
<変数名>: ARRAY[*, *, ...] OF <基底型>
実行時に配列次元の始点と終点の情報を取得するには、標準ファンクションLOWER_BOUND
、UPPER_BOUND
を使用します。
LOWER_BOUND
、UPPER_BOUND
ファンクションは、「ANY_INT LOWER_BOUND(arr: ANY ARRAY; VAR_IN_OUT, dim: ANY_INT; VAR_INPUT)」のように2つの引数をとります。引数arr
には、通常可変長配列を指定しますが、固定長配列を指定することもできます。引数dim
には、どの次元の始点(又は終点)の値を取得するかを1以上で指定します。戻り値の正確な型は未規定です。
PROGRAM Main VAR a1: ARRAY[3..5] OF LREAL; a3: ARRAY[7..11, 13..17, 19..23] OF LREAL; END_VAR LOWER_BOUND(a1, 1); // 3 UPPER_BOUND(a1, 1); // 5 LOWER_BOUND(a3, 1); // 7 UPPER_BOUND(a3, 1); // 11 LOWER_BOUND(a3, 2); // 13 UPPER_BOUND(a3, 2); // 17 LOWER_BOUND(a3, 3); // 19 UPPER_BOUND(a3, 3); // 23 END_PROGRAM
VAR_CONFIGは、変数のAT属性の設定と初期値の設定を、プログラム実行前まで遅延できる機能です。
通常、ある変数に対するAT属性と初期値の設定値については、プログラムを記述した時点で定まる設定値を用いてプログラムが実行されます。
例えば、次のファンクションブロックfbにおいて、変数nが示すデバイスは%IW7であり、初期値は2です。これは、どのFBインスタンスでも変わりません。v1.n、v2.nの双方IW7にアクセスします。インスタンスごとにアクセス先を変えたい場合、ファンクションブロックの複製、ソースコードの修正、再コンパイル、そして再テストが必要になることもあります。
FUNCTION_BLOCK fb VAR n AT %IW7 : INT := 2; END_VAR : END_FUNCTION_BLOCK PROGRAM main VAR v1: fb; v2: fb; END_VAR v1.n := 3; // v2.nも3になる。 v2.n := 4; // v1.nも4になる。 x := v1 = v2; // TRUE END_PROGRAM
VAR_CONFIGは、次の例に示す変数nのようにAT属性の設定値を未決定(*
)と設定し、VAR_CONFIG
~END_VAR
内で表現・設定される追加の設定にて、%IW5や%IW6などの具体的なAT属性と、初期値を設定します。この設定が、mainやfbのソースコードに影響を与えることなく、プログラム実行直前に設定できることがポイントです。本例では、同じソースコード内にすべて記載していますが、実運用上においては、VAR_CONFIG
~END_VAR
部分は、時間的にもファイル的にも分離されることになります。
FUNCTION_BLOCK fb VAR n AT %I* : INT; END_VAR : END_FUNCTION_BLOCK PROGRAM main VAR v1: fb; v2: fb; END_VAR v1.n := 3; // VAR_CONFIGにて別のデバイス先に設定されているため、v2.nには影響しない。 v2.n := 4; // 同様に、v1.nには影響しない。 x := v1.n = v2.n; // FALSE END_PROGRAM CONFIGURATION cfg RESOURCE rsc ON plc TASK t(); PROGRAM main WITH t: main(); END_RESOURCE VAR_CONFIG rsc.main.v1.n AT %IW5 : INT := 16; rsc.main.v2.n AT %IW6 : INT := 17; END_VAR END_CONFIGURATION
AT属性の*指定については、ファンクションブロックだけでなく、プログラムPOU内の変数に対しても使用できます。VAR_INPUT、VAR_IN_OUT、およびVAR_EXTERNALを除く変数に使用できます。プログラム実行前に、*が設定されているすべての変数に対して、AT属性が設定されていなければ異常です。%I*の表現については、他にも特性に応じて、%I*, %QW*などを使用できます。※1
※1 部分的にAT属性を設定するという意味で、IEC 61131-3規格においては、「部分指定」という表現が使用されています。よって、全てを省略する%*という表現は、IEC 61131-3規格に示されません。効用的には、部分指定というより、遅延指定という表現の方が適切と考えていますし、%*という表現もあって良いと考えます。
IEC 61131-3の演算子の一覧を示します。
複数の演算子が混在する式においては、優先順位の高いものから評価されます。同一の優先順位をもつ場合、左から右に評価されます。他のプログラミング言語同様、括弧を使用することで、優先的に評価することができます。
r := 1 + 2; // 3 r := 1 + 2 * 3; // 7 = 1 + (2 * 3) r := (1 + 2) * 3; // 9 r := -2 ** 4; // 16 = (-2)**4 r := f() * g() * h(); // f→g→hの順に呼ばれる。 r := f() + g() * h(); // g→h→fの順に呼ばれる。
演算子 | 名称 | 優先順位 | 結合規則 |
---|---|---|---|
() |
呼び出し | 11 | 実装依存(一般的に左) |
. |
メンバアクセス・部分アクセス | 実装依存 | 実装依存(一般的に左) |
[] |
配列添え字 | 実装依存 | 実装依存(一般的に左) |
# |
列挙子アクセス | 実装依存 | 実装依存(一般的に左) |
^ |
デリファレンス | 10 | 実装依存(一般的に右) |
+ - |
単項プラス 単項マイナス | 9 | 実装依存(一般的に右) |
not |
論理否定・ビット否定 | 9 | 実装依存(一般的に右) |
** |
べき乗 | 8 | 左 |
* |
乗算 | 7 | 左 |
/ |
除算 | 7 | 左 |
mod |
剰余 | 7 | 左 |
+ |
加算 | 6 | 左 |
- |
減算 | 6 | 左 |
> >= <= < |
比較 | 5 | 左 |
= <> |
等価 非等価 | 4 | 左 |
and & |
論理積・ビット積 | 3 | 左 |
xor |
排他的論理和・ビット排他的論理和 | 2 | 左 |
or |
論理和・ビット和 | 1 | 左 |
:= => |
代入 引数代入 | 実装依存 | 実装依存(一般的に左) |
形式として、仮引数有り呼び出し(フォーマルな呼び出し)と、仮引数無し呼び出し(インフォーマルな呼び出し)があります。一部の引数を省略するためには、仮引数有り呼び出しを使用する必要があります。
pou_instance(in1 := 2, in2 := true, out => v); // インスタンス呼び出し(フォーマル) move(in := 3, out => intv); // 戻り値のないFUNCTION呼び出し(フォーマル) strv := concat(in1 := key, in2 := ':', in3 := value); // 戻り値のあるFUNCTION呼び出し(フォーマル)
pou_instance(2, true, v); // インスタンス呼び出し(インフォーマル) move(3, intv); // 戻り値のないFUNCTION呼び出し(インフォーマル) strv := concat(key, ':', value); // 戻り値のあるFUNCTION呼び出し(インフォーマル)
演算子**
は、べき乗を計算します。
r := 2 ** 3; // 8 = 2 * 2 * 2 r := 4.0 ** 0.5; // 2 = SQRT(4) r := 2.0 ** (-3); // 0.125 = 1/(2 ** 3) = 1 / (2 * 2 * 2) r := 5 ** 0; // 1 r := -2 ** 4; // 16 = (-2) ** 4 r := 2 ** 3 ** 2; // 64 = (2 ** 3) ** 2 = 8 * 8 r := -2 ** 0.5; // 実装依存; 複素数 r := 0 ** 2; // 0 r := 0 ** 0; // 実装依存
数学の2^3^2は、2^(3^2)=512と右から左へ評価されますが、IEC 61131-3の2**3**2は、(2**3)**2=64と左から右に評価されることに注意が必要です。
ビット列型変数の部分的なビット列値に簡単にアクセスできます。演算子.
を使用し、<ビット列型変数>.%<幅><位置>
のように記述します。
boolv0 := wordv.%X0 ; // word型変数のビット0(最下位ビット) boolv7 := wordv.%X7 ; // word型変数のビット7(最上位ビット) low_bytev := wordv.%B0 ; // word型変数のバイト0(下位バイト) high_bytev := wordv.%B1 ; // word型変数のバイト1(上位バイト) lowlow_bytev := dwordv.%B0 ; // dword型変数のバイト0(最下位バイト)
<幅>は、X
(BOOL), B
(BYTE), W
(WORD), D
(DWORD), L
(LWORD)のいずれかです。<位置>は、0以上で指定し、ビット列型変数のビット幅を超えないように指定する必要があります。例えば、dword型変数に対する部分アクセスであれば、%X0~%X31, %B0~%B3, %W0~%W1 となります。
単一のビットアクセスの場合には省略記法があり、<ビット列型変数>.<位置>
のように記述できます。
boolv6 := wordv.6 ; // wordv.%X6と等価です。
なお、次のようなプログラムについての明確な規定はありません。
wordv.%X0 := TRUE; // 実装依存: 部分アクセス式に対する書き込み。 boolv := WORD#16#FF.%X0 ; // 実装依存: ビット列リテラルに対する部分アクセス。 boolv := (WORD#16#000F AND wordv).%X3 ; // 実装依存: ビット列型の式の結果に対する部分アクセス。 boolv := wordFunction().%X1 ; wordv := wordv.%W0 ; // 実装依存: 恒等的な部分アクセス。なお、<幅>にLが定義されており、lword.%L0 としか使いようがない。
x := 2; x := 3 + 5; x := y := 7; // Compile-time error! syntax error.
IF x = 0 THEN zero_num := zero_num + 1; END_IF;
IF (x mod 2) = 0 THEN even_num := even_num + 1; ELSE odd_num := odd_num + 1; END_IF;
IF x > 0 THEN positive_num := positive_num + 1; ELSIF x < 0 THEN negative_num := negative_num + 1; ELSE zero_num := zero_num + 1; END_IF;
n := 3; CASE n OF 2: // n is 2 k := 4; 3: // n is 3 k := 9; 4: // n is 4 k := 16; 5..7: // n is 5 or 6 or 7. k := n * n; ELSE // n is other value. k := -1; END_CASE; // k = 9
sum := 0; FOR n := 1 TO 4 BY 1 DO sum := sum + n; END_FOR; // sum = 10 = 1 + 2 + 3 + 4, n = 5.
sum := 0; FOR n := 1 TO 4 DO sum := sum + n; END_FOR; // sum = 10 = 1 + 2 + 3 + 4, n = 5.
sum := 0; n := 1; WHILE n <= 4 DO sum := sum + n; n := n + 1; END_WHILE; // sum = 10 = 1 + 2 + 3 + 4, n = 5.
sum := 0; n := 1; REPEAT sum := sum + n; n := n + 1; UNTIL n = 5 END_REPEAT; // sum = 10 = 1 + 2 + 3 + 4, n = 5.
sum := 0; n := 1; WHILE n <= 4 DO IF n = 2 THEN n := n + 1; CONTINUE; END_IF; sum := sum + n; n := n + 1; END_WHILE; // sum = 8 = 1 + 3 + 4, n = 5.
sum := 0; n := 1; WHILE n <= 4 DO IF n = 3 THEN EXIT; END_IF; sum := sum + n; n := n + 1; END_WHILE; // sum = 3 = 1 + 2, n = 3.
PROGRAM main ... IF n = 0 THEN RETURN; END_IF; ... END_PROGRAM FUNCTION f: INT ... IF n = 0 THEN f := 1; RETURN; END_IF; ... RETURN 9; // Compile-time error! Syntax error. END_IF;
有用そうな自作のライブラリを公開しています。コンパイル確認・動作確認をしていません。不具合ありましたら、こちらのブログにご連絡いただければ可能な範囲で対応致します。
// Xorshift32 FUNCTION xorshift32: DWORD VAR_IN_OUT state: DWORD; END_VAR VAR_TEMP x: DWORD; END_VAR // {ST} x := state; x := x XOR SHL(x, 13); x := x XOR SHR(x, 17); x := x XOR SHL(x, 5); state := x; xorshift32 := x; // {END} END_FUNCTION FUNCTION nextBits32: DWORD VAR_IN_OUT state: DWORD; END_VAR VAR_INPUT bits: DINT := 32; END_VAR // {ST} nextBits32 := SHR(xorshift32(state), 32 - bits); // {END} END_FUNCTION FUNCTION nextBOOL: BOOL VAR_IN_OUT state: DWORD; END_VAR // {ST} nextBOOL := nextBits32(state, 1) = DWORD#16#0000_0001; // {END} END_FUNCTION // [16#0, 16#ffff_ffff] FUNCTION nextDWORD: DWORD VAR_IN_OUT state: DWORD; END_VAR // {ST} nextDWORD := nextBits32(state, 32); // {END} END_FUNCTION // [16#0, 16#ffff_ffff_ffff_ffff] FUNCTION nextLWORD: LWORD VAR_IN_OUT state: DWORD; END_VAR VAR_TEMP b1: LWORD; b2: LWORD; END_VAR // {ST} b1 := DWORD_TO_LWORD(nextBits32(state, 32)); b2 := DWORD_TO_LWORD(nextBits32(state, 32)); nextLWORD := SHL(b1, 32) OR b2; // {END} END_FUNCTION // [0, 2**32-1] FUNCTION nextUDINT: UDINT VAR_IN_OUT state: DWORD; END_VAR // {ST} nextUDINT := DWORD_TO_UDINT(nextDWORD(state)); // {END} END_FUNCTION // [0, 2**64-1] FUNCTION nextULINT: ULINT VAR_IN_OUT state: DWORD; END_VAR // {ST} nextULINT := LWORD_TO_ULINT(nextLWORD(state)); // {END} END_FUNCTION // A 52-bit based pseudo-random float number, the range is [0.0, 1.0). // ref. Why is it not 64-bit? // --> See https://www.ecma-international.org/ecma-262/5.1/#sec-8.5 FUNCTION nextLREAL: LREAL VAR_IN_OUT state: DWORD; END_VAR VAR_TEMP n1: ULINT; n2: ULINT; END_VAR // {ST} n1 := DWORD_TO_ULINT(nextBits32(state, 26)); n2 := DWORD_TO_ULINT(nextBits32(state, 26)); nextLREAL := ULINT_TO_LREAL(n1 * ULINT#16#0400_0000 + n2) / LREAL#4503599627370496.0; // 2**52 // {END} END_FUNCTION使用例
PROGRAM Main VAR state: DWORD; r: LREAL; END_VAR // {ST} state := DWORD#16#cafeface; r := nextLREAL(state); // [0.0, 1.0) r := nextLREAL(state); // [0.0, 1.0) // {END} END_PROGRAM
// 配列要素数; LREAL配列 FUNCTION arraySize_LREAL: DINT VAR_IN_OUT x: ARRAY[*] OF LREAL; END_VAR // {ST} arraySize_LREAL := UPPER_BOUND(x, 1) - LOWER_BOUND(x, 1) + 1; // {END} END_FUNCTION
// バブルソート; LREAL配列 FUNCTION bubbleSort_LREAL VAR_IN_OUT x: ARRAY[*] OF LREAL; END_VAR VAR_TEMP i: DINT; j: DINT; t: LREAL; END_VAR // {ST} FOR i := LOWER_BOUND(x, 1) TO UPPER_BOUND(x, 1) - 1 BY +1 DO FOR j := UPPER_BOUND(x, 1) TO i + 1 BY -1 DO IF x[j-1] > x[j] THEN t := x[j - 1]; x[j - 1] := x[j]; x[j] := t; END_IF; END_FOR; END_FOR; // {END} END_FUNCTION
// バブルソート; DINT配列 FUNCTION bubbleSort_DINT VAR_IN_OUT x: ARRAY[*] OF DINT; END_VAR VAR_TEMP i: DINT; j: DINT; t: DINT; END_VAR // {ST} FOR i := LOWER_BOUND(x, 1) TO UPPER_BOUND(x, 1) - 1 BY +1 DO FOR j := UPPER_BOUND(x, 1) TO i + 1 BY -1 DO IF x[j-1] > x[j] THEN t := x[j - 1]; x[j - 1] := x[j]; x[j] := t; END_IF; END_FOR; END_FOR; // {END} END_FUNCTION
// 平均値 FUNCTION mean: LREAL VAR_IN_OUT x: ARRAY[*] OF LREAL; END_VAR VAR_TEMP i: DINT; n: DINT; sum: LREAL; END_VAR // {ST} sum := 0.0; FOR i := LOWER_BOUND(x, 1) TO UPPER_BOUND(x, 1) DO sum := sum + x[i]; END_FOR; n := arraySize_LREAL(x); mean := sum / n; // {END} END_FUNCTION
// 分散: V(X) = E(X^2) - {E(X)}^2 FUNCTION variance: LREAL VAR_IN_OUT x: ARRAY[*] OF LREAL; END_VAR VAR_TEMP i: DINT; sum_of_x2: LREAL; sum_of_x: LREAL; n: DINT; END_VAR // {ST} sum_of_x2 := 0.0; sum_of_x := 0.0; FOR i := LOWER_BOUND(x, 1) TO UPPER_BOUND(x, 1) DO sum_of_x2 := sum_of_x2 + x[i] * x[i]; sum_of_x := sum_of_x + x[i]; END_FOR; n := arraySize_LREAL(x); variance := (sum_of_x2 / n) - (sum_of_x / n)**2; // {END} END_FUNCTION
// 不偏分散: s^2 = V(X) * n / (n-1) FUNCTION sampleVariance: LREAL VAR_IN_OUT x: ARRAY[*] OF LREAL; END_VAR VAR_TEMP n: DINT; dof: DINT; END_VAR // {ST} n := arraySize_LREAL(x); dof := n - 1; sampleVariance := variance(x) * n / dof; // {END} END_FUNCTION
// 標準偏差: √(分散) FUNCTION standardDiviation: LREAL VAR_IN_OUT x: ARRAY[*] OF LREAL; END_VAR // {ST} standardDiviation := SQRT(variance(x)); // {END} END_FUNCTION
// 変動係数: 標準偏差 / 平均 FUNCTION coefficientOfVariation: LREAL VAR_IN_OUT x: ARRAY[*] OF LREAL; END_VAR // {ST} coefficientOfVariation := standardDiviation(x) / mean(x); // {END} END_FUNCTION
// 標準誤差: √(不偏分散 / サンプルサイズ) FUNCTION standardError: LREAL VAR_IN_OUT x: ARRAY[*] OF LREAL; END_VAR VAR_TEMP n: DINT; END_VAR // {ST} n := arraySize_LREAL(x); standardError := SQRT(sampleVariance(x) / n); // {END} END_FUNCTION