基于Unity串口通信的解决方案

1 篇文章 0 订阅

一、简介

1、几个月前我发布过一篇关于Unity的串口通信问题,只是阐述了问题,但是没有什么好的解决方案。经过我几个对串口相关的Unity项目开发,也发现了几种解决方案。开发中遇到的一些问题都详细的描述出来。
2、在上一篇文章我曾提过Unity因为采用的是Mono .NET 2.0。这个版本对COM支持不是很好,所以导致Unity在串口通信方面有些问题。不过最近发布了Mono .NET4.6版本的Unity 5.5测试版,该问题可能会解决掉,不过可能需要等到2017年了。
3、言归正传,我们首先要知道C#接收串口的主要几种方式:接收字节byte,接收字节数据byte[],接收字符串string。在Unity中往串口中发送数据是为没有问题的,主要是接收数据会存在问题,下面图片我总结了一下,这结果是我经过不下于100次测试而来的,可能每个人的测试结果都不一样或我的测试还有一些局限,如果看到此文章的朋友也有不同的结果,可以加我QQ或者发我邮件(1158078383@qq.com)共同探讨。本人在此感谢。
这里写图片描述
还有其他的串口读取方法,但是我就没有测试了,因为我实际项目中就需要这几种,所以其他的我就不好意思说。

二、Unity与Winform(WPF)串口通信的几种解决方案

我开发过三个项目采用收发字符串(Unity接收字符串,发送字符串)、收发单个字节(Unity接收单字节,发送字节数组)、收发字节数组。三个项目都是Unity通过串口与Winform程序(或WPF程序)进行串口通信。

  • 项目一
    1、该项目有两种串口接收方式,一种是收发字符串(Unity接收字符串,发送字符串),另一个是收单个字节,然后对每个字节进行组装解析,在发送字节数组。
    2、经过项目的开发和测试,我发现收发字符串是没有问题的,但是在我自己写的测试程序中却出现异常,出现数据错误,数据丢失以及接收不到数据等随机性错误第一篇关于串口文章)。不过经过我发现实际项目中的串口收发格式是有标记位和校验位,但是当我自己写程序去测试时,却发现了异常。针对这个问题我到时候后期会在进行仔细研究下,因为我现在也没找到合适的理由去说服自己以及读者,所以我不会去做详细的介绍只是提醒读者,后期如果解决了我会在博客上写出来。

  • 项目二
    1、该项目是接收字符串数组和发送字符串数组,在实际开发项目中却出现了Unity接收数据错误的问题,针对该问题加上项目时间紧急,不可在此问题耗费我太大心力,所以无意中想到用中间件程序来做Unity与winform程序通信的一个桥梁。
    2、我写一个中间件程序,让Winform程序与我的中间件程序进行串口通信,中间件程序与Unity程序Socket通信
    3、首先启动我的中间件程序,然后中间件程序启动我的Unity程序。中间件程序隐藏起来并与Unity程序互相监听,当Unity程序关闭时,中间件程序也关闭。这样从表面上看起来就只是Unity一个程序在工作,实际上中间还有一个中间件程序在做幕后工作。从而巧妙的完成了所谓的Unity与Winform程序之间的串口通信。但是这终究不是一个很好的解决方案。

  • 项目三
    1、这个项目三就是我们的重头戏了,这也是我最新研究的一种解决方案,而且经过测试和实际项目开发,也是没什么很大问题的,不过有个项目会有一些小问题,但是被我很简单的解决掉了。后面我都会详细的讲解这个解决方案,如果有朋友与我阐述的不符,那么可以参考项目二的解决方案给您一点小思路。
    2、该项目采用的是什么呢,首先Winform程序往Unity中发送字节数组,从Unity中读取字节数组数据。按照正常的逻辑上我们的Unity也应该从串口中读取字节数组和发送字节数组。但是在这一块出现了问题,上面图片我阐述过,Unity从串口接收字节数组时会出现需要两次才能接收完,第一次接收一个字节第二次接收剩下的。数据倒不会出现异常,但是这对我们的数据处理显然是不好的。针对此问题,我下面一大章节来结合实际项目来解释。

三、Unity解析串口数据,得到完整数据

结合我实际项目,来讲解!以项目三方式为例。
在项目三中说过,Unity中接收单个字节,并且进行组装,在解析。
1、定义存储串口数据变量

[NonSerialized]
private List<byte> listReceive = new List<byte>();
//定义一个泛型,暂时存储接收到的串口数据。这个泛型的特性不用理会。

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("串口已经打开");
    }
}
 /*
 代码解释:
 1)在串口号前加"\\\\?\\"是因为我的串口号大于10了,这是因为我采用的是虚拟串口号,为什么要加这个是因为.NET2.0的原因,详细的可看(http://blog.csdn.net/iothua/article/details/51657106)。
 2)代码中有个线程,线程有个方法PortReceive()该方法是读取串口数据的
 */

3、打印串口数据
这个打印串口数据是一个方法,就是在Unity中打印接收到的串口数据,怕读者看代码是有点不懂这方法是干嘛的,所以我贴出来。

void PrintData()
{
    string str = string.Empty;
    for(int i = 0; i < listReceive.Count; i++) {
        str += listReceive[i].ToString("X2");
    }
    UnityEngine.Debug.Log("协调器打印:" + str+"  字节长度:"+listReceive.Count);
}

4、读取串口数据

 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());从串口中接收单个字节,然后转化为byte类型的,默认是int类型的。
 2)this.port.DiscardInBuffer();清除串口缓存数据,不过暂时我没发现这个方法有什么很明显的用户。不过先写着吧。
 3)listReceive.Add(addr);添加到泛型中
 4)PrintData();打印接收到的数据
 5)在这里catch下面的代码最好为空,因为我们是用线程来接收串口数据,当unity没有接收到数据时,就会报异常,所以我们需要在catch下不要写代码,本来是有个属性可以用的,不过在Unity中存在问题,详细的可看我第一篇文章。
 6)其他的就不用理会了,那是我后期的一些处理,跟我们所讲没关系,之所以全部展示出来也是在实际项目中的一些处理。
 */

从winform程序中发送数据过去
这里写图片描述
Unity接收到的数据情况,发送数据经过我们组合后是没有问题的。
这里写图片描述
但是当我Unity中发送给上位机数据时,突然unity串口中接收到一个00数据,该测试不是从我写的测试程序测试的,而是从实际项目中测试的,但是我在自己写的测试程序中又没有这个问题。针对此结果加上一些数据规律,我后期自己进行了改动(后面会详细介绍)。
经过解析数据,然后返回给Winform数据,返回后突然又接到异常数据。
Unity发送数据给Winform
Winform接收到的数据
Winform接收到的数据
5、解析串口数据

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();//打印当前接收到的数据
                    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就好,直接停止循环
                        if(listReceive[i] == 0xA5) {
                            count = i;
                            break;
                        }
                    }
                    listReceive.RemoveRange(0, count);
                }

            }
        }
    }
}
 /*
 解析:
 1)这是我对首尾标记的检测,从而截取到我想要的数据,这个解析一般是应自身项目需求。
 //if(listReceive[0] == 0xA5 && listReceive[8] == 0x5A)
 2)截取到我想要的数据,从而转化为字符串。并且从泛型中将这些数据移除掉。
 string temp = string.Empty;for(int i = 0; i < 9; i++) {
    temp += listReceive[i].ToString("X2");
    }
    listReceive.RemoveRange(0, 9);

3)然后解析我得到的数据,从而通过串口发送给Winform,这里应自身项目需求,所以可不理会。
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)之所以做这个处理原因是当我给Winform程序发送数据时,突然会接收到异常数据0x00,但是我的Winform没有回数据,所以这数据怎么来的我也不清楚,后期我会在研究下是我的代码问题还是其他原因,不过这是我的一个处理,所以也不需要理会。
else if(listReceive[0] == 0x00 && listReceive[8] == 0x5A) {
    listReceive[0] = 0xA5;
}
5)、当数据异常时,把异常数据处理掉,保证数据的正常。
else {
    int count = 0;
    for(int i = 0; i < listReceive.Count; i++) {
        //截取到泛型中的第一个5A就好,直接停止循环
        if(listReceive[i] == 0xA5) {
            count = i;
            break;
        }
    }
    listReceive.RemoveRange(0, count);
}
6)从3),4),5)开始都是我对接收到的串口数据一些处理,从而来保证接收数据正常,这也是实际项目中需要干的事情,可能在测试中不需要。
*/

6、总结
1、上述可能会让一些读者觉得有比较多的漏洞,我后续如果发现更好的解决方案和问题,也会陆续更新。一方面是记录下曾经问题方便以后,另一方面也是让Unity开发串口这边的开发者一个思路和想法吧。因为我深感此处的坑。
2、如果有读者看到了,有一些好的解决方案、帮助等都可以联系我,我们共同探讨。我平时不看自己的博客,所以有需要详细的我可以发邮件给我(1158078383@qq.com)。

  • 26
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值