LoginSignup
32
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機器の制御をするまで発展させたいと思っています。(実験が纏まるまで、少々お待ちください・・・)

参考資料

32
8
2

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
  3. You can use dark theme
What you can do with signing up
32
8