7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-05-24

#1.はじめに

横河電機の「リアルタイムOSコントローラ(e-RT3)」における入出力モジュールの制御を、Rust言語から制御する方法をまとめてみました。
image.png

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

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

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

#2.考え方

libloadingというクレートを使って、共有ライブラリにアクセスして、e-RT3のIOを操作します。

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

[Rust]
 ↓
[libloadingクレート] -> [libm3.so] -> e-RT3のIOユニット

名称 役割
libm3.so e-RT3のIOユニット制御のライブラリ(メーカから提供)

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

#3.Rustのインストール

e-RT3のOSはubuntu18.04ですので、公式マニュアルに従ってインストールします。

コマンドライン
$ cd Download
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
(ここでインストールが始まります)

$ source $HOME/.cargo/env
$ rustc --version
rustc 1.51.0 (2fd73fabe 2021-03-23)

ログイン時に、自動的にrust関連のパスを通すようにします。

コマンドライン
$ nano ~/.bashrc
.bashrc
・・・(省略)・・・
# for Rust
. $HOME/.cargo/env

#4.Rustで共有ライブラリの関数を呼び出す仕組み

libloadingというクレートを使います。

##ソース例

たとえば、「libmine.so」という共有ライブラリに下記add関数が定義されているとします。

mine.c
//引数2つの値の和を返す
int add(int a, int b)
{
    return(a + b);
}

上記を共有ライブラリとしてビルドします。

コマンドライン
$ gcc -fPIC -shared -o libmine.so mine.c

次に、rustから下記のコードで呼び出します。

main.rs
const LIBPASS: &str = "<共有ライブラリのパス>/libmine.so";

fn main() {
   sharedadd(1,2);
}

//足し算の表示
fn sharedadd(x: i32, y: i32) -> i32{
    let ret: i32;
    unsafe {
        //共有ライブラリを検索
        match libloading::Library::new(LIBPASS) {
            Ok(lib) => {
                //共有ライブラリの中の関数を検索
                match lib.get::<libloading::Symbol<unsafe extern fn(x: i32, y: i32) -> i32>>(b"add") {
                    Ok(func) => {
                        //共有ライブラリの中の関数を実行
                        ret = func(x, y);
                        //計算結果を表示
                        println!("add: {:?}", ret);
                        return ret;
                    },
                    Err(_) => {
                        //関数が見つからない
                        eprintln!(" ERROR: No function");
                    }
                }
            },
            Err(_) => {
                //ライブラリが見つからない
                eprintln!(" ERROR: No library");
            }
        }
    }
    return -1;
}

##実行例

共有ライブラリのadd関数を使って、足し算の結果を取得しています。

コマンドプロンプト
$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.11s
     Running `target/debug/libhelloso`
add: 3

#5.ソースコード

長くなるので、主要な部分だけ抜粋してます。

※※まだGithubにソースを上げていません(21/5/24時点)※※

新規にプロジェクトを作成します。

コマンドライン
$ cd gitwork/rust
$ cargo new --lib ert3io
$ cd ert3io

Cargo.toml を編集します。

Cargo.toml
[package]
name = "ert3io"
version = "0.1.0"
authors = ["ert3"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
libloading = "0.7.0"    # ←追記

lib.rsを編集します。

src/lib.rs
pub mod libm3;          // ←これから作るモジュール名をpubを付けて追記

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

e-RT3の共有ライブラリlibm3.soを操作するモジュールを作ります。

コマンドライン
$ cd src
$ touch libm3.rs
src/libm3.rs
pub mod libm3 {
    //! ---------------------------------------------
    //! Rust API Drivers for e-RT3 Relay
    //! ---------------------------------------------
    //! Copyright (c) 2021 myasu
    //! MIT Licence
    //! ---------------------------------------------

    //libm3のパス
    const PATHLIBM3: &str = "/usr/local/lib/libm3.so";

・・・(省略)・・・

    ///リレー出力:ブロック
    ///Description.
    ///呼び出し先:writeM3OutRelay
    /// * `unit` - ユニット番号を指定(0~7)
    /// * `slot` - スロット番号を指定(1~16)
    /// * `pos` - 出力リレー番号を指定(1, 17, 33, 49)
    /// * `num` - 書き込みブロック数(1~4)
    /// * `data` - 書き込みデータ
    /// * `mask` - 書き込みデータマスク
    pub fn write_out_relay(
        unit: i32,
        slot: i32,
        pos: i32,
        num: i32,
        data: u32,
        mask: u32,
    ) -> Result<u32, String> {
        //アンセーフで呼び出し
        unsafe {
            //libm3.soから関数を呼び出す
            match libloading::Library::new(PATHLIBM3) {
                //共有ライブラリが見つかった
                Ok(lib) => {
                    match lib.get::<libloading::Symbol<
                        unsafe extern "C" fn(
                            unit: i32,
                            slot: i32,
                            pos: i32,
                            num: i32,
                            data: &[u16; 4],
                            mask: &[u16; 4],
                        ) -> i32,
                    >>(b"writeM3OutRelay")
                    {
                        //関数が見つかった
                        Ok(func) => {
                            //書き込みデータを配列に代入
                            let data_d = data as u16;
                            let data_u = (data >> 16) as u16;
                            let writedata: [u16; 4] = [0xffff & data_u, 0xffff & data_d, 0, 0];
                            let mask_d = mask as u16;
                            let mask_u = (mask >> 16) as u16;
                            let writemask: [u16; 4] = [0xffff & mask_u, 0xffff & mask_d, 0, 0];
                            //libm3.soの関数を実行
                            match func(unit, slot, pos, num, &writedata, &writemask) {
                                //正常完了
                                0 => {
                                    //型変換
                                    let data2 = data as u32;
                                    let mask2 = mask as u32;
                                    //返り値に実際に書き込んだ値を付加
                                    Ok(data2 & mask2)
                                }
                                //関数の引数の値範囲間違い
                                _ => Err(errmes("Function args")),
                            }
                        }
                        //関数が見つからない
                        Err(_) => Err(errmes("Function load")),
                    }
                }
                //共有ライブラリが見つからない
                Err(_) => Err(errmes("Library load")),
            }
        }
    }

・・・(省略)・・・

    ///リレー入力:ブロック
    ///Description.
    ///呼び出し先:writeM3OutRelay
    /// * `unit` - ユニット番号を指定(0~7)
    /// * `slot` - スロット番号を指定(1~16)
    pub fn read_in_relay(unit: i32, slot: i32) -> Result<u32, String> {
        //アンセーフで呼び出し
        unsafe {
            //libm3.soから関数を呼び出す
            match libloading::Library::new(PATHLIBM3) {
                //共有ライブラリが見つかった
                Ok(lib) => {
                    match lib.get::<libloading::Symbol<
                        unsafe extern "C" fn(
                            unit: i32,
                            slot: i32,
                            pos: i32,
                            num: i32,
                            data: &[u16; 4],
                        ) -> u16,
                    >>(b"readM3InRelay")
                    {
                        //関数が見つかった
                        Ok(func) => {
                            //書き込みデータを配列に代入
                            let readdata: [u16; 4] = [0, 0, 0, 0];
                            //libm3.soの関数を実行
                            match func(unit, slot, 1, 2, &readdata) {
                                //正常完了
                                0 => {
                                    //型変換
                                    let data_d = readdata[0] as u32;
                                    let data_u = readdata[1] as u32;
                                    //読み込んだ値を直接出力
                                    Ok((data_u << 16) | data_d)
                                }
                                //関数の引数の値範囲間違い
                                _ => Err(errmes("Function args")),
                            }
                        }
                        //関数が見つからない
                        Err(_) => Err(errmes("Function load")),
                    }
                }
                //共有ライブラリが見つからない
                Err(_) => Err(errmes("Library load")),
            }
        }
    }

    ///エラーメッセージの生成
    ///Description.
    /// * `message` - エラーメッセージの文字列
    fn errmes(message: &str) -> String {
        // 文字列の変換 https://qiita.com/smicle/items/29a4d5d1d14ad7f77f60
        //先頭にエラー共通文字を付加してエラーメッセージを生成
        let a = "! <ERROR> : ".to_string();
        let b = message.to_string();
        return a + &b;
    }
}

上記を操作します。

src/main.rs
use std::thread;
use std::time::Duration;

//モジュールの読み込み
use ert3io::libm3::libm3;

//実行
fn main() {
    println!(" ---  libm3 test  ---");

    out_relay();

    in_relay();

    println!(" done.");
}

/// ---------------------------------------------
/// 各動作チェック用関数
/// ---------------------------------------------

///リレー出力・ブロックまとめて点灯/消灯
fn out_relay() {
    println!(" ---  > Output Block");
    // スレッドを起動する
    let th = thread::spawn(|| {
        for n in 0..4 {
            println!(" > Output:  ON {:?}", n);
            //                                       点灯データ   マスク:1で有効
            match libm3::write_out_relay(0, 2, 1, 2, 0xf0f0a0a0, 0xffffffff) {
                Ok(_) => {}
                Err(err) => {
                    //エラーメッセージ
                    eprintln!("{}", err.to_string())
                }
            }
            //100msのウェイト
            thread::sleep(Duration::from_millis(300));
            //                                       点灯データ   マスク:1で有効
            match libm3::write_out_relay(0, 2, 1, 2, 0x0f0f0a0a, 0xffffffff) {
                Ok(_) => {}
                Err(err) => {
                    //エラーメッセージ
                    eprintln!("{}", err.to_string())
                }
            }
            //ウェイト
            thread::sleep(Duration::from_millis(300));
        }
    });
    th.join().unwrap();
}

///リレー入力・ロックまとめて確認
fn in_relay() {
    println!(" ---  > Input Block");
    // スレッドを起動する
    let th = thread::spawn(|| {
        //一定間隔で読み込み
        for _ in 0..10 {
            match libm3::read_in_relay(0, 3) {
                Ok(v) => {
                    //入力状態の表示
                    println!(" > Input : {:?}", v);
                }
                Err(err) => {
                    //エラーメッセージ
                    eprintln!("{}", err.to_string())
                }
            }
            //ウェイト
            thread::sleep(Duration::from_millis(500));
        }
    });
    th.join().unwrap();
}

##実行例

YDの出力が数回点滅した後、XDの入力状態を500ms毎に10回読み込んで入力状態を数値で表示します。

コマンドライン
$ cargo run
   Compiling ert3io v0.1.0 (/home/ert3/gitwork/rust/ert3io)
    Finished dev [unoptimized + debuginfo] target(s) in 6.81s
     Running `target/debug/ert3io`
 ---  libm3 test  ---
 ---  > Output Block
 > Output:  ON 0
 > Output:  ON 1
 > Output:  ON 2
 > Output:  ON 3
 ---  > Input Block
 > Input : 15
 > Input : 15
 > Input : 15
 > Input : 1
 > Input : 1
 > Input : 1
 > Input : 1
 > Input : 0
 > Input : 0
 > Input : 0

#6.おわりに

Python→Elixirと試してきたので、今度はRustだろうと(笑)取り組んでみましたが、libloadingクレートにたどり着くまでに結構手間取りました・・・
Golangも、同じような考え方でできるようですので、次回取り組んでみます。

#参考資料

7
2
0

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
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?