6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Unity】M5Stackというデバイスを、UIFlowでUnityとシリアル通信してみました

Last updated at Posted at 2023-06-05

■はじめに

M5Stack というデバイスを使う機会があったので、使ってみました。
また、今回はUIFlowというビジュアルスクリプティングツールを使用してます。

僕は、シリアル通信を今回初めて利用しましたが、コードはそこまで複雑ではなく、簡単に実装できました。
(セットアップなどは少し面倒でした)

■実行結果

こんな感じで、デバイスの加速度をUnity側に反映させることができます!
test_2.gif

■準備

●M5Stack

今回は、USB接続でやってます。
M5Stackには色々な種類がありますが、デバイス操作以外はほとんど同じです。

必要ツールのインストール

DLページはこちら

  1. DesktopIDE
    image.png

  2. M5Burner
    image.png

Burnerの使い方

  • ファームウェアってのを書き込むために必要っぽい。
  • 使用するデバイスを探して、[Download]からの[Burn]でOK
  • エラーが出る場合、UIFlowなどの接続を切断すれば解決するかもです。

UIFlowのセットアップ

  • まず、起動時に以下のような画面が表示されます。
    image.png

    • COMシリアルポートというもの。

      • とりあえず、選択できるものを選択すればOK
      • COMが複数ある場合は、TeraTermというソフトを利用して接続確認できます。
    • Device:使用デバイスを選択。


  • 次に、デバイスとの接続を行います。
  1. USBに接続して、電源をつける

  2. デバイス側で、こんな感じの画面になるように設定する(デバイスによって異なるので省略)
    IMG20230604215527.jpg

  3. UIFlow側で、左下の[リツイートみたいなアイコンのボタン]をクリック

  4. 左下に[Connected]と表示されたら、接続完了!!
    image.png

接続できない場合

  • 左上の[DriverInstallation]でドライバーをインストールしてみる
    • たぶん、一番初めに利用する場合にインストールする必要がある。
  • もう一度、Burnerで[Burn]してみる
    • 僕はこれで解決することが多いです。
  • 何度か[Refreshボタン(RTみたいなやつ)]をクリックする
  • デバイスの設定を見直す
  • Web版の方を利用する
    • 最終手段です。Wifiの環境があればWebでも大丈夫です。
    • 多分WebのほうがBurnerとか使わなくてもいけるから楽かもです。

Unity

PlayerSettingsの「Other Settings」で、API Compatibility Levelを「.NFT Framework」に変更する必要がある

image.png

■実装

●UIFlowでデータ送信機能を作る

シリアル通信に必要な機能などを紹介します。

今回は、デバイスの傾きをUnity側に送信するようにしました。
image.png

  • シリアル通信の一種である、UARTで送信しています。
    • UART関連のブロックは、[HardWares/UART]に存在します。
      image.png

最低限必要な要素は、以下の2つです。

初期化

  • 初期化ブロックのパラメータは、こんな感じです。
    image.png
    • 送信ピン、受信ピンに適切な値を入力。
      • USB接続の場合、送信ピン(tx)=1,受信ピン(rx)=3
    • ボーレートに適当な値を入力。
      • ボーレートの値が大きいほど、データ送れるらしい。
      • でも大きすぎるとエラーになるっぽい

書き込み

image.png

書き込みには3種類あります。今回は一番上の「1行書き出し」を利用しています。
今回は、xとyの値が欲しいので、CSVのようにカンマ区切りをしたものを書き込みます。
image.png

今回は、送信するデータ量が少ないのでこのように1行だけで書き込みをしていますが、より多くのデータを送信したい場合は、一番下の[write raw data]を利用するといいかもです。


これらの処理を追加したら、右下の[Download]でデバイスにこのプロジェクトをダウンロードします。
image.png

ダウンロードが完了したら、デバイス上で傾きが表示されます。
(※裏画面切り替え処理をしていないので、テキストがチカチカすると思います。ご了承ください。)

無事に表示されたら、UIFlow側の操作は完了です!

●Unity側でデータ受信する

クラス図

クラス図はこんな感じです。
SerialClasses.jpg

僕のコードでは、一応きっちり継承とか使って設計していますが、
簡易的なプロジェクトだったら、SerialHandlerクラスとクライアントのクラスだけで大丈夫です。

Scene

SerialHandler
image.png

  • シリアル通信のあれこれを行う、空のオブジェクトです。
  • SerialHandlerクラスでは、シリアルポート名とボーレートを指定します。

Player
image.png

  • デバイスの傾きをもとに、回転するプレイヤーです。
  • 可動範囲や、向きの反転などのパラメータを用意しています。

コード

リポジトリ↓
https://github.com/kiku09020/Unity_SerialCommunication_Test

  • 以下のページのコードを参考にさせていただいています。

SerialHandler

シリアル通信のあれこれを担うクラスです。

📄クリックしてコードを表示
SerialHandler.cs
using UnityEngine;
using System.IO.Ports;
using System.Threading;
using System;

// データを送受信する、シリアル通信クラス
public class SerialHandler : MonoBehaviour
{
	// シリアル通信で、データを受け取った時のイベント
	public event Action OnDataReceived;

    [Header("Serial Options")]
    [SerializeField,Tooltip("開かれるポート名")] string openedPortName = "COM1";

    [SerializeField, Tooltip("開かれるポートのボーレート")] int baudRate = 9600;

    SerialPort serialPort;      // シリアルポート

    // thread
    Thread readingThread;       // 読み取り用のスレッド
    bool isThreadRunning;       // スレッド実行中フラグ

    // message
    string message;
    bool isNewMessageReceived;  // 新しくメッセージを受け取ったかどうか

    //--------------------------------------------------

    // 開始時にポートを開く
    void Awake()
    {
        Open();
    }

    void Update()
    {
        // 受け取ったら、受け取り時の処理を実行
        if(isNewMessageReceived) {
             OnDataReceived();
        }

        isNewMessageReceived = false;
    }

    // 終了時にポートを閉じる
	private void OnDestroy()
	{
		Close();
	}

	//--------------------------------------------------

	// シリアルポートを開く
	void Open()
    {
        serialPort = new SerialPort(openedPortName, baudRate, Parity.None, 8, StopBits.One);        // ポートインスタンス作成
        serialPort.Open();                                      // 作成したポートを開く

        isThreadRunning = true;                                 // 実行中フラグ立てる

        readingThread = new Thread(Read);                              // スレッド作成
        readingThread.Start();                                         // スレッド開始(読み込み)

        print("port was setuped.");
    }

    // シリアルポートを閉じる
    void Close()
    {
        // フラグ降ろす
        isNewMessageReceived = false;
        isThreadRunning = false;

        if (readingThread != null && readingThread.IsAlive) {
            // スレッドが終了するまで待機
            print("waiting");

            readingThread.Join(TimeSpan.FromSeconds(.5f));
        }

        // ポートが開いていたら、閉じる
        if (serialPort != null && serialPort.IsOpen) {
            serialPort.Close();         // 閉じる
            serialPort.Dispose();       // リソース開放

            print("port was closed.");
        }
    }

	//--------------------------------------------------

	// シリアルポートに読み込む
	void Read()
    {
        while (isThreadRunning && serialPort != null && serialPort.IsOpen) {

            try {
                // ポートからのバイト数が0より多かったら、読み込み
                if (serialPort.BytesToRead > 0) {
                    message = serialPort.ReadLine();            // データ読み込み
                    isNewMessageReceived = true;                // メッセージ受け取りフラグ立てる
                }
            }

            // 例外
            catch(Exception exception) {
                Debug.LogWarning(exception.Message);
            }
        }
    }

	// シリアルポートに書き込む
	public void Write(string message)
    {
        // 書き込み
        try {
            serialPort.Write(message);
        }

        // 警告
        catch (Exception exception){
            Debug.LogWarning(exception.Message);
        }
    }

    //--------------------------------------------------

    // コンマで区切られたメッセージを返す
    public string[] GetSplitedData()
    {
        // 受け取ったメッセージを区切る
        return message.Split(",");
    }
}

Point💡

  1. Open()
    • M5Stackのシリアルポートをインスタンス化して開きます。
    • また、データの読み取りを別のスレッドで行うために、読み取り用のスレッドを作成して開始します
  2. Close()
    • スレッドを終了して、シリアルポートを閉じます。
  3. Read()
    • 受信されたデータを読み取ります。
    • 別スレッドで処理が実行されます。
  4. Write()
    • データをシリアルポートに送信します。(今回は使用しません。)
  5. GetSplitedData()
    • コンマ区切りされたデータのメッセージを、コンマで区切って文字列の配列にして返します。
  6. OnDataReceived()
    • データ受け取り時のイベントです。
    • Receiverクラスで処理が追加されています。(データを読み取り、文字列の配列から値に変換して、フィールドに値を代入しています)

DataReceiver_Base

データ受信用の基底クラスです。
必須ではないです。

📄クリックしてコードを表示
DataReceiver_Base.cs
using UnityEngine;

// データ受信基底クラス
public abstract class DataReceiver_Base:MonoBehaviour
{
    [SerializeField] protected SerialHandler handler;

	//--------------------------------------------------

	/// <summary>
	/// データ受信時の処理
	/// </summary>
	protected abstract void OnReceivedData();

	//--------------------------------------------------

	private void Awake()
	{
		handler.OnDataReceived += OnReceivedData;
	}
}

Point💡

  • Awake()で、派生クラスで定義されたOnReceivedData()handlerのイベントに追加します。

AccelarationDataReceiver

デバイスの加速度(傾き)を受けとるクラスです。
傾きなんですが、UIFLOWでは加速度と表示されてるので合わせてます

📄クリックしてコードを表示
AccelarationDataReceiver.cs
using UnityEngine;

// デバイスの加速度を取得する
public class AccelarationDataReceiver : DataReceiver_Base
{
	// デバイスの加速度
	public Vector2 Accelaration { get; private set; }       

    //--------------------------------------------------

    // 受信
    protected override void OnReceivedData()
    {
        var data = handler.GetSplitedData();     // データ取得

        try {
            if (data.Length >= 2) {
                // データ文字列からfloat型に変換して、ベクトルに適用
                float x = float.Parse(data[0]);
                float y = float.Parse(data[1]);

                Accelaration = new Vector2(x, y);
            }
        }

        // 例外
        catch (System.Exception exc) {
            Debug.LogWarning(exc.Message);
        }
    }
}

Point💡

  • 文字列の配列から、それぞれの要素をfloat型に変換して、それをUnityのVector2型の要素に適用させています。

PlayerController

プレイヤーの回転をさせているクラスです。

📄クリックしてコードを表示
PlayerController.cs
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    [Header("Parameters")]
    [SerializeField, Tooltip("可動範囲")] float rotatableRigion = 90;
    [SerializeField,Tooltip("反転するか")] bool isReverced;

    [Header("Components")]
    [SerializeField] AccelarationDataReceiver receiver;

	//--------------------------------------------------

	private void Awake()
	{
        // 反転させる場合、-1をかける
        if (isReverced) {
            rotatableRigion *= -1;
        }
	}

	// 移動
	void LateUpdate()
    {
        var euler = receiver.Accelaration * rotatableRigion;

        transform.rotation = Quaternion.Euler(euler.y, euler.x, 0);
    }
}

Point💡

  • AccelarationDataReceiverの加速度の値を適用させて、回転させています。

■さいごに

デバイスを利用したゲームを制作するのは、新鮮で楽しいなーって思いました。

●参考サイト

  • セットアップ方法

  • M5Stackのrx,txなどの指定方法、ボーレートについて

  • コード

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?