IEC 61131-3 ST言語

はじめに

IEC 61131-3

IEC 61131-3は、FA(Factory Automation)のプログラミング言語に関する国際規格です。次の特徴があります。

  1. 論理型・整数型・実数型に加え、ビット列型・時間型・時刻型・列挙型・構造体型などの豊富なデータ型
  2. Java™言語ライクなオブジェクト指向もできるマルチパラダイムプログラミング言語
  3. テキスト言語ST、グラフィカル言語LD・FBD・SFCの4言語を定義 ※本サイトでは、ST言語を中心に扱います。
  4. 静的型付けと動的型付けの混在
  5. 動的メモリ確保なし
  6. 物理デバイスのメモリにアクセスする構文
  7. サイクリック実行があることを前提とする言語要素

このページの目的

日本のFAにおけるIEC 61131-3の浸透は十分とは言えません。従来のアドレスプログラミングやラダーによる巻物スタイルのプログラミングは根強く残っており、IEC 61131-3が良いだろうことは感じていても、約20年もの間、IEC 61131-3への移行は進んでいません。

これには、IEC 61131-3規格側の問題、PLCメーカー側の問題、およびユーザー側の様々な事情があります。

  1. ユーザ側に非IEC 61131-3の膨大な資産があり、IEC 61131-3に切り替えてしまうと教育も含め、生産性が急激に下がる。
  2. IEC 61131-3のプログラムを実行したり試してみる手頃な環境がなく、導入を検討する人や学ぼうとする人の機会損失している。
  3. IEC 61131-3仕様の理解が困難であり、PLCメーカーのIEC 61131-3に対する準拠度合いも高くなく、やりたいことができるのか分からない。
  4. 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の規定ですが、各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のいずれかが最初に実行される。
同一優先度にあるプログラム3とプログラム5のどちらが先に実行されるかは不確定。

プログラムインスタンスには、プログラム間でデータをやりとりするための引数があります。。。これが、IEC 61131-3の規定ですが、各PLCメーカーの実装はそうではありません。プログラム間でデータをやりとりするには、グローバル変数を使用します。

コメント

RightsideIsLinecomment := TRUE; // これは行コメントです。

(* これは
複数行コメント
です。 *)
AbovesideIsBlockcomment := TRUE;

RightsideIsAnotherBlockcomment := TRUE; /* これは
もう一つの
複数行コメント
です。 */

文字集合・エンコーディング

プログラムやコメントで使用できる文字集合は、ISO/IEC 10646に従います。エンコーディングは、各メーカーの実装依存です。

ISO/IEC 10646を符号化文字集合と解釈するか、文字符号化方式と解釈するかで違ってきますが、規格において、"Character set"の項に記載あるため、ここでは前者と解釈します。

識別子

識別子は、変数やデータ型などを識別するための名前であり、次のすべてを満たさなければいけません。
  1. 文字・数字・アンダースコアで構成される。
    • 規格上、"文字"の明確な定義はありません。A-Z、a-z、および印刷可能な多バイト文字と解釈するのが良さそうです。
  2. 数字で始まってはいけない。
  3. アンダースコアで終端してはいけない。
  4. 連続するアンダースコアを含んではいけない。

例えば、`abc`、`D2E_F3`、`_hi_jk_lmn`は識別子になりえますが、`5op`、`__qr`、`s__t`、`uvw_`は識別子ではありません。

また、識別子の大文字・小文字を区別しません。`Abc`、`abc`、`ABC`は同じ識別子と解釈されます。

変数・基本データ型・リテラル

(準備中)

基本データ型

下表に示す通り、整数値、実数値、ビット列値、真偽値、時間、時間軸上の点、文字、文字列など多くの基本データ型があります。
基本データ型の一覧
データ型キーワードビット幅範囲デフォルト初期値リテラルの例説明
符号あり整数型SINT8-128~1270-59,
13,
1_234_567,
2#11_01,
-16#2f
INT16-32768~32767
DINT32-2147483648~2147483647
LINT64-9223372036854775808~9223372036854775807
符号なし整数型USINT80~255
UINT160~65535
UDINT320~4294967295
ULINT640~18446744073709551615
浮動小数点型REAL32IEC 60559単精度浮動小数点規格の範囲に従う00.1,
1.0e-6,
-1e3,
-5,
16#ff
LREAL64IEC 60559倍精度浮動小数点規格の範囲に従う
ビット列型BYTE816#00~16#ff16#00BYTE#16#ff,
BYTE#2#1111_1111_1111_1111,
DWORD#16#cafe_babe
WORD1616#0000~16#ffff16#0000
DWORD3216#0000_0000~16#ffff_ffff16#0000_0000
LWORD6416#0000_0000_0000_0000~16#ffff_ffff_ffff_ffff16#0000_0000_0000_0000
ブール型BOOL1FALSE(偽)、又はTRUE(真)FALSETRUE,
BOOL#0
時間型TIME実装依存実装依存0秒TIME#1s,
T#1s,
LTIME#1s,
LT#1s,
LT#1d2h3m4s5ms6us7ns,
LT#1.234567ms,
LT#1m234us567ns
最小単位も含めて実装依存のため、TIME型ではなく、LTIME型の使用を推奨します。
LTIME64-9223372036854775808ナノ秒~9223372036854775807ナノ秒
最小単位はナノ秒
日付型DATE実装依存実装依存実装依存 e.g. 1970年1月1日DATE#1978-9-7,
D#2022-11-07,
LDATE#1978-9-7,
LD#2554-07-21
LDATE641970年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_DAY640時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_TIME641970年1月1日0時0分0秒~2554年7月21日23時34時33.709551615s
最小単位はナノ秒
1970年1月1日0時0分0秒
LDT
文字型CHAR8ISO/IEC 10646規格に従う一文字。NULL文字('$00''a',
'→',
'$00' // NULL文字,
文字符号化方式は、UTF-8。
WCHAR16ISO/IEC 10646規格に従う一文字。NULL文字("$0000""a",
"→",
"$0000" // NULL文字
文字符号化方式は、UTF-16。
文字列型STRING8*NISO/IEC 10646規格に従う0文字以上の文字の列。空文字列('''This is a STRING literal.',
'これは文字列リテラルです。',
'abc',
'$61$62$63' // = 'abc',
'' // 空文字列
文字符号化方式は、UTF-8。
WSTRING16*NISO/IEC 10646規格に従う0文字以上の文字の列。空文字列("""This is a STRING literal.",
"これはWSTRING型のリテラルです。",
"abc",
"$0061$0062$0063" // = 'abc',
"" // 空文字列
文字符号化方式は、UTF-16。

※リテラルは、値のプログラミング言語における表現です。例えば、IEC 61131-3の真偽値である「真」の値の表現は、「TRUE」や「BOOL#1」があります。

変数の初期値

(準備中)

変数のその他の属性

(準備中)

CONSTANT属性

(準備中)

AT属性

(準備中)

エッジ属性

(準備中)

保持・非保持属性

(準備中)

演算子

(準備中)

制御文

(準備中)

ファンクション

(準備中)

ファンクションブロック(FB)

(準備中)

プログラムPOU

(準備中)

ユーザ定義データ型

(準備中)

列挙型

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#eRedDay#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においても列挙子に対し、整数値を設定する構文が存在します。実体の値を指定する列挙型は、単なる列挙型とは区別され、値付き列挙型といいます。

値付き列挙型

(準備中)

構造体型

(準備中)

相対位置付き構造体型

(準備中)

絶対位置付き構造体型

(準備中)

範囲型

(準備中)

参照型

(準備中)

配列

(準備中)

可変長配列

(準備中)

直接表現変数とVAR_CONFIG

(準備中)

クラス

(準備中)

メンバ変数

(準備中)

メソッド

(準備中)

継承クラス・インタフェース

(準備中)

オブジェクト指向ファンクションブロック(OOFB)

(準備中)

名前空間

(準備中)

自作ライブラリ

有用そうな自作のライブラリを公開しています。コンパイル確認・動作確認をしていません。不具合ありましたら、こちらのブログにご連絡いただければ可能な範囲で対応致します。

疑似乱数

Xorshift32

// Xorshift32
FUNCTION xorshift32: DWORD
	VAR_IN_OUT
		state: DWORD;
	END_VAR
	VAR_TEMP
		x: DWORD;
	END_VAR

	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_FUNCTION

FUNCTION nextBits32: DWORD
	VAR_IN_OUT
		state: DWORD;
	END_VAR
	VAR_INPUT
		bits: DINT := 32;
	END_VAR

	nextBits32 := SHR(xorshift32(state), 32 - bits);
END_FUNCTION

FUNCTION nextBOOL: BOOL
	VAR_IN_OUT
		state: DWORD;
	END_VAR

	nextBOOL := nextBits32(state, 1) = DWORD#16#0000_0001;
END_FUNCTION

// [16#0, 16#ffff_ffff]
FUNCTION nextDWORD: DWORD
	VAR_IN_OUT
		state: DWORD;
	END_VAR

	nextDWORD := nextBits32(state, 32);
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

	b1 := DWORD_TO_LWORD(nextBits32(state, 32));
	b2 := DWORD_TO_LWORD(nextBits32(state, 32));
	nextLWORD := SHL(b1, 32) OR b2;
END_FUNCTION

// [0, 2**32-1]
FUNCTION nextUDINT: UDINT
	VAR_IN_OUT
		state: DWORD;
	END_VAR

	nextUDINT := DWORD_TO_UDINT(nextDWORD(state));
END_FUNCTION

// [0, 2**64-1]
FUNCTION nextULINT: ULINT
	VAR_IN_OUT
		state: DWORD;
	END_VAR

	nextULINT := LWORD_TO_ULINT(nextLWORD(state));
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

	n1 := DWORD_TO_ULINT(nextBits32(state, 26));
	n2 := DWORD_TO_ULINT(nextBits32(state, 26));
	nextLREAL := (n1 * ULINT#16#0400_0000 + n2)
		/ LREAL#4503599627370496.0; // 2**52
END_FUNCTION
使用例
PROGRAM Main
	VAR
		state: DWORD;
		r: LREAL;
	END_VAR

	state := DWORD#16#cafeface;
	r := nextLREAL(state); // [0.0, 1.0)
	r := nextLREAL(state); // [0.0, 1.0)
END_PROGRAM

配列

// 配列要素数; LREAL配列
FUNCTION arraySize_LREAL: DINT
	VAR_IN_OUT CONSTANT
		x: ARRAY[*] OF LREAL;
	END_VAR

	arraySize_LREAL := UPPER_BOUND(x, 1) - LOWER_BOUND(x, 1) + 1;
END_FUNCTION

ソート

バブルソート

// バブルソート; LREAL配列
FUNCTION bubbleSort_LREAL
	VAR_IN_OUT CONSTANT
		x: ARRAY[*] OF LREAL;
	END_VAR
	VAR_TEMP
		i: DINT;
		j: DINT;
		t: LREAL;
	END_VAR

	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_FUNCTION
// バブルソート; DINT配列
FUNCTION bubbleSort_DINT
	VAR_IN_OUT CONSTANT
		x: ARRAY[*] OF DINT;
	END_VAR
	VAR_TEMP
		i: DINT;
		j: DINT;
		t: DINT;
	END_VAR

	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_FUNCTION

文字列操作

(準備中)

統計関数

平均値

// 平均値
FUNCTION mean: LREAL
	VAR_IN_OUT CONSTANT
		x: ARRAY[*] OF LREAL;
	END_VAR
	VAR_TEMP
		i: DINT;
		n: DINT;
		sum: LREAL;
	END_VAR

	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_FUNCTION

分散

// 分散: V(X) = E(X^2) - {E(X)}^2
FUNCTION variance: LREAL
	VAR_IN_OUT CONSTANT
		x: ARRAY[*] OF LREAL;
	END_VAR
	VAR_TEMP
		i: DINT;
		sum_of_x2: LREAL;
		sum_of_x: LREAL;
		n: DINT;
	END_VAR

	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_FUNCTION

不偏分散

// 不偏分散: s^2 = V(X) * n / (n-1)
FUNCTION sampleVariance: LREAL
	VAR_IN_OUT CONSTANT
		x: ARRAY[*] OF LREAL;
	END_VAR
	VAR_TEMP
		n: DINT;
		dof: DINT;
	END_VAR

	n := arraySize_LREAL(x);
	dof := n - 1;
	sampleVariance := variance(x) * n / dof;
END_FUNCTION

標準偏差

// 標準偏差: √(分散)
FUNCTION standardDiviation: LREAL
	VAR_IN_OUT CONSTANT
		x: ARRAY[*] OF LREAL;
	END_VAR

	standardDiviation := SQRT(variance(x));
END_FUNCTION

変動係数

// 変動係数: 標準偏差 / 平均
FUNCTION coefficientOfVariation: LREAL
	VAR_IN_OUT CONSTANT
		x: ARRAY[*] OF LREAL;
	END_VAR

	coefficientOfVariation := standardDiviation(x) / mean(x);
END_FUNCTION

標準誤差

// 標準誤差: √(不偏分散 / サンプルサイズ)
FUNCTION standardError: LREAL
	VAR_IN_OUT CONSTANT
		x: ARRAY[*] OF LREAL;
	END_VAR
	VAR_TEMP
		n: DINT;
	END_VAR

	n := arraySize_LREAL(x);
	standardError := SQRT(sasmpleVariance(x) / n);
END_FUNCTION

APPENDIX

IEC 61131-3にないもの