使用環境
- arduino1.6.9
- unity5.2.2
- Genuino101
ここでは、**「Genuino101の加速度センサを使用してUnity上のGameobjectの角度を変えてみる」**ということをやろうと思う。
一般的にunityとarduinoを繋ぐ際にはunityのassetであるuniduinoとarduino側のスケッチの例に入っているfirmata(ふぁるまーた)を使用するという手法がある。しかし、uniduinoを使用する際はaruduino側のライブラリを使用できないらしい
今回そんなこととは知らず僕はGenuino101を買ってしまった。
Genuino101はとても優れていて、ボード自体に最初からジャイロセンサ、加速度センサ、BLE(Bluetooth)が付いている。
では、Genuino101を買ったことの何がいけないのかというとGenuino101に元々搭載されている機器を使用するには、ライブラリを使用しないといけなかったのである。
ということでuniduinoを使うことができなくなってしまったので、SerialPortを通して自力でデータの通信を行ってみた。
ということで、Uniduinoを使ってunityとarduinoの連動をしてみたいという人はこの下で紹介しているサイト内でそちらも紹介しているので参考にしてみてください。また、Genuino101をライブラリを使用しないで使用する方法を知っている方はご一報よろしくお願いします!
参考サイト
- SerialPort または Uniduino を使った Unity と Arduino を連携させる方法調べてみた
- CurieIMUライブラリ
- gとは何か(傾きを読み取ってみるのとこで説明しています)
- キャリブレーションの仕方
- スレッド
- デリゲートとは
##作業開始!
主に行うことの手順
- Genuinoで加速度センサの値を読み取り、シリアル通信で送信する
- unityでシリアル通信で送られてきた値を受け取る
- 立方体にその値を反映させて立方体を回転させる
###1. Genuinoで加速度センサの値を読み取り、シリアル通信で送信する
今回は加速度センサを使用して角度の取得をしていく。
まずはコードを載せておくことにする
#include "CurieIMU.h"//Genuino101の加速度センサを使用するためのライブラリをインクルード
namespace {
const int AVERAGE_NUM = 10;//値をある程度均一化するための数字
const int adjustX = 5;//センサの値を調整するための定数、水平面に置いた時のズレの値
const int adjustY = -2;
const int adjustZ = -1;
}
void setup()
{
Serial.begin(9600);//シリアル通信スタート
// IMUセッティング----------------------------------------
CurieIMU.begin();//加速度センサ起動
CurieIMU.setAccelerometerRange(2);//加速度センサのレンジ(測定範囲)の設定
}
void loop()
{
readAccelerometer();
}
//function----------------------------------------------
void readAccelerometer()
{
int axRaw = 0,ayRaw = 0,azRaw = 0;//加速度センサの値を受け取るための変数を用意
CurieIMU.readAccelerometer(axRaw, ayRaw, azRaw);//加速度センサの値を読み取り格納
int x = 0, y = 0, z = 0;//加速度センサの値を均した値を格納する変数を用意
//値の平均化================================
for (int i = 0; i < AVERAGE_NUM; ++i) {//AVERAGE_NUMで指定した数だけ加速度を測定して平均化する
x += axRaw;
y += ayRaw;
z += azRaw;
}
x /= AVERAGE_NUM;
y /= AVERAGE_NUM;
z /= AVERAGE_NUM;
//=========================================
int angleX = map(axRaw,-16384,16384,-90,90);//センサで受け取った値を角度に変換
int angleY = map(ayRaw,-16384,16384,-90,90);
int angleZ = map(azRaw,-16384,16384,-90,90);
angleX += adjustX;//角度を手動で調整
angleY += adjustY;
angleZ += adjustZ;
Serial.print(angleX);
Serial.print("\t");
Serial.print(angleY);
Serial.print("\t");
Serial.print(angleZ);
Serial.println("");
}
コードの主な意味はコメントの通り。
加速度センサで取得した値を角度に変換する時にがっつり嵌った。
最初は参考サイトの通り
const int angleX = atan2(x - BASE_X, z - BASE_Z) / PI * 180;
const int angleY = atan2(y - BASE_Y, z - BASE_Z) / PI * 180;
のようにアークタンジェントを使って変換しようとしたのだがよくわからなかった。
どうやらここら辺の話らしいのだが、理解しきれなかった。tanの逆関数を使うということはわかったが、3.14と180°を割り掛けするのはどういうことなんだろう...誰か詳しい人教えてください(--)
結局分からなかったので、int angleX = map(axRaw,-16384,16384,-90,90);
といった感じでmapを使って角度に変換。
そしてまたしても嵌った...
センサというものは使っていくとズレが生じていくものらしい。それを修正する作業が「キャリブレーション」と言われるらしい。CurieIMUにもキャリブレーション用の関数は用意されている。
**autoCalibrateAccelerometerOffset()**がその関数なのだが、引数としてセンサの値をgに変換したものを与えて使ったがうまくいかなかった。これも誰かわかる人いたら教えてください!
キャリブレーションの仕方わからないからしょうがなく自分で調整することに。
しかしこれでarduinoからxyzの角度をシリアルポートに送ることに成功。
###2. unityでシリアル通信で送られてきた値を受け取る
unity側で一番最初に設定しなきゃいけないこと
Player Settings から API Compatibility Level を .NET 2.0 Subset から .NET 2.0 へと変更
これをすることでマルチスレッド機能を使うことができるようになるらしい。
コードを載せておく。
using UnityEngine;//unityのデフォルトの機能を使うためのクラス
using System.Collections;//C#の機能を使うためのクラス?
using System.IO.Ports;//下2つはシリアル通信を行うためのクラス
using System.Threading;//マルチスレッドを可能にするためのクラス
public class SerialHandler : MonoBehaviour
{
public delegate void SerialDataReceivedEventHandler(string message);//デリゲートの宣言
public event SerialDataReceivedEventHandler OnDataReceived;//ondatereceivedっていうイベントを作っていると思われる。
public string portName = "/dev/tty.usbmodem1411";//arduino側と同じシリアルポートを入れる
public int baudRate = 9600;//arduino側と同じボーレートを入れる
private SerialPort serialPort_;//serial portクラスのインスタンス生成
private Thread thread_;//スレッドクラスのインスタンス生成
private bool isRunning_ = false;//シリアルポートが開いているかどうか
private string message_;//
private bool isNewMessageReceived_ = false;//シリアルポートから値が送られてきているかどうか
void Awake()//SerialHandlerインスタンス生成後すぐに実行される関数
{
Open();
}
void Update()
{
if (isNewMessageReceived_) {//値が送られてきたら
OnDataReceived(message_);
}
}
void OnDestroy()
{
Close();
}
private void Open()//シリアルポートを開いて、新しいスレッドを開く
{
serialPort_ = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One);//シリアルポートインスタンス生成
serialPort_.Open();//シリアルポートを開いて
isRunning_ = true;//開いた証としてtrueに変更
thread_ = new Thread(Read);//新しいスレッドを作成してread関数を実行させる
thread_.Start();//thread開始
}
private void Close()//
{
isRunning_ = false;//シリアルポートを閉じた証としてfalseに変更
if (thread_ != null && thread_.IsAlive) {//threadが存在しているなら
thread_.Join();//元のスレッドの統合
}
if (serialPort_ != null && serialPort_.IsOpen) {//シリアルポートにインスタンスが格納されていて、シリアルポートが開いていれば
serialPort_.Close();//シリアルポートを閉じる
serialPort_.Dispose();
}
}
private void Read()
{
while (isRunning_ && serialPort_ != null && serialPort_.IsOpen) {//シリアルポートが開いているかつシリアルポートインスタンスが存在する
try {
if (serialPort_.BytesToRead > 0) {//受信したデータがあったら
message_ = serialPort_.ReadLine();//一行ずつデータを読み込む
isNewMessageReceived_ = true;//メッセージを受け取った証として変更
}
} catch (System.Exception e) {//例外処理
Debug.LogWarning(e.Message+"A");
}
}
}
public void Write(string message)
{
try {
serialPort_.Write(message);//メッセージをシリアルポートで送る
} catch (System.Exception e) {//例外処理
Debug.LogWarning(e.Message+"B");
}
}
}
unityは授業でしか触ったことがないレベルなので、基礎部分から調べなければならなくて時間がかかってしまった。
スレッドとデリゲートというワード、概念を知らなくて調べたので、参考サイトの欄に載せておきます。
SerialHandler.csで行っていることの概要は、
- 1つのスレッドでシリアルポートからデータを受信すること(ここでは受信しているが読み取ってはいない)
- 1つのスレッドで受信したデータを読み取る
ということです。
一つだけわからないところがあってserialPort_.Dispose();
が何をするメソッドなのかわかりませんでした。教えてください笑
まあそういうことであとは読み取った値をunity上で扱うだけ!
###3. 立方体にその値を反映させて立方体を回転させる
最後に
随時更新していきます!