LoginSignup
8

More than 1 year has passed since last update.

リアルタイムOSコントローラ e-RT3 (Elixirから制御編)

Last updated at Posted at 2020-11-23

1.はじめに

横河電機の「リアルタイムOSコントローラ(e-RT3)」における入出力モジュールの制御を、並行処理や耐障害性、ホットデプロイなどの特徴を持つElixir言語から制御する方法をまとめてみました。
image.png

目標としては、前回記事のように、Elixirのシェルからコマンドで出力リレーの操作、入力の検知ができることとしています。

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

第1回 セットアップ編
第2回 入出力ユニット編 (PythonとC言語から制御)
第3回(今回) Elixirから制御編
第4回 ROS2から制御編
第5回 Rustから制御編
第6回 Goから制御編

2.なぜElixir?

PLCの多くはラダーなどの言語で書かれています。
FA系では非常に馴染みのある言語ですが、C#やPythonなどの言語とは大分概念が違うので、IT系の方が触るとなると取っ掛かりに戸惑いがあります。

比較的新しいPLC(e-RT3など)では、LinuxやリアルタイムOS等が自由に動かせる環境が整ってきて、多くの高級言語も容易に扱う事ができるようになりました。

それらの言語の中で、長期に渡る安定動作や分散処理、RUN中書き込み1など、PLCならではの機能を要求する場合に対応出来そうなのが、Elixirかなと考えています。

まだまだ作例が少ないのですが、上手くいけばラダーに変わる高級言語になり得るかと期待しているので、個人的に色々模索しています。
現状、筆者自身の組込ElixirのターゲットはRaspberryPiですが、同じDebian系のLinuxが使えるe-RT3でも行けるんじゃないか?と思って、今回の記事にチャレンジしてみました。

(参考)組込みElixirに関するコミュニティの紹介

3.考え方

ここでは、NIFs2 という仕組みを使います。

大ざっぱな操作の流れのイメージは下記の通りです。

[Elixir]
 ↓
[nifm3.c] -> [relay.c] -> [libm3.so] -> e-RT3のIOユニット

名称 役割
libm3.so e-RT3のIOユニット制御のライブラリ(メーカから提供)
relay.c libm3.soのAPIを操作するC言語のソース
nifm3.c Elixirからrelay.cの関数を呼ぶためのラッパー(NIFs)

libm3.soの配置状況

コマンドライン
$ ls -l /usr/local/lib
total 692
lrwxrwxrwx 1 root root      19 Mar 21  2020 libert3dgc.so -> libert3dgc.so.1.1.1
lrwxrwxrwx 1 root root      19 Mar 21  2020 libert3dgc.so.1 -> libert3dgc.so.1.1.1
-rw-r--r-- 1 root root  662644 Mar 17  2020 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

4.Elixirのインストール

別記事を参照してください。

5.ソースコード

長くなるので、主要な部分だけ抜粋してます。
全てのソースファイルはGithubに置いてありますので、こちらを参照してください。(ファイル置いただけですので、読みにくくてすみません・・・)

(1)NIFs

static ErlNifFunc nif_funcsのところで、Elixir側から呼び出す関数名と、C言語側(relayio.c)との対応付けをしています。

nifm3.c
#include <stdio.h>
#include <erl_nif.h>

#include "relayio.h"

(・・・省略・・・)

/**
 * @brief 出力リレー書き込み
 * @param env nif指定の引数
 * @param argc nif指定の引数
 * @param argv nif指定の引数
 * @return ERL_NIF_TERM 
 */
static ERL_NIF_TERM _writeM3OutRelayP(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
    int unit, slot, pos, data;
    int ret = -1;

    //引数を取り出し
    enif_get_int(env, argv[0], &unit);
    enif_get_int(env, argv[1], &slot);
    enif_get_int(env, argv[2], &pos);
    enif_get_int(env, argv[3], &data);

    //処理
    ret = WriteOutRelay1(unit, slot, pos, data);
    if (ret == 0)
        //正常なら、得た値を代入
        ret = data;

    //読み込み結果を返す
    //エラー時は負の値
    return enif_make_uint(env, ret);
}

/**
 * @brief define the array of ErlNifFunc beforehand
 * 
 */
static ErlNifFunc nif_funcs[] =
    {
        // {erl_function_name, erl_function_arity, c_function}
        {"readinrelay", 4, _readM3InRelay},
        {"readinrelay", 3, _readM3InRelayP},
        {"readoutrelay", 4, _readM3OutRelay},
        {"writeoutrelay", 5, _writeM3OutRelay},
        {"writeoutrelay", 4, _writeM3OutRelayP},
};

ERL_NIF_INIT(Elixir.NifTest, nif_funcs, NULL, NULL, NULL, NULL)

(2)API呼び出しのためのC言語ソース

ここは、前記事の「リアルタイムOSコントローラ e-RT3 (入出力ユニット編)」を参考に書いています。

relayio.c
#include <stdio.h>
#include <ert3/m3lib.h>
#include <errno.h>

#include "relayio.h"

(・・・省略・・・)

/**
 * @brief 出力リレー書き込み
 * 
 * @param unit ユニット番号を指定(0~7)
 * @param slot スロット番号を指定(1~16)
 * @param pos 出力リレー番号を指定(1~64)
 * @param data 書き込みデータ
 * @return int 
 */
unsigned int WriteOutRelay1(int unit, int slot, int pos, unsigned short data)
{
    unsigned int result = -1;

    //API呼び出し
    result = writeM3OutRelayP(unit, slot, pos, data);

    if (result != 0)
        //エラーメッセージ
        result = errcommon((char *)__func__, __LINE__, result);
    else
        //返り値にスロット番号・ポジション・データを付加
        result = (slot * 1000 + pos * 10 + data);

    //書き込み結果を返す
    return result;
}

(・・・省略・・・)

relayio.h
#ifndef RELAYIO_H
#define RELAYIO_H

int ReadInRelayCommon(int , int , int , int , int*);
unsigned int ReadOutRelayCommon(int , int , int , int , unsigned int*);
unsigned int WriteOutRelay1(int unit, int slot, int pos, unsigned short data);
unsigned int WriteOutRelay(int unit, int slot, int pos, unsigned int data, unsigned int mask);
int errcommon(char *func, int line, int result);

#endif

(3)Makefile

関連するソースファイルをまとめてビルドします。

Makefile
#----------------------------------------------------------
# @file Makefile
# @author myasu
# @brief nif for e-RT3 API Makefile
# @version 0.1
# @date 2020-08-10
# 
# @copyright Copyright (c) 2020
#----------------------------------------------------------

CC              = gcc
CFLAGS          = -O4 -Wall -I/usr/local/include -shared -fPIC -I$(ERL_INCLUDE_PATH)
ERL_INCLUDE_PATH=$(shell elixir -e 'IO.puts [:code.root_dir, "/erts-", :erlang.system_info(:version), "/include"]')
DEST            = /usr/local/bin
LDFLAGS         = -L/usr/local/lib
LIBS            = -lm -lm3
OBJS            = nifm3.o relayio.o relayio.h
PROGRAM         = nifm3.so

all:            $(PROGRAM)

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

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

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

(4)Elixir側の実装

nif_test.ex
defmodule NifTest do
  @moduledoc """
  Documentation for `NifTest`.
  Elixirからのm3ライブラリへのアクセスサンプル
  """
  @on_load :init

  @doc """
  init
  初期化
  """
  def init do
    #nifのロード
    :erlang.load_nif("./nifm3", 0)
  end

(・・・省略・・・)

  @doc """
  writeoutrelay
  出力リレー書き込み(1ビット単位)
  ## Parameter
    - 1:ユニット番号を指定(0~7)
    - 2:出力リレーを設置のスロット番号を指定(1~16)
    - 3:出力リレー番号を指定(1~32)
    - 4:書き込みデータ(0 / 1)
  ## Examples
      iex> NifTest.writeoutrelay(0,2,1,1)   #出力リレー1だけON
      iex> NifTest.writeoutrelay(0,2,10,0)  #出力リレー10だけOFF
  """
  def writeoutrelay(_, _, _, _) do
    "writeoutrelay/4 : NIF library not loaded"
  end
end

補足

@on_load :initは、Elixirのビルドの段階3 で展開されます。(実行時ではない)

(5)ビルド

コマンドライン
$ make
gcc -O4 -Wall -I/usr/local/include -shared -fPIC -I/home/ert3/.asdf/installs/erlang/22.0.7/erts-10.4.4/include   -c -o relayio.o relayio.c
gcc nifm3.o relayio.o relayio.h -O4 -Wall -I/usr/local/include -shared -fPIC -I/home/ert3/.asdf/installs/erlang/22.0.7/erts-10.4.4/include -L/usr/local/lib -lm -lm3 -o nifm3.so

(6)実行例

モジュールの配置状態は、第1回の記事と同じです。
image.png

スロット番号 モジュール
1 (CPU)
2 出力リレー32ch
3 入力リレー32ch
4 (ADコンバータ)
5 (DAコンバータ)
コマンドライン
ert3@ert3u:~/gitwork/elixir/nifm3$ iex nif_test.ex 
Erlang/OTP 22 [erts-10.4.4] [source] [smp:2:2] [ds:2:2:10] [async-threads:1] [hipe]

Interactive Elixir (1.9.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 

リレー出力

①出力リレーをまとめてON/OFF

iexシェル
#出力リレーをまとめてON/OFF
iex(1)> NifTest.writeoutrelay(0,2,1,0xffffffff,0xffffffff)
4294967295
iex(2)> NifTest.writeoutrelay(0,2,1,0x00000000,0xffffffff)
0

上記を実行したときの、出力の状態。
全てOFF状態→ON状態です。

image.png

②出力リレーの一部だけONにして、その状態を読み取り

iexシェル
#特定の出力リレー1つだけ(ch1)をON/OFF
iex(3)> NifTest.writeoutrelay(0,2,1,1)                    
iex(4)> NifTest.writeoutrelay(0,2,1,0)

#特定の出力リレー1つだけ(ch32)をON/OFF
iex(5)> NifTest.writeoutrelay(0,2,32,1)                    
iex(6)> NifTest.writeoutrelay(0,2,32,0)

#特定の出力リレー2つ(ch1, ch2)をONにして、そのリレーの状態を読み取り
iex(15)> NifTest.writeoutrelay(0,2,1,0x00000011,0xffffffff)
17
iex(16)> NifTest.readoutrelay(0,2,1,2)                     
17

#特定の出力リレー2つ(ch1, ch32)をONにして、そのリレーの状態を読み取り
iex(7)> NifTest.writeoutrelay(0,2,1,1)                    
iex(8)> NifTest.writeoutrelay(0,2,32,1)                    
iex(9)> NifTest.readoutrelay(0,2,1,2)                     
2147483649

上記を実行したときの、出力の状態。
ch1と32がON、残りは全てOFF状態です。

image.png

リレー入力

③入力リレーをまとめて読み取り

iexシェル
#入力リレーをまとめて読み取り(1~32)
iex(2)> NifTest.readinrelay(0,3,1,2)
4294967295
iex(3)> NifTest.readinrelay(0,3,1,2)
0

上記を実行したときの、入力(スイッチ)の状態。
全てON状態です。
image.png

④特定の入力リレー1つだけを読み取り

iexシェル
#特定の入力リレー1つだけ(ch1→ON)を読み取り
iex(4)> NifTest.readinrelay(0,3,1)  
1

#特定の入力リレー1つだけ(ch32→ON)を読み取り
iex(5)> NifTest.readinrelay(0,3,32)
1

#特定の入力リレー1つだけ(ch2→OFF)を読み取り
iex(6)> NifTest.readinrelay(0,3,2)
0

上記を実行したときの、入力(スイッチ)の状態。
ch1と32がON、残りは全てOFF状態です。
image.png

6.おわりに

ElixirのNIFsを扱う勉強を兼ねて、試してみました。
今回は、とりあえず”Lチカ”する程度の内容で留めていますが、FA機器の制御をするまで発展させたいと思っています。(実験が纏まるまで、少々お待ちください・・・)

参考資料

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
8