IEC 61131-3は、FA(Factory Automation)のプログラミング言語に関する国際規格です。次の特徴があります。
一方、安全性を重視しているため、関数の再帰呼出しを制限していたり、関数ポインタを含むポインタ型を代替する機能が制約されていたりと機能的な自由度は高くありません。「APPENDIX. IEC 61131-3にないもの」も参照ください。
日本のFAにおけるIEC 61131-3の浸透は十分とは言えません。従来のアドレスプログラミングやラダーによる巻物スタイルのプログラミングは根強く残っており、IEC 61131-3が良いだろうことは感じていても、約20年もの間、IEC 61131-3への移行は進んでいません。
これには、IEC 61131-3規格側の問題、ベンダー側(PLCメーカーなどIEC 61131-3プログラミング環境提供側)の問題、およびユーザー側の様々な事情があります。
これらの問題が解消されることで、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の規定ですが、各ベンダーの実装はそうではありません。優先度の高い一つのタスク、もしくはそのベンダーにとって"メインのタスク"があり、そのタスクに割り付けたプログラムインスタンスがタスクごとに上から順番に実行されます。
| IEC 61131-3 | ベンダーの実装 | |
|---|---|---|
| タスク割付 | プログラム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の規定ですが、各ベンダーの実装はそうではありません。プログラム間でデータをやりとりするには、グローバル変数を使用します。
ST言語には、1つの行コメントと、2つの複数行コメントがあります。
RightsideIsLinecomment := true; // これは行コメントです。 (* これは 複数行コメント です。 *) AbovesideIsBlockcomment := true; RightsideIsAnotherBlockcomment := true; /* これは もう一つの 複数行コメント です。 */
プログラムやコメントで使用できる文字集合は、ISO/IEC 10646に従います。エンコーディングは、各ベンダーの実装依存です。※1
※1 ISO/IEC 10646を符号化文字集合と解釈するか、文字符号化方式と解釈するかで違ってきますが、規格において、"Character set"の項に記載あるため、ここでは前者と解釈します。
キーワードは、プログラム全体を構成するための文字列です。大文字・小文字を区別しない、キーワードを識別子として使用できない、などの特性があります。
IEC 61131-3規格にキーワードの具体的な定義はなく、構文規則から推測されるキーワードは次のすべての文字列です。※1
abstract, action, and, array, at, bool, by, byte, case, char, class, configuration, constant, continue, date, date_and_time, dint, do, dt, dword, else, elsif, end_action, end_case, end_class, end_configuration, end_for, end_function, end_function_block, end_if, end_interface, end_method, end_namespace, end_program, end_repeat, end_resource, end_step, end_struct, end_transition, end_type, end_var, end_while, exit, extends, final, for, from, function, function_block, f_edge, if, implements, initial_step, int, interface, internal, ldate, ldate_and_time, ldt, lint, lreal, ltime, ltime_of_day, ltod, lword, method, mod, namespace, non_retain, not, null, of, on, or, overlap, override, private, program, protected, public, read_only, read_write, real, ref, ref_to, repeat, resource, retain, return, r_edge, sint, step, string, struct, super, task, then, this, time, time_of_day, to, tod, transition, type, udint, uint, ulint, until, using, usint, var, var_access, var_config, var_external, var_global, var_input, var_in_out, var_output, var_temp, wchar, while, with, word, wstring, xor
※1 IEC 61131-3規格上は、IL演算子のCALもキーワードと読み取れますが、本書ではIL演算子をキーワードに含めていません。
識別子は、変数やデータ型などを識別するための名前であり、次のすべてを満たさなければいけません。
例えば、`abc`、`D2E_F3`、`_hi_jk_lmn`は識別子になりえますが、`5op`、`__qr`、`s__t`、`uvw_`は識別子ではありません。
また、識別子の大文字・小文字を区別しません。`Abc`、`abc`、`ABC`は同じ識別子と解釈されます。
パス、又はパス文字列は、以下の要素で登場する%で始まる文字列です。
%[A-Z0-9\.\*]+%[A-Z0-9](\.?[A-Z0-9])*\*?|%\*%[IQM][XBWDL]?[0-9]+(\.[0-9]+)*|%[IQM][XBWDL]?\*|%\*|%[XBWDL][0-9]+(\.[0-9]+)?プラグマは、{ ~ }で囲われた要素であり、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 未知のプラグマについては、無視してもコンパイルエラーとならず、プログラムの意味が変わらない範囲での活用が望ましいと考えます。
(準備中)
下表に示す通り、整数値、実数値、ビット列値、真偽値、時間、時間軸上の点、文字、文字列など多くの基本データ型があります。
| データ型 | キーワード | ビット幅 | 範囲 | デフォルト初期値 | リテラル※1の例 | 説明 |
|---|---|---|---|---|---|---|
| 符号あり整数型 | 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。 |
※1 リテラルは、値のプログラミング言語における表現です。例えば、IEC 61131-3の真偽値である「真」の値の表現は、「true」や「bool#1」があります。
(準備中)
(準備中)
ビット列型は、コンピュータサイエンスにおけるビット列(0と1のみから構成されるデータの列)を表現する型です。
表現できるビット数の違いにより、8ビットのbyte型、16ビットのword型、32ビットのdword型、および64ビットのlword型の4つのビット列型があります。例えば、word型は、2#0000_0000_0000_0000から2#1111_1111_1111_1111までの65536(216)パターンのビット列を表現できます。
プログラムコードにおいて、ビット列値は、非負の整数リテラルを使用します。ビット列型固有のリテラルはありません。
wordv := 2#1010_0101_0000_1111; dwordv := 16#A50F; wordv := 10; // 16#000A bytev := byte#10; // 16#0A
次の例にも示す負の整数リテラルの代入は、不正なプログラムコードです。負の整数リテラルがシステムにより整数値として解釈され、整数からビット列への暗黙の型変換がないためです。
dwordv := -1; // error
以下は、ビット列型の要素に固有の演算です。これらの演算が整数型の要素には用意されていないことに注意してください。
特にバイナリ転写は実数型のフォーマットであるIEEE 754のビット列を扱うことができます。※1
LREAL_TO_LWORD(10.0); // lword#16#4024_0000_0000_0000 DWORD_TO_REAL(16#3DCC_CCCD); // real#0.1
もちろん整数型との間で相互変換できます。負整数を2の補数で得たりすることができます。
DWORD_TO_DINT(16#FF); // 255 DWORD_TO_DINT(16#FFFF_FFFF); // -1 DWORD_TO_UDINT(16#FFFF_FFFF); // 4294967295; 2^32-1 DINT_TO_DWORD(-1); // dword#16#FFFF_FFFF
※1 執筆時点では、本規定に従う実装をしているベンダーは、Phoenix Contact社のPLCnext Engineerのみかも知れません。他のベンダーの場合、上記例では、整数変換され、lwordv=16#Aやrealv=1.036831936e+9を得ます。
(準備中)
(準備中)
(準備中)
(準備中)
(準備中)
(準備中)
(準備中)
(準備中)
(準備中)
(準備中)
外部変数は、他のモジュールに定義されるグローバル変数を参照するために使用します。C言語におけるextern変数に相当します。
グローバル変数の用途の一つに、複数のPOU間で共有するデータがあります。あるPOUにて同じ名前のローカル変数定義があるときにエラーになるのと同じように、あるユーザーのシステム全体にて同じ名前のグローバル変数定義があるときにもエラーになります。複数POU間で共有するデータとして使用するグローバル変数をエラーにならないようにシステム全体のどこで定義し、どうPOUで使用するのか、解決策や指針が必要です。
IEC 61131-3におけるグローバル変数は、configuration、又はresourceにのみ定義することができます。POU内においては、どのグローバル変数を使用するかを特定する必要があり、このときに外部変数宣言を使用し、名前によってグローバル変数を特定します。
configuration Cfg resource Rsc on Plc var_global gintv: int; gboolv: bool; end_var end_resource end_configuration
program Main var_external gintv: int; end_var // gintv := 2; // Rscのグローバル変数gintvへの書き込み end_program program Sub1 var_external gboolv: bool; end_var // gboolv := true; // Rscのグローバル変数gboolvへの書き込み gintv := 3; // Compile time error! Unknown symbol 'gintv'. end_program program Sub2 var_external gwordv: word; // Link time error! No global variable 'gwordv' found. end_var end_program
外部変数宣言は、グローバル変数を特定するためのリンクのようなものであり、グローバル変数を定義しないことに注意が必要です。
外部変数宣言に関して、次のようなケースはエラーです。
🛈なぜ外部変数宣言のような仕組みが必要なのか補足します。C言語にてextern宣言が必要な理由は、コンパイルの概念があるからです。つまり、C言語で記述された一つのソースファイルをコンパイルしモジュール化することを可能にするために、extern宣言の構文があります。extern宣言により変数の名前やデータ型を人間がコンパイラに教えてあげることにより、コンパイラはシステム全体を知らずともコンパイルできるのです。では、IEC 61131-3にコンパイルの概念はあるのでしょうか?外部変数宣言があるので、そうしたかったのかも知れませんが、実現しているベンダー(POUに対するコンパイルというメニューがあるベンダー)は皆無と思います。これは、IEC 61131-3の機能不足に原因があると考えています。C言語では、コンパイルを実現するためにもう一つの構文があります。関数のextern宣言(関数のプロトタイプ宣言)です。これもまた、関数の戻り値のデータ型や、引数のデータ型を人間がコンパイラに教えるためにあります。IEC 61131-3においても、ファンクションのみならず全てのPOUに対するextern宣言、もしくは、それらをまとめて行うimport構文相当があって初めてコンパイルを実現できます。グローバル変数を定義しているのに外部変数宣言を再度書かなければいけない理由に納得できないのはその通りで、IEC 61131-3の機能不足に一因があります。
(準備中)
(準備中)
(準備中)
(準備中)
エッジ属性は、FBインスタンス呼び出しにおけるbool型入力変数の値の変化を検出します。例えば、信号の立ち上がり検出では、オフからオンに変化したとき、又はオンからオフに変化したときだけ処理を実行したいときに有用です。
r_edge属性・f_edge属性を総称してエッジ属性といい、FBにおけるbool型入力変数に付加できます※1。
r_edge属性(Rising EDGE)は、オフからオンに変化したときにオン、即ち、前回呼び出し時の実引数の値がfalseで今回呼び出し時がtrueのとき、当該入力変数の値がtrueになります。f_edge属性(Falling EDGE)は、オンからオフに変化したときにオン、即ち、前回呼び出し時の実引数の値がtrueで、今回呼び出し時がfalseのとき、当該入力変数の値がtrueになります。
function_block REdgeAttrSample var_input d: bool r_edge; end_var end_function_block function_block FEdgeAttrSample var_input d: bool f_edge; end_var end_function_block program Main var r: REdgeAttrSample; f: FEdgeAttrSample; end_var // r_edge変数の例 r(true); // d=true、初回呼び出し時にtrueの場合、r_edge変数の値はtrueです。 r(true); // d=false r(false); // d=false r(false); // d=false r(true); // d=true // f_edge変数の例 f(false); // d=true、初回呼び出し時にfalseの場合、f_edge変数の値はtrueです。 f(false); // d=false f(true); // d=false f(true); // d=false f(false); // d=true end_program
※1 en変数にエッジ属性を付加して、いわゆるラダーの微分型命令を実現したいところですが、IEC 61131-3にそのような例はありません。そもそもユーザーにenの宣言を書かせるのか、書かせないのか実装依存ですので、文法的には、FBに対する属性とするのが良いのかも知れません。
(準備中)
LD言語におけるパワーフローや、FBD言語における信号の流れによる実行制御は、IEC 61131-3の特徴の一つです。電気的に導通しているときや信号が入ったときに機能を実行することをソフトウェア的に実現する仕組みとして、en変数(ENable)・eno変数(ENable Out)があります。en・enoを使用することにより、機能の実行・非実行、即ちPOUの呼び出し・非呼び出しを制御できます。本節に示すことは、ST言語にも適用されます。
en・enoは、個々のファンクション、FBインスタンス、およびメソッド内に次のように定義される変数です。
var_input en: bool := true; end_var var_output eno: bool; end_var
本変数をベンダーが定義しておくか、ユーザーが定義するかは実装依存ですが、どちらが定義するにしても、本変数には以下で示す特別な動作仕様があります。
重要なことは、POU呼び出し側でenがfalseであれば、そのPOUは実行されないし、呼び出し側でenoが falseと観測されれば、その呼び出し後の出力(出力変数、入出力変数、および戻り値)は意図したものではない可能性があるということです。また、上記動作仕様から、POU内部ではenの値はtrueであることが分かります。
POU内でユーザーがenoの値を書き換え可能ですが、ユーザーは上記のような動作仕様があることを踏まえておかなければいけません。
なお、ST言語において、en変数、eno変数を指定する場合、フォーマルな呼び出し、即ち仮引数指定での呼び出し記法を使用する必要があります。例えば、move(en := cond, in := src, out => dest, eno => result) のように記述します。move(src, dest) のようなインフォーマルな呼び出し記法では、en、enoを指定できません。
⚠️en変数・eno変数に関する本規定は有用なものですが、難解さと実装の選択肢の多さにより致命的なポータビリティ低下を招く規定の一つです。筆者個人としては、他ベンダーからの移行可能性を高めるためにも、ベンダーは次に示すユーザー向け仕様を維持する実装が望ましいと考えます。
ファンクションやメソッドを呼び出した結果の戻り値は、戻り値変数の値で決まります。
戻り値変数は、ファンクションやメソッド内に暗黙的に定義されるローカル変数です。
function is_even_num: bool var_input in: int; end_var // is_even_num := (in mod 2) = 0; end_function program Main var r: bool; end_var // r := is_even_num(3); // r=false r := is_even_num(6); // r=true end_program
戻り値のないファンクションで、戻り値変数が暗黙的に定義されるか否かは、実装依存です。
function no_return_function var_input in: int; end_var ... no_return_function := false; // ? end_function
(準備中)
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
可変長配列は、配列長が実行時に定まる変数の型です。入出力変数の型として使用できます※1。
可変長配列を使用することで、あらゆる配列要素数にも対応できる汎用的なライブラリを作成することができます。
// lreal型配列要素の総和を求めます。 function sum: lreal var_in_out values: array[*] of lreal; // 1次元可変長配列変数の定義 end_var var_temp 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
※1 規格上は「可変長配列をFBの入力変数、出力変数にも適用可能」とありますが、動的メモリ確保が必要となることについての説明なく規定は不完全です。また「可変長配列をファンクションの入出力変数、およびFBの入力変数、入出力変数、出力変数のデータ型として適用可能」とちぐはぐな規定ともなっており、本書では「可変長配列を入出力変数の型として使用可能」と限定しました。
(準備中)
ファンクションは、一般的なプログラミング言語の関数(function)と同等の機能をもつPOUです。ファンクションに引数(入力)を与えて呼び出すことにより、ファンクション内の処理が実行され、呼び出した側では、処理の結果である戻り値(出力)を得ることができます。ファンクションを適切に利用することにより、同じプログラムコードを何度も書く手間を減らし、可読性を高め、管理を容易にします。
// 戻り値のデータ型がlrealのファンクションabsmaxの定義です。 // 2つの引数のうち絶対値の大きい方を結果として得ます。 function absmax: lreal // absmaxは、lreal型のinput1とinput2の2つの引数をもちます。 var_input input1: lreal; input2: lreal; end_var // // ファンクション名と同じ変数(戻り値変数)の値を、呼び出し側で戻り値として得ることができます。 if abs(input1) > abs(input2) then absmax := input1; else absmax := input2; end_if; end_function program Main var y1, y2: lreal; end_var // // ファンクションabsmaxの呼び出しです。 // input1に2、input2に3を設定し、absmaxの処理が実行され、y1に3が代入されます。 y1 := absmax(input1 := 2.0, input2 := 3.0); // input1に-7、input2に5を設定し、absmaxの処理が実行され、y2に-7が代入されます。 y2 := absmax(-7.0, 5.0); end_program
// 二つのint型変数の値を入れ替えるファンクションです。戻り値はありません。 function swap_INT // swap_INTは、in1とin2の2つの引数をもちます。 var_in_out in1, in2: int; end_var var_temp t: int; end_var // t := in1; in1 := in2; in2 := t; end_function program Main var x1: int := 2; x2: int := 3; end_var // x1=2, x2=3 swap_INT(x1, x2); // x1=3, x2=2 end_program
一般的なプログラミング言語の関数との違いや特徴を以下に示します。
ファンクション定義を一般的に次のように記述できます。
function <ファンクション名>:<戻り値のデータ型> <引数変数の宣言> <変数の宣言> <処理> end_function
<ファンクション名>は、ファンクションを呼び出すときに使用する名前です。この名前を使って、プログラムコードの別の場所からファンクション内の処理を実行することができます。ファンクション内では、ファンクションの実行結果である戻り値を意味する変数の名前にもなります。
<戻り値のデータ型>は、ファンクションの戻り値のデータ型です。コロン(:)以降を省略する場合、戻り値のないファンクションとなります。また、配列の戻り値とする場合、配列型の直接派生型での指定が必要になります※1。
<引数変数の宣言>には、以下に示す3つの引数変数を宣言することができます。
<変数の宣言>には、ファンクション内で使用する変数を宣言します。内部変数(キーワードvar)、一時変数(var_temp)、および外部変数(var_external)※2を宣言できます。ファンクションにおいては、内部変数と一時変数の違いはありません※3。
<処理>には、ファンクションが実行する処理(プログラムコード)を記述します。制御文や式を記述できます。<ファンクション名>を名前とする戻り値変数に値を代入しておくことで、呼び出し側でその値を受け取ることができます。<処理>の終端に達するか、return文が実行されると、呼び出し元に制御が戻ります。なお、自分自身を呼び出す再帰呼び出しの可否については実装依存です※4。
※1 function fun: array[0..2] of lreal ~ end_functionのような配列戻り値の構文サポートについては実装依存です。
※2 入力が同じであれば、同じ出力を返す関数を一般的に純関数といいます。純関数には、関数外へ副作用がないことに起因する様々なメリットがあります。一方、外部変数宣言を含むファンクションは、ファンクション外部の環境に依存したり影響を与えるため、純関数となりません。
※3 IEC 61131-3は、ファンクション内でのインスタンス変数の定義を禁止しています。インスタンスは状態を持つから、というのが理由のようですが、基本レベルの汎用的なFBやクラスであれば、普通に使いたくなるものです。ベンダーは必ずしも本制約に従う必要はないと考えます。
※4 再帰呼び出しを制限する主な理由は、実行系において、必要量が予測困難で動的なメモリ確保を必要とするためです。
ファンクション呼び出しの記述方法には、仮引数を記述するフォーマル呼び出しと、仮引数を省略するインフォーマル呼び出しがあります。インフォーマル呼び出しでは、すべての引数の値を記述しないといけないのに対し、フォーマル呼び出しでは、一部又は全ての引数の記述を省略できます。省略した引数の値は、当該変数の初期値やデータ型のデフォルト値が処理系により設定されています。
ファンクションのフォーマル呼び出しは、一般的に次のように記述できます。
<ファンクション名>(<仮引数名1> := <実引数値1>, <仮引数名2> := <実引数値2>, ...)
例:
y := absmax(input1 := 13, input2 := -17); // -17 move(in := 3, out => intv); // intvは3。出力変数の演算子には、=>を使用します。
フォーマル呼び出しにおいて、入力変数と出力変数の引数を省略することができます。省略する場合、変数の値は、変数宣言時に指定した初期値、又は初期値を指定していなければ、データ型のデフォルト値です。入出力変数の引数を省略することはできません。また、出力変数の引数のみ演算子に => を使用します(e.g. output2 => intv)。演算子からも分かる通り、呼び出し後に左辺値を右辺に代入します。bool型の出力変数の場合のみ使用できるnot bool_output => boolvの構文では、左辺値を否定した値を右辺に代入します※1。
フォーマル呼び出しでのみen変数・eno変数を指定できます。インフォーマル呼び出しでは、en変数・eno変数を指定できません。
y := div( in1 := intv1, in2 := intv2, not eno => maybe_intv2_is_zero);
ファンクションのインフォーマル呼び出しは、一般的に次のように記述できます。
<ファンクション名>(<実引数値1>, <実引数値2>, ...)
例:
y := absmax(13, -17); // -17 move(3, intv); // intvは3
インフォーマル呼び出しの括弧内で引数を記述する順番は、ファンクション定義時の引数変数定義の順番に一致します。en変数・eno変数を含まないことに注意してください。
// 線形合同法による擬似乱数生成器 function LCGrand: ULINT var_in_out state: ULINT; end_var var_input a, b, m: ULINT; end_var // state := (a * state + b) MOD m; LCGrand := state; end_function // Park & Miller提案の定数に従う擬似乱数生成器 function LMrand: ULINT var_in_out state: ULINT; end_var // LMrand := LCGrand(state, 48271, 0, 16#7fff_ffff); // state, a, b, mの順番です。 end_function
※1 POU呼び出しにおけるnot bool_output => boolvの構文は、LD言語やFBD言語の出力変数信号線の"o"の表記に対応します。
※本章には、オブジェクト指向拡張されたファンクションブロックの説明を含みません。
ファンクションブロック(FB)は、ただ一つの処理が関連付けされた構造体、もしくはクラスに相当するPOUです。コンピュータサイエンスにおけるカプセル化の特徴をもちます。FBは、特定の種類のプログラム要素を作成するためのテンプレートとして機能し、属性(データ)と一つの処理をFBにまとめて定義し、管理できます。
// 呼び出すたびにカウントするFBの定義です。 // 属性nと、インクリメントする処理がまとめられています。 function_block Counter var_output n: int := 0; end_var // n := n + 1; end_function_block program Main var // Counter FBのインスタンスの宣言です。 // c.nは0で初期化されます。 c: Counter; v: int; end_var // c(); v := c.n; // 1 c(); c(); v := c.n; // 3 end_program
FBに定義した処理の実行は、FB呼び出し、又はFBインスタンス呼び出しと呼ばれるファンクション呼び出しに似た構文を用います。ファンクションとの違いの一つが、呼び出しを、FBインスタンスと呼ぶFBの実体に対して行うことです。FBインスタンスは、構造体の変数と同様、ユーザーが必要なときに定義し、FBインスタンスごとに独立した属性値(FBインスタンスメンバの値)の環境の元で処理を実行させることができます。また、その環境は呼び出しによって変更されることもあり、同じFBインスタンス、同じ入力であっても呼び出しの結果は、必ずしも一致しません。
構造体変数のメンバアクセスと同じように、FBインスタンスのメンバにもドット演算子を使用してアクセスできます(e.g. myfbinstance.outputvar)。ただし、デフォルトでアクセスできるのは、FBの入力変数や出力変数のみです。
// 平均値を計算するFBの定義です。 function_block Mean var_input x: lreal; end_var var_output output: lreal := 0.0; end_var var n: dint := 0; sum: lreal := 0.0; end_var // sum := sum + x; n := n + 1; output := sum / n; end_function_block program Main var // Mean FBのインスタンスの宣言です。 a, b: Mean; m_a, m_b: lreal; end_var // a(2); a(3); b(2); b(3); b(5); b(7); // m_a := a.output; // 2.5=(2+3)/2 m_b := b.output; // 4.25=(2+3+5+7)/4 end_program
一般的なプログラミング言語の知識を持たれている方は、本節の内容に注意してください。
FB定義を一般的に次のように記述できます。
function_block <FB名> <引数変数の宣言> <変数の宣言> <処理> end_function_block
<FB名>は、FBインスタンスを定義するときに使用する名前です。
<引数変数の宣言>には、以下に示す3つの引数変数を宣言することができます。
<変数の宣言>には、FB内で使用する変数を宣言します。内部変数(キーワードvar)、一時変数(var_temp)、および外部変数(var_external)を宣言できます。内部変数は呼び出しにおいて値を保持しますが、一時変数は呼び出しのたびに初期化されます。
function_block FBVarBehaviorSample var_output o: int := 1; end_var var v: int := 0; end_var var_temp t: int := 0; end_var // v := v + 100; t := t + 10; o := o + v + t; end_function_block program Main var x: FBVarBehaviorSample; y: int; end_var // x(o => y); // y=111=1+100+10 x(o => y); // y=321=111+200+10 end_program
<処理>には、FBが実行する処理(プログラムコード)を記述します。制御文や式を記述できます。<処理>の終端に達するか、RETURN文が実行されると、呼び出し元に制御が戻ります。
FBインスタンス呼び出しの記述方法には、仮引数を記述するフォーマル呼び出しと、仮引数を省略するインフォーマル呼び出しがあります。インフォーマル呼び出しでは、すべての引数の値を記述しないといけないのに対し、フォーマル呼び出しでは、一部又は全ての引数の記述を省略できます。省略した引数の値は、前回呼び出したときの値が保持されるか、初回呼び出し時であれば、当該変数の初期値やデータ型のデフォルト値が処理系により設定されています。
function_block FBArgBehaviorSample var_input i1: int := 1; i2: int; end_var var_output o: int; end_var // o := i1 + i2; end_function_block program Main var x: FBArgBehaviorSample; y: int; end_var // x(o => y); // y=1; 1+0 x(in1 := 2, in2 := 3, o => y); // y=5; 2+3 x(in2 := 5, o => y); // y=7; 2+5 end_program
FBインスタンスのフォーマル呼び出しは、一般的に次のように記述できます。
<FBインスタンス名>(<仮引数名1> := <実引数値1>, <仮引数名2> := <実引数値2>, ...)
例:
ton_instance(in := on, pt := time#5s, q => timeup, et => cur); // 出力変数の演算子には、=>を使用します。 ctu_int_instance(cu := counting, pv := 17, q => countup); // 引数を省略できます。
フォーマル呼び出しにおいて、入力変数と出力変数の引数を省略することができます。省略する場合、変数の値は、変数宣言時に指定した初期値、又は初期値を指定していなければ、データ型のデフォルト値です。入出力変数の引数を省略することはできません。また、出力変数の引数のみ演算子に=>を使用します(e.g. q => trig)。演算子からも分かる通り、呼び出し後に左辺値を右辺に代入します。BOOL型の出力変数の場合のみ使用できるnot bool_output => boolvの構文では、左辺値を否定した値を右辺に代入します※1。
フォーマル呼び出しでのみen変数・eno変数を指定できます。インフォーマル呼び出しでは、en変数・eno変数を指定できません。
ctu_int_instance( en := no_error, cu := using_stock, pv := 19, not q => stock_available, not eno => no_error);
FBインスタンスのインフォーマル呼び出しは、一般的に次のように記述できます。
<FBインスタンス名>(<実引数値1>, <実引数値2>, ...)
例:
ton_instance(on, time#5s, trig, cur); // in, pt, q, et ctu_int_instance(counting, false, 17, countup, counter); // cu, r, pv, q, cv
インフォーマル呼び出しの括弧内で引数を記述する順番は、FB定義時の引数変数定義の順番に一致します。en変数・eno変数を含まないことに注意してください。
function_block ArgumentsOrderSample var_input in1, in2: int; in3: int; end_var var_output q: bool; end_var var_input opt: int := 0; end_var end_function_block program Main var x: ArgumentsOrderSample; r: bool; end_var // x( 2, // in1 3, // in2 5, // in3 r, // q 0 // opt ); end_program
※1 POU呼び出しにおけるnot bool_output => boolvの構文は、LD言語やFBD言語の出力変数信号線の"o"の表記に対応します。
FBインスタンスを介したメンバへのアクセスは、一般的に次のように記述できます。
<FBインスタンス名>.<ローカル変数名>
例:
// 入力変数へのアクセス ton_instance.en := true; ton_instance.in := on; ton_instance.pt := time#5s; // インスタンス呼び出し ton_instance(); // 出力変数へのアクセス timeup := ton_instance.q; cur := ton_instance.et; no_error := ton_instance.eno; // 上記は、以下のプログラムコードと等価です。 // ton_instance(en := true, in := on, pt := time#5s, q => timeup, et => cur, eno => no_error);
デフォルトでは、内部変数にはアクセスできません。public修飾子のある内部変数にはアクセスできます。
function_block AccessModifierSample var private_v: int; end_var var public public_v: int; end_var end_function_block program Main var x: AccessModifierSample; end_var // x.private_v := 2; // Compile time error! Inaccesible element. x.public_v := 3; // ok end_program
なお、アクセス修飾は静的変数に対してのみ可能です。入力変数・出力変数は暗黙的に常にpublic、外部変数は暗黙的に常にprotected、一時変数は暗黙的に常にprivateです。入出力変数について、アクセス修飾子の概念があるかないかは実装依存ですが、呼び出した内部でのみアクセス可能です。
(準備中)
(準備中)
(準備中)
(準備中)
(準備中)
(準備中)
(準備中)
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_task with t: Main(); end_resource var_config rsc.main_task.v1.n at %IW5 : int := 16; rsc.main_task.v2.n at %IW6 : int := 17; end_var end_configuration
at属性の*指定については、FBだけでなく、プログラムPOU内の変数に対しても使用できます。出力変数、静的変数、グローバル変数に使用できます。プログラム実行前に、*が設定されているすべての変数に対して、at属性が設定されていなければ異常です。%I*の表現については、他にも特性に応じて、%I*, %QW*などを使用できます。※1
※1 *を含むパス文字列は、部分的にat属性を設定するという意味で、規格上は「部分指定」という表現が使用されています。全てを省略する%*という表現は、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(); // f→g→hか、g→h→fの順に呼ばれるか実装依存
| 演算子 | 名称 | 優先順位 | 結合規則 |
|---|---|---|---|
() |
呼び出し | 11 | 実装依存(一般的に左) |
. |
メンバアクセス・部分アクセス | 実装依存 | 実装依存(一般的に左) |
[] |
配列添え字 | 実装依存 | 実装依存(一般的に左) |
# |
列挙子アクセス | 実装依存 | 実装依存(一般的に左) |
ref |
リファレンス | 実装依存 | 実装依存(一般的に右) |
^ |
デリファレンス | 10 | 実装依存(一般的に右) |
+ - |
単項プラス・単項マイナス | 9 | 実装依存(一般的に右) |
not |
論理否定・ビット否定 | 9 | 実装依存(一般的に右) |
** |
べき乗 | 8 | 左 |
* |
乗算 | 7 | 左 |
/ |
除算 | 7 | 左 |
mod |
剰余 | 7 | 左 |
+ |
加算 | 6 | 左 |
- |
減算 | 6 | 左 |
> >= <= < |
比較 | 5 | 左 |
= <> |
等価 非等価 | 4 | 左 |
and & |
論理積・ビット積 | 3 | 左 |
xor |
排他的論理和・ビット排他的論理和 | 2 | 左 |
or |
論理和・ビット和 | 1 | 左 |
:= => ?= |
代入・引数代入・試行代入 | 実装依存 | 実装依存(一般的に右) |
※1 優先順位が実装依存となっている演算子は、IEC 61131-3の規定に"演算子"として定義されていないものです。一般的なプログラミング言語で演算子として定義されているものは筆者の判断で含めています。
呼び出しにおいて、入力変数(var_input)と出力変数(var_output)は値渡しであり、入出力変数(var_in_out)は参照渡しです※1。入出力変数は、C++言語の参照変数と同等の機能をもちます。
以下の例に示す通り、入出力変数への書き込みは、呼び出し元の変数への書き込みになります。
type t: struct m: int; end_struct; end_type function call_by_ref_sample var_in_out rintv: int; rstrv: string; rtv: t; end_var // rintv := 4; rstrv := 'five'; rtv.m := 6; end_function program Main var intv: int := 1; strv: string := 'two'; tv: t := (m := 3); end_var // // intv=1, strv='two', tv.m=3 call_by_ref_sample( rintv := intv, rstrv := strv, rtv := tv ); // intv=4, strv='five', tv.m=6 end_program
参照渡しは、呼び出し元の変数の値を変更する必要がある機能の実現で有用です。また、一般的な参照渡しの特性として文字列型、配列、構造体型などの大きなデータを比較的高速に渡すことができます。
※1 IEC 61131-3には、入出力変数が参照渡しであるとは明確には記載されていません。しかし、呼び出し元の変数に作用あること、入出力変数による多態性の特性あること、ファンクションの可変長配列の宣言が入出力変数にのみ許されていることから参照渡しでの実現が望ましいと判断しています。他の実現方法として、呼び出し直前に値を渡し、呼び出し直後に値を受け取ることで、参照渡しに似た機能を実現できます。この実現方法の場合、呼び出しの処理中には呼び出し元の変数に作用がない特性があります。また、書き込みが一回多いため高速化どころか低速化します。
(準備中)
形式として、仮引数有り呼び出し(フォーマルな呼び出し)と、仮引数無し呼び出し(インフォーマルな呼び出し)があります。一部の引数を省略するためには、仮引数有り呼び出しを使用する必要があります。
pou_instance(in1 := 2, in2 := true, out => v); // インスタンス呼び出し(フォーマル) move(in := 3, out => intv); // 戻り値のないファンクション呼び出し(フォーマル) strv := concat(in1 := key, in2 := ':', in3 := value); // 戻り値のあるファンクション呼び出し(フォーマル)
pou_instance(2, true, v); // インスタンス呼び出し(インフォーマル) move(3, intv); // 戻り値のないファンクション呼び出し(インフォーマル) strv := concat(key, ':', value); // 戻り値のあるファンクション呼び出し(インフォーマル)
(準備中)
(準備中)
(準備中)
(準備中)
(準備中)
(準備中)
(準備中)
演算子**は、べき乗を計算します。
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;
SINT_TO_INT, SINT_TO_DINT, SINT_TO_LINT, SINT_TO_USINT, SINT_TO_UINT, SINT_TO_UDINT, SINT_TO_ULINT, SINT_TO_REAL, SINT_TO_LREAL, INT_TO_SINT, INT_TO_DINT, INT_TO_LINT, INT_TO_USINT, INT_TO_UINT, INT_TO_UDINT, INT_TO_ULINT, INT_TO_REAL, INT_TO_LREAL, DINT_TO_SINT, DINT_TO_INT, DINT_TO_LINT, DINT_TO_USINT, DINT_TO_UINT, DINT_TO_UDINT, DINT_TO_ULINT, DINT_TO_REAL, DINT_TO_LREAL, LINT_TO_SINT, LINT_TO_INT, LINT_TO_DINT, LINT_TO_USINT, LINT_TO_UINT, LINT_TO_UDINT, LINT_TO_ULINT, LINT_TO_REAL, LINT_TO_LREAL, USINT_TO_SINT, USINT_TO_INT, USINT_TO_DINT, USINT_TO_LINT, USINT_TO_UINT, USINT_TO_UDINT, USINT_TO_ULINT, USINT_TO_REAL, USINT_TO_LREAL, UINT_TO_SINT, UINT_TO_INT, UINT_TO_DINT, UINT_TO_LINT, UINT_TO_USINT, UINT_TO_UDINT, UINT_TO_ULINT, UINT_TO_REAL, UINT_TO_LREAL, UDINT_TO_SINT, UDINT_TO_INT, UDINT_TO_DINT, UDINT_TO_LINT, UDINT_TO_USINT, UDINT_TO_UINT, UDINT_TO_ULINT, UDINT_TO_REAL, UDINT_TO_LREAL, ULINT_TO_SINT, ULINT_TO_INT, ULINT_TO_DINT, ULINT_TO_LINT, ULINT_TO_USINT, ULINT_TO_UINT, ULINT_TO_UDINT, ULINT_TO_REAL, ULINT_TO_LREAL
REAL_TO_SINT, REAL_TO_INT, REAL_TO_DINT, REAL_TO_LINT, REAL_TO_USINT, REAL_TO_UINT, REAL_TO_UDINT, REAL_TO_ULINT, REAL_TO_LREAL, LREAL_TO_SINT, LREAL_TO_INT, LREAL_TO_DINT, LREAL_TO_LINT, LREAL_TO_USINT, LREAL_TO_UINT, LREAL_TO_UDINT, LREAL_TO_ULINT, LREAL_TO_REAL
BYTE_TO_WORD, BYTE_TO_DWORD, BYTE_TO_LWORD, WORD_TO_BYTE, WORD_TO_DWORD, WORD_TO_LWORD, DWORD_TO_BYTE, DWORD_TO_WORD, DWORD_TO_LWORD, LWORD_TO_BYTE, LWORD_TO_WORD, LWORD_TO_DWORD
BYTE_TO_SINT, BYTE_TO_INT, BYTE_TO_DINT, BYTE_TO_LINT, BYTE_TO_USINT, BYTE_TO_UINT, BYTE_TO_UDINT, BYTE_TO_ULINT, WORD_TO_SINT, WORD_TO_INT, WORD_TO_DINT, WORD_TO_LINT, WORD_TO_USINT, WORD_TO_UINT, WORD_TO_UDINT, WORD_TO_ULINT, DWORD_TO_SINT, DWORD_TO_INT, DWORD_TO_DINT, DWORD_TO_LINT, DWORD_TO_USINT, DWORD_TO_UINT, DWORD_TO_UDINT, DWORD_TO_ULINT, LWORD_TO_SINT, LWORD_TO_INT, LWORD_TO_DINT, LWORD_TO_LINT, LWORD_TO_USINT, LWORD_TO_UINT, LWORD_TO_UDINT, LWORD_TO_ULINT
SINT_TO_BYTE, SINT_TO_WORD, SINT_TO_DWORD, SINT_TO_LWORD, INT_TO_BYTE, INT_TO_WORD, INT_TO_DWORD, INT_TO_LWORD, DINT_TO_BYTE, DINT_TO_WORD, DINT_TO_DWORD, DINT_TO_LWORD, LINT_TO_BYTE, LINT_TO_WORD, LINT_TO_DWORD, LINT_TO_LWORD, USINT_TO_BYTE, USINT_TO_WORD, USINT_TO_DWORD, USINT_TO_LWORD, UINT_TO_BYTE, UINT_TO_WORD, UINT_TO_DWORD, UINT_TO_LWORD, UDINT_TO_BYTE, UDINT_TO_WORD, UDINT_TO_DWORD, UDINT_TO_LWORD, ULINT_TO_BYTE, ULINT_TO_WORD, ULINT_TO_DWORD, ULINT_TO_LWORD
LWORD_TO_LREAL, LREAL_TO_LWORD, DWORD_TO_REAL, REAL_TO_DWORD
BOOL_TO_BYTE, BOOL_TO_WORD, BOOL_TO_DWORD, BOOL_TO_LWORD
BYTE_TO_BOOL, WORD_TO_BOOL, DWORD_TO_BOOL, LWORD_TO_BOOL
BOOL_TO_SINT, BOOL_TO_INT, BOOL_TO_DINT, BOOL_TO_LINT, BOOL_TO_USINT, BOOL_TO_UINT, BOOL_TO_UDINT, BOOL_TO_ULINT
TIME_TO_LTIME, LTIME_TO_TIME, DT_TO_DATE, DT_TO_LDATE, DT_TO_TOD, DT_TO_LTOD, DT_TO_LDT, LDT_TO_DATE, LDT_TO_LDATE, LDT_TO_TOD, LDT_TO_LTOD, LDT_TO_DT, TOD_TO_LTOD, LTOD_TO_TOD
CHAR_TO_BYTE, CHAR_TO_WORD, CHAR_TO_DWORD, CHAR_TO_LWORD, WCHAR_TO_WORD, WCHAR_TO_DWORD, WCHAR_TO_LWORD
STRING_TO_WSTRING, WSTRING_TO_STRING, CHAR_TO_WCHAR, WCHAR_TO_CHAR, CHAR_TO_STRING, STRING_TO_CHAR, WCHAR_TO_WSTRING, WSTRING_TO_WCHAR
TO_SINT, TO_INT, TO_DINT, TO_LINT, TO_USINT, TO_UINT, TO_UDINT, TO_ULINT, TO_REAL, TO_LREAL, TO_BOOL, TO_BYTE, TO_WORD, TO_DWORD, TO_LWORD, TO_TIME, TO_LTIME, TO_DATE, TO_LDATE, TO_TOD, TO_LTOD, TO_DT, TO_LDT, TO_STRING, TO_WSTRING, TO_CHAR, TO_WCHAR
UPPER_BOUND, LOWER_BOUND
NOT, AND, OR, XOR
ABS, SQRT, LN, LOG, EXP, SIN, COS, TAN, ASIN, ACOS, ATAN, ATAN2, ADD, MUL, SUB, DIV, MOD, EXPT
MOVE
SHL, SHR, ROL, ROR
SEL, MAX, MIN, LIMIT, MUX
LEN, LEFT, RIGHT, MID, CONCAT, INSERT, DELETE, REPLACE, FIND
ADD_TIME, ADD_LTIME, ADD_TOD_TIME, ADD_LTOD_LTIME, ADD_DT_TIME, ADD_LDT_LTIME, SUB_TIME, SUB_LTIME, SUB_DATE_DATE, SUB_LDATE_LDATE, SUB_TOD_TIME, SUB_LTOD_LTIME, SUB_TOD_TOD, SUB_LTOD_LTOD, SUB_DT_TIME, SUB_LDT_LTIME, SUB_DT_DT, SUB_LDT_LDT, MUL_TIME, MUL_LTIME, DIV_TIME, DIV_LTIME, CONCAT_DATE_TOD, CONCAT_DATE_LTOD, CONCAT_DATE, CONCAT_TOD, CONCAT_LTOD, CONCAT_DT, CONCAT_LDT, SPLIT_DATE, SPLIT_TOD, SPLIT_LTOD, SPLIT_DT, SPLIT_LDT, DAY_OF_WEEK
REAL_TRUNC_SINT, REAL_TRUNC_INT, REAL_TRUNC_DINT, REAL_TRUNC_LINT, REAL_TRUNC_USINT, REAL_TRUNC_UINT, REAL_TRUNC_UDINT, REAL_TRUNC_ULINT, LREAL_TRUNC_SINT, LREAL_TRUNC_INT, LREAL_TRUNC_DINT, LREAL_TRUNC_LINT, LREAL_TRUNC_USINT, LREAL_TRUNC_UINT, LREAL_TRUNC_UDINT, LREAL_TRUNC_ULINT, TRUNC_SINT, TRUNC_INT, TRUNC_DINT, TRUNC_LINT, TRUNC_USINT, TRUNC_UINT, TRUNC_UDINT, TRUNC_ULINT
BYTE_BCD_TO_SINT, BYTE_BCD_TO_INT, BYTE_BCD_TO_DINT, BYTE_BCD_TO_LINT, BYTE_BCD_TO_USINT, BYTE_BCD_TO_UINT, BYTE_BCD_TO_UDINT, BYTE_BCD_TO_ULINT, WORD_BCD_TO_SINT, WORD_BCD_TO_INT, WORD_BCD_TO_DINT, WORD_BCD_TO_LINT, WORD_BCD_TO_USINT, WORD_BCD_TO_UINT, WORD_BCD_TO_UDINT, WORD_BCD_TO_ULINT, DWORD_BCD_TO_SINT, DWORD_BCD_TO_INT, DWORD_BCD_TO_DINT, DWORD_BCD_TO_LINT, DWORD_BCD_TO_USINT, DWORD_BCD_TO_UINT, DWORD_BCD_TO_UDINT, DWORD_BCD_TO_ULINT, LWORD_BCD_TO_SINT, LWORD_BCD_TO_INT, LWORD_BCD_TO_DINT, LWORD_BCD_TO_LINT, LWORD_BCD_TO_USINT, LWORD_BCD_TO_UINT, LWORD_BCD_TO_UDINT, LWORD_BCD_TO_ULINT, BCD_TO_SINT, BCD_TO_INT, BCD_TO_DINT, BCD_TO_LINT, BCD_TO_USINT, BCD_TO_UINT, BCD_TO_UDINT, BCD_TO_ULINT, SINT_TO_BCD_BYTE, SINT_TO_BCD_WORD, SINT_TO_BCD_DWORD, SINT_TO_BCD_LWORD, INT_TO_BCD_BYTE, INT_TO_BCD_WORD, INT_TO_BCD_DWORD, INT_TO_BCD_LWORD, DINT_TO_BCD_BYTE, DINT_TO_BCD_WORD, DINT_TO_BCD_DWORD, DINT_TO_BCD_LWORD, LINT_TO_BCD_BYTE, LINT_TO_BCD_WORD, LINT_TO_BCD_DWORD, LINT_TO_BCD_LWORD, USINT_TO_BCD_BYTE, USINT_TO_BCD_WORD, USINT_TO_BCD_DWORD, USINT_TO_BCD_LWORD, UINT_TO_BCD_BYTE, UINT_TO_BCD_WORD, UINT_TO_BCD_DWORD, UINT_TO_BCD_LWORD, UDINT_TO_BCD_BYTE, UDINT_TO_BCD_WORD, UDINT_TO_BCD_DWORD, UDINT_TO_BCD_LWORD, ULINT_TO_BCD_BYTE, ULINT_TO_BCD_WORD, ULINT_TO_BCD_DWORD, ULINT_TO_BCD_LWORD, TO_BCD_BYTE, TO_BCD_WORD, TO_BCD_DWORD, TO_BCD_LWORD
TO_BIG_ENDIAN, TO_LITTLE_ENDIAN, BIG_ENDIAN_TO, LITTLE_ENDIAN_TO
IS_VALID, IS_VALID_BCD
SR, RS
R_TRIG, F_TRIG
CTU_INT, CTU_DINT, CTU_LINT, CTU_UDINT, CTU_ULINT, CTD_INT, CTD_DINT, CTD_LINT, CTD_UDINT, CTD_ULINT, CTUD_INT, CTUD_DINT, CTUD_LINT, CTUD_UDINT, CTUD_ULINT
TP, TP_TIME, TP_LTIME, TON, TON_TIME, TON_LTIME, TOF, TOF_TIME, TOF_LTIME
JiecLib@GitHubに有用・無用含めて自作のライブラリを公開しています。ツールの試用期間活用し、オムロン、キーエンスにて動作確認しています。不具合ありましたら、こちらのブログにご連絡いただければ可能な範囲で対応致します。
本書のプログラム例などで示されるIEC 61131-3規格のテキスト表現をIEC 61131-10規格のXML表現に変換するツールJieccも使用ください。いくつかのベンダーでインポートできるようになります。
see JiecLib
see JiecLib
see JiecLib
see JiecLib
see JiecLib
文字列操作に関するIEC 61131-3の特徴として、文字のインデクスは0始まりではなく、1始まりです。例えば、FIND('abcde', 'cd') は3を得ます。
see JiecLib
指定位置の文字を取得する方法は、ベンダーの準拠状況で異なります。
P番目の文字を取得するには、次のようにします。
var
strv: string;
p: dint;
c: char;
end_var
//
strv := 'abcdefg';
p := 1;
c := strv[p]; // 'a'
p := 2;
c := strv[p]; // 'b'
see JiecLib
文字列が等しいか調べるには、等価比較演算子=を使用するか、EQファンクションを使用します。
strv := 'xyz'; boolv := strv = 'xyz'; // true boolv := strv = 'XYZ'; // false boolv := EQ(strv, 'xyz'); // true
see JiecLib
see JiecLib
see JiecLib
see JiecLib
文字列を切り出すには、MIDファンクションを使用します。
P番目の文字からL文字切り出すには次のようにします。
strv1 := 'abcdef'; p := 2; l := 3; strv2 := MID(strv1, l, p); // 'bcd' 2番目の文字から3文字
P番目の文字から末尾まで切り出すには次のようにします。
strv1 := 'abcdef'; p := 2; strv2 := MID(strv1, LEN(strv1), p); // 'bcdef' 2番目の文字から末尾まで
P1番目からP2番目までを切り出すには次のようにします。
strv1 := 'abcdef'; p1 := 2; p2 := 5; strv2 := MID(strv1, p2 - p1 + 1, p1); // 'bcde' 2番目の文字から5番目までの文字
P番目の文字からL文字削除するには次のようにします。
strv1 := 'abcdef'; p := 2; l := 3; strv2 := DELETE(strv1, l, p); // 'aef' 2番目の文字から3文字を削除
P番目の文字から末尾まで削除するには次のようにします。
strv1 := 'abcdef'; p := 3; strv2 := DELETE(strv1, LEN(strv1), p); // 'ab' 3番目の文字から末尾までを削除
P1番目からP2番目までの文字を削除するには次のようにします。
strv1 := 'abcdef'; p1 := 2; p2 := 5; strv2 := DELETE(strv1, p2 - p1 + 1, p1); // 'af' 2番目の文字から5番目までの文字を削除
see JiecLib
see JiecLib
see JiecLib
see JiecLib
see JiecLib
(準備中)
see JiecLib
see JiecLib
see JiecLib
P番目の文字の位置に文字列を挿入するには、INSERTファンクションを使用します。
strv := 'abc'; p := 2; inserted := INSERT(strv, 'xyz', p); // 'axyzbc'
see JiecLib
see JiecLib
(準備中)
文字の文字コードを取得するIEC 61131-3規格標準のファンクションは提供されていません。
次のようにします。
strv := 'Ab4'; code := BYTE_TO_USINT(CHAR_TO_BYTE(strv[1])); // 16#41 'A' code := BYTE_TO_USINT(CHAR_TO_BYTE(strv[2])); // 16#62 'b' code := BYTE_TO_USINT(CHAR_TO_BYTE(strv[3])); // 16#34 '4'
※ASCII範囲内の文字に限定しています。
see JiecLib
see JiecLib
(準備中)
(準備中)
(準備中)
(準備中)
(準備中)
see JiecLib