WMX3 C#サンプルコードの説明 I/O制御

コーディング

本記事では、WMXとC# を用いたプログラミング学習の第一歩として、公式サンプルコードの読み解き方を解説します。開発環境はWMX3.6とMicrosoft Visual Studio 2019を想定しており、C#の初心者でも理解しやすいよう、基本的な文法や概念についても補足します。

ソフトモーションのI/O制御、基本の「き」

装置制御エンジニアの皆さん、今までの解説記事でモーターは思い通りに回せていますか?

ソフトウェアモーションコントロール(PC制御)の学習を始めると、まずは「軸を動かすこと(モーション)」に集中しがちです。しかし、実際の装置開発現場で直面するのは、「センサーが反応しない」「シリンダーが動かない」といった、I/O(入出力)周りの地道な課題ではないでしょうか。

PLC(ラダー言語)であれば X001 や Y001 と記述するだけの処理も、C#やC++などの言語でAPIを叩くとなると、途端に難しく感じるかもしれません。

今回は、センサー入力の監視やランプの点灯といった、基本かつ必須の「I/O制御」について、サンプルコードを使いながら噛み砕いて解説します。

C#コードの補足

サンプルコードのrefについて

refは変数の「値のコピー」ではなく、「場所そのもの」をメソッドに渡すためのC#のキーワードです。これにより、メソッドの中でその変数の値を変更すると、呼び出し元の変数も直接変更されます。

これを「参照渡し」と呼びます。(アドレスを関数の中に渡します)

※C言語でのポインターになります。

refで渡す配列サイズについて

記事を作るため、わざと配列サイズエラーを出そうと、配列サイズが小さいものを渡して実験しました。

取得サイズと渡す配列サイズは一致させないといけないと考えておりましたが、そうではないようです。 null配列を渡しても、指定した取得サイズに拡張されて帰ってきます。

サンプルコードのunsigned char配列について

WMXの取説にはunsigned char配列と記述がありますが、C#ではbyte配列と同じ意味です。

サンプルコードの解説[1_BasicMotion]06_ControlIO

さて、WMXのサンプルを実行しましょう。I/Oの操作方法を説明します。制御の個本的な説明はこちらを参照してください。

サンプルコードは以下の場所に配置されています。

C:\Program Files\SoftServo\WMX3\Samples\CSharp\VS2019\1_BasicMotion\06_ControlIO

デジタル値の読み書きの方法を行います。今回の時事では、Execute IO Functionの部分に着目してください。

using System;
using WMX3ApiCLR;

namespace CSharpAPISample
{
    class ControlIO
    {
        /*------------------------------------------------------------------*/
        /* デジタル入出力の制御サンプル
        /*------------------------------------------------------------------*/
        static void Main(string[] args)
        {
            WMX3Api Wmx3Lib = new WMX3Api();
            EngineStatus EnStatus = new EngineStatus();
            Io Wmx3Lib_Io = new Io(Wmx3Lib);

            byte[] inData = new byte[Constants.MaxIoInSize];
            byte[] outData = new byte[Constants.MaxIoOutSize];

            Console.WriteLine("Program Start.");
            System.Threading.Thread.Sleep(1000);

            // おまじない
            Wmx3Lib.CreateDevice("C:\\Program Files\\SoftServo\\WMX3\\",
                DeviceType.DeviceTypeNormal,
                0xFFFFFFFF);

            // このデバイスの名前を設定します。(デバック時に動作してるかを確認する時用)
            Wmx3Lib.SetDeviceName("ControlIO");

            // WMX管理下のネットワークに接続します
            for (uint i = 0; i < 100; i++)
            {
                Wmx3Lib.StartCommunication(0xFFFFFFFF);

                Wmx3Lib.GetEngineStatus(ref EnStatus);
                if (EnStatus.State == EngineState.Communicating)
                {
                    break;
                }
            }


            //----------------------------------------------------------------
            // Execute IO Function.
            //----------------------------------------------------------------
            //複数の入力バイトの値を取得します(開始アドレス,データ量,戻り値(渡した変数に値が書き込まれます))
            byte[] inData2 = null;         // 受け取り配列サイズが小さくても、正しく取得できました。
            Wmx3Lib_Io.GetInBytes(0x00, 2, ref inData2);

            // 複数バイトで出力
            Wmx3Lib_Io.SetOutBytes(0x00,4, new byte[] { 0x07});

            // 複数の出力バイトの値を取得
            byte[] outData3 = new byte[2];
            Wmx3Lib_Io.GetOutBytes(0x00, outData3.Length, ref outData3);
  

            // 入力バイトの値を取得
            Wmx3Lib_Io.GetInByte(0x00, ref inData[0]);

            // 出力バイトの値を設定
            Wmx3Lib_Io.SetOutByte(0x00, outData[0]);

            // 複数の出力バイトの値を取得
            Wmx3Lib_Io.GetOutByte(0x00, ref outData[0]);

            // 入力ビットの値を取得
            Wmx3Lib_Io.GetInBit(0x00, 0x00, ref inData[0]);

            // 複数の出力ビットの値を設
            Wmx3Lib_Io.SetOutBit(0x00, 0x00, outData[0]);

            // 出力ビットの値を取得
            Wmx3Lib_Io.GetOutBit(0x00, 0x00, ref outData[0]);

            Console.WriteLine("なにかキーを押すとプログラムが終了します");
            Console.ReadLine();

            Wmx3Lib.StopCommunication(0xFFFFFFFF);

            // おまじない
            Wmx3Lib.CloseDevice();
            Wmx3Lib_Io.Dispose();
            Wmx3Lib.Dispose();

            Console.WriteLine("Program End.");
            System.Threading.Thread.Sleep(3000);
        }
    }
}

ここで使用しているアプリはWMX標準のConsoleになります。使い方はこちらを参照してください。

GetInByte() 1接点の値を読み取る

基本的なデジタル入力です。 引数は、GetInByte(読み取り位置, ref データが入る変数)になります。スイッチやセンサ入力を行うときに使用します。

GetInBytes() 複数の入力バイトの値を取得

この関数は、1接点の値を読み取るのではなく、まとめて読み取る命令になります。引数は、GetInBytes(読み取り位置,取得サイズ,データを入れる配列)になります。

Io.SetOutBit() 1接点の出力

Io.SetOutBit(0x00, 0x00, outData[0]); (書き込みアドレス、bitのアドレス、出力値)

SetOutBytes() 複数の出力バイトの値を設定

引数はSetOutBytes (読み取り位置,設定サイズ,書き込むデータ配列)になります。
指定方法は以下の通りです。

複数バイトで出力を行った結果

Wmx3Lib_Io.SetOutBytes(0x00, 2, new byte[] { 0x02, 0x04});

アドレス0に0x02。

アドレス1に0x04が書き込まれました。

new byte[]で、いきなり配列を作成することができます。

設定サイズを配列よりも小さくした場合の挙動

Wmx3Lib_Io.SetOutBytes(0x00, 1, new byte[] { 0x06, 0x08}); // BADコード

出力させる配列サイズ関係なく、指定したサイズが出力されます。配列2つ名の0x08は無視されます。

設定サイズを配列よりも大きくした場合の挙動

Wmx3Lib_Io.SetOutBytes(0x00,4, new byte[] { 0x07}); // BADコード

出力配列が足りない場合、指定したサイズ数分0として書き込まれます。エラーにはなりません。new byte[] { 0x07,0x00,0x00,0x00}と同じ挙動になります。

GetOutByte() デジタル出力バイトの値を取得

デジタル出力の現在の出力設定値を取得する処理です。使用方法を説明します。

1. GetOutByte() とは?

GetOutByte() は、現在のデジタル出力ポート(1バイト単位)の論理的な出力状態を取得する関数です。

通常、特定のビット(Bit)だけをON/OFFする「Bit単位」の制御APIを使用する場合、API内部で現在の状態が管理されているため、ユーザーが現在の値を意識する必要はほとんどありません。

しかし、複数bitである部分のみ制御する場合は話が別です。ここに、データの不整合を引き起こすリスクが潜んでいます。

2. なぜ「現在の値」を取得する必要があるのか?

制御プログラム(スレッドやタスク)が完全に1つだけで、そのプログラムが全てのI/Oを管理している場合、変数の値と実際の出力状態は一致するため、出力状態をわざわざ読み出す必要はありません。

しかし、実際の装置制御では以下のようなケースが多々あります。

  • マルチスレッド制御: メイン制御タスクとは別に、RTXタスクやWMXタスクが動いている。
  • リソースの共有: 1つの出力ポート(8bit)のうち、Bit 0-3を「軸制御用」、Bit 4-7を「周辺機器用」として別のモジュールが制御している。

このような状況で、GetOutByte() を使わずにByte単位の書き込みを行うとどうなるでしょうか?

3. 発生するリスク:意図しない「上書き」

例えば、あるタスクがBit 0-3を変更するために、Byte単位でデータを書き込むとします。この時、別のタスクがBit 4-7を操作していたとしても、その状態を考慮せずにバイト全体を書き換えてしまうと、他方のタスクが行った変更を勝手に上書き(リセット)してしまうことになります。

これを防ぐためには、以下の手順(Read-Modify-Write)が必要です。

  1. Read: GetOutByte() で現在の出力ポート全体の状態(8bit)を読み取る。
  2. Modify: 変更したいBit部分だけをマスク処理(AND/OR演算)で書き換える。
  3. Write: 編集後のデータを SetOutByte() で書き込む。

GetOutByte() は、この「安全な書き換え」を実現し、システム全体の**データ整合性(一貫性)を保つために不可欠な機能です。

センサ入力が変わったとき、速攻別の処理を行いたいとき

例えばEtherCATの周期が1msの場合、

while (true)    // BADコード
{
    Wmx3Lib_Io.GetInBytes(0x00, 1, ref inData);
    if ((inData[0] & 0x01) == 0x01)
        break;
}

こんな感じで、全力で値を取得しても意味がありません。この関数は、最新の通信サイクルの値が帰ってきます。(EtherCAT周期が1msの場合、実際の値は1msで更新されます)無駄にCPUの使用率が上がることになります。

C#でリアルタイム処理を行う場合は、WMXのイベントを検討してください。

おわりに

以上で、WMXを用いたデジタルI/O(DIO)の入出力制御の実装は完了です。

単にモーターを回すだけでなく、センサからの入力で処理を変えたり、異常検知時に停止させたりといった「シーケンス制御」の基礎が、今回のC#コードで構築できるようになりました。

実際の装置開発では、リミットセンサによる安全対策や、シリンダの着座確認など、I/O制御はモーションコントロールと密接に関わります。今回解説した記述方法をベースに、より複雑で堅牢なアプリケーション開発に挑戦してみてください。

この記事が、あなたの快適な装置制御開発(ソフトウェアモーションコントロールライフ)の一助となれば幸いです。

コメント

タイトルとURLをコピーしました