おもちゃラボ

Unityで遊びを作ってます

【Unity】UniRxを使ってArduinoとシリアル通信

前回の記事では、シリアル通信のためのPluginを作ることで、Arduino⇔Unityのシリアル通信を実現しました。

nn-hokuson.hatenablog.com

今回はUniRxを使うことで、前回よりも簡単にArduinoとシリアル通信をする方法を紹介します。今回の記事の内容は次のとおりです。

UniRxとは

UniRxとはUnity Reactive Extensionsの略で、名前の通り(?)Unityで非同期通信をしたいときに使うと便利なツールになります。UniRxはAsset Storeから無料で手に入れることができます。

f:id:nn_hokuson:20170912190532j:plain

今回の記事では、UniRxはスレッドを作るためだけに使います(贅沢)UniRxの詳しい説明はしませんが、非常に便利なライブラリなのでいつか解説を書く・・・かも。

Arduino側のプログラムを作る

こちらはシリアル通信でデータを一方的に送りつけるだけのプログラムです。次のプログラムをスケッチに書いて下さい。

byte a = 0;

void setup() {
 Serial.begin(9600);
}

void loop() {
 a++;
 Serial.println(a);
}

プログラムの内容は・・・setupメソッドの中でシリアル通信を開始しています。loopメソッドの中では、変数aの値をインクリメントしながらシリアルにどんどん流し込んでいるだけのプログラムになります。

プログラムができたら、書き込みボタンでArduinoに書き込んでおきましょう。

f:id:nn_hokuson:20170912191350p:plain:w350

Unity側のプログラムを作る

続いてUnity側のシリアル通信プログラムを作ります。こちらはシリアル通信で送られてくるデータを受け取るプログラムです。

UniRxを使うため、Asset Storeからダウンロードしてインポートしておいてください。

f:id:nn_hokuson:20170912190532j:plain:w400

また、Unityでは標準で.NET 2.0のサブセットを使うように設定されていますが、サブセットにはSerialクラスが含まれていないため、.NET 2.0 Subsetから.NET 2.0に変更する必要があります。

メニューバーからEdit -> Project Settings -> Playerを選択し、API Compatibility Levelを.NET 2.0に変更してください。

f:id:nn_hokuson:20170912190634p:plain:w400

続いて、シリアル通信用のスクリプトを作成します。プロジェクトビューで右クリックし、「Create」→「Script」→「C# Script」を選択し、SerialController.csという名前で保存し、作成したファイルに次のスクリプトを入力してください。

using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System;
using System.IO.Ports;
using UnityEngine;
using UniRx;

public class Serial : MonoBehaviour {

    public string portName;
    public int baurate;

    SerialPort serial;
    bool isLoop = true;

    void Start () 
    {
        this.serial = new SerialPort (portName, baurate, Parity.None, 8, StopBits.One);

        try
        {
            this.serial.Open();
            Scheduler.ThreadPool.Schedule (() => ReadData ()).AddTo(this);
        } 
        catch(Exception e)
        {
            Debug.Log ("can not open serial port");
        }
    }
	
    public void ReadData()
    {
        while (this.isLoop)
        {
            string message = this.serial.ReadLine();
            Debug.Log( message );
        }
    }

    void OnDestroy()
    {
        this.isLoop = false;
        this.serial.Close ();
    }
}

ここでは、UniRxを使うため、using UniRX;を追加しています。Startメソッドの中でSerialクラスのインスタンスを作成した後、Scheduleメソッドを使ってReadDataスレッドを立ちあげています。

ReadDataメソッドの中ではSerialクラスのReadLineメソッドを使って、シリアルで送られてくるデータを読んで表示しています。

ゲームを終了する場合はOnDestroyメソッドが実行されます。その中で、スレッドを抜けて、シリアルポートを閉じています。

実行してみる

スクリプトが作成できたら、空のゲームオブジェクトを作って、それにスクリプトをアタッチしておきましょう。アタッチしたゲームオブジェクトのインスペクタから、ボーレートとポート名を設定してください。ボーレートはArduino側で指定した9600に設定します。

ポート名はArduinoを起動し、メニューバーからツール -> ポートで確認することができます。

f:id:nn_hokuson:20170912190808p:plain:w300

これで準備完了です。ArduinoをPCに接続し、Unityを実行してみてください。コンソールビューの数字が増加して1〜255を繰り返せば成功です。

f:id:nn_hokuson:20170912190819g:plain

まとめ

Unityで非同期通信やスレッドなどを使いたい場合はUniRxを使うことで、非常にスッキリとしたプログラムを書くことができます。

【Arduino】アセンブラ入門 その1

ArduinoはAVR(ATMega328)というマイコンの外側にIO用の電子回路をくっつけただけの構造なので、もちろんAVRの文法に従ってアセンブラでプログラムを書くことが出来ます。

このご時世アセンブラて!と思わないこともないですが、ディスプレイのドライバだったりネットワークのパケット処理だったり、結構速度を求められる場面では役に立つことがあります。

普通にC言語で書く場合と比べると10倍以上の速度の差が出ることもざらにあるので、覚えておいて損はないですよ〜

今回の記事の内容は次のとおりです。

まずは何もしないアセンブラを書いてみよう

アセンブラでプログラムを書く、といっても何もプログラムのすべてをアセンブラにする必要はありません。遅い所だけを狙って、その処理だけをアセンブラで書く。プログラムを高速化する時の鉄則ですね〜。

このように、プログラムの一部だけをアセンブラにすることをインラインアセンブラと呼びます。この記事では基本的にインラインアセンブラの書き方を紹介しています。

まずは、アセンブリ言語版ハローワールド(?)の何もしない命令を書いてみましょう。次のプログラムを書いてください。

void setup()
{
	asm( "nop \n" );
}

これだけ?と思ったかもしれませんね。はい、なんとコレだけです(笑)インラインでアセンブラを書くにはasmの()の中にアセンブラで文字列としてプログラムを書くだけです。

nopはNo Operationの略で「何もしない命令」です。実行しても何も起こりませんが、ちゃんとコンパイルも通ってプログラムも実行できている、ということが大切です。

変数?いやレジスタを使ってみよう

アセンブラのプログラムが思いのほか簡単にかけることがわかったところで、つぎは変数を使ってみましょう。といっても、アセンブラに変数という概念はなくて、ハードウエアとして実装されている「レジスタ」に値を入れたり、取り出したりして使うことになります。

次のプログラムを入力してみてください。実はかなりお行儀が悪いプログラムなのですが、それは次のお話・・・ということで(笑)

volatile byte a=0;

void setup() {
  Serial.begin(9600);
 
  asm (
    "ldi r23, 37  \n"
    "sts (a), r23 \n"
  );
 
  Serial.print("a = "); Serial.println(a);
}

ここでは37という数値を23番レジスタに格納し、それを変数aに代入しています。なんとなく、雰囲気で分かっちゃいますね(といって説明を省こうとする布石だったりする)。

Arduinoには次の3種類のメモリが用意されています。

  • EEPROM
  • SRAM
  • フラッシュメモリ

1つ目はEEPROM、電源を切ってもデータが消えないメモリです。2つ目がSRAMで、データや一時変数などの値が置かれるメモリになります。3つ目がフラッシュメモリ、これはArduinoで作ったプログラムを置く所ですね。

上で書いたレジスタはSRAMの一部として実装されています。Arduinoには32個のレジスタ(r0〜r31)があり、それぞれ8bitの値を格納することが出来ます。

f:id:nn_hokuson:20170911231349j:plain

上のプログラムではLDI命令(Load Immediate)を使っています。LDIは定数をレジスタに格納する命令です。ここでは37という定数を、23番レジスタに格納しています。LDI命令で指定できるレジスタはr16〜r31までなので注意してください。

アセンブラの2行目ではSTS命令(Store direct to data Space)を使っています。STS命令はレジスタの値をSRAMに置かれている変数に代入する命令です。ここでは23番レジスタに入っている値を変数aに代入しています。

まとめると、アセンブラを実行すると

  1. 37という値が23番レジスタに格納され
  2. 23番レジスタの値が変数aに代入されます

したがって、最後にシリアルコンソールに表示される値は37になります。

37

今回、変数aはグローバル変数として宣言しました。これは変数の値をSRAM内に置くためです。もしローカル変数としてsetup関数の中で宣言すると、SRAMには置かれない(stackに配置される)ため、STS命令でエラーが出てしまいます。

Clobber(クラバー)を使って安全にレジスタを使おう

先程のプログラムでは、37という値を格納するレジスタを「えいや」で23番レジスタと決めました。もし、運悪くこの23番レジスタが使われていた場合は、そこにあった数値は37で上書きされて消えてしまいます。

このようなレジスタのバッティングを防ぐため、Clobberという構文があります。Clobberで使用するレジスタを宣言しておくと、コンパイラに「このレジスタは使わないでね〜」と伝えることが出来ます。

クローバの書き方が少し特殊で次のようになります。

volatile byte a=0;

void setup() {
  Serial.begin(9600);
 
  asm (
    "ldi r23, 37  \n"
    "sts (a), r23 \n"
    :::"r23"
  );
 
  Serial.print("a = "); Serial.println(a);
}

コロン3つに続けて、使用するレジスタの名前を文字列として記述します。ここでは23番レジスタを使うので"r23"と書いています。

コロン3つってなんやねん、となりますよね?。私もなりました。このあたりで一般的なインラインアセンブラの書き方を紹介しておきます。インラインアセンブラは次の4つのパートから成り立っています。

asm (
  "プログラム"
  "プログラム"
  ・・・・・
  :"出力":"入力":"Clobber"
);

プログラムのパートはこれまで書いてきたアセンブラプログラムになります。「出力」と「入力」のパートは後ほど説明しますが、簡単に言うと「インラインアセンブラに入力する値のリストと出力する値を書く場所」になります(そのままだ・・・)。入出力のレジスタ宣言に続けて、最後にClobberを書きます。

上の例では出力と入力のパートに何も記述していないので「:::"r23"」という不思議な書き方になってしまっているのです。

不思議な書き方ですが、このように書くことで23番レジスタは自分のプログラムで使うと宣言し、安全にプログラムを実行することが出来ます。

2つの値をスワップしよう

最後に今回のまとめとして2つの値をスワップするプログラムを作ってみましょう。C言語で普通に書くと次のようなプログラムになります。

volatile byte a = 73;
volatile byte b = 41;
 
void setup() {
  Serial.begin(9600);
 
  byte tmp c;
  c = a;
  a = b;
  b = c;
     
  Serial.println(a);
  Serial.println(b);
}

一時変数cを使って値を入れ替えているだけです。なんというか、めっちゃオーソドックス、普通中の普通のプログラムですね。

実行すると次のように変数aと変数bの値が入れ替わって表示されます。

41
73

これをインラインアセンブラで書き換えてみましょう。

volatile byte a = 73;
volatile byte b = 41;
 
void setup() {
  Serial.begin(9600);
 
  asm (
    "lds r17, (a) \n"
    "lds r19, (b) \n"
    "sts (b), r17 \n"
    "sts (a), r19 \n"
    : : : "r17", "r19"
  );
 
  Serial.println(a);
  Serial.println(b);
}

このプログラムでは、まず変数の値をレジスタに格納するためにLDS命令を使っています。LDS命令はLoad Direct from data spaceの略で、SRAMに置かれたデータをレジスタに読み込みます。LDI命令(値をレジスタに読み込む)とは異なるので注意してください。

  • LDI命令:数値をレジスタに読み込む
  • LDS命令:変数の値をレジスタに読み込む

aと変数bの値を17番と19番のレジスタに格納してから,STS命令を使ってレジスタの値を変数に書き戻しています。

このプログラムでは17番レジスタと19番レジスタを使っているので、クローバには"r17"と"r19"を指定しています。このように複数のレジスタを使う場合には「,」で区切ってレジスタ番号を書きます。

まとめ

今回はインラインアセンブラの書き方やレジスタの役割、レジスタに値を格納する方法、クローバの意味などを説明しました。次回はインラインアセンブラへの入出力について説明していきます。

[asin:B00E1EA6XM:detail]

【Unity】iOSにアプリをインストールするときのエラー対処法

UnityでXcodeのプロジェクトを書き出して、いざiPhoneにインストールしようとすると、ビルドまではうまくいったのに転送の段階で次のようなエラーが出ることがあります。

Could not launch "アプリ名"
Verify the Developer App certificate for your account is trusted on your device.

f:id:nn_hokuson:20170906200436p:plain

これは、無料のApple Developper IDを使っている場合に出るエラーで、インストールしたアプリをiPhone側で認証する必要があります。

iPhone/iPadの端末から「設定アプリ」を起動し、「一般」→「デバイス管理」をタップして下さい(iOSのバージョンによっては「プロファイルとデバイス管理」になっている可能性があります)

f:id:nn_hokuson:20170906201152j:plain:w550

続いて、お使いのApple IDを選択し、「"Apple ID"を信頼」を選択します。最後に、アラートビューの「信頼」をタップして設定完了です。

f:id:nn_hokuson:20170906202141j:plain

詳しくはアップルのサイトでも解説されていますので、参考にしてみて下さい。

support.apple.com