リアルタイムOSコントローラ e-RT3 （入出力ユニット編）

#1.はじめに

横河電機の「[リアルタイムOSコントローラ（e-RT3）](https://www.yokogawa.co.jp/solutions/products-platforms/control-system/real-time-os-controller/)」での、入出力モジュールの制御について、公式マニュアル[^manual]のサンプルプログラムを補足する内容でまとめてみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145581/2f2932c0-83b7-f87f-73c9-e1a9696e09c9.png)

[^manual]: [F3RP70⽤ Ubuntuイメージ使⽤⼿順TI34M06T02-02_001](https://partner.yokogawa.com/japan/itc/ert3-software/sfrd14.htm)

**リアルタイムOSコントローラ e-RT3 関連記事**

|||
|:--|:--|
|[第1回](https://qiita.com/myasu/items/057ce9ec2fa39148a5a3)|セットアップ編|
|第2回（今回）|入出力ユニット編　(PythonとC言語から制御)|
|[第3回](https://qiita.com/myasu/items/0dbf308947b878423112)|Elixirから制御編|
|[第4回](https://qiita.com/myasu/items/605587bd44576699c2f4)|ROS2から制御編|
|[第5回](https://qiita.com/myasu/items/d0f2c2277afdab9b7a20)|Rustから制御編|
|[第6回](https://qiita.com/myasu/items/8b32dc8e66ec4685973c)|Goから制御編|

#2.Python版

e-RT3用のライブラリを間接的に使いながら制御します。

##(1)入力モジュールXD

```python:xd_read16.py
#!/usr/bin/env /usr/bin/python3
# -*- coding: utf-8 -*-

import ctypes

def xd_read():
    """
    読み込み関数
    """
    # ライブラリ読み込み
    libc = ctypes.cdll.LoadLibrary("/usr/local/lib/libm3.so.1")

    # pythonの変数をint型に変換
    # ユニット番号
    # ベースユニットが複数あるときは、この数値が変わる
    c_unit = ctypes.c_int(0)
    # スロット番号
    # XDはスロット3に設置
    c_slot = ctypes.c_int(3)
    # リレー番号
    # 1番から読み込み。ブロック読み込みの場合は1, 17, 33, (16n+1)･･･を指定
    c_pos = ctypes.c_int(1)
    # 読み出しブロック数（1ブロック16点）
    # 今回使用のものは32点までコネクタ有りなので
    # 2ブロック32点読み込み
    c_num = ctypes.c_int(2)

    # 読み出しデータ格納バッファ用の
    # 要素数4のshort型の配列
    # （XDユニットは最大64点まで対応、今回使用のものは32点までコネクタ有り）
    short_arr = ctypes.c_uint16 * 4
    # 配列を作成
    c_data = short_arr()

    # ライブラリ関数呼び出し
    libc.readM3InRelay(c_unit, c_slot, c_pos, c_num, c_data)
    # 読み込み結果を返す
    return (c_data[0], c_data[1])

if __name__ == '__main__':
    """
    メイン
    """
    print("--- read sample ---")

    # XD読み込み
    (lower, upper) = xd_read()

    # 32～17番と、16～1番のXDの値をバイナリ表示
    print( f"lower: {bin(upper)}, upper: {bin(lower)}" )
```

###実行

`watch`コマンドを使って、定期的（ここでは1秒間隔）にXDの状態を読み込みます。

```sh:コマンドライン
$ watch -n 1 ./xd_read16.py 
Every 1.0s: ./xd_read16.py                                ert3u: Mon Aug 10 22:48:40 2020
--- read sample ---
lower: 0b1111, upper: 0b11

(Ctrl-Cで終了)
```

**上記の時の、スイッチ入力の状態**

|ONの入力点|バイナリ表記|
|:--:|:--:|
|1, 2, 3, 4|0b1111|
|17, 18|0b0011|

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145581/d401098f-1e27-9979-29c6-c829f90aac26.png)

##(2)出力モジュールYD

```python:yd_write16.py
#!/usr/bin/env /usr/bin/python3
# -*- coding: utf-8 -*-

import ctypes
import time


def yd_write(upper, lower):
    """
    書き込み関数
    """

    # ライブラリ読み込み
    libc = ctypes.cdll.LoadLibrary("/usr/local/lib/libm3.so.1")

    # pythonの変数をint型に変換
    # ユニット
    c_unit = ctypes.c_int(0)
    # スロット
    # YDはスロット2に設置
    c_slot = ctypes.c_int(2)
    # リレー番号
    # ブロック読み込みの場合は1, 17, 33, (16n+1)･･･を指定
    c_pos = ctypes.c_int(1)
    # 書き込みブロック数（1ブロック16点）
    c_num = ctypes.c_int(2)
    # 書き込みデータ格納バッファ用のshort型配列を作る
    # 要素数4のshort型の配列
    short_arr = ctypes.c_uint16 * 4
    # 32 x 2 点分の書き込みデータ作成
    data = [lower, upper]
    c_data = short_arr(*data)
    # 32 x 2 点分のマスクデータ作成
    mask = [0xffff, 0xffff]
    c_mask = short_arr(*mask)

    # ライブラリ関数呼び出し
    libc.writeM3OutRelay(c_unit, c_slot, c_pos, c_num, c_data, c_mask)


if __name__ == '__main__':
    """
    メイン
    """
    print("--- write sample ---")
    while True:
        try:
            # YD書き込み
            yd_write(0xf00f, 0x00f0)
            # スリープ
            time.sleep(0.5)
            yd_write(0x00f0, 0x0f00)
            time.sleep(0.5)
            yd_write(0x0f00, 0xf00f)
            time.sleep(0.5)
        except KeyboardInterrupt:
            break
        except:
            pass

    # 消灯
    yd_write(0x0000, 0x0000)
```

###実行

```sh:コマンドライン
$ ./yd_write16.py 
--- write sample ---

(Ctrl-Cで終了)
```

**上記の時の、YD出力の状態**
LEDが縦４つ点灯して、右側に流れていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145581/3ac0d0c9-29f3-1398-f443-3537e7200afd.png)

**動画**
![aa_640x480_fps5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145581/d2ae7e96-14b8-2708-c391-1146526f6df0.gif)


#3.C言語版

公式マニュアルには、（APIのリファレンスはありますが）Lチカ確認できる様なC言語のサンプルがなかったので、こちら[^softec]を参考にしました。

[^softec]: https://www.softech.co.jp/mm_190306_tr.htm, https://www.softech.co.jp/mm_190306_tr.htm

```c:readwrite.c
/**
 * @file readwrite.c
 * @author myasu (https://qiita.com/myasu/)
 * @brief m3 Read / Write Sample
 * @version 0.1
 * @date 2020-08-10
 *
 * @copyright Copyright (c) 2020
 *
 */

#include <stdio.h>
#include <unistd.h>
#include <errno.h>

#include <ert3/m3lib.h>

/**
 * @brief XDの読み込み
 * 
 * @return int 0：成功、-1：失敗
 */
int xd_read(void)
{
    //読み出しデータ格納バッファ
    unsigned short data[4];

    //ライブラリ関数呼び出し
    //ユニット番号、スロット番号、開始リレー番号、読み込みブロック数（2=32点）
    int ret = readM3InRelay(0, 3, 1, 2, data);

    if(ret == 0)
        printf("lower: %x, upper: %x\r\n", data[0], data[1]);
    else
        //エラーの時、エラーコードを表示
        printf("error: %d\r\n", errno);
    
    return ret;
}

/**
 * @brief YDに書き込み
 * 
 * @param arg_data 書き込みデータ4byte分
 * @return int 0：成功、-1：失敗
 */
int yd_write(unsigned int arg_data)
{
    //書き込みデータ
    unsigned short data[4];
    //書き込みマスク
    unsigned short mask[4];

    //引数のデータを上位・下位に分解
    data[0] = 0xffff & (arg_data);
    data[1] = 0xffff & (arg_data >> 16);
    // 32 x 2 点分のマスクデータ作成
    mask[0] = 0xffff;
    mask[1] = 0xffff;

    //ライブラリ関数呼び出し
    //ユニット番号、スロット番号、開始リレー番号、書き込みブロック数（2=64点）
    int ret = writeM3OutRelay(0, 2, 1, 2, data, mask);

    if(ret == 0){}
    else
        //エラーの時、エラーコードを表示
        printf("error: %d\r\n", errno);

    return ret;
}

int main(void)
{
    printf("--- read / write sample ---\r\n");

    while(1)
    {
        //XD読み込み
        xd_read();
        //YD書き込み
        yd_write(0x0f00f00f);
        //スリープ
        usleep(0.3 * 1000 * 1000);
        yd_write(0xf00f00f0);
        usleep(0.3 * 1000 * 1000);
        yd_write(0x00f00f00);
        usleep(0.3 * 1000 * 1000);
    }

    return 0;
} 
```

###Makefile

`e-RT3`のライブラリとヘッダファイルは、それぞれ下記にあります。

|||
|:--|:--|
|ヘッダファイル|/usr/local/include/ert3|
|ライブラリ|/usr/local/lib|

```sh:コマンドライン
#ヘッダファイル
$ ls /usr/local/include/ert3/ -la
total 84
drwxr-xr-x 2 root root  4096 Mar 17 17:55 .
drwxr-xr-x 3 root root  4096 Mar 17 17:55 ..
-rw-r--r-- 1 root root  4555 Mar 17 17:55 ert3dgc.h
-rwxr-xr-x 1 root root  8952 Mar  5  2019 m3cpu.h
-rwxr-xr-x 1 root root  4317 Mar  5  2019 m3dev.h
-rwxr-xr-x 1 root root  4828 Mar  5  2019 m3flnet.h
-rwxr-xr-x 1 root root 10804 Mar  5  2019 m3io.h
-rwxr-xr-x 1 root root  8822 Mar  5  2019 m3lib.h
-rwxr-xr-x 1 root root  1261 Mar  5  2019 m3mcom.h
-rwxr-xr-x 1 root root  2115 Mar  5  2019 m3ras.h
-rwxr-xr-x 1 root root  2613 Mar  5  2019 m3sysctl.h
-rwxr-xr-x 1 root root  1506 Mar  5  2019 m3wdt.h

#ライブラリ
$ ls /usr/local/lib -la
total 700
drwxr-xr-x  4 root root    4096 Aug  7 14:34 .
drwxr-xr-x 10 root root    4096 Aug  7 14:34 ..
lrwxrwxrwx  1 root root      19 Mar 21 19:29 libert3dgc.so -> libert3dgc.so.1.1.1
lrwxrwxrwx  1 root root      19 Mar 21 19:29 libert3dgc.so.1 -> libert3dgc.so.1.1.1
-rw-r--r--  1 root root  662644 Mar 17 17:50 libert3dgc.so.1.1.1
lrwxrwxrwx  1 root root      14 Mar  5  2019 libm3.so -> libm3.so.1.0.1
lrwxrwxrwx  1 root root      14 Mar  5  2019 libm3.so.1 -> libm3.so.1.0.1
-rw-r--r--  1 root root   36348 Mar  5  2019 libm3.so.1.0.1
drwxrwsr-x  4 root staff   4096 Aug  7 08:31 python2.7
drwxr-xr-x  3 root root    4096 Mar 17 18:13 python3.6
```

コンパイルのオプションと、リンカのオプション（`libm3.so`→`-lm3`）にそれぞれ追記します。

```makefile:Makefile
CC          = gcc
CFLAGS      = -O4 -Wall -I/usr/local/include
DEST        = /usr/local/bin
LDFLAGS     = -L/usr/local/lib
LIBS        = -lm -lm3
OBJS        = readwrite.o
PROGRAM     = readwrite

all:        $(PROGRAM)

$(PROGRAM): $(OBJS)
            $(CC) $(OBJS) $(CFLAGS) $(LDFLAGS) $(LIBS) -o $(PROGRAM)

clean:;     rm -f *.o *~ $(PROGRAM)

install:    $(PROGRAM)
            install -s $(PROGRAM) $(DEST)
```

###実行

実行の様子は、Python版と同じです。

```sh:コマンドライン
$ make
gcc -O4 -Wall -I/usr/local/include   -c -o readwrite.o readwrite.c
gcc readwrite.o -O4 -Wall -I/usr/local/include -L/usr/local/lib -lm -lm3 -o readwrite

$ ./readwrite
--- read / write sample ---
lower: 0, upper: 0
lower: 0, upper: 0
lower: f, upper: 3
lower: f, upper: 3

(Ctrl-Cで終了)
```

#4.おわりに

見た目はPLCですが、Raspberry PiやJetsonの感覚で、カンタンにIOを制御することが出来ました。

実際のアプリは別の記事で紹介します。（作成中）

**次回**

ROS2を使ってみます。（作成中）


#参考資料