近期研究 YDLIDAR G4 雷達,網上搜了下沒有 Unity 做的,遂自己踩坑,下面是自己一些小小的總結。
輔助資料:
- 激光雷達 G4 使用手冊
- 激光雷達 G4 數據手冊
- 激光雷達 G4 開發手冊
主要使用上面三個資料,可以從官網進行下載。注意選擇正確的雷達型號 G4 文檔。
http://www.ydlidar.cn/cn/download
輔助資料重點信息:
激光雷達 G4 使用手冊
- 開發套件。可以看到雷達上有一個 USB Type - C 接口, 還有一根突出來的線,線上有五個接口,他們叫物理接口。
兩個接口都可以直接和電腦電腦連接進行使用開發,即
- 一種是使用自帶的 USB 數據線連接雷達上的USB Type -C 接口進行開發,
- 一種是將物理接口和 USB 轉接板進行連接,USB 轉接板一邊又五個小針孔的就是和物理接口進行連接,一邊有兩個接口,這兩個接口上面有刻字(Power,Data),Data 用 Type - C 數據線和電腦連接即可(原理上 Power 是不需要連接的,供電充足的話雷達會轉一下,不轉可能雷達供電不足,所以扯出一根安卓的充電線插上)。
- 注意:使用物理接口連接的時候,連接物理接口和 USB 轉接板不要太用力,不然拔不出來,測試的時候太用力拔不出來諮詢了客服小姐姐,左右晃動溫柔點拔出來,否則接口的針會歪了。
- 驅動安裝。
- 可以從官網下載也可以點此處連接進行下載(自己上傳連接)工具包 http://www.ydlidar.cn/cn/download
解壓後工具 —— Tool —— Driver—— USB 串口驅動,USB 轉接口驅動。分別對應 Type-c 接口連接的驅動以及使用 USB 轉接板連接的驅動。我使用的是 USB 轉接板,嘗試過 Type-c 接口驅動,沒有成功,後來用的 USB 轉接板,而且後者的驅動安裝也比較簡單。使用手冊中也用的這個案例。按照使用手冊進行安裝即可。
- 使用評估軟件。同樣也是在剛纔下載的工具包中,工具——Tool——PointCloudViewer——Windows ——點擊 exe 直接運行即可。不過首先保證上面驅動安裝成功,在設備管理器中可以看到對應的串口。
- PointCloudViewer.exe 評估軟件使用注意:
- 每次先插上線,然後運行 Point CloudViewer
- 使用的過程中出現串口阻塞,雷達異常的報錯。解決辦法:每次測試結束後,一定要先點擊評估軟件上的暫停按鈕,再把雷達從電腦上拔下來。
-
我用的是 Windows 系統,所以使用 Linux 系統的自行測試。
激光雷達 G4 數據手冊 -
看接口定義。即可看到 G4 對外提供兩個接口, USB Type- C 和 PH2.0-5P(物理接口),使用時,兩者選其一。
-
極座標系定義。中心爲極點,角度順時針爲正,零位角位於 G4 PH2.0-5P 接口線的出線口方向。
-
開發和支持。可以按照文檔網址進行下載 SDK 開發包和 Ros 開發包(Ros 針對的是 Linux 系統的,Windows 用戶忽略)
-
解壓的 SDK 包中有三個東西
SDK 驅動包可以先不用看。這個是你自己重新建項目的時候要使用雷達,需要將這個 SDK 包引用到項目中。
VS 2015 工程實例。是一個完整的項目,可以直接運行。使用 C++ 寫的,上面的 SDK 包也是用 C++ 寫的。
YDLIDAR SDK 使用手冊。看 上面兩個工程的時候可以參考一下,這個裏面是一些封裝的函數。
主要看 VS 2015 工程實例 -
VS2015 工程實例。
- 解壓裏面的 YDLIDAR,運行即可。
- YDLIDAR SKD WINDOWS 使用手冊
- SDK 文件引用。YDLIDAR 項目中已經引用了這個 SDK ,不需要你重新引用。
- 常見問題。
- 該項目用的是 VS2015 編寫的,我用的是 VS 2017,導入進來,報錯:vs2017 找不到源文件 stdio.h 。原因是原來的項目所採用windows SDK 已經發生了變化,解決辦法:打開解決方案管理器,選擇對應項目,右擊,屬性,配置屬性,常規,Windows SDK 版本選擇你現在的版本即可。現在版本的查看C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0 最後這一串數字就是你當前的版本。
- 編譯 Demo 工程有報錯。按照文檔說的,使用其他版本的 VS 時,請選擇對應的平臺工具集。解決辦法:解決方案資源管理器中選擇需要的項目,右擊,屬性,配置屬性,常規,平臺工具類,選擇 v140 以上,我安裝的是Visual Studio 2017 (v141),選擇這個相當於升級了,選擇應用之後,點擊解決方案資源管理器總文件夾,解決方案,右擊,選擇重定解決方案目標即可。或者在VS 上面的菜單欄選擇項目,第一個選項就是重定解決方案目標。
- 修改代碼中串口號和波特率。找到 YDLIDAR_DEMO.cpp
只需要修改入口函數 main 方法中的這兩個值即可。com5 是我在設備管理器上的串口名字,一般都是 com3,你可以從你的設備管理器上看到,修改成對應的即可。230400 是 G4 這款雷達的波特率。其他型號的雷達波特率可以諮詢官網諮詢客服。 - 修改完成即可運行。
好了,上面的都是一系列的前期準備以及官網給的測試工具和 c++ 測試案例。下面開始 Unity 的。
我本來想把 YDLIDAR 案例做成 dll 文件,引用到 Unity 中,爲此還學了下 C++ 生成 dll 文件的方法以及 dll 導入到 Unity 項目中如何應用。事實證明,走了歪路,沒有成功。後來這個不行我就想着把官網給的 SDK 導入到 Unity 中進行開發,不過我沒有嘗試,主要我對 C++ 代碼不熟悉,導入進去也不知道怎麼調用。後來主管給了新的思路,網上很多 Unity 讀串口信息的方法,你直接操作串口通信,然後根據開發文檔中的數據進行解析,就是跳過給的 SDK 包,自己寫函數進行解析。恩,下面就是這個方法。
- 首先,我們用串口調試工具看一下串口返回出來的數據。
我自己下的友善串口調試工具,不過各種問題,後來問了官網的技術支持,他給了我另一個串口調試工具。下面是鏈接:需要自取。(上傳串口調試工具)
- 設置串口調試工具
設置界面最好和這個一樣,尤其是紅色方框中的。 - 點擊串口操作,打開串口,輸入 A560 (掃描命令),點擊發送。就可以看到上面的綠色數據即串口返回出來的數據,然後輸入 A565 (關閉掃描命令),點擊發送即可停止,然後關閉串口即可。
-
激光雷達 G4 開發手冊
- 通信機制。外部設備(例如 Unity)發送一個命令給雷達,雷達解析命令,返回對應的數據(應答內容)。雷達根據得到的命令切換工作狀態,外部設備根據返回的數據解析,得到角度,距離等信息。
- 系統命令。外部設備可以發送給雷達的命令。上面串口通信工具發送的命令就是根據這個來的。首字節統一爲 0xA5 ,其他的後面不同命令對應不同狀態。
- 系統報文。
- 着重看三種應答模式。表格中也有各種命令對應的應答模式。
- 系統報文數據協議。起始標誌:由於 A560 爲掃描模式,持續應答,輸出數據比較多。所以可以通過輸入 A590 (獲取設備信息),可以看到返回的命令中前兩個爲 A55A,後面的數據根據協議常看每個字節代表什麼。
- **注意 1 :G4 的數據通信採用的是小端模式,低位在前。**這個不熟悉的可以 Google ,簡單解釋下: 一個 16 進制的數字,A5 11 ,從左到右,A5 屬於高位,11 屬於低位,小端模式是先存儲低位,再存儲高位,即小端模式的表示方法就是 11 A5,大端模式和小端模式相反即可,表示方法 A5 11。所以,在雷達返回的應答內容解析的時候時刻注意它採用的是小端模式,進行位置調整後再進行解析。
- 數據協議。主要看 A560 ,A565。掃描模式和停止掃描。
- 掃描命令。第 6 個字節高 2 位爲 01,因此應答模式取值爲 0x1,爲持續應答長度(此處我不太清楚,只是按照其他命令這個位置都是 0 ,掃描模式此處不爲 0 進行區分)。應答內容是類型碼後面的東西,爲點雲數據,即串口調試工具中後面一直輸出的內容。
- 應答內容。固定開頭爲 0x55AA;包類型分爲兩種,可以看到點雲數據包這個位置是 0x00;採樣數量:這個數據和 Si 的數量相等(注意 Si 是兩個字節,即兩個字節表示一個數據);FSA 起始角,兩個字節;LSA 結束角,兩個字節;校驗碼,兩個字節。
- 零位解析。按照文檔來。
- 距離解析。同樣注意 Si 兩位,小端模式,計算出來的數據單位爲 mm,Unity 中默認單位是 m。
- 角度解析。先不用看 C 校驗位。一級解析:按照文檔來。右移一位相當於這個數字除以 2 ,所以解析的時候搞不清楚右移的直接除以 2 就可以。二級解析:文檔中寫的反三角函數,我剛開始沒弄懂這個公式。文檔中的這個寫法是 MatLab 中反正切的寫法。tan 函數參數範圍 (-PI/2,PI/2),值的範圍是 (- 無窮,+無窮),反正切函數就是正切函數的參數和值的範圍換過來。Unity 解析的時候使用 Mathf.Atan(文檔中括號中計算的值) 或者 Math.Atan(文檔中括號中計算的值),極端得到的記過是弧度,此處角度修正值需要的是角度,所以得到的值再轉換爲角度即可。
- 校驗碼解析。看文檔中的 圖 7 ,PHFSA,1,S2,LSA,都是兩個字節,而通過上面的應答內容解析表可以看到 CT,LSN 分別只有一個字節,而校驗碼採用的是雙字節異或,所以把 CT,LSN 組合成兩個字節進行計算。**驗證方法:**按照 圖 7 中的數據對每一行的兩個字節進行異或得到後面的 C1 到 C -end,然後再將 C1 到 C - end 從頭到尾進行異或得到的結果(CS)和應答內容中的校驗碼位置的數據進行比較,相等則表示接受的這條數據是正確的。
- 注意 2:可以先通過串口調試工具查看輸出的點雲數據,可以複製出來部分數據觀察。掃描命令發送後,雷達返回的應答數據中:起始標誌(A55A),應答長度,應答模式,類型碼只輸出一次,應答內容則是持續發送(會只看到一個 A55A,而看到個 55AA(應答內容固定開頭)),即掃描模式的持續應答代表的是應答內容持續應答。
- A565 注意,當前系統處於掃描狀態時,發送 A565 停止掃描。不會應答,即沒有返回內容。只有停止命令能在掃描模式時進行發送。其他命令都只能在待機狀態(即沒有掃描)的時候進行發送,例如查看設備信息等等。這個在文檔的使用注意中可以看到。
-
Unity 具體解析代碼。
好了,終於到了最後解析的地方。在解析的時候要根據上面的開發手冊進行操作。
ISerialCommunication.cs 接口 定義方法,串口連接,串口斷開,發送信息
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Assets.SerialPortUtility.Interfaces
{
public interface ISerialCommunication
{
void Connect(int baudrate, string portName);
void Disconnect();
void SendMessage(byte[] byteArray);
}
}
SerialCommunication.cs 用於串口打開,關閉,發送信息,接收信息具體實現
using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.IO.Ports;
using System.Threading;
using UnityEngine;
using Assets.SerialPortUtility.Scripts;
public delegate void SerialPortMessageEventHandler(byte[] sendData);
public delegate void SerialPortSendMessageReportHandler(byte[] sendData);
public class SerialCommunication
{
// 從串口發出消息事件
public event SerialPortMessageEventHandler SerialPortMessageEvent;
// 給串口發消息事件
public event SerialPortSendMessageReportHandler SerialPortSendMessageReportEvent;
private SerialPort serialPort;
private Thread threadReceive;
// 儲存接收到的消息
private List<byte> buffer = new List<byte>(4096);
[NonSerialized]
private List<byte> listReceive = new List<byte>();
private int lsnIndex = 3;
public SerialCommunication(SerialPort serialPort)
{
this.serialPort = serialPort;
}
public SerialCommunication(string portName, int boudrate)
{
serialPort = new SerialPort(portName, boudrate);
}
public void OpenSerialPort()
{
//Debug.Log("讀取端口" + ConvertXml._instance.COM);
serialPort.Open();
serialPort.ReadTimeout = 1;
threadReceive = new Thread(ListenSerialPort);
threadReceive.IsBackground = true;
threadReceive.Start();
}
public bool IsSerialPortIsOpen()
{
return serialPort.IsOpen;
}
public void CloseSerialPort()
{
if(threadReceive!=null)
{
threadReceive.Abort();//關閉線程
threadReceive = null;
serialPort.Close();//關閉串口
serialPort.Dispose();//將串口從內存中釋放掉,注意如果這裏不釋放則在同一次運行狀態下打不開此關閉的串口
}
Debug.Log("close thread");
}
/// <summary>
/// 監聽串口,讀取串口消息
/// </summary>
private void ListenSerialPort()
{
//string recvData = "";
//int flag = 0;
while (serialPort != null && serialPort.IsOpen)
{
try
{
#region 原寫法
//int bufferSize = serialPort.ReadBufferSize;
////
//byte[] buf = new byte[bufferSize];
//int count = serialPort.Read(buf, 0, bufferSize);
//if (count > 9)
//{
// if (SerialPortMessageEvent != null && SerialPortMessageEvent.GetInvocationList().Length > 0) // If somebody is listening
// {
// SerialPortMessageEvent.Invoke(buf);// Invoke方法防止主線程擁堵衝突
// }
//}
#endregion
#region 使用ReadByte()的寫法 加判斷包頭和包尾
byte buf = Convert.ToByte(serialPort.ReadByte());
buffer.Add(buf);
while (buffer.Count >= 2)
{
if (buffer[0] == 0xAA&& buffer[1] == 0x55)
{
//Debug.Log("內層收到未處理消息");
if (buffer.Count < 4)
{
break;
}
int numLen = buffer[3];
if(buffer.Count<numLen*2+10)
{
break;
}
//Debug.Log("numLen: " + numLen);
Data_Process(numLen, buffer);
//一條完整數據 存儲 進行處理 移除前面一條完整數據
buffer.RemoveRange(0, numLen * 2 + 10);
}
else
{
buffer.RemoveAt(0);
}
}
//Debug.Log("buffer.Count: " + buffer.Count + " " + RunSerial.byteToHexStr(buffer.ToArray()));
#endregion
}
catch (System.Exception e)
{
//Debug.LogWarning(e.Message);
}
}
}
void Data_Process(int numLen,List<byte> bufferSrc)
{
byte[] readBuffer = null;
readBuffer = new byte[numLen*2+10 ];
bufferSrc.CopyTo(0, readBuffer, 0, numLen * 2 + 10);
SerialPortMessageEvent(readBuffer);// Invoke方法防止主線程擁堵衝突
}
#region
/// <summary>
/// ASCII碼轉字符:
/// </summary>
/// <param name="asciiCode"></param>
/// <returns></returns>
public static string Chr(int asciiCode)
{
if (asciiCode >= 0 && asciiCode <= 255)
{
System.Text.ASCIIEncoding asciiEncoding = new System.Text.ASCIIEncoding();
byte[] byteArray = new byte[] { (byte)asciiCode };
string strCharacter = asciiEncoding.GetString(byteArray);
return (strCharacter);
}
else
{
throw new Exception("ASCII Code is not valid.");
}
}
/// <summary>
/// 使用事件觸發方式來實現串口數據的讀取
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
byte[] Resoursedata = new byte[serialPort.BytesToRead];
int count = serialPort.Read(Resoursedata, 0, Resoursedata.Length);//在此就可以讀取到當前緩衝區內的數據
//執行數據操作
serialPort.DiscardInBuffer();//丟棄傳輸緩衝區數據
serialPort.DiscardOutBuffer();//每次丟棄接收緩衝區的數據
if (count > 0)
{
if (SerialPortMessageEvent != null && SerialPortMessageEvent.GetInvocationList().Length > 0) // If somebody is listening
{
SerialPortMessageEvent.Invoke(Resoursedata);// Invoke方法防止主線程擁堵衝突
}
}
}
#endregion
/// <summary>
/// 給串口發消息
/// </summary>
/// <param name="byteArray"></param>
/// <returns></returns>
public bool SendMessageFromSerialPort(byte[] byteArray)
{
if (serialPort != null && serialPort.IsOpen == true)
{
serialPort.Write(byteArray, 0, byteArray.Length);
if (SerialPortSendMessageReportEvent != null && SerialPortSendMessageReportEvent.GetInvocationList().Length > 0) // If somebody is listening
{
SerialPortSendMessageReportEvent(byteArray);
}
return true;
}
else
{
return false;
}
}
}
SerialCommunicationFacade.cs 具體的應答內容解析
using Assets.SerialPortUtility.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace Assets.SerialPortUtility.Scripts
{
public class SerialCommunicationFacade : MonoBehaviour,ISerialCommunication
{
SerialCommunication serialCom;
private string reciveStr;
private string oldStr;
float fsaAngle;
float lsaAngle;
float angle;//中間角
public void Connect(int baudrate, string portName)
{
serialCom = new SerialCommunication(portName, baudrate);
serialCom.OpenSerialPort();// 打開串口
// 綁定方法觸發,監聽讀取串口
serialCom.SerialPortMessageEvent += SerialCom_SerialPortMessageEvent;
// 綁定方法觸發,給串口發消息
serialCom.SerialPortSendMessageReportEvent += SerialCom_SerialPortSendMessageReportEvent;
}
public void Disconnect()
{
serialCom.CloseSerialPort();
Debug.Log("Serial Disconnected");
}
public void SendMessage(byte[] byteArray)
{
try
{
if (serialCom.IsSerialPortIsOpen() == true)
{
serialCom.SendMessageFromSerialPort(byteArray);
//Debug.Log("Message Sended");
}
else
{
Debug.Log("Message Send Failed!");
}
}
catch (Exception)
{
}
}
/// <summary>
/// 監聽給串口發的消息
/// </summary>
/// <param name="sendData"></param>
private void SerialCom_SerialPortSendMessageReportEvent(byte[] sendData)
{
string text = RunSerial.byteToHexStr(sendData);
Debug.Log("Message Send From Serial.. Message => " + text.ToString());
}
/// <summary>
/// 串口發過來的消息
/// </summary>
/// <param name="sendData"></param>
private void SerialCom_SerialPortMessageEvent(byte[] sendData)
{
Debug.Log("____________shoudaole _____________");
reciveStr = RunSerial.byteToHexStr(sendData);
Debug.Log("Message Coming From Serial.. Message => " + reciveStr.ToString());//打印一條數據信息
Data_ResponseContentProcess(sendData);
//if (!reciveStr.Contains("D") || !reciveStr.Contains("m"))
//{
// return;
//}
////Debug.Log("收到的數據:" + reciveStr.ToString());
//if (reciveStr == oldStr)
//{
// return;
//}
////AllUiManger._Instance.DealReceiveString(reciveStr.ToString());
//oldStr = reciveStr;
}
List<float> angles = new List<float>();//一級解析角度
List<float> distances = new List<float>();//所有的距離
List<double> angleCorrects = new List<double>();//角度偏差
List<float> correctedAngles = new List<float>();//二級解析角度
public static bool isSaveAll = false;//距離和角度都存進去了
public static Dictionary<float, float> angleAndDistances = new Dictionary<float, float>();
/// <summary>
/// 應答數據處理S
/// </summary>
///數據處理
///responseData[0] AA responseData[1] 55 數據包頭
///responseData[2] 包類型
///responseData[3] 採樣數量
///responseData[4] responseData[5] 起始角
///responseData[6] responseData[7] 結束角
///responseData[8] responseData[9]校驗碼
///responseData[................] 採樣數據 兩位爲一個數據
/// <param name="responseData"></param>
void Data_ResponseContentProcess(byte[] responseData)
{
//零位解析 該數據包中 LSN = 1,即 Si 的數量爲 1,S1 = 零位距離數據 ;
//FSA = LSA = 零位角度數據; 其距離和角度的具體值解析參見距離和角度的解析
if (responseData[2] == 0x01 && responseData[3] == 1)
{
//AA55 01 01 DFA0 DFA0 AB54 0000
//零位距離
string disStr = BinaryConversion(responseData[11], responseData[10]);
int dis = Convert.ToInt32(disStr, 16);
float Distancezero = dis / 4;
//AA55 01 01 7958 7958 AB54 0000
//起始角度 結束角度
string fsaStr = BinaryConversion(responseData[5], responseData[4]);
string lsaStr = BinaryConversion(responseData[7], responseData[6]);
Debug.Log(fsaStr);
int fsa = Convert.ToInt32(fsaStr, 16);
Debug.Log(fsa);
float fsaAnglezero = (fsa / 2) / 64;//起始角計算公式 右移一位除以 64
string debugMsg = string.Format("零位解析 距離 = {0}, FSA = {1}, LAS = {2} ", Distancezero, fsaAnglezero, fsaAnglezero);
Debug.Log(debugMsg);
}
// AA55 000F FD27 F129 1252
//00 000000000000000000000000000000000000000000000000000000B406
//點雲數據包
if (responseData[2] == 0x00)
{
Debug.Log("dianyunshuju ---------------");
Debug.Log(responseData[3]);
byte[] dataDis = new byte[responseData[3]*2];
Array.Copy(responseData, 10, dataDis, 0, responseData[3] * 2);
//距離解析 兩位爲一個距離數據 距離開始不對,注意距離數據是去除掉前十位之後的數據
for (int i = 10; i < responseData[3]*2+10; i++)
{
string distanceStr = BinaryConversion(responseData[i+1], responseData[i]);
int dis = Convert.ToInt32(distanceStr, 16);
float Distance = dis / 4;
distances.Add(Distance);
Debug.Log("距離解析: 距離 Distance = " + Distance);
i++;
}
//角度解析
//一級解析 注意小端模式
string fsaStr = BinaryConversion(responseData[5], responseData[4]);
string lsaStr = BinaryConversion(responseData[7], responseData[6]);
int fsa=Convert.ToInt32(fsaStr, 16);
fsaAngle = (fsa / 2) / 64;//起始角計算公式 右移一位除以 64
angles.Add(fsaAngle);
Debug.Log("角度一級解析: FSA = " +fsaAngle );
int lsa = Convert.ToInt32(lsaStr, 16);
lsaAngle = (lsa / 2) / 64;//起始角計算公式 右移一位除以 64
angles.Add(lsaAngle);
Debug.Log("角度一級解析: LSA = " + lsaAngle);
//中間角
float diffAngle = lsaAngle - fsaAngle;
for (int i = 2; i < responseData[3]; i++)
{
angle = diffAngle / (responseData[3] - 1) * (i - 1) + fsaAngle;
angles.Insert(i-1, angle);
Debug.Log("角度一級解析: Angle 中間角 = " + angle);
}
double angleCorrect;
//二級解析 偏差角
foreach (var item in distances)
{
if(item==0)
{
angleCorrect = 0;
}
else
{
float mid = 21.8f * (155.3f - item) / (155.3f * item);//
angleCorrect = Mathf.Atan((float)mid);//反正切函數參數範圍(-無窮,+ 無窮) Mathf.Atan 和 Math.Atan 返回的都是弧度。此處需要得到角度
angleCorrect = (180/Math.PI) * angleCorrect;//弧度轉角度 得到偏差角度
}
angleCorrects.Add(angleCorrect);
}
for (int i = 0; i < angleCorrects.Count; i++)
{
float correctedAngle = angles[i] + (float)angleCorrects[i];
Debug.Log("角度二級解析: 所有角度 = " + correctedAngle);
correctedAngles.Add(correctedAngle);
}
Debug.Log("angleCorrects.Count,distances.Count: " + angleCorrects.Count + " " + distances.Count);
for (int i = 0; i < responseData[3]; i++)
{
angleAndDistances.Add(correctedAngles[i], distances[i]);//距離計算出的是毫米,Unity 中單位默認是米
}
isSaveAll = true;
angles.Clear();
distances.Clear();
angleCorrects.Clear();//注意 LIST 要進行清空 否則數據出錯
correctedAngles.Clear();
//校驗碼解析
}
}
/// <summary>
/// 進制轉換以及字符串重組
/// </summary>
string BinaryConversion(byte one,byte two)
{
string returnStr;
string oneStr;
string twoStr;
if (one<16)
{
oneStr="0"+Convert.ToString(one, 16);//十進制轉十六進制 byte 出來的直接是十進制
}
else
{
oneStr = Convert.ToString(one, 16);
}
if(two<16)
{
twoStr = "0" + Convert.ToString(two, 16);
}
else
{
twoStr = Convert.ToString(two, 16);
}
returnStr = oneStr + twoStr;
return returnStr;
}
}
}
RunSerial.cs 程序運行入口,解析出來的數據可以在控制檯打印出來
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Assets.SerialPortUtility.Scripts
{
public class RunSerial : MonoBehaviour {
SerialCommunicationFacade facade;
private string portName = "COM5";
private int baudRate = 230400;
byte[] sendMsg;
//UI 用於顯示
public GameObject img;
public GameObject canvas;
List<GameObject> imgs = new List<GameObject>();
int MaxLength = 30;
List<float> angless;
private void Awake()
{
for (int i = 0; i < MaxLength; i++)
{
GameObject obj = GameObject.Instantiate(img);
obj.transform.SetParent(canvas.transform);
obj.SetActive(false);
imgs.Add(obj);
}
}
void Start() {
facade = this.GetComponent<SerialCommunicationFacade>();
facade.Connect(baudRate, portName);
sendMsg = strToToHexByte("A560");
facade.SendMessage(sendMsg);
}
Vector2 GetPos(float angle, float distance)
{
Vector2 pos;
pos = new Vector2(distance * Mathf.Sin(angle), distance * Mathf.Cos(angle));
return pos;
}
void Update()
{
if (SerialCommunicationFacade.isSaveAll)
{
//Unity在Dictionary中刪除修改元素時出現InvalidOperationException: out of sync 出現這個錯誤
//foreach (var item in SerialCommunicationFacade.angleAndDistances)
//{
// BuildObj(item.Key, item.Value);
//}
//angless = new List<float>(SerialCommunicationFacade.angleAndDistances.Keys);
//for (int i = 0; i < angless.Count; i++)
//{
//float distance = SerialCommunicationFacade.angleAndDistances[angless[i]];
//BuildObj(angless[i], distance);
//}
SerialCommunicationFacade.angleAndDistances.Clear();
angless.Clear();
SerialCommunicationFacade.isSaveAll = false;
//SerialCommunicationFacade.angles.Clear();
//SerialCommunicationFacade.distances.Clear();
}
}
private void OnApplicationQuit()
{
sendMsg = strToToHexByte("A565");
facade.SendMessage(sendMsg);
facade.Disconnect();
}
/// <summary>
/// 字符串轉16進制字節數組
/// </summary>
/// <param name="hexString"></param>
/// <returns></returns>
public static byte[] strToToHexByte(string hexString)
{
hexString = hexString.Replace(" ", "");
if ((hexString.Length % 2) != 0)
hexString += " ";
byte[] returnBytes = new byte[hexString.Length / 2];
for (int i = 0; i < returnBytes.Length; i++)
returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
return returnBytes;
}
/// <summary>
/// 字節數組轉16進制字符串
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static string byteToHexStr(byte[] bytes)
{
string returnStr = "";
if (bytes != null)
{
for (int i = 0; i < bytes.Length; i++)
{
returnStr += bytes[i].ToString("X2");
}
}
return returnStr;
}
}
}
上面代碼只進行了距離解析,角度一級解析,角度二級解析。其他的想要加入的可以根據開發文檔中的命令以及應答內容進行解析。運行的時候只需要把 RunSerial.cs,SerialCommunicationFacade.cs 綁定到一個空物體上即可。
好了,上面就是自己的一點小總結,歡迎交流。另外,附上 GitHub 上完整的項目,需要的可以下載。
場景是 Scene1 項目下載地址
Unity 中使用注意:
- 沒有找到 System.IO.Ports 程序集,在[Edit->Project Settings->Player]下,修改[Other Settings]下的[Optimization]的[API Compatibility Level]爲[.NET 2.0](默認爲[.NET 2.0 Subset])。
- SerialPort 中的 SerialDataReceivedEventHandler DataReceived 還不能使用,即無法調用,採用線程的方式進行讀取。