[Unity] Genuino 101 のシリアル通信

  • 0
    いいね
  • 0
    コメント

    Arduino Uno では動作していた Unity 間シリアル通信が、Genuino 101 で動作しない、ということがありました。対応できたのでまとめておきます。

    環境

    • Unity5.3.6p1
    • Mac OSX 10.11.6
    • Genuino 101

    Genuino 101

    Genuino 側のコード

    #include "CurieIMU.h"
    
    void setup()
    {
      Serial.begin(9600);
      while (!Serial);    // wait for the serial port to open
    
      // // initialize device
      CurieIMU.begin();
    
      // Set the accelerometer range to 250 degrees/second
      CurieIMU.setGyroRange(250);
      CurieIMU.setGyroRate(25);
    }
    
    void loop()
    {
      int gx, gy, gz; //scaled Gyro values
    
      // read gyro measurements from device, scaled to the configured range
      CurieIMU.readGyro(gx, gy, gz);
    
      // display tab-separated gyro x/y/z values
      Serial.print("g:\t");
      Serial.print(gx);
      Serial.print("\t");
      Serial.print(gy);
      Serial.print("\t");
      Serial.print(gz);
      Serial.println();
      Serial.flush();
    
      delay(16);
    }
    

    Genuino は加速度センサまで搭載していて、便利ですね。加速度センサの値をシリアル通信で送っておきます。送信部分は参考にしたサイトそのままですけど、loop()関数の最後に delay(16) を入れておきました。16msec ウェイトをかけておくことで、送りすぎるのを抑制しておきます。Arduino Uno よりも高速に動作するので、ウェイトがないと挙動の違いに影響するかもしれません。(精度を上げるなら、delay を呼ばずにその間のジャイロの値を積算するなどの工夫があり得るでしょう)

    Unity

    まず SerialPort クラスを使用するために

    Player Settings から API Compatibility Level を .NET 2.0 Subset から .NET 2.0 へと変更しましょう。

    Unity 側のコード

    その前に、

    Unity5.4 で SerialPort 動作しないよね?

    Unity5.4.0f3 ですが、実行時に
    DllNotFoundException: libMonoPosixHelper.dylib
    とか出ますね。
    https://issuetracker.unity3d.com/issues/osx-editor-dllnotfoundexception-libmonoposixhelper-dot-dylib-when-trying-to-open-a-serial-port
    こちらに報告済みでした。修正を待つとして、Unity5.3で試しましょう。

    2016/08/17 追記: Unity 5.4.0p1 で修正されていました。

    Unity 側のコード

    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;
    
        private const string portName = "/dev/tty.usbmodem1411";
        private const int baudRate = 9600;
    
        private SerialPort serial_port_;
        private Thread thread_;
        private bool isRunning_is_running_ = false;
    
        private string message_;
        private bool is_message_received_ = false;
    
        void Awake()
        {
            open();
        }
    
        void Update()
        {
            if (is_message_received_) {
                OnDataReceived(message_);
                is_message_received_ = false;
            }
        }
    
        void OnDestroy()
        {
            close();
        }
    
        private void open()
        {
            serial_port_ = new SerialPort(portName, baudRate,
                                          Parity.None,
                                          8,
                                          StopBits.One);
            serial_port_.Open();
            serial_port_.DtrEnable = true;
            serial_port_.RtsEnable = true;
            isRunning_is_running_ = true;
    
            thread_ = new Thread(read_entry);
            thread_.Start();
        }
    
        private void close()
        {
            is_message_received_ = false;
            isRunning_is_running_ = false;
    
            if (thread_ != null && thread_.IsAlive) {
                thread_.Abort();
                thread_.Join();
            }
    
            if (serial_port_ != null && serial_port_.IsOpen) {
                serial_port_.Close();
                serial_port_.Dispose();
            }
        }
    
        private void read_entry()
        {
            while (isRunning_is_running_ &&
                   serial_port_ != null &&
                   serial_port_.IsOpen) {
                try {
                    const int max_loop_num = 2;
                    for (var i = 0;
                         serial_port_.BytesToRead > 0 && i < max_loop_num;
                         ++i) {
                        message_ = serial_port_.ReadLine();
                        is_message_received_ = true;
                    }
                } catch (System.Exception e) {
                    Debug.LogWarning(e.Message);
                }
                Thread.Sleep(16);
            }
        }
    
        public void Write(string message)
        {
            try {
                serial_port_.Write(message);
            } catch (System.Exception e) {
                Debug.LogWarning(e.Message);
            }
        }
    }
    

    シリアルポートを Open した直後に

    serial_port_.DtrEnable = true;
    serial_port_.RtsEnable = true;

    というのを実行しています。どうもこれが、Genuino 101 では必要だったようです。また、これがある状態でも Arduino Uno への接続に問題はありません。あと、

    thread_.Abort();

    を Join の直前に読んでいます。実験しているとエディタが固まる現象がときどき起こるんですが、Join のせいだと思うので Abort しておきます。
    そのほか、こちらにも Sleep(16)を入れておきます。(たぶん ReadLine の中で寝ててくれてますけどね・・)
    これで通信できるようになったので、次のスクリプトを Cube オブジェクトとかに貼り付けます。

    GyroRotater.cs
    using UnityEngine;
    
    public class GyroRotater : MonoBehaviour {
    
        public SerialHandler serialHandler_;
    
        void Start()
        {
            serialHandler_.OnDataReceived += OnDataReceived;
        }
    
        void OnDataReceived(string message)
        {
            char delimiter = '\t';
            string[] cols = message.Split(delimiter);
            if (cols.Length < 4) return;
            if (cols[0] != "g:") return;
            float ax = ((float)int.Parse(cols[1]))/32768.9f*10f;
            float ay = ((float)int.Parse(cols[2]))/32768.9f*10f;
            float az = ((float)int.Parse(cols[3]))/32768.9f*10f;
            transform.Rotate(ay, -az, -ax);
        }
    
    }
    

    各軸のスケーリングの調整は適当ですので信用しないように。

    これで

    Genuino を傾けると Unity 内のオブジェクトが回転するようになりました。配線一切なしでできちゃうわけで、Genuino 便利ですね。

    参考

    凹みTips : SerialPort または Uniduino を使った Unity と Arduino を連携させる方法調べてみた : http://tips.hecomi.com/entry/2014/07/28/023525