🥂

Sysmac StudioとSimumatikを使用して仮想のリニアガイドを動かす

2025/01/26に公開

はじめに

本記事は、PLC(Programmable Logic Controller)向けのソフトウェア開発に従事、または関心のある方で、Structured Text(ST)による開発に興味がある方向けです。OMRON社のSysmac Studioを使用します。

今回は、Simumatikを使用して仮想環境下でサーボ制御のリニアガイドを動かします。Simumatik上にあるジョイスティックとボタンでリニアガイドを操作できるようにします。Simumatikは、いわゆるデジタルツインプラットフォームの一つです。仮想環境を使用すれば、人が介在するインタラクティブな操作もテストしやすくなります。

SysmacシミュレータとSimumatikによる仮想リニアガイド操作
SysmacシミュレータとSimumatikによる仮想リニアガイド操作

制御ソフトウェアは、Sysmacシミュレータ上で動作し、ローカルで動作するSimumatikのGatewayが介在して、ローカルのWorkspaceでシミュレーションを実行した後、ブラウザ上のWeb-app clientにレンダリングされます。基にしたSystemはCODESYS向けですが、少し修正するだけで汎用的に使用できます。Simumatikのウェビナーに、Sysmac Studioによるモーション制御があったのですが、もう少し単純でインタラクティブなものが欲しかったのです。

FA向けの仮想環境は、構造的な解析や実物ができる前のお披露目の為にあるわけではありません。大規模な装置であれば、現在操作を行っている場所から死角となる対象についても操作を行いたいということはよくあります。仮想環境があればこれらを事前に確認し、その品質を高めることができます。納品後に大きな変更を加えたり、立ち合い時に慌てて行うことに比べれば、顧客からの信頼も違ったものになります。装置は、完全に自動化していない限り人が介在します。また、完全に自動化していてもメンテナンスには人が介在します。そして、操作性の過不足は装置の生産性に影響します。

Sysmacプロジェクト

次にSysmacプロジェクトがあります。

https://github.com/kmu2030/Motion-control-on-Simumatik-with-Sysmac-Studio

Simumatikのセットアップ

Simumatik Platformは、ブラウザ上でシミュレーション結果のレンダリングを行うWeb-app client、ローカルでシミュレーション処理を行うWorkspace、SysmacシミュレータのようなサードパーティーとWorkspaceのやり取りを仲介するGatewayから成ります。Web-app clientはSimumatikのWebプラットフォームへのサインイン、WorkspaceとGatewayは端末へのソフトウェアインストール(Simumatik Launcher)を必要とします。

Simumatik Platformへのサインインは次にアクセスします。

http://app.simumatik.com/

Simumatik Launcherのインストールは次を参考にします。

https://docs.simumatik.com/launcher/

SimumatikのSystemのセットアップ

Simumatikにシミュレーションを行うSystemのセットアップを行います。無料アカウントは制限により公開されているSystemのCloneとSaveができないため、公開されているSystemをロードし、変更を加えてセットアップします。今回は、"Introduction to Motion Control System" を使用します。次の手順で行います。モデリングツールやUnity環境に触れたことがあれば困難はないと思います。

SimumatikのSystemのセットアップ
SimumatikのSystemのセットアップ

変更項目は次です。操作ユニットのボタン配置は好みに合わせて変更します。

Component Property Value
Inductive_sensor_IN4 Z 0.797m
Linear_Driver_1 Actual position variable Drive1.ActualPosition
Driver type Omron_nexsocket
Encoder relation 1000.0
Setpoint variable Drive1.SetPosition
Linear_Driver_2 Actual position variable
Driver type Omron_nexsocket
Setpoint variable
Linear_motion_slide_encoder Relation 0.001
PB_NC_IN1 Label visible
Label text #Stop
PB_NO_IN0 Label visible
Maintained
Label text #Servo on
PB_NO_IN2 Label visible
Label text #Move to Zero
PLC_16DIO_4AIO Analog range -4000.00 to 4000.00
Driver type Omron_nexsocket
Var AI1 AI1
Var DI1 DI1
Var DI2 DI2
Var DO1 DO1
Var DO2 DO2
Voltage range -10.00 to 10.00
Potentionmeter Initial value 100.0

Simumatikでのシミュレーション

Simumatikは、Sysmacシミュレータの実行周期に同期した処理を行うわけではありません。Simumatikでシミュレーションを行う場合、時間に強く依存した制御は避けます。例えば、位置制御のモーション制御について、モーション制御命令にSimumatik内の現在位置をフィードバックとして与えてもうまくいきません。時間がかかりすぎて位置偏差異常になります。時間に強く依存した制御は、Sysmacシミュレータ内だけで完結させ、Simumatikからのフィードバックは使わないようにします。

一方、イベントベースの制御は、Simumatikをどの程度の負荷で動作させるかにもよりますが、極端にシビアでなければSimumatikからのフィードバックを使用してもうまくいきます。目視レベルで判別できるものであれば機能しそうです。

使用する端末は、そこそこのものが必要です。Sysmac Studioで作業を行いながら、Sysmacシミュレータを起動し、Simumatikも走らせる必要があります。

Sysmacプロジェクトの構成

リニアガイドの寸動操作と原点位置移動操作だけですが、モーション制御命令を使用します。今回使用するSystem内のリニアドライブは、いわゆるサーボモータを細かにシミュレートするわけではありません。外部シミュレータとやり取りするのは、目標位置、現在位置だけです。よって、サーボドライブの細かなパラメータ設定の体験はできません。また、モーション軸設定はSysmac Studioに比べると抽象的で簡素であり、Sysmac Studioのモーション軸設定とは概念が異なるため設定の転用はできません。1Sサーボを模したComponentが公開されれば同様の設定で意図した動作を行うものを使えるようになるかもしれません。どうしても必要であれば自作という手もあります。

Sysmac Studio側は、ドライブプロファイルを満たしたサーボドライバを使用する前提とし、リニアガイドは、長さが1mの適当な仕様のものとします。軸設定で単位とスケールを合わせます。エンコーダの設定とサーボパラメータは、1Sサーボによる標準的な単軸位置制御としておけばよいです。詳細は、Sysmacプロジェクトを確認してください。I/Oについては、入出力共に16bit分、アナログ1chを満たす構成にすれば十分です。

プログラム

プログラムは次のPOUから成ります。

  • プログラム/Main
    制御プログラムのエントリポイントです。
  • プログラム/SimDriveReflection
    シミュレーション時にモーション制御値を全ての使用軸に反映します。
  • ファンクションブロック/SliderController
    リニアガイドを制御します。
  • ファンクションブロック/SimMCReflector
    シミュレーション時にモーション制御値を軸変数に反映します。

Main

Mainは制御プログラムのエントリポイントです。"入力-処理-出力"の基本的な構造です。IO割り付けは、Simumatik上のPLCに合わせています。Simumatikは仮想環境ですが、構築したSysmacプロジェクトは、NXで同様に機能させるのに必要なデバイス構成とI/O割り付けを行っています。もし、同等の物理デバイスがあればそのまま動かせることになります。そして、このMain下に含まれるコードは、シミュレーションに関連したコードを含んでいません。実行環境に関係なく同一のコードが動作します。

POU/プログラム/Main
// IO in
iSliderModel.CtrlServoOn := DI1[0];
iSliderModel.CtrlStop := NOT DI1[1];
iSliderModel.CtrlMoveToZero := DI1[2];
iSliderModel.PowerOn := DI1[3];
iSliderModel.NegLimit := DI1[4];
iSliderModel.PosLimit := DI1[5];
iSliderModel.CtrlJogLeft := DI1[6];
iSliderModel.CtrlJogRight := DI1[7];
iSliderModel.Velocity
  := ScaleTrans(SclIN:=AI1,
                X0:=0, Y0:=0,
                X1:=4000, Y1:=190,
                SclOfs:=10);

// Process
iSliderModel.AllowMotion := TRUE;
iSliderController(Enable:=TRUE,
                  Axis:=_MC_AX[0],
                  Model:=iSliderModel);

// IO out
DO1[0] := _MC_AX[0].DrvStatus.ServoOn;
DO1[1] := _MC_AX[0].DrvStatus.DrvAlarm;

SimDriveReflection

SimDriveReflectionは、シミュレーション時にモーション軸制御の出力を全ての使用軸変数に反映します。SimDriveReflectionはデバッグを有効とし、実機では動作しないように設定します。

POU/プログラム/SimDriveReflection
// Simulation
FOR i := 0 TO 15 DO
  iSimMCRefrectors[i](Enable:=TRUE,
                      Axis:=_MC_AX[i]);
END_FOR;

// Simumatik
Drive1.SetPosition := TO_REAL(_MC_AX[0].Act.Pos);
Drive2.SetPosition := TO_REAL(_MC_AX[1].Act.Pos);

SliderController

SliderControllerは、リニアガイドを制御するFBです。分量はありますが、単純なモーション制御の集合です。ソフトウェアリミット、セーフティ機能は使用していません。

POU/ファンクションブロック/SliderController
// FB control
IF Enable AND NOT Busy THEN
  Busy := TRUE;
  
  iState := STATE_WAIT;
END_IF;

// IO in
iPrevStop := iStop;
iStop := Model.CtrlStop
  OR iMC_Power.Error
  OR NOT Model.AllowMotion;

// Control
IF Model.CtrlImmediateStop THEN
  iState := STATE_IMMEDIATE_STOP;
ELSIF iStop AND NOT iPrevStop THEN
  CASE iState OF
    20..69:
      iMC_MoveJog.NegativeEnable := FALSE;
      iMC_MoveJog.PositiveEnable := FALSE;
      iMC_MoveZeroPosition.Execute := FALSE;
      
      iState := STATE_STOP;
  END_CASE;
END_IF;

// Process
CASE iState OF
  // STATE_INIT
  0:
    iMC_ImmediateStop.Execute := FALSE;
    iMC_Stop.Execute := FALSE;
    iMC_Power.Enable := FALSE;
    iMC_Reset.Execute := FALSE;
    iMC_Home.Execute := FALSE;
    iMC_MoveJog.NegativeEnable := FALSE;
    iMC_MoveJog.PositiveEnable := FALSE;
    iMC_MoveZeroPosition.Execute := FALSE;
    
    IF NOT Enable THEN
      iState := STATE_DONE;
    ELSE
      Inc(iState);
    END_IF;
  1:
    IF Model.PowerOn THEN
      Inc(iState);
    END_IF;
  2:
    IF Model.CtrlServoOn THEN
      iMC_Power.Enable := TRUE;
      
      Inc(iState);
    END_IF;
  3:
    IF Axis.DrvStatus.ServoOn THEN
      iState := STATE_WAIT;
    ELSIF iMC_Power.Error THEN
      iState := STATE_INIT;
    END_IF;

  // STATE_WAIT
  10:
    IF NOT Model.PowerOn
      OR NOT Model.CtrlServoOn
      OR iMC_Power.Error
    THEN
      iMC_Power.Enable := FALSE;
      
      iState := STATE_INIT;      
    ELSIF Model.CtrlStop THEN
      iState := STATE_STOP;
    ELSIF Model.CtrlResetError THEN
      iState := STATE_RESET_ERROR;
    ELSIF Model.CtrlHome THEN
      iState := STATE_HOME;
    ELSIF Model.AllowMotion THEN
      IF Model.CtrlJogLeft THEN
        iState := STATE_JOG_LEFT;
      ELSIF Model.CtrlJogRight THEN
        iState := STATE_JOG_RIGHT;
      ELSIF Model.CtrlMoveToZero THEN
        iState := STATE_MOVE_TO_ZERO;
      END_IF;
    END_IF;

  // STATE_JOG_LFET
  20:
    IF Model.NegLimit THEN
      iState := iState + 9;
    ELSIF Model.CtrlJogLeft THEN
      iMC_MoveJog.NegativeEnable := TRUE;
      iMC_MoveJog.Velocity := Model.Velocity;
      
      Inc(iState);
    END_IF;
  21:
    IF iMC_MoveJog.Error THEN
      iMC_MoveJog.NegativeEnable := FALSE;
      iMC_Stop.Execute := TRUE;
      
      Inc(iState);
    ELSIF NOT Model.CtrlJogLeft OR Model.NegLimit THEN
      iMC_MoveJog.NegativeEnable := FALSE;
      iMC_Stop.Execute := TRUE;
          
      Inc(iState);
    END_IF;
  22:
    IF iMC_Stop.Done OR iMC_Stop.Error THEN
      iMC_Stop.Execute := FALSE;
      
      iState := iState + 7;
    END_IF;
  29:
    IF Axis.Status.Standstill THEN
      iState := STATE_WAIT;
    END_IF;

  // STATE_JOG_RIGHT
  30:
    IF Model.PosLimit THEN
      iState := iState + 9;
    ELSIF Model.CtrlJogRight THEN
      iMC_MoveJog.PositiveEnable := TRUE;
      iMC_MoveJog.Velocity := Model.Velocity;
      
      Inc(iState);
    END_IF;
  31:
    IF iMC_MoveJog.Error THEN
      iMC_MoveJog.PositiveEnable := FALSE;
      iMC_Stop.Execute := TRUE;
      
      Inc(iState);
    ELSIF NOT Model.CtrlJogRight OR Model.PosLimit THEN
      iMC_MoveJog.PositiveEnable := FALSE;
      iMC_Stop.Execute := TRUE;
          
      Inc(iState);
    END_IF;
  32:
    IF iMC_Stop.Done OR iMC_Stop.Error THEN
      iMC_Stop.Execute := FALSE;
      
      iState := iState + 7;
    END_IF;
  39:
    IF Axis.Status.Standstill THEN
      iState := STATE_WAIT;
    END_IF;
  
  // STATE_MOVE_TO_ZERO
  40:
    iMC_MoveZeroPosition.Velocity := Model.Velocity;
    iMC_MoveZeroPosition.Execute := TRUE;
    
    Inc(iState);
  41:
    IF iMC_MoveZeroPosition.Done OR iMC_MoveZeroPosition.Error THEN
      iMC_MoveZeroPosition.Execute := FALSE;
      
      iState := iState + 8;
    END_IF;
  49:
    IF NOT Model.CtrlMoveToZero THEN
      iState := STATE_WAIT;
    END_IF;

  // STATE_HOME
  70:
    iMC_Home.Execute := TRUE;
    
    Inc(iState);
  71:
    IF iMC_Home.Done OR iMC_Home.Error THEN
      iMC_Home.Execute := FALSE;
      
      iState := iState + 8;
    END_IF;
  79:
    IF NOT Model.CtrlHome THEN
      iState := STATE_WAIT;
    END_IF;

  // STATE_STOP
  80:
    iMC_MoveJog.NegativeEnable := FALSE;
    iMC_MoveJog.PositiveEnable := FALSE;
    iMC_MoveZeroPosition.Execute := FALSE;
    
    IF Axis.Status.Standstill THEN
      iState := iState + 9;
    ELSE
      iMC_Stop.BufferMode := _eMC_BUFFER_MODE#_mcAborting;
      iMC_Stop.Execute := TRUE;
    
      Inc(iState);
    END_IF;
  81:
    IF iMC_Stop.Done OR iMC_Stop.Error THEN
      iMC_Stop.Execute := FALSE;
      
      iState := iState + 8;
    END_IF;
  89:
    IF Axis.Status.Standstill THEN
      iState := STATE_WAIT;
    END_IF;

  // STATE_RESET_ERROR
  90:
    iMC_Reset.Execute := TRUE;
    
    Inc(iState);
  91:
    IF iMC_Reset.Done OR iMC_Reset.Error THEN
      iMC_Reset.Execute := FALSE;
      
      iState := iState + 8;
    END_IF;
  99:
    IF NOT Model.CtrlResetError THEN
      iState := STATE_WAIT;
    END_IF;
    
  // STATE_IMMADIATE_STOP
  100:
    iMC_MoveJog.PositiveEnable := FALSE;
    iMC_MoveJog.NegativeEnable := FALSE;
    iMC_MoveZeroPosition.Execute := FALSE;

    iMC_ImmediateStop.StopMode := _eMC_STOP_MODE#_mcImmediateStop;
    iMC_ImmediateStop.Execute := TRUE;
    
    Inc(iState);
  101:
    IF iMC_ImmediateStop.Done OR iMC_ImmediateStop.Error THEN
      iMC_ImmediateStop.Execute := FALSE;
      
      iState := iState + 8;
    END_IF;
  109:
    IF NOT Model.CtrlImmediateStop THEN
      iState := STATE_WAIT;
    END_IF;
  
  // STATE_DONE
  1000:
    Busy := FALSE;
    
    Inc(iState);
END_CASE;

iMC_ImmediateStop(Axis:=Axis);
iMC_Stop(Axis:=Axis);
iMC_Power(Axis:=Axis);
iMC_Reset(Axis:=Axis);
iMC_Home(Axis:=Axis);
iMC_MoveJog(Axis:=Axis);
iMC_MoveZeroPosition(Axis:=Axis);

SimMCReflector

SimMCReflectorは、シミュレーション時にモーション制御値を軸変数に反映するFBです。軸変数は、直接に値を書き込むことができないため、このような処理が必要です。

POU/ファンクションブロック/SimMCReflector
CASE Axis.Cfg.AxEnable OF
  _eMC_AXIS_USE#_mcNoneAxis,
  _eMC_AXIS_USE#_mcUnusedAxis:
    RETURN;
END_CASE;

IF Enable AND NOT Busy THEN
  Busy := TRUE;
  
  iState := STATE_INIT;
END_IF;

CASE iState OF
  // STATE_INIT
  0:
    iSimSetActPos.Enable := FALSE;
    iSimSetActVel.Enable := FALSE;
    iSimSetActTrq.Enable := FALSE;
    
    iState := STATE_WAIT;
  
  // STATE_WAIT
  10:
    IF NOT Enable THEN
      iState := STATE_DONE;
    ELSIF NOT Axis.Status.Standstill THEN
      iSimSetActPos.SetPos := Axis.Cmd.Pos;
      iSimSetActVel.SetVel := Axis.Cmd.Vel;
      iSimSetActTrq.SetTrq := Axis.Cmd.Trq;
      iSimSetActPos.Enable := TRUE;
      iSimSetActVel.Enable := TRUE;
      iSimSetActTrq.Enable := TRUE;

      iState := STATE_REFRECT_MOTION;
    END_IF;

  // STATE_REFRECT_MOTION
  20:
    iSimSetActPos.SetPos := Axis.Cmd.Pos;
    iSimSetActVel.SetVel := Axis.Cmd.Vel;
    iSimSetActTrq.SetTrq := Axis.Cmd.Trq;
    IF Axis.Status.Standstill THEN
      iState := iState + 9;
    END_IF;
  29:
    iSimSetActPos.Enable := FALSE;
    iSimSetActVel.Enable := FALSE;
    iSimSetActTrq.Enable := FALSE;
    
    iState := STATE_WAIT;
  
  // STATE_DONE
  1000:
    iSimSetActPos.Enable := FALSE;
    iSimSetActVel.Enable := FALSE;
    iSimSetActTrq.Enable := FALSE;
    
    Busy := FALSE;
    
    Inc(iState);
END_CASE;

iSimSetActPos(Axis:=Axis);
iSimSetActVel(Axis:=Axis);
iSimSetActTrq(Axis:=Axis);

シミュレーションの実行

SimumatikとSysmacシミュレータを使用したシミュレーションは次の手順で実行します。

  1. Simumatik LauncherでGatewayが動作していることを確認
  2. Web-app clientとGatewayが接続していることを確認
  3. Sysmacシミュレータを開始
  4. Web-app clientでシミュレーションを開始

Web-app clientでシミュレーション中にSysmacシミュレータを終了した場合、再度、Sysmacシミュレータを開始してもシミュレーションに反映されないことがあります。その場合、一度Web-app clientのシミュレーションを終了し、再度、開始します。シミュレーションを無事に開始できると、Web-app client内で次のような操作が行えるようになります。

SysmacシミュレータとSimumatikによる仮想リニアガイド操作
SysmacシミュレータとSimumatikによる仮想リニアガイド操作

まとめ

Simumatikは、まだドキュメントが弱いですが、Sysmacシミュレータを使用でき、構成物がシンプルでComponentの振る舞いであるBehaviorの記述も簡易なので手軽に使えます。Simumatikはどことなくゲームのような雰囲気がありますが、そのカジュアルさが良さです。シミュレーションは便利なのですが、どのような妥当性を確認できるのかを見失わないようにする必要があります。Simumatikは万能CAEではありません。Simumatikでシミュレートした対象について、シミュレーションで確認した事項が、その対象を具現化した場合にどのよう事項の妥当性を評価するものであるのかを読み違えないようにする必要があります。

Discussion