1、幾個(gè)月前我發(fā)布過一篇關(guān)于Unity的串口通信問題,只是闡述了問題,但是沒有什么好的解決方案。經(jīng)過我?guī)讉€(gè)對(duì)串口相關(guān)的Unity項(xiàng)目開發(fā),也發(fā)現(xiàn)了幾種解決方案。開發(fā)中遇到的一些問題都詳細(xì)的描述出來。
2、在上一篇文章我曾提過Unity因?yàn)椴捎玫氖荕ono .NET 2.0。這個(gè)版本對(duì)COM支持不是很好,所以導(dǎo)致Unity在串口通信方面有些問題。不過最近發(fā)布了Mono .NET4.6版本的Unity 5.5測(cè)試版,該問題可能會(huì)解決掉,不過可能需要等到2017年了。
3、言歸正傳,我們首先要知道C#接收串口的主要幾種方式:接收字節(jié)byte,接收字節(jié)數(shù)據(jù)byte[],接收字符串string。在Unity中往串口中發(fā)送數(shù)據(jù)是為沒有問題的,主要是接收數(shù)據(jù)會(huì)存在問題,下面圖片我總結(jié)了一下,這結(jié)果是我經(jīng)過不下于100次測(cè)試而來的,可能每個(gè)人的測(cè)試結(jié)果都不一樣或我的測(cè)試還有一些局限,如果看到此文章的朋友也有不同的結(jié)果,可以加我QQ或者發(fā)我郵件(1158078383@qq.com)共同探討。本人在此感謝。
我開發(fā)過三個(gè)項(xiàng)目采用收發(fā)字符串(Unity接收字符串,發(fā)送字符串)、收發(fā)單個(gè)字節(jié)(Unity接收單字節(jié),發(fā)送字節(jié)數(shù)組)、收發(fā)字節(jié)數(shù)組。三個(gè)項(xiàng)目都是Unity通過串口與Winform程序(或WPF程序)進(jìn)行串口通信。
項(xiàng)目一
1、該項(xiàng)目有兩種串口接收方式,一種是收發(fā)字符串(Unity接收字符串,發(fā)送字符串),另一個(gè)是收單個(gè)字節(jié),然后對(duì)每個(gè)字節(jié)進(jìn)行組裝解析,在發(fā)送字節(jié)數(shù)組。
2、經(jīng)過項(xiàng)目的開發(fā)和測(cè)試,我發(fā)現(xiàn)收發(fā)字符串是沒有問題的,但是在我自己寫的測(cè)試程序中卻出現(xiàn)異常,出現(xiàn)數(shù)據(jù)錯(cuò)誤,數(shù)據(jù)丟失以及接收不到數(shù)據(jù)等隨機(jī)性錯(cuò)誤(第一篇關(guān)于串口文章)。不過經(jīng)過我發(fā)現(xiàn)實(shí)際項(xiàng)目中的串口收發(fā)格式是有標(biāo)記位和校驗(yàn)位,但是當(dāng)我自己寫程序去測(cè)試時(shí),卻發(fā)現(xiàn)了異常。針對(duì)這個(gè)問題我到時(shí)候后期會(huì)在進(jìn)行仔細(xì)研究下,因?yàn)槲椰F(xiàn)在也沒找到合適的理由去說服自己以及讀者,所以我不會(huì)去做詳細(xì)的介紹只是提醒讀者,后期如果解決了我會(huì)在博客上寫出來。
項(xiàng)目二
1、該項(xiàng)目是接收字符串?dāng)?shù)組和發(fā)送字符串?dāng)?shù)組,在實(shí)際開發(fā)項(xiàng)目中卻出現(xiàn)了Unity接收數(shù)據(jù)錯(cuò)誤的問題,針對(duì)該問題加上項(xiàng)目時(shí)間緊急,不可在此問題耗費(fèi)我太大心力,所以無意中想到用中間件程序來做Unity與winform程序通信的一個(gè)橋梁。
2、我寫一個(gè)中間件程序,讓Winform程序與我的中間件程序進(jìn)行串口通信,中間件程序與Unity程序Socket通信。
3、首先啟動(dòng)我的中間件程序,然后中間件程序啟動(dòng)我的Unity程序。中間件程序隱藏起來并與Unity程序互相監(jiān)聽,當(dāng)Unity程序關(guān)閉時(shí),中間件程序也關(guān)閉。這樣從表面上看起來就只是Unity一個(gè)程序在工作,實(shí)際上中間還有一個(gè)中間件程序在做幕后工作。從而巧妙的完成了所謂的Unity與Winform程序之間的串口通信。但是這終究不是一個(gè)很好的解決方案。
結(jié)合我實(shí)際項(xiàng)目,來講解!以項(xiàng)目三方式為例。
在項(xiàng)目三中說過,Unity中接收單個(gè)字節(jié),并且進(jìn)行組裝,在解析。
1、定義存儲(chǔ)串口數(shù)據(jù)變量
[NonSerialized]private List<byte> listReceive = new List<byte>();//定義一個(gè)泛型,暫時(shí)存儲(chǔ)接收到的串口數(shù)據(jù)。這個(gè)泛型的特性不用理會(huì)。
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)在串口號(hào)前加"\\\\?\\"是因?yàn)槲业拇谔?hào)大于10了,這是因?yàn)槲也捎玫氖翘摂M串口號(hào),為什么要加這個(gè)是因?yàn)?NET2.0的原因,詳細(xì)的可看(http://blog.csdn.net/iothua/article/details/51657106)。 2)代碼中有個(gè)線程,線程有個(gè)方法PortReceive()該方法是讀取串口數(shù)據(jù)的 */
3、打印串口數(shù)據(jù)
這個(gè)打印串口數(shù)據(jù)是一個(gè)方法,就是在Unity中打印接收到的串口數(shù)據(jù),怕讀者看代碼是有點(diǎn)不懂這方法是干嘛的,所以我貼出來。
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é)長(zhǎng)度:"+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());從串口中接收單個(gè)字節(jié),然后轉(zhuǎn)化為byte類型的,默認(rèn)是int類型的。 2)this.port.DiscardInBuffer();清除串口緩存數(shù)據(jù),不過暫時(shí)我沒發(fā)現(xiàn)這個(gè)方法有什么很明顯的用戶。不過先寫著吧。 3)listReceive.Add(addr);添加到泛型中 4)PrintData();打印接收到的數(shù)據(jù) 5)在這里catch下面的代碼最好為空,因?yàn)槲覀兪怯镁€程來接收串口數(shù)據(jù),當(dāng)unity沒有接收到數(shù)據(jù)時(shí),就會(huì)報(bào)異常,所以我們需要在catch下不要寫代碼,本來是有個(gè)屬性可以用的,不過在Unity中存在問題,詳細(xì)的可看我第一篇文章。 6)其他的就不用理會(huì)了,那是我后期的一些處理,跟我們所講沒關(guān)系,之所以全部展示出來也是在實(shí)際項(xiàng)目中的一些處理。 */
從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++) { //截取到泛型中的第一個(gè)5A就好,直接停止循環(huán) if(listReceive[i] == 0xA5) { count = i; break; } } listReceive.RemoveRange(0, count); } } } }} /* 解析: 1)這是我對(duì)首尾標(biāo)記的檢測(cè),從而截取到我想要的數(shù)據(jù),這個(gè)解析一般是應(yīng)自身項(xià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)自身項(xiàng)目需求,所以可不理會(huì)。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)之所以做這個(gè)處理原因是當(dāng)我給Winform程序發(fā)送數(shù)據(jù)時(shí),突然會(huì)接收到異常數(shù)據(jù)0x00,但是我的Winform沒有回?cái)?shù)據(jù),所以這數(shù)據(jù)怎么來的我也不清楚,后期我會(huì)在研究下是我的代碼問題還是其他原因,不過這是我的一個(gè)處理,所以也不需要理會(huì)。else if(listReceive[0] == 0x00 && listReceive[8] == 0x5A) { listReceive[0] = 0xA5;}5)、當(dāng)數(shù)據(jù)異常時(shí),把異常數(shù)據(jù)處理掉,保證數(shù)據(jù)的正常。else { int count = 0; for(int i = 0; i < listReceive.Count; i++) { //截取到泛型中的第一個(gè)5A就好,直接停止循環(huán) if(listReceive[i] == 0xA5) { count = i; break; } } listReceive.RemoveRange(0, count);}6)從3),4),5)開始都是我對(duì)接收到的串口數(shù)據(jù)一些處理,從而來保證接收數(shù)據(jù)正常,這也是實(shí)際項(xiàng)目中需要干的事情,可能在測(cè)試中不需要。*/
6、總結(jié)
1、上述可能會(huì)讓一些讀者覺得有比較多的漏洞,我后續(xù)如果發(fā)現(xiàn)更好的解決方案和問題,也會(huì)陸續(xù)更新。一方面是記錄下曾經(jīng)問題方便以后,另一方面也是讓Unity開發(fā)串口這邊的開發(fā)者一個(gè)思路和想法吧。因?yàn)槲疑罡写颂幍目印?
2、如果有讀者看到了,有一些好的解決方案、幫助等都可以聯(lián)系我,我們共同探討。我平時(shí)不看自己的博客,所以有需要詳細(xì)的我可以發(fā)郵件給我(1158078383@qq.com)。
聯(lián)系客服