身の回りにある動くものの多くはモーターかエンジンで動いています。中でも工作機械や電子部品の実装装置、医療機器など、正確な位置決めを行うものにはサーボモーターが使われています。

サーボモーターはモーター本体に加えて、モーターの回転位置や速度を検出するエンコーダー、モーターを駆動するサーボアンプ、制御するサーボコントローラーで構成されます。エンコーダーからの情報をフィードバックしてモーターを制御することで正確な位置決めなどが可能になっています。

今回、日本パルスモーター様から「IoTサーボコントローラーTITAN」とモーターをお借りできたので、ラズベリパイにつないでサーボモーターの動作データを取得してみました。

IoTサーボコントローラーTITAN(左)とステッピングモーター(右)

IoTサーボコントローラーTITAN

TITANはサーボアンプとサーボコントローラーの機能を持ったサーボコントローラーで、ステッピングモーター、ロータリーサーボモーター、リニアサーボモーターなど様々なタイプのモーターが制御できます。スクリプト言語でモーターに対する命令を記述でき、スクリプトプログラムをTITAN内部に保存することで、TITANとモーターだけでスタンドアローン動作可能です。またTITANはイーサネットやRS485、USBでパソコンなどと通信できます。モーター稼働させながら動作状態を監視するような、IoTシステムを構成するのに適したサーボコントローラーです。

今回は最初に設定フェーズとして、パソコンとTITAN、モーターを接続してTITANの初期設定およびスクリプトプログラムの書き込みをおこない、次にパソコンを外してTITANとモーターをスタンドアローン動作させ、ラズベリパイをつないでモーターの動作データを取得しました。

関連資料

TITANの初期設定は次の資料に沿っておこないます。この資料はユーザー登録無しでダウンロードできます。

TITANでサーボモーターを制御するためには、次の関連資料が参考になります。

  • TITAN-SVX-ETHハードウェア取扱説明書
  • TITANソフトウェア取扱説明書
  • TITANコミュニケーション(ASCII通信)取扱説明書
  • TITANコミュニケーション(MODBUS)取扱説明書
  • TITANプログラミング言語(A-Script)取扱説明書

これらの資料は日本パルスモーター社のWebサイトのダウンロードページからダウンロードできます。ダウンロードの際にはユーザー登録が必要です。

使用機材と接続

  • IoTサーボコントローラー:TITAN-SVX-ETH
  • モーター:Prostepper社製エンコーダー付2相ステッピングモーターPEM28H245E2KZS-03
  • DC24V6.4A電源
  • ラズベリパイ 3BPlus
  • USB to RS485コンバーター DSD TECH SH-U10

モーターには電源ケーブルと回転位置や速度を検出するエンコーダーの信号ケーブルがあります。電源ケーブルをTITANのモーターコネクタに、エンコーダーからの信号ケーブルをTITANのエンコーダーコネクタに接続します。

設定フェーズではTITANとWindowsパソコンをUSBケーブルで接続します。TITAN側はmini-Bタイプなので、mini-BタイプのUSBケーブルが必要です。

スタンドアローン動作フェーズではTITANとラズベリパイをRS485で接続しました。ラズベリパイ側はUSB to RS485コンバーターをラズベリパイのUSBポートに接続し、TITANとは次のようにRS485で接続します。

初期設定

TITANソフトウェアでTITANの初期設定をおこないます。TITANソフトウェアは次のARCUS Servo Motion社のサイトからダウンロードします。ARCUS社のサイトでもダウンロードにはユーザー登録が必要です。

TITANソフトウェアをインストールして起動すると、次のような画面が表示されます。

「TITANスタートアップガイド」に沿ってTITANの初期設定をします。大まかには次のような流れです。

  1. USB通信の設定
    Communicationアイコンをクリックし、USBケーブルをつないだCOMポートの設定と通信のテストをします。
  2. モーターパラメーター設定
    Motorアイコンをクリックし、モーターの種類を選択します。モーターに何もつながず無負荷の状態でモーターを起動し、モーターの特性を自動認識します。
  3. チューニング
    Tuningアイコンをクリックし、モーターで動かす対象物(機構)をつなぎ、モーターを起動して、機構を含めた特性を自動認識します。今回は対象物はないので、何もつながずにチューニングをおこないました。
  4. 動作モード等の設定
    Configurationアイコンをクリックし、動作モード等を設定し、最後に設定情報をTITANにダウンロードし、TITANのメモリに書き込みます。

動作確認

初期設定ができたので、TITANでモーターを動かしてみます。まずは1分間33回転でモーターを回転させてみます。

TITANソフトウェアのTest Driveアイコンをクリックし、①「SVON」でサーボ制御をONにして、②「SPEED」を33に設定し、③「JV+」をクリックするとモーターが回り始めます。④「STOP」をクリックするとモーターが止まります。この状態でモーターの軸をつまんで回そうとしてもサーボ制御が有効なため回りません。⑤「SVOFF」でサーボ制御をOFFにするとモーターは指で回せるようになります。

ちなみに、サーボ制御をONにしないで「JV+」をクリックすると、「Motor not enabled. Enable first.」というメッセージが表示され、モーターは動きません。

スクリプトプログラムでサーボモーターを制御

TITANには「A-Script」というスクリプト言語があり、サーボ制御のON/OFF、モーター速度や加速度の設定、モーター電流などの情報の読み取り、IF、WHILEといった条件制御ができます。位置を指定して「位置0までモーターを動かす」といったサーボモーター特有の制御もできます。

「A-Script」を使って簡単なプログラムを書きました。

PRG 0
HSPD = 800 ; 速度を800に設定
ACC = 800 ; 加速度を800に設定
SVON ; サーボ制御をON
DELAY = 1000 ; 1000ミリ秒(1秒)待つ
WHILE TRUE
    X80000 ; モーターを位置80000に移動させる
    WAITX ; 動作の完了を待つ
    DELAY = 1000 ; 1秒待つ
    X0 ; モーターを位置0に移動させる
    WAITX ; 動作の完了を待つ
    DELAY = 1000 ; 1秒待つ
ENDWHILE
END

「;」から右はコメントです。実際には全角文字のコメントは使えませんが、説明のために日本語でコメントを書きました。全角文字が入ったプログラムをダウンロードしようとすると、「アプリケーションのコンポーネントで、ハンドルされていない例外が発生しました。」というエラーが表示されます。このエラーが表示されたらプログラムのどこかに全角文字が含まれているので、探して削除しましょう。全角の空白「 」などが見つけにくいので要注意です。

プログラムは、速度と加速度を設定した後、サーボ制御をONにして、モーター位置を80000に移動させ、移動が完了したら1秒待ち、位置0に移動させ、移動が完了したら1秒待ち、また位置80000に移動させ、という動作を繰り返します。

このプログラムをTITANソフトウェアのTestDrive画面右側の①プログラムエリアに書き、②TITANにダウンロードして、③メモリに書き込みます。④「RUN」をクリックすると⑤の「Prog0」の状態がIDLEからRUNNINGに変わり、モーターが動き始めます。TestDrive画面左下の⑥を見ると、モーターの位置が表示され、0から80000に移動し、1秒停止し、80000から0に戻り、1秒停止し、を繰り返しているのが確認できました。

スタンドアローン動作

A-scriptプログラムをTITANにダウンロードし、メモリに書き込むと、TITANの電源を切ってもプログラムが保存されるようになります。プログラムをメモリに書き込んで、TITANソフトウェアのConfiguration画面右側の①「Misc」タブを選択し、「Power Up State」で②「Servo On」と③「Autorun」の「0」にチェックを入れ、設定を④ダウンロードし、⑤メモリに書き込むと、TITANの電源がONになるとプログラム0が自動実行されるようになります。これでパソコンをつながず、TITANとモーターだけでプログラムに従ってモーターを動作させることができました。

ラズベリパイでモーターの動作状態を取得

スタンドアローン動作ができたので、RS485でTITANとラズベリパイをつなぎ、モーターの動作状態を取得してみます。TITANのRS485での通信には次の2つの方式があります。

  • TITAN-ASCIIコマンド通信
  • Modbus通信

ASCIIコマンドで動作データ取得

まずWindowsパソコンのTITANソフトウェアでTITANの通信モードを①RS485 TITAN-ASCIIモードに設定し、②設定をダウンロードして、③メモリに書き込みます。

パソコンを外し、RS485ケーブルでTITANとラズベリパイを接続します。今回使っているUSB to RS485コンバーター(以下、RS485コンバーター)はCP2102という通信チップを使っています。ラズベリパイのOSが出すメッセージから「cp21」に関係するものを見ると、RS485コンバーターが /dev/ttyUSB0 でアクセスできることが分かります。

$ dmesg | grep cp21
[ 438.379957] usbcore: registered new interface driver cp210x
[ 438.380014] usbserial: USB Serial support registered for cp210x
[ 438.380141] cp210x 1-1.5:1.0: cp210x converter detected
[ 438.386169] usb 1-1.5: cp210x converter now attached to ttyUSB0

PythonプログラムでRS485コンバーターにアクセスしてみます。TITANとラズベリパイはRS485で通信していますが、USB to RS485コンバーターを介するとシリアル回線としてアクセスできます。Pythonでシリアル回線にアクセスするには pyserial というライブラリを使うので、まず pyserial をインストールします。

$ python3 -m pip install pyserial

TITANのRS485の通信パラメーターは次の通りです。

ボーレート115,200
バイトサイズ8ビット
パリティなし
フロー制御なし
ストップ ビット1

ボーレート以外は pyserial のデフォルト値なので、ボーレートだけ設定すれば次のようにRS485コンバーターにアクセスできます。データがないときや通信エラーの時に早くタイムアウトさせるために0.1秒に設定しています。

ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=0.1)

TITAN-ASCIIコマンド通信のフォーマットは次のようなフォーマットです。

コマンド開始文字「@」TITANのNetID区切り文字「:」コマンド列\r\n
応答開始文字「#」TITANのNetID区切り文字「:」応答列\r\n

TITANのNetIDはTITANソフトウェアのCommunication設定で設定するTITANのIDで、デフォルトは1です。コマンド列は「コマンド1;コマンド2」のようにコマンドを「;」でつないだものです。応答列はコマンドに対する応答を「コマンド1=値1;コマンド2=値2」のように「;」でつないだものです。最後の\rと\nは「\」「r」「\」「n」のばらばらの4文字ではなく、「\r」「\n」という2文字です。

例えばID1のTITANに対してモーターの状態と位置を取得するコマンドとその応答は次のようになります。

コマンド @01:MST;EX\r\n
応答   #01:MST=03;EX=1234\r\n

PythonでTITANに接続し、エラー情報、モーター状態、位置、速度、モーター電流値を取得してみました。

# -*- coding: utf-8 -*-
import serial
import time

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=0.1)

if ser.is_open:
    print('RS485 to TITAN is open')

    data = []
    startT = time.perf_counter()
    while (time.perf_counter() - startT) < 20.0:
        loopT = time.perf_counter()
        ser.write(b'@01:FLT;MST;EX;VX;CURQA;CURDA\r\n')  # エンコーダ位置取得
        line = ser.readline()
        # b'FLT=0x0;MST=0x3;EX=9999;CURQA=-0.0379776;CURDA=0.0160128\r\n'
        line.decode()[4:-2].split(';')
        # ['FLT=0x0', 'MST=0x3', 'EX=9999', 'CURQA=-0.0379776', 'CURDA=0.0160128']
        d = {'time': time.perf_counter() - startT}
        for x in line.decode()[4:-2].split(';'):
            k, v = x.split('=')
            try:
                d[k] = int(v, 0)
            except ValueError:
                d[k] = float(v)
        data.append(d)
        time.sleep(max(0.1 - (time.perf_counter() - loopT), 0))  # sleep()の値をマイナスにしないようにする
    print(len(data))

    t = [a['time'] for a in data]
    x = [a['EX'] for a in data]
    cq = [a['CURQA'] for a in data]
    v = [a['VX'] for a in data]

    fig = plt.figure(figsize=(9,6))
    ax = fig.add_subplot(2,1,1)
    ax.plot(t, x)
    ax = fig.add_subplot(2,1,2)
    ax.plot(t, v)

    fig = plt.figure(figsize=(9,4))
    ax1 = fig.add_subplot()
    ax1.plot(t, v)
    ax2 = ax1.twinx()
    ax2.plot(t, cq, 'r')

PythonでTITANにコマンドを送信するにはserialオブジェクトのwriteメソッドを使います。writeメソッドの引数はbyte列なので、コマンド文字列を b” で囲ってbyte列にして送ります。

ser.write(b'@01:MST;EX\r\n')

TITANからの応答は最後が改行コードなので、readlineメソッドで読み込めます。

line = ser.readline()
b'#01:MST=0x5;EX=59530\r\n'

応答もbyte列で得られます。これを {‘コマンド1’ : 値1, ‘コマンド2’ : 値2} という辞書形式に変換しておくと便利です。

21行目のようにdecodeメソッドでbyte列を文字列に変換し、先頭の4文字(#01:)と最後の2文字(\r\n)を削除し、「;」で分割して’コマンド=値’という文字列のリストに変換します。更に24行目から29行目のようにして辞書形式に変換します。TITANからの応答の値は10進整数、16進整数、不動小数で表示されている場合があります。10進整数か16進整数であれば int(v, 0) で整数に変換し、不動小数であれば ValueError 例外が発生するので、float(v) で数値に変換しています。

プログラムでは0.1秒ごとに20秒間、エラー情報、モーター状態、位置、速度、モーター電流値を取得し、取得した値を matplotlib でグラフ化して確認しています。

A-Scriptプログラムで指定したように、モーター位置が0から80000に移動し、1秒停止し、80000から0に戻り、1秒停止し、を繰り返しています。位置0から80000に移動する時には速度が0から加速し、後半は減速し、位置80000に達すると速度は0になります。位置0に戻る時は逆方向に加速、減速し、位置0に戻ると速度も0になることが確認できました。

一般的には加速、減速中はモーターを駆動するために電流が多く流れ、一定速度で動いているときは電流値は少なくなります。実際のデータではそのような関係は確認できませんでした。モーターに何もつないでいない無負荷の状態なので、顕著な電流変化がないのかもしれません。

Modbusによる動作データ取得

TITANのRS485通信にはASCIIコマンドでの通信の他に、Modbusによる通信が可能です。

Modbusは産業用電子機器をつなぐための通信プロトコルです。マスター/スレーブ型のプロトコルで、パソコンからセンサーデータを読む場合、パソコンがマスターとなってリクエストを出し、センサーがスレーブとして応答を返します。物理的なネットワークとしてはRS485やイーサネットなどに対応しています。TITANもRS485とイーサネットの両方でのModbus通信が可能です。今回はRS485のModbus通信でデータを取得してみます。

WindowsパソコンのTITANソフトウェアでTITANの通信モードを①MODBUS RTUモードに設定します。さらに「MODBUS Register Format」の②「Little Endian-32 bit signed」を選択し、③「Byte Swapped」にチェックを入れ、設定をTITANに④ダウンロードして、⑤メモリに書き込みます。

PythonでModbus通信をおこなうには、Pymodbusというライブラリを使うので、まずPymodbusをインストールします。

$ python3 -m pip install pymodbus

ASCIIコマンド通信のときと同様に、モーター位置と速度を取得してみます。TITANではこれらの情報はModbusの read_holding_registers() 関数で読み出せます。

レジスタアドレス長さデータ
02エンコーダー実位置
22実速度
42目標位置
62目標速度
82位置エラー
102モーターステータス
122エラーコード
142スタンドアローンプログラムステータス

Pythonプログラムは次のようになります。TITANのアクセスはクラスにまとめました。Titanクラスを生成すると、TITANをアクセスする /dev/ttyUSB0 というデバイスとボーレートを指定してModbusのインスタンスを作り、TITANに接続します。Modbusではスレーブデバイスからの応答が得られないとデフォルトで3秒待ってしまうので、0.1秒のタイムアウトを指定し、エラー時の待ち時間を短くしています。

# -*- coding: utf-8 -*-
from pymodbus.client.sync import ModbusSerialClient
from pymodbus import exceptions
import time
import struct
import binascii

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

def hex_to_float(s):
    if s.startswith('0x'):
        s = s[2:]
    return struct.unpack('>f', binascii.unhexlify(s))[0]

def twosComplement_hex(hexval):
    bits = 32
    val = int(hexval, 16)
    if val & (1 << (bits-1)):
        val -= 1 << bits
    return val

class Titan:
    def __init__(self):
        self.client = ModbusSerialClient(port="/dev/ttyUSB0", method="rtu", baudrate=115200, timeout= 0.1)
        self.client.connect()
    
    def readHRegs(self, addr, count, unit=1):
        while True:
            try:
                res = self.client.read_holding_registers(address=addr, count=count, unit=unit)
            except Exception as e:
                print('Exceptio in read_holding_registers')
                print(e, flush=True)
                res = None

            if res == None or isinstance(res, exceptions.ModbusIOException):
                pass
            else:
                regs = [x + y * 0x10000 for (x, y) in zip(res.registers[::2], res.registers[1::2])]
                break
        return regs

titan = Titan()

data = []
startT = time.perf_counter()
while (time.perf_counter() - startT) < 20.0:
    loopT = time.perf_counter()

    regs = titan.readHRegs(0, 16)
    d = {'time': time.perf_counter() - startT}
    d['EX'] = twosComplement_hex('{:x}'.format(regs[0]))
    d['VX'] = twosComplement_hex('{:x}'.format(regs[1]))
    d['ex'] = regs[0]
    d['vx'] = regs[1]

    data.append(d)
    time.sleep(max(0.1 - (time.perf_counter() - loopT), 0))  # sleep()の値をマイナスにしないようにする

t = [a['time'] for a in data]
x = [a['EX'] for a in data]
v = [a['VX'] for a in data]

fig = plt.figure(figsize=(9,6))
ax = fig.add_subplot(2,1,1)
ax.plot(t, x)
ax = fig.add_subplot(2,1,2)
ax.plot(t, v)

TITANでは、read_holding_registers() で通信エラーがあると ModbusIOException というオブジェクトが返されます。Titanクラスの readHRegs() 関数では、例外や ModbusIOException オブジェクトが返されたときは読み出しを再実行し、正常な応答を得るようにしています。

Modbusでは16ビットのレジスタの個数を指定して読み出すことができますが、TITANでは2つのレジスタで1つのデータを表しており、偶数個のレジスタを指定した読み出しだけがサポートされています。2レジスタ(1データ)をアドレスを指定して読むこともできますし、1回の読み出しで連続した複数のデータを読むことも出来ます。サンプルプログラムではアドレス0(エンコーダー実位置)からスタンドアローンプログラムステータスまで16個のレジスタ(8データ)を連続して読み出しています。

各データは16進数で表現されていますが、位置、速度などは符号付きの10進数で、モーターステータスなどはビットパターンでと、データによって扱いが異なるので、注意が必要です。

Modbusでも0.1秒ごとに20秒間、モーター位置と速度を取得し、値を matplotlib でグラフ化して確認しました。モーター位置が0から80000を行ったり来たりしているのが確認できました。

まとめ

IoTサーボコントローラーTITANはサーボアンプとサーボコントローラーの機能を持ち、複数タイプのサーボモーターを制御できるサーボコントローラーです。TITANを使うと、モーターの制御に加えて、制御しているモーターの位置、速度、電流などのデータをPythonなどのプログラムで比較的簡単に取得できます。サーボモーターを使った装置の稼働実績管理など、IoTシステムを構成する上で使いやすいサーボコントローラーです。