1、幾個月前我發(fā)布過一篇關(guān)于Unity的串口通信問題,只是闡述了問題,但是沒有什么好的解決方案。經(jīng)過我?guī)讉€對串口相關(guān)的Unity項目開發(fā),也發(fā)現(xiàn)了幾種解決方案。開發(fā)中遇到的一些問題都詳細的描述出來。
2、在上一篇文章我曾提過Unity因為采用的是Mono .NET 2.0。這個版本對COM支持不是很好,所以導(dǎo)致Unity在串口通信方面有些問題。不過最近發(fā)布了Mono .NET4.6版本的Unity 5.5測試版,該問題可能會解決掉,不過可能需要等到2017年了。
3、言歸正傳,我們首先要知道C#接收串口的主要幾種方式:接收字節(jié)byte,接收字節(jié)數(shù)據(jù)byte[],接收字符串string。在Unity中往串口中發(fā)送數(shù)據(jù)是為沒有問題的,主要是接收數(shù)據(jù)會存在問題,下面圖片我總結(jié)了一下,這結(jié)果是我經(jīng)過不下于100次測試而來的,可能每個人的測試結(jié)果都不一樣或我的測試還有一些局限,如果看到此文章的朋友也有不同的結(jié)果,可以加我QQ或者發(fā)我郵件(1158078383@qq.com)共同探討。本人在此感謝。
我開發(fā)過三個項目采用收發(fā)字符串(Unity接收字符串,發(fā)送字符串)、收發(fā)單個字節(jié)(Unity接收單字節(jié),發(fā)送字節(jié)數(shù)組)、收發(fā)字節(jié)數(shù)組。三個項目都是Unity通過串口與Winform程序(或WPF程序)進行串口通信。
項目一
1、該項目有兩種串口接收方式,一種是收發(fā)字符串(Unity接收字符串,發(fā)送字符串),另一個是收單個字節(jié),然后對每個字節(jié)進行組裝解析,在發(fā)送字節(jié)數(shù)組。
2、經(jīng)過項目的開發(fā)和測試,我發(fā)現(xiàn)收發(fā)字符串是沒有問題的,但是在我自己寫的測試程序中卻出現(xiàn)異常,出現(xiàn)數(shù)據(jù)錯誤,數(shù)據(jù)丟失以及接收不到數(shù)據(jù)等隨機性錯誤(第一篇關(guān)于串口文章)。不過經(jīng)過我發(fā)現(xiàn)實際項目中的串口收發(fā)格式是有標記位和校驗位,但是當(dāng)我自己寫程序去測試時,卻發(fā)現(xiàn)了異常。針對這個問題我到時候后期會在進行仔細研究下,因為我現(xiàn)在也沒找到合適的理由去說服自己以及讀者,所以我不會去做詳細的介紹只是提醒讀者,后期如果解決了我會在博客上寫出來。
項目二
1、該項目是接收字符串?dāng)?shù)組和發(fā)送字符串?dāng)?shù)組,在實際開發(fā)項目中卻出現(xiàn)了Unity接收數(shù)據(jù)錯誤的問題,針對該問題加上項目時間緊急,不可在此問題耗費我太大心力,所以無意中想到用中間件程序來做Unity與winform程序通信的一個橋梁。
2、我寫一個中間件程序,讓Winform程序與我的中間件程序進行串口通信,中間件程序與Unity程序Socket通信。
3、首先啟動我的中間件程序,然后中間件程序啟動我的Unity程序。中間件程序隱藏起來并與Unity程序互相監(jiān)聽,當(dāng)Unity程序關(guān)閉時,中間件程序也關(guān)閉。這樣從表面上看起來就只是Unity一個程序在工作,實際上中間還有一個中間件程序在做幕后工作。從而巧妙的完成了所謂的Unity與Winform程序之間的串口通信。但是這終究不是一個很好的解決方案。
結(jié)合我實際項目,來講解!以項目三方式為例。
在項目三中說過,Unity中接收單個字節(jié),并且進行組裝,在解析。
1、定義存儲串口數(shù)據(jù)變量
[NonSerialized]private List<byte> listReceive = new List<byte>();//定義一個泛型,暫時存儲接收到的串口數(shù)據(jù)。這個泛型的特性不用理會。
2、打開串口
public bool OpenPort(string portName) { if (this.port != null && this.port.IsOpen == false) { try { this.port = new SerialPort("\\\\?\\" + portName, 9600); this.port.ReadTimeout = 500; this.port.WriteTimeout = 500; this.port.Open(); this.tPort = new Thread(new ThreadStart(PortReceive)); this.tPort.IsBackground = true; this.tPort.Start(); return true; } catch (Exception err) { throw err; } } else { throw new System.Exception("串口已經(jīng)打開"); }} /* 代碼解釋: 1)在串口號前加"\\\\?\\"是因為我的串口號大于10了,這是因為我采用的是虛擬串口號,為什么要加這個是因為.NET2.0的原因,詳細的可看(http://blog.csdn.net/iothua/article/details/51657106)。 2)代碼中有個線程,線程有個方法PortReceive()該方法是讀取串口數(shù)據(jù)的 */
3、打印串口數(shù)據(jù)
這個打印串口數(shù)據(jù)是一個方法,就是在Unity中打印接收到的串口數(shù)據(jù),怕讀者看代碼是有點不懂這方法是干嘛的,所以我貼出來。
void PrintData(){ string str = string.Empty; for(int i = 0; i < listReceive.Count; i++) { str += listReceive[i].ToString("X2"); } UnityEngine.Debug.Log("協(xié)調(diào)器打印:" + str+" 字節(jié)長度:"+listReceive.Count);}
4、讀取串口數(shù)據(jù)
private void PortReceive(){ while (this.port!=null && this.port.IsOpen) { Thread.Sleep(1); try { byte addr = Convert.ToByte(port.ReadByte()); this.port.DiscardInBuffer(); listReceive.Add(addr); PrintData(); if(this.PropertyChanged_Coordinator != null) { this.PropertyChanged_Coordinator.Invoke(this, new PropertyChangedEventArgs("Receive")); Thread.Sleep(10); this.PropertyChanged_Coordinator.Invoke(this, new PropertyChangedEventArgs("Send")); } //MessageAddReceive(addr.ToString("X2")); if(EventPortRead != null) { EventPortRead(new byte[] { addr }); } } catch { //listReceive.Clear(); } }} /* 解析: 1)byte addr = Convert.ToByte(port.ReadByte());從串口中接收單個字節(jié),然后轉(zhuǎn)化為byte類型的,默認是int類型的。 2)this.port.DiscardInBuffer();清除串口緩存數(shù)據(jù),不過暫時我沒發(fā)現(xiàn)這個方法有什么很明顯的用戶。不過先寫著吧。 3)listReceive.Add(addr);添加到泛型中 4)PrintData();打印接收到的數(shù)據(jù) 5)在這里catch下面的代碼最好為空,因為我們是用線程來接收串口數(shù)據(jù),當(dāng)unity沒有接收到數(shù)據(jù)時,就會報異常,所以我們需要在catch下不要寫代碼,本來是有個屬性可以用的,不過在Unity中存在問題,詳細的可看我第一篇文章。 6)其他的就不用理會了,那是我后期的一些處理,跟我們所講沒關(guān)系,之所以全部展示出來也是在實際項目中的一些處理。 */
從winform程序中發(fā)送數(shù)據(jù)過去
private void ParseReceive(){ while (true) { Thread.Sleep(1); if(listReceive.Count > 0) { if(listReceive.Count >= 9) { if(listReceive[0] == 0xA5 && listReceive[8] == 0x5A) { //UnityEngine.Debug.Log("****************************"); //UnityEngine.Debug.Log("解析接收前******************"); //PrintData();//打印當(dāng)前接收到的數(shù)據(jù) string temp = string.Empty; for(int i = 0; i < 9; i++) { temp += listReceive[i].ToString("X2"); } listReceive.RemoveRange(0, 9); //UnityEngine.Debug.Log("解析接收后******************"); //PrintData(); //UnityEngine.Debug.Log("****************************"); try { foreach(Sensor.SensorBase sensor in this.ListSensor) { ParameterizedThreadStart pts = new ParameterizedThreadStart(SensorSetData); Thread tSetData = new Thread(pts); object o = new object[] { sensor, temp }; tSetData.Start(o); } } catch { listReceive.Clear(); } } else if(listReceive[0] == 0x00 && listReceive[8] == 0x5A) { listReceive[0] = 0xA5; } else { int count = 0; for(int i = 0; i < listReceive.Count; i++) { //截取到泛型中的第一個5A就好,直接停止循環(huán) if(listReceive[i] == 0xA5) { count = i; break; } } listReceive.RemoveRange(0, count); } } } }} /* 解析: 1)這是我對首尾標記的檢測,從而截取到我想要的數(shù)據(jù),這個解析一般是應(yīng)自身項目需求。 //if(listReceive[0] == 0xA5 && listReceive[8] == 0x5A) 2)截取到我想要的數(shù)據(jù),從而轉(zhuǎn)化為字符串。并且從泛型中將這些數(shù)據(jù)移除掉。 string temp = string.Empty;for(int i = 0; i < 9; i++) { temp += listReceive[i].ToString("X2"); } listReceive.RemoveRange(0, 9);3)然后解析我得到的數(shù)據(jù),從而通過串口發(fā)送給Winform,這里應(yīng)自身項目需求,所以可不理會。try { foreach(Sensor.SensorBase sensor in this.ListSensor) { ParameterizedThreadStart pts = new ParameterizedThreadStart(SensorSetData); Thread tSetData = new Thread(pts); object o = new object[] { sensor, temp }; tSetData.Start(o); }}catch { listReceive.Clear();}4)之所以做這個處理原因是當(dāng)我給Winform程序發(fā)送數(shù)據(jù)時,突然會接收到異常數(shù)據(jù)0x00,但是我的Winform沒有回數(shù)據(jù),所以這數(shù)據(jù)怎么來的我也不清楚,后期我會在研究下是我的代碼問題還是其他原因,不過這是我的一個處理,所以也不需要理會。else if(listReceive[0] == 0x00 && listReceive[8] == 0x5A) { listReceive[0] = 0xA5;}5)、當(dāng)數(shù)據(jù)異常時,把異常數(shù)據(jù)處理掉,保證數(shù)據(jù)的正常。else { int count = 0; for(int i = 0; i < listReceive.Count; i++) { //截取到泛型中的第一個5A就好,直接停止循環(huán) if(listReceive[i] == 0xA5) { count = i; break; } } listReceive.RemoveRange(0, count);}6)從3),4),5)開始都是我對接收到的串口數(shù)據(jù)一些處理,從而來保證接收數(shù)據(jù)正常,這也是實際項目中需要干的事情,可能在測試中不需要。*/
6、總結(jié)
1、上述可能會讓一些讀者覺得有比較多的漏洞,我后續(xù)如果發(fā)現(xiàn)更好的解決方案和問題,也會陸續(xù)更新。一方面是記錄下曾經(jīng)問題方便以后,另一方面也是讓Unity開發(fā)串口這邊的開發(fā)者一個思路和想法吧。因為我深感此處的坑。
2、如果有讀者看到了,有一些好的解決方案、幫助等都可以聯(lián)系我,我們共同探討。我平時不看自己的博客,所以有需要詳細的我可以發(fā)郵件給我(1158078383@qq.com)。