unity的串口编程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huannichao0209/article/details/78968956

        第一次在csdn上写文章,各种格式之类的完全不熟悉,请见谅。 串口是用来做pc端中控程序与硬件对接的,本来嘛,可以用c++或者vs的wpf这些来做的,但实际上做出来的界面比较丑,很难做得好,也试过用flash加载ane来做串口,但flash貌似有可能被淘汰了,所以现在选择用unity来做中控程序界面试试,感觉有点大材小用了。

         一开始以为unity可以采用c#编程,那么串口就应该不会有什么问题了,但其实不是,unity貌似对串口支持不算很好,会出现各种意想不到的事情。测试了几个,才找到一个可以使用的代码,链接如下:http://www.cnblogs.com/zhaozhengling/p/3696251.html,采用两个线程一个接收,一个处理。但在这里有个缺点,他是一次处理固定的数据长度,但接收数据的长度有时未必固定,或者是只要有一个数据丢失,后面的数据将会全部错误。

         上述链接中最致命的缺点在于没有适当清空liststr的数据,当出现各种意外,使得某次发送长度大于所需长度时,下一次的计算就会从多出的数据开始计算长度。那么解决的方式也很简单。开启个定时器计数,时间间隔为inftimer,定时刷新检测liststr的长度,如果liststr.Count不为0,也就是数据在接收中,当下次liststr.Count大于上次,表示在接收中,否则接收完毕并重置清零。代码如下:

 /****************长时间没继续接收到数据,默认输出读取到的数据,并清空数据**************/
            if (liststr.Count != 0)
            {
                timer -= Time.deltaTime;
                if (timer <= 0)
                {              
                        if (liststr.Count > countcom)
                        {
                            countcom = liststr.Count;
                        }
                        else
                        {                     
                                Porthandle();                //处理函数                       
                        }                  
                    timer = inftimer;
                }
            }        

       在这里会引起其它的问题,一:串口读取后有一个延迟,取决于设定的inftimer的大小;二:这里是读取完数据后才响应的,对于数据连续循环的时候,将毫无办法,然而很多硬件的串口数据其实是不停的。我们着重对处理函数做修改,代码已经补充了很多了,代码如下:

       

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO.Ports;
using System.Linq;
using System.Threading;
using UnityEngine;
using System.Collections;




/// <summary>
/// C#用SerialPort类实现串口编程
/// </summary>
public class MePortDataHandle : MonoBehaviour
{
    public bool Specialformat = false;   //特殊格式项选择
    public bool Continuousreading = true;  //是否连续读取
    List<byte> liststr;//在ListByte中读取数据,用于做数据处理
    List<byte> ListByte;//存放读取的串口数据


    int Frameheadloopindex = 0;    //用于循环计数判断


    bool isFrameheadmain = false;  //判断是否接收到开始符
    bool[] isFramehead;          //开始符每一个对应的校正对应符
    public string[] Framehead;   //帧头1:字符的ASCII的16进制表示
    int Headtruecount = 0;      //开始校验正确位计算
    bool initFrameheadmain = true; //是否是第一次前缀判断结束


    bool isEnddatamain = false; //判断是否接收到结束符
    bool[] isEnddata;           //结束符每一个对应的校正对应符
    public string[] Enddata;    //结束符1:字符的ASCII的16进制表示
    int Endtruecount = 0;       //结束校验正确位计算     




    private Thread tPort;    //读取串口数据的线程
    private Thread tPortDeal;//处理数据的线程
    bool isStartThread;//控制FixedUpdate里面的两个线程是否调用(当准备调用串口的Close方法时设置为false)
    public SerialPort spstart; //串口变量
    public ComType Comnow=ComType.COM1;     //串口选择
    public BaudRateType BaudRatenow=BaudRateType.B9600;  //波特率选择




    public float inftimer = 0.05f;     //串口读取超时参数
    float timer;                      //串口读取超时变量
    int countcom = 0;                 //串口读取超时辅助计数


    public int revlenght=-1;           //在无前后缀的时候,读取数据的长度
    private byte[] tempArr;           //读取到的byte数据
    /****************输出16进制字符串****************************/
    private string temp16String;      //读取到的16进制字符串数据
    private string tempString;        //读取到的字符串




    private string NoSpecialformatstr16=null;
    private string NoSpecialformatstr=null;
    public MeRevPortclass MeNoSpecialPortdata = new MeRevPortclass();


    private string Specialformatstr16 = null;
    private string Specialformatstr = null;
    public MeRevPortclass MeSpecialPortdata = new MeRevPortclass();
















    void Start()
    {      
        if (Specialformat)
        {
            Continuousreading = false;
        }
        timer = inftimer;
        InitJudgementsign();
        OpenSerialPort(Comnow.ToString(), (int)BaudRatenow);
    }


    /****************开启串口函数***************/
    void OpenSerialPort(string portName, int BaudRateindex)
    {
        isStartThread = true;
        liststr = new List<byte>();
        ListByte = new List<byte>();
        spstart = new SerialPort();
        spstart.PortName = portName;
        spstart.BaudRate = BaudRateindex;
        spstart.Parity = Parity.None;
        spstart.StopBits = StopBits.One;
        spstart.DataBits = 8;
        spstart.Handshake = Handshake.None;
        
        try
        {
            spstart.Open();
            print("SP Open");
        }
        catch (Exception e)
        {
            Debug.LogError(e.ToString());
        }
        tPort = new Thread(DealData);
        tPort.Start();
        tPortDeal = new Thread(ReceiveData);
        tPortDeal.Start();
    }




    void FixedUpdate()
    {
        if (isStartThread)
        {
            //定期回收垃圾
            if (Time.frameCount % 120 == 0) System.GC.Collect();
            if (!tPortDeal.IsAlive)
            {
                tPortDeal = new Thread(ReceiveData);
                tPortDeal.Start();
            }
            if (!tPort.IsAlive)
            {
                tPort = new Thread(DealData);
                tPort.Start();
            }


            /****************长时间没继续接收到数据,默认输出读取到的数据,并清空数据**************/
            if (liststr.Count != 0||!Specialformat)
            {
                timer -= Time.deltaTime;
                if (timer <= 0)
                {
                    if (!Specialformat)
                    {
                        if(NoSpecialformatstr16 != null)
                        {
                            if(!Continuousreading)
                            {
                                MeNoSpecialPortdata.PortNoSpecialissue(NoSpecialformatstr, NoSpecialformatstr16);
                            }                         
                        }
                        NoSpecialformatstr16 = null;
                        NoSpecialformatstr = null;
                    }
                    else
                    {
                        if (liststr.Count > countcom)
                        {
                            countcom = liststr.Count;
                        }
                        else
                        {
                            if (Framehead.Length != 0 && Enddata.Length == 0)
                            {
                                Porthandle();                //处理函数
                            }
                            ResetFramehead();
                            isEnddatamain = false;
                        }
                    }
                    timer = inftimer;
                }
            }        
        }
    }


    IEnumerator ClosePort()//该方法为关闭串口的方法,当程序退出或是离开该页面或是想停止串口时调用。
    {
        isStartThread = false;//停止掉FixedUpdate里面的两个线程的调用
        yield return new WaitForSeconds(1);//等一秒钟,让两个线程确实停止之后在执行Close方法
        spstart.Close();
    }


    /****************关闭串口函数,开始协程确保线程关闭***************/
    public void Close()
    {
        StartCoroutine(ClosePort());
    }


    void OnApplicationQuit()
    {
        isStartThread = false;
        spstart.Close();
    }


    /// <summary>
    /// 获取数据
    /// </summary>
    private void ReceiveData()
    {  
        try
        {
            Byte[] buf = new Byte[1];              //必须一个个字符接收,数据才能准确


            if (spstart.IsOpen)
            {
                spstart.Read(buf, 0, 1);            //接收一个字符到buf中            
                //isReadingData = true;
            }
            if (buf.Length == 0)
            {
                return;
            }
            if (buf != null)
            {
                for (int i = 0; i < buf.Length; i++)
                {
                    ListByte.Add(buf[i]);
                }
            }
        }
        catch (Exception e)
        {
            Debug.Log(e.ToString());
        }
    }


    private void DealData()
    {
        if (!Specialformat)                    //无任何特殊格式要求
        {
            liststr.Add(ListByte[0]);
            ListByte.Remove(ListByte[0]);
            
            timer = inftimer;
            if (liststr.Count != 0)
            {
                PortnoSpecialhandle();
                liststr.Clear();
            }
        }
        else                                  //有特殊格式要求
        {
            if (Framehead.Length == 0)         //前缀不存在
            {
                if (Enddata.Length == 0)       //前后缀都没有,靠定义的长度来控制
                {
                    liststr.Add(ListByte[0]);
                    ListByte.Remove(ListByte[0]);
                    if (liststr.Count == revlenght)
                    {
                        Porthandle();                //处理函数
                        liststr.Clear();
                    }
                }
                else                          //只有后缀
                {
                    liststr.Add(ListByte[0]);
                    ListByte.Remove(ListByte[0]);
                    Endtruecount = 0;
                    /****************接收结束符判断**************************************/
                    for (int Endloopindex = 0; Endloopindex < Enddata.Length; Endloopindex++)
                    {
                        if (liststr[liststr.Count - Enddata.Length + Endloopindex].ToString("X2").Equals(Enddata[Endloopindex]))
                        {
                            Endtruecount++;
                        }
                        if (Endtruecount == Enddata.Length)
                        {
                            isEnddatamain = true;
                        }
                    }


                    /*************************后缀验证完成处理*****************************/
                    if (isEnddatamain)
                    {
                        Porthandle();                //处理函数                   
                        ResetFramehead();                //清除
                        isEnddatamain = false;
                    }
                }
            }
            else                             //前缀存在
            {
                if (initFrameheadmain)           //判断是否是第一次接收到前缀
                {
                    /********前缀判断(在不停刷新DealData()函数下,相当于有循环)****/
                    if (ListByte[0].ToString("X2").Equals(Framehead[Frameheadloopindex]))     //如果开始符正确
                    {
                        if (Frameheadloopindex == 0)   //读取开始标识符的第一个字母,重置所有参数
                        {
                            ResetFramehead();
                        }
                        isFramehead[Frameheadloopindex] = true;             //标识符对应位置true
                        liststr.Add(ListByte[0]);                           //数据存到liststr
                        if (Frameheadloopindex == Framehead.Length - 1)     //如果计数完开始符所有的位,进行开始符校验
                        {
                            /****************校验开始符成功个数***************/
                            Headtruecount = 0;
                            for (int Frameheadindex = 0; Frameheadindex < Framehead.Length; Frameheadindex++)
                            {
                                if (isFramehead[Frameheadindex] == true)
                                {
                                    Headtruecount++;
                                }
                            }
                            /****************如果校验成功,校验成功标识符置true***************/
                            if (Headtruecount == Framehead.Length)
                            {
                                isFrameheadmain = true;
                                Frameheadloopindex = 0;
                            }
                        }
                        else if (Frameheadloopindex < Framehead.Length - 1)         //未计数完,计数+1
                        {
                            Frameheadloopindex = Frameheadloopindex + 1;
                        }
                        else                 //计数超过对应长度,计数值置零(重新计数时有相应清零函数)
                        {
                            Frameheadloopindex = 0;
                        }
                    }
                    else                    //如果开始标识符中某一位不正确,计数值置零(重新计数时有相应清零函数)
                    {
                        Frameheadloopindex = 0;
                    }
                }


                if (isFrameheadmain == true)      //已读取到开始标识符
                {
                    if (Enddata.Length == 0)     //只有前缀
                    {
                        if (revlenght > 0)                   //固定获取的长度
                        {
                            if (!initFrameheadmain)
                            {
                                liststr.Add(ListByte[0]);
                            }
                            else
                            {
                                initFrameheadmain = false;
                            }
                            if (liststr.Count == revlenght)
                            {
                                Porthandle();                //处理函数
                                ResetFramehead();
                            }
                        }
                        else                                //获取长度不固定
                        {
                            int headnexttruecount = 0;
                            bool isheadnextmain = false;
                            if (!initFrameheadmain)
                            {
                                liststr.Add(ListByte[0]);


                                for (int headnextloopindex = 0; headnextloopindex < Framehead.Length; headnextloopindex++)
                                {
                                    if (liststr[liststr.Count - Framehead.Length + headnextloopindex].ToString("X2").Equals(Framehead[headnextloopindex]))
                                    {
                                        headnexttruecount++;


                                    }
                                    if (headnexttruecount == Framehead.Length)
                                    {
                                        isheadnextmain = true;


                                    }
                                }
                            }
                            else
                            {
                                initFrameheadmain = false;
                            }
                            if (isheadnextmain)
                            {
                                liststr.RemoveRange(liststr.Count - Framehead.Length, Framehead.Length);
                                Porthandle();                //处理函数
                                liststr.Clear();
                                isheadnextmain = false;
                                initFrameheadmain = false;
                                isFrameheadmain = true;
                                for(int loopindex=0;loopindex<Framehead.Length;loopindex++)
                                {
                                    liststr.Add((byte)Convert.ToInt32(Framehead[loopindex], 16));
                                }                                                                                      
                            }
                        }
                    }
                    else                         //前后缀均有
                    {
                        if (!initFrameheadmain)
                        {
                            liststr.Add(ListByte[0]);
                        }
                        else
                        {
                            initFrameheadmain = false;
                        }
                        Endtruecount = 0;
                        /****************接收结束符判断**************************************/
                        for (int Endloopindex = 0; Endloopindex < Enddata.Length; Endloopindex++)
                        {
                            if (liststr[liststr.Count - Enddata.Length + Endloopindex].ToString("X2").Equals(Enddata[Endloopindex]))
                            {
                                Endtruecount++;
                            }
                            if (Endtruecount == Enddata.Length)
                            {
                                isEnddatamain = true;
                            }
                        }
                        if (isEnddatamain == true)
                        {
                            Porthandle();                //处理函数
                                                         /****************清除***************************************/
                            ResetFramehead();
                            isEnddatamain = false;
                        }


                    }
                }
                ListByte.Remove(ListByte[0]);         //清除ListByte的数据
            }
        }
    }


       


    /****************初始化开始结束符数组***************/
    private void InitJudgementsign()
    {
        isFramehead = new bool[Framehead.Length];
        ClearisFramehead();
        isEnddata = new bool[Enddata.Length];
        ClearisEndindex();
    }


    /****************判断开始标志位全部重置***************/
    private void ClearisFramehead()
    {
        for (int Frameheadindex = 0; Frameheadindex < Framehead.Length; Frameheadindex++)
        {
            isFramehead[Frameheadindex] = false;
        }
    }
    /****************判断结束标志位全部重置***************/
    private void ClearisEndindex()
    {
        for (int Endindex = 0; Endindex < Enddata.Length; Endindex++)
        {
            isEnddata[Endindex] = false;
        }
    }
    /****************全部重置函数***************/
    private void ResetFramehead()
    {
        liststr.Clear();
        isFrameheadmain = false;
        ClearisFramehead();
        initFrameheadmain = true;
        timer = inftimer;
        countcom = 0;
    }
    /****************byte转字符串函数***************/
    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;
    }


    /****************发送字符串函数***************/
    public void Writestr(string Writestring)
    {
        spstart.WriteLine(Writestring);
    }


    /****************发生16进制数据函数***************/
    public void Writestr16(string message)
    {
        byte[] data = new byte[message.Length / 2];
        for (int i = 0; i < message.Length / 2; i++)
        {
            data[i] = Convert.ToByte(message.Substring(i * 2, 2), 16);
        }
        spstart.Write(data, 0, data.Length);
    }


    /****************串口接收处理函数***************/
    private void Porthandle()
    {
        tempArr = liststr.ToArray();
        tempString = System.Text.Encoding.ASCII.GetString(tempArr);
        temp16String = byteToHexStr(tempArr);


        MeSpecialPortdata.PortSpecialissue(tempString, temp16String);    //发送委托消息
        //Debug.Log(tempString);
        //Debug.Log(temp16String);
    }


    private void PortnoSpecialhandle()
    {
        tempArr = liststr.ToArray();
        /****************输出16进制字符串****************************/
        NoSpecialformatstr16 += byteToHexStr(tempArr);
        NoSpecialformatstr += System.Text.Encoding.ASCII.GetString(tempArr);
        if (Continuousreading)
        {
            MeNoSpecialPortdata.PortNoSpecialissue(NoSpecialformatstr, NoSpecialformatstr16);  //发送委托消息
        }
    }
    /// <summary>
    /// 关闭程序
    /// </summary>
    public void CloseSp()
    {
        spstart.Close();
        Application.Quit();
    }




    public class MeRevPortclass 
    {
        public delegate void MeNoSpecialRevPort(string datastr,string datastr16); //声明事件所需的代理
        public event MeNoSpecialRevPort OnMeNoSpecialRevPort; //事件的声明
        public void PortNoSpecialissue(string _datastr, string _datastr16) //触发事件的方法
        {
            if (OnMeNoSpecialRevPort != null)
            {
                OnMeNoSpecialRevPort(_datastr, _datastr16);
            }
        }


        public delegate void MeSpecialRevPort(string datastr, string datastr16); //声明事件所需的代理
        public event MeSpecialRevPort OnMeSpecialRevPort; //事件的声明
        public void PortSpecialissue(string _datastr, string _datastr16) //触发事件的方法
        {
            if (OnMeSpecialRevPort != null)
            {
                OnMeSpecialRevPort(_datastr, _datastr16);
            }
        }
    }   

    public enum ComType
    {
        COM1,
        COM2,
        COM3,
        COM4,
        COM5,
        COM6,
        COM7,
        COM8,
        COM9,
    }
    public enum BaudRateType
    {
        B110 = 110,
        B300 = 300,
        B600 = 600,
        B1200 = 1200,
        B2400 = 2400,
        B4800 = 4800,
        B9600 = 9600,
        B14400 = 14400,
        B19200 = 19200,
        B38400 = 38400,
        B43000 = 43000,
        B56000 = 56000,
        B57600 = 57600,
        B115200 = 115200,
        B128000 = 128000,
        B256000 = 256000,
    }
}


         我把串口的情况分为几种情况:

一:数据无任何格式,接收完毕产生一次委托消息。

二:数据无任何格式,接收到一个byte即触发一次委托。

三:数据每次读取相应长度,比如6,如果接收到15长度的数据,则会分为3段,分别是6,6,3长度的数据,最后一次靠计时器输出。

四:数据存在前缀,在连续的数据流中,接收到前缀则默认是另一段数据,最后一段一样靠计时器输出。

五:数据存在前缀,并且在连续的数据流中,只取从前缀开始算的固定长度。

六:数据只存在后缀,在连续的数据流中,检测到后缀即默认一次接收完毕。

七:数据同时存在前后缀,这个最好理解。


         为什么要分多种情况呢?主要是Unity不支持DataReceived的方法,同时在实际项目当中,串口的数据格式是不可能没有的,不过其实只要完成了第二种分类,再加上定时清空接收,已经基本等同于实现了DataReceived了的,加上各种格式限制,是根据我一般接触的项目要求而封装的,不是很想每接收一个byte就触发一次,这样消耗会大不少,同时重置完全取决于定时的设定,对于不同硬件,有点难把握这个时间,所以只能尽可能减少其出现,只在数据停止接收的最后一段才有效。

        测试方法是:

    private MePortDataHandle MePortHandle;
    
// Use this for initialization
void Start () {
        MePortHandle= GameObject.Find("PortManage").GetComponent<MePortDataHandle>();   
        MePortHandle.MeNoSpecialPortdata.OnMeNoSpecialRevPort += new  MePortDataHandle.MeRevPortclass.MeNoSpecialRevPort(MeNoSpecialPortReceive);
        MePortHandle.MeSpecialPortdata.OnMeSpecialRevPort += new MePortDataHandle.MeRevPortclass.MeSpecialRevPort(MeSpecialPortReceive);
    }

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

}


    private void MeNoSpecialPortReceive(string revstr, string revstr16) //定义事件处理程序
    {
        Debug.Log(revstr);
        //Debug.Log(revstr16);
    }


    private void MeSpecialPortReceive(string revstr, string revstr16) //定义事件处理程序
    {
        //Debug.Log(revstr);
        Debug.Log(revstr16);
    }


    public void PortSendData(string PortData)
    {
        MePortHandle.Writestr(PortData);
    }
    public void PortSendData16(string PortData16)
    {
        MePortHandle.Writestr16(PortData16);
    }

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页