Unity3D内部串口通信和Unity3D与Winform程序的串口通信的实现和异常问题

前言

1、有些人其实会觉得Unity3D用到的.NET是2.0的,其实不然;Unity3D有用到.NET3.5,为什么说Unity用到的是3.5呢,从一个很常用却很重要的一个命名空间说起,他就是System.Linq命名空间,这个命名空间是.NET3.5重要的一次改革和核心部分(本命名空间与该文章并没有什么很大的联系,只是提下而已)。至于为什么显示成2.0我也不是很清楚,可能只支持部分3.5吧,不过对我们来说关系并不是很大。只要支持Linq就可以了。

2、前提工作:虚拟串口和Unity3D切换成.NET。
2.1 虚拟串口的创建,可以从网上下载一个创建虚拟串口的软件,比如“VSPD虚拟串口”,还是挺好用的,不过因为我做Unity3D的虚拟串口工作,所以根据VSPD专门写了一个创建虚拟串口的程序(暂时不提供)。在创建虚拟串口的时候注意一个很重要的问题,就是尽量创建串口号大于10的,比如COM10、COM11甚至夸张点COM100等,为什么要这样子,后面我会介绍Unity3D打开串口时,串口号大于10时,打开串口方式与.NET打开串口的方式是不一样的。
2.2 将Unity3D的API平台切换成.NET2.0。如何切换“Edit–>project Setting–>Player–>Other Setting –>Api Compatibility level”。在这里将“.NET2.0 Subset”切换为“.NET2.0”

切换.NET2.0
2.3 Unity的目标平台一定要切换为Windows平台,否则是其他平台会报错误,本人就是深有体会,针对这个问题找原因找了很久,什么百度、谷歌、论坛都查阅了,最后还是无意中自己发现解决的了。

Unity3D内部串口通信和Unity3D与Winform程序的串口通信的实现和异常问题_第1张图片
切换为Web平台时报的错误
Unity3D内部串口通信和Unity3D与Winform程序的串口通信的实现和异常问题_第2张图片
3、Unity的串口与.NET的串口对象参数有些不一样,比如在Unity3D中打开串口,SerialPort对象的属性、方法、事件等要比.NET SerialPort对象的属性、事件、方法要少一些。(图片不能显示,所以不就贴图了,只是说明下情况),甚至Unity3D的有些属性还是错误的,比如BytesToRead和BytesToWrite两个属性都是“未将对象引用值对象的实例”,但是在.NET中这两个参数默认是为0。这两个参数用于接收串口发送字节数组时,是很有用处的。
这是Unity中串口对象里的属性
Unity3D内部串口通信和Unity3D与Winform程序的串口通信的实现和异常问题_第3张图片
这是WinForm中串口对象里的属性
Unity3D内部串口通信和Unity3D与Winform程序的串口通信的实现和异常问题_第4张图片
4、虚拟串口的创建,不像是真实串口线那样子,它是以对来创建的,比如COM100与COM101一对……至于怎么成对完全是有那个创建虚拟串口的软件以及你输入的串口来决定的。
Unity3D内部串口通信和Unity3D与Winform程序的串口通信的实现和异常问题_第5张图片

一、Unity3D内部串口通信

1、内部通信思路
1.1 打开串口
之前在前言中说过,Unity打开串口方式不一样,因为在.NET2.0打开串口时,如果串口超过10,则必须在前面加上“\\?\”,比如我需要打开COM301,在Unity中你实际传给串口的参数必须是“”\\?\” + “COM301””。
在命名空间中引用System.IO.Ports
创建两个串口类对象

private SerialPort gatewayPort, coorPort;
//分别是网关串口和协调器串口,命名的话随自己。
    然后写一个打开串口方法
    注意:下面的网关串口和协调器串口只是我的命名而已,其实串口之间的通信。
    **打开网关串口**
        gatewayPort = new SerialPort("\\\\?\\" + "COM301", 9600);
        gatewayPort.ReadTimeout = 500;
        gatewayPort.Open();
        Debug.Log("网关串口打开成功");
        //用于接收协调器串口发送过来的数据
        tgateway = new Thread(ReceivePortThread2);
        tgateway.IsBackground = true;
        tgateway.Start();
    *打开协调器串口*
        coorPort = new SerialPort("\\\\?\\" + "COM3301", 9600);
        coorPort.ReadTimeout = 500;
        coorPort.Open();
        Debug.Log("协调器串口打开成功");
        //用于接收网关串口发送过来的数据
        tcoor = new Thread(ReceivePortThread1);
        tcoor.IsBackground = true;
        tcoor.Start();

1.2 线程接收数据
两个串口接收数据,并且打印出来,一般接收数据的方法常用的有两种,一种是接收字符串ReadLine()另一种接收字节Read,稍微我会将接收字节已注释的形式写出来。

字节接收的方式,把代码放在网关接收方法里和协调器方法里,根据个人需求吧
//byte[] buffer = new byte[1024];
 //int count = this.coorPort.Read(buffer, 0, buffer.Length);
 //if(count == 0) {
 //    continue;
 //}

 //byte[] bufferRead = new byte[count];

 //System.Array.Copy(buffer, 0, bufferRead, 0, count);
 //string strRec = ClassConvert.BytesToString(bufferRead);
 ClassConvert类是一些字节、字符串转化的方法,后面会提供

网关接收数据方法

    private void ReceivePortThread2()
    {
        while(true) {
            Thread.Sleep(1);
            if(this.gatewayPort != null && this.gatewayPort.IsOpen) {
                try {
                    string strRec = gatewayPort.ReadLine(); 
                    Debug.Log("网关读取数据:" + strRec);
                }
                catch {
                    //continue;
                }
            }
        }
    }

协调器接收数据方法

    private void ReceivePortThread1()
    {
        while(true) {
            Thread.Sleep(1);
            if(this.coorPort != null && this.coorPort.IsOpen) {
                try {
                    string strRec = coorPort.ReadLine(); 
                    Debug.Log("协调器读取数据:" + strRec);
                }
                catch {
                    //continue;
                }
            }
        }
    }

1.3 发送数据
将这下面两个方法分别加入到UI Button的事件中,具体如何加这里就不解释了。

    /// 
    /// 网关
    /// 
    public void OnGateWay()
    {
        this.gatewayPort.DiscardOutBuffer();
        gatewayPort.WriteLine("FF0000");

        //byte[] buffer = new byte[] { 0xFF, 0x00, 0x01 };
        //gatewayPort.Write(buffer, 0, buffer.Length);
    }

    /// 
    /// 协调器
    /// 
    public void OnCoor()
    {
        this.coorPort.DiscardOutBuffer();
        coorPort.WriteLine("00FFFF");
        //byte[] buffer = new byte[] { 0x00, 0xFF, 0xFE };
        //coorPort.Write(buffer, 0, buffer.Length);
    }

2、代码
主要类PortsTest.cs,字节字符串转化类ClassConvert.cs

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System.IO.Ports;
using System.Threading;

public class PortsTest : MonoBehaviour {

    private SerialPort gatewayPort, coorPort;

    public Thread tgateway, tcoor;

    // Use this for initialization
    void Start () {
        GateWayOpen();
        CoorOpen();
    }

    // Update is called once per frame
    void Update () {

    }

    void GateWayOpen()
    {
        gatewayPort = new SerialPort("\\\\?\\" + "COM301", 9600);
        gatewayPort.ReadTimeout = 500;
        gatewayPort.Open();
        Debug.Log("网关串口打开成功");

        tgateway = new Thread(ReceivePortThread2);
        tgateway.IsBackground = true;
        tgateway.Start();

    }

    void CoorOpen()
    {
        coorPort = new SerialPort("\\\\?\\" + "COM3301", 9600);
        coorPort.ReadTimeout = 500;
        coorPort.Open();
        Debug.Log("协调器串口打开成功");

        tcoor = new Thread(ReceivePortThread1);
        tcoor.IsBackground = true;
        tcoor.Start();
    }

    /// 
    /// 串口接收数据方法
    /// 
    private void ReceivePortThread1()
    {
        while(true) {
            Thread.Sleep(1);

            if(this.coorPort != null && this.coorPort.IsOpen) {
                try {
                    //byte[] buffer = new byte[1024];
                    //int count = this.coorPort.Read(buffer, 0, buffer.Length);
                    //if(count == 0) {
                    //    continue;
                    //}

                    //byte[] bufferRead = new byte[count];

                    //System.Array.Copy(buffer, 0, bufferRead, 0, count);
                    //string strRec = ClassConvert.BytesToString(bufferRead);

                    string strRec = coorPort.ReadLine();   

                    Debug.Log("协调器读取数据:" + strRec);
                }
                catch {
                    //continue;
                }
            }
        }
    }

    /// 
    /// 串口接收数据方法
    /// 
    private void ReceivePortThread2()
    {
        while(true) {
            Thread.Sleep(1);

            if(this.gatewayPort != null && this.gatewayPort.IsOpen) {
                try {
                    string strRec = gatewayPort.ReadLine();   
                    Debug.Log("网关读取数据:" + strRec);
                }
                catch {
                    //continue;
                }
            }
        }
    }

    /// 
    /// 网关
    /// 
    public void OnGateWay()
    {
        this.gatewayPort.DiscardOutBuffer();
        gatewayPort.WriteLine("FF0000");

        //byte[] buffer = new byte[] { 0xFF, 0x00, 0x01 };
        //gatewayPort.Write(buffer, 0, buffer.Length);
    }

    /// 
    /// 协调器
    /// 
    public void OnCoor()
    {
        this.coorPort.DiscardOutBuffer();
        coorPort.WriteLine("00FFFF");
        //byte[] buffer = new byte[] { 0x00, 0xFF, 0xFE };
        //coorPort.Write(buffer, 0, buffer.Length);
    }
}
using System;

public class ClassConvert
{
    /// 
    /// 字节数据转字符串
    /// 
    /// 
    /// 
    public static string BytesToString(byte[] bytes)
    {
        string result = "";
        foreach (byte b in bytes)
        {
            result = result + string.Format("{0:X2}", b);
        }
        return result;
    }

    /// 
    /// 字节数据转字符串(带格式)
    /// 
    /// 
    /// 
    public static string BytesToStringFormat(byte[] bytes)
    {
        string result = "";
        foreach (byte b in bytes)
        {
            result = result + string.Format("{0:X2}", b) + "-";
        }
        return result.Substring(0, result.Length - 1);
    }

    /// 
    /// 2位字符串转字节
    /// 
    /// 
    /// 
    public static byte StringToByte(string str)
    {
        try
        {
            str = System.Convert.ToInt32(str, 16).ToString();
        }
        catch (Exception err)
        {
            throw err;
        }

        byte result = 0;
        if (byte.TryParse(str, out result) == true)
        {
            return result;
        }
        else
        {
            throw new Exception("StringToByte error");
        }
    }

    /// 
    /// 字符串转字节数据
    /// 
    /// 
    /// 
    public static byte[] StringToBytes(string str)
    {
        byte[] result = new byte[str.Length / 2];
        for (int i = 0; i < str.Length; i = i + 2)
        {
            result[i / 2] = StringToByte(str.Substring(i, 2));
        }
        return result;
    }
}

3、运行结果和异常解析
运行程序后,会提示网关串口打开成功和协调器串口打开成功。
3.1、当以字符串形式发送串口数据和接收串口数据时,会发现一个问题就是在接收串口数据时,会出现数据丢失的情况,网关串口向协调器发送”FF0000”时,协调器接收数据偶尔会接收到“F0000”甚至是为空,只有当连续发送两次时,才会成功。
Unity3D内部串口通信和Unity3D与Winform程序的串口通信的实现和异常问题_第6张图片
3.2、当以字节发送和接收串口数据时,会出现一条完整的数据会以两次打印出来。比如将“new byte[] { 0xFF, 0x00, 0x01 }”发送过去,然后打印出来的结果是第一条是FF 第二条是00 01等等情况,感觉像是随机的。
Unity3D内部串口通信和Unity3D与Winform程序的串口通信的实现和异常问题_第7张图片
3、当以字节发送,字符串形式接收时,是无法接收数据的。

以上问题目前我也不知道是什么情况,解决思路是怎样的,发生该问题的原因可能是因为Unity对串口这块本身支持就不是很大,毕竟不是专门针对Windows平台的,如果有看到本文章的读者知道的话,请联系我的邮箱:[email protected] 或者微博:http://weibo.com/5062862016/profile?topnav=1&wvr=6&is_all=1 本人非常感谢!!

二、Unity3D与Winform程序之间的串口通信

在第一部分中介绍了Unity3D内部间的通信,现在测试Unity3D与Winform程序之间的串口通信。
首先Unity3D串口程序跟第一节类似的,只不过把网关打开串口那一部分代码移植到Winform中,然后修改一下打开串口的方式即可。
1、打开串口方式

private SerialPort gatewayPort;
public Thread tgateway;

gatewayPort = new SerialPort("COM202", 9600);
gatewayPort.ReadTimeout = 500;
gatewayPort.Open();

tgateway = new Thread(new ThreadStart(ReceivePortThread2));
tgateway.IsBackground = true;
tgateway.Start();

private void ReceivePortThread2()
{
    while(true) {
        Thread.Sleep(1);
        if(this.gatewayPort != null && this.gatewayPort.IsOpen) {
            try {
                string strRec = gatewayPort.ReadLine();  
                MessageBox.Show(strRec);
            }
            catch {
            }
        }
    }
}

以上就是核心代码。
2、遇到的问题
发送字符串和接收字符串遇到以下发生过的问题
2.1 winform程序发送数据成功了,但是Unity接收不到
2.2 Unity往Winform程序总发送数据时,是没有问题的。而Unity却接收不到。
Unity3D内部串口通信和Unity3D与Winform程序的串口通信的实现和异常问题_第8张图片
发送字节和接收字节遇到以下发生过的问题
2.3 WinForm程序发送数据成功了,但是Unity接收到的数据存在问题,数据不符或数据中断,要想解决这个问题有两种方法:
第一可能是Unity官方的错误,如果能做成跟.NET串口通信一致的话,那么这个问题很好解决。不过这个问题不够现实,因为Unity本身就是为游戏而开发的。
第二那就自己去解决了,看到Unity接收到的数据存在数据不符,还有数据断层,只能根据自身的要求,然后去测试,添加校验位,根据首校验位和末校验位来截取你想要的字节。只有这样子你才可能接收到正常的串口数据。但是这样子也存在很多的局限性!!!

2.4 Unity往WinForm程序中发送的数据时,是没有问题的。
Unity3D内部串口通信和Unity3D与Winform程序的串口通信的实现和异常问题_第9张图片
WinForm 接收数据关键代码:

if(gatewayPort.BytesToRead > 0) {
     byte[] bufferRead = new byte[this.gatewayPort.BytesToRead];
     this.gatewayPort.Read(bufferRead, 0, bufferRead.Length);
     string strRec = ClassConvert.BytesToString(bufferRead);
}

WinForm发送数据关键代码:

gatewayPort.DiscardInBuffer();
byte[] buffer = new byte[] { 0xFF, 0x00, 0x01 };
gatewayPort.Write(buffer, 0, buffer.Length);

Unity的接收数据关键代码和发送数据关键代码已在第一节都贴出来了。

你可能感兴趣的:(Unity)