Arduino
Unity

UnityとArduinoをシリアル通信

More than 1 year has passed since last update.

はじめに (読み飛ばしてください 笑)

ちょっとした趣味でデータグローブを作ってUnityで遊べたらなと思いました。
ハードウェアの部分はArduinoで制御すればいいんですがUnityとArduino間の通信とどうしたもんじゃと。
調べたらUniduinoという大変素晴らしいAssetがあるみたいですが、使うのに$30するようです。

ふえぇ...学生には高いよぉ...

ということでいろんなネットの情報を参考にUniduinoを使わずにUnityとArduino間のシリアル通信を行いました。

環境

  • Windows 7
  • Unity 5.5.5f1 Personal

設定

今回はSytem.IO.Ports.SerialPortクラスを使います。
参考によると、SerialPortクラスを使うにはAPI Compatibility Levelを.NET 2.0 Subnetから.NET2.0へ変更する必要があるらしいです。

以下の手順で変更してください。
1. Edit > Project Setting > Playerを選択
2. Inspector > Other Settings > Optimizationにある Api Compatibility Levelを.NET 2.0に

プログラム

設定を行ったら後はプログラムを書き書きするだけ。
Arduino-Unity間で通信するにあたって気になったことを書いておきます。

Arduino側

Arduino側からUnity側に信号を送るときには区切り文字が必要です。ここでは参考に倣って"\t"を区切り文字とします。

Serial.print(Signal1);
Serial.print("\t");
Serial.print(Signal2);
Serial.print("\t");
...
Serial.print(Signaln);
Serial.println();

文字を読取るときは以下のように

  if ( Serial.available() ) {
    char mode = Serial.read();
    switch (mode) {
      case '0' : digitalWrite(13, LOW);  break;
      case '1' : digitalWrite(13, HIGH); break;
    }
  }

Unity側

Unity側ではシリアル通信の設定を行うスクリプト(SerialHandler.cs)と実際にシリアル通信を行うスクリプト(DoSomething.cs)をつくります。

SerialHandler.cs

データの送受信の設定を行うプログラムです。ポート名やボーレートなどを除き、ほぼ同じプログラムを記述したスクリプトを作成し、送信する文字列や受信した文字列の処理はDoSomething.csで行うのが良いかと思います。

クラス

SerialHandlerでは通常の他に以下のクラスを使います。

using System.IO.Ports;
using System.Threading; //スレッド通信
SerialDataReceivedEventHandler(DataReceiveHandler)
  • 非同期通信においてデータが到着したことを教えてくれるDataReceivedイベント
  • このハンドラを登録することで、何かを受信するたびにハンドラが呼び出される
  • データ構造などにアクセスするときは排他制御を行うなどの注意が必要
SerialPort
  • リンクを参照
SerialHandler.cs
using UnityEngine;
using System.Collections;
using System.IO.Ports;
using System.Threading;

public class SerialHandler : MonoBehaviour
{
    public delegate void SerialDataReceivedEventHandler(string message);
    public event SerialDataReceivedEventHandler OnDataReceived;

    //ポート名
    //例
    //Linuxでは/dev/ttyUSB0
    //windowsではCOM1
    //Macでは/dev/tty.usbmodem1421など
    public string portName = "COM1";
    public int baudRate    = 9600;

    private SerialPort serialPort_;
    private Thread thread_;
    private bool isRunning_ = false;

    private string message_;
    private bool isNewMessageReceived_ = false;

    void Awake()
    {
        Open();
    }

    void Update()
    {
        if (isNewMessageReceived_) {
            OnDataReceived(message_);
        }
        isNewMessageReceived_ = false;
    }

    void OnDestroy()
    {
        Close();
    }

    private void Open()
    {
        serialPort_ = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One);
         //または
         //serialPort_ = new SerialPort(portName, baudRate);
        serialPort_.Open();

        isRunning_ = true;

        thread_ = new Thread(Read);
        thread_.Start();
    }

    private void Close()
    {
        isNewMessageReceived_ = false;
        isRunning_ = false;

        if (thread_ != null && thread_.IsAlive) {
            thread_.Join();
        }

        if (serialPort_ != null && serialPort_.IsOpen) {
            serialPort_.Close();
            serialPort_.Dispose();
        }
    }

    private void Read()
    {
        while (isRunning_ && serialPort_ != null && serialPort_.IsOpen) {
            try {
                message_ = serialPort_.ReadLine();
                isNewMessageReceived_ = true;
            } catch (System.Exception e) {
                Debug.LogWarning(e.Message);
            }
        }
    }

    public void Write(string message)
    {
        try {
            serialPort_.Write(message);
        } catch (System.Exception e) {
            Debug.LogWarning(e.Message);
        }
    }
}

DoSomething.cs

SerialHandler.csのSerialHandlerクラスを使用することでシリアル通信を行います。

hogehoge.cs
public class hogehoge : MonoBehaviour
{
  //先ほど作成したクラス
  public SerialHandler serialHandler;

  ...

  void Start()
  {
     //信号を受信したときに、そのメッセージの処理を行う
     serialHandler.OnDataReceived += OnDataReceived;
  }

  void Updata()
  {
    //文字列を送信
    serialHandler.Write("hogehoge");
  }

    //受信した信号(message)に対する処理
    void OnDataReceived(string message)
    {
        var data = message.Split(
                new string[]{"\t"}, System.StringSplitOptions.None);
        if (data.Length < 2) return;

        try {
           ...
        } catch (System.Exception e) {
            Debug.LogWarning(e.Message);
        }
    }
}

スクリプトの追加

以上のプログラムを作成したらDoSomething.csとSerialHandler.csを任意のオブジェクトに追加します。

このあと、Inspector>DoSomethingスクリプトのSerialHandlerメニューにSerialHandlerを追加したオブジェクトを選択します

確認エラー

SerialHandler.cs

new SerialPortにおいて

serialPort_ = new SerialPort(portName,baudRate, Parity.None, 8, StopBits.One);

がうまく設定できていないとき、Unityコンソールで

が出力されます。

またArduinoのシリアルモニターを起動してはいけません。以下のようなエラーが出ます。

IOException: アクセスが拒否されました

参考

凹みTipさん(いつも勝手にお世話になっております)
09rさん
金澤ソフト設計さん
.NET Framework 2.0 コア機能解説 ~ 第 2 回 シリアルポートのサポート ~