2023年5月6日 星期六

Unity 串列埠連接Arduino:測試(一)

底下三篇文章是要測試Arduino透過串列埠傳輸資料到PC,藉以控制Unity專案上的物件。



想法:

(1)Arduino以固定(或幾乎固定)時間間隔,每次傳送一個字串(一行),裡面包含幾個相關數據。
(2)Unity以固定(或幾乎固定)時間間隔,每次讀取一行字串,再拆解取得相關數據。
(3)Arduino端以較短的時間間隔傳送資料,Unity端以較長的時間間隔讀取資料,為避免資料累積造成lag,Unity端可以將多的資料丟棄。
(4)Unity端利用UI上的下拉式選單、按鈕,藉以設定串列埠名稱、開啟連線、結束連線等功能。
(5)串列埠連線需常駐,不會因Unity端變更場景而造成斷線或需重新連線。

因測試文章較長,亦可直接看最後第三篇的作法。

=====================================================================
 在第一篇文章中,我們首先建立Arduino與Unity專案的串列埠連線的基本功能:

利用Arduino連接搖桿產生數據,一開始為簡化問題,先只傳送按鈕數據(1個字元),按鈕沒按下時傳送1,按下時傳送0。以Serial.println傳送。

S-K接Pin12
S-X接A0、S-Y接A1
Arduino程式:
-----------------------------------------------------
int SK, SX, SY;
String SSK, SSX, SSY; 
void setup() {
  // put your setup code here, to run once:
    Serial.begin(9600);
    pinMode(12, INPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
     SK=digitalRead(12);
     SX=analogRead(A0);
     SY=analogRead(A1);
     SSK=String(SK);
     SSX=String(SX);
     SSY=String(SY);
     Serial.println(SSK);
     delay(5);
}

=========================================

先利用Arduino IDE的Serial Monitor檢查資料是否正確

按鈕未按下時,傳輸1,按鈕按下時,傳輸0

測試完,記得關閉Serial Monitor。

==========================================

==========================================

Unity端

在場景中放置一個Cube

調整Main Camera高度與角度

目的:當Arduino端連接的搖桿未按下時,Cube不旋轉。搖桿按下時,Cube旋轉。

先在專案Asset視窗中新建一個C#檔ComData.cs,設定共用變數

C#程式:
-----------------------------------------------------
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

static class ComData
{
    public static int R=1;
}

=========================================

再建立一個腳本Ctrlmotion.cs,附加在Cube上。

C#程式:
-----------------------------------------------------
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Ctrlmotion : MonoBehaviour
{
public float rspeed =30f;
    void Start()
    {
    }
    // Update is called once per frame
    void Update()
    {
        if (ComData.R==0)
{
       transform.Rotate(Vector3.up * Time.deltaTime*rspeed);
      }
    }
}

----------------------------------------------------------------

可在inspector視窗調整rspeed數值,改變旋轉速度。 

==========================================

接著在Hierarchy視窗中加入Canvas,在Canvas中加入2個按鈕、1個下拉式選單。

修改物件名稱如下


在下拉式選單的inspector視窗中,將Options先刪除。

其選項可在C#程式中,由SerialPort.GetPortNames()取得。

但SerialPort.GetPortNames()的資料型態是string陣列,

需再轉為List。

==========================================

接著建立一個腳本Serialsetup.cs,附加在EventSystem上。

C#程式:
-----------------------------------------------------
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.IO.Ports;

public class Serialsetup : MonoBehaviour
{
    public static SerialPort sp = new SerialPort();
    private string V = "1";
    public GameObject Canv, B_C, B_D;
    public Dropdown Dp1;
    private string PN = null;
    private string[] Com_name=null;
    private List<string> list;
    private bool b_conv = true;
    // Start is called before the first frame update
    public void AddSerial()
    {        
        PN = Dp1.options[Dp1.value].text;
        if (!sp.IsOpen)
        {
            B_C.SetActive(true);
            B_D.SetActive(false);
        }
        
    }

    public void Connect()
    {
        try
        {
            sp.BaudRate = 9600;
            sp.Parity = Parity.None;
            sp.DataBits = 8;
            sp.StopBits = StopBits.One;
            sp.PortName = PN;
            sp.Open();
            sp.ReadTimeout = 1;
            B_D.SetActive(true);
            B_C.SetActive(false);
        }
        catch (System.Exception)
        {
        }
    }
    public void Disconnect()
    {
        sp.Close();
        B_C.SetActive(true);
        B_D.SetActive(false);
    }
    void Start()
    {

        B_C.SetActive(false);
        B_D.SetActive(false);
        Com_name = SerialPort.GetPortNames();
        list= new List<string>(Com_name);
        Dp1.AddOptions(list);
    }
    // Update is called once per frame
    void Update()
    {
        if (sp.IsOpen)
        {
            if (Input.GetKeyDown(KeyCode.Escape))
            {
                b_conv = !b_conv;
                Canv.SetActive(b_conv);
            }
        }
        else
        {
            Canv.SetActive(true);
        }        
    }
    private void FixedUpdate()
    {
        
        if (sp.IsOpen)
        {
            try
            {
                V = sp.ReadLine();
                sp.DiscardInBuffer();
            }
            catch (System.Exception)
            {
            }           
        }

        if (V == "1")
        {
            ComData.R = 1;
        }
        else if (V == "0")
        {
            ComData.R = 0;
        }
    }
}

----------------------------------------------------------------

這邊須注意,如果無法加入System.IO.Ports,檢查Api Compatibility Level

是否設定為.Net 4.x


在EventSystem的inspector視窗中,在加入的腳本元件中,拉入對應的物件:


在Button1的inspector視窗中,將EventSystem拉入,選擇對應的function:


Button2和下拉式選單也是相同設定:



    說明:
    一開始先將2個Button設定為不執行;
        B_C.SetActive(false);
        B_D.SetActive(false);
    取得串列埠PortName(字串陣列),轉換成List,再加入成為下拉式選單的選項:    
       Com_name = SerialPort.GetPortNames();
        list= new List<string>(Com_name);
        Dp1.AddOptions(list);
    
    當下拉式選單所選的項目被改變時,執行AddSerial()
    首先取得被選項的文字(字串)
     PN = Dp1.options[Dp1.value].text;
     當串列埠未開啟時,將Button(Connect)設為可使用,Button(Disconnect)設為不可使用。
     反之,則不改變2個Button的狀態。
        if (!sp.IsOpen)
        {
            B_C.SetActive(true);
            B_D.SetActive(false);
        }
        
    當Button(Connect)可使用時,其被按下,則執行Connect()。
    設定串列埠的一些參數,並開啟連線。並改變2個Button的狀態。

    當Button(Disconnect)可使用時,其被按下,則執行Disconnect()。
    其關閉連線,並改變2個Button的狀態。

    使用FixedUpdate(),固定時間間隔執行。
    當串列埠開啟時,讀取一行字串:
     V = sp.ReadLine();
    並將inBuffer的資料捨棄。
     sp.DiscardInBuffer();
    如果該字串內容是"1",則將ComData的共用參數R設為1;
    如果該字串內容是"0",則將ComData的共用參數R設為0。

    此外,在Update()執行鍵盤input判斷,當連線開啟時,按下Esc可使介面隱藏或出現。

===============================================================

執行結果:

下拉式選單中選擇PortName

接著在螢幕按鈕按下Connect

在實體搖桿,按下搖桿時,Cube旋轉;放開則Cube不旋轉。

沒有留言:

張貼留言