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のいずれかが最初に実行される。

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

コメント

ST言語には、1つの行コメントと、2つの複数行コメントがあります。

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

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

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

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

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

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

識別子

識別子は、変数やデータ型などを識別するための名前であり、次のすべてを満たさなければいけません。
  1. 文字・数字・アンダースコアで構成される。
  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属性

(準備中)

エッジ属性

(準備中)

保持・非保持属性

(準備中)

直接表現変数とVAR_CONFIG

(準備中)

ユーザ定義データ型

(準備中)

列挙型

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

値付き列挙型

(準備中)

構造体型

(準備中)

構造体型変数の初期化

構造体型変数の初期化は、変数宣言時に以下のように書くことができます。

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_BOUNDUPPER_BOUNDを使用します。

LOWER_BOUNDUPPER_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

直接派生型

(準備中)

ファンクション

(準備中)

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

(準備中)

プログラムPOU

(準備中)

クラス

(準備中)

メンバ変数

(準備中)

メソッド

(準備中)

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

(準備中)

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

(準備中)

名前空間

(準備中)

演算子

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
:= => 代入 引数代入 実装依存 実装依存(一般的に左)

呼び出し

(準備中)

メンバアクセス

(準備中)

配列アクセス

(準備中)

デリファレンス

(準備中)

加算・減算

(準備中)

乗算・除算・剰余算

(準備中)

べき乗

演算子**は、べき乗を計算します。

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と左から右に評価されることに注意が必要です。

比較

(準備中)

等価

(準備中)

論理否定・論理積・論理和・排他的論理和

(準備中)

ビット否定・ビット積・ビット和・ビット排他的論理和

(準備中)

部分アクセス

(準備中)

代入

(準備中)

制御文

(準備中)

代入文

(準備中)
x := 2;
x := 3 + 5;
x := y := 7; // Compile-time error! syntax error.

呼び出し文

(準備中)
pou_instance(in := true);

IF文

(準備中)
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;

CASE文

(準備中)
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

FOR文

(準備中)
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.

WHILE文

(準備中)
sum := 0;
n := 1;
WHILE n <= 4 DO
	sum := sum + n;
	n := n + 1;
END_WHILE;
// sum = 10 = 1 + 2 + 3 + 4, n = 5.

REPEAT文

(準備中)
sum := 0;
n := 1;
REPEAT
	sum := sum + n;
	n := n + 1;
UNTIL n = 5 END_REPEAT;
// sum = 10 = 1 + 2 + 3 + 4, n = 5.

CONTINUE文

(準備中)
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.

EXIT文

(準備中)
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.

RETURN文

(準備中)
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

// 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(sampleVariance(x) / n);
END_FUNCTION

APPENDIX

IEC 61131-3にないもの