Unity3d中的串口通信实现方案!

1、实现思想:

      串口接收,采用多线程+数据池设计模式,使用队列数据结构队列,避免线程阻塞

2、Code样例:


/*******************************/
 using UnityEngine;
 using System.Collections;
 //Other libraries
 using System;
 using System.Threading;
 using System.Collections.Generic;
 using System.ComponentModel;
 //串口命名空间
 using System.IO.Ports;
 using System.Text.RegularExpressions;
 using System.Text;




 public class ComTest : MonoBehaviour {
     /******定义一个串口字段*******/
    private SerialPort sp;
     /*******定义三个外部接口***************/
public static float FangXiangJiao;
public static float YangJiao;
public static float XuanZhuanJiao;
/*******定义三个角度存储的临时字段***********/
public static float _FangXiangJiao;
public static float _YangJiao;
public static float _XuanZhuanJiao;
     /***********三种角度的循环队列****************/
     private Queue<float> AVEFangXiangJiao = new Queue<float>();
     private Queue<float> AVEYangJiao = new Queue<float>();
     private Queue<float> AVEXuanZhuanJiao = new Queue<float>();
private byte[] DataBuf;
     
     private bool flag = true;
     private bool showMessage = false;//显示消息
     private bool open = false;


private int NumberCount=0;
private Thread SerialPortThread;

private StringBuilder sbReadline;
     private string strOutPool = string.Empty;
private Queue<string> queueDataPool;
     private Thread tPort;
     private Thread tPortDeal;
     /***********Unity初始化方法*****************/
private void Start () {
         sbReadline = new StringBuilder();
         queueDataPool = new Queue<string>();
         //DataBuf = new byte[10];


         sp = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
         if (!sp.IsOpen){
             // 开启串口
             sp.Open();
             open = true;
         }


         //开启2个线程分别用于接收数据和处理数据
         tPort = new Thread(DealData);
         tPort.Start();
         tPortDeal = new Thread(ReceiveData);
         tPortDeal.Start();
//StartCoroutine(DealData()); 

}


     /***************程序结束的时候,关闭串口**********************/
    void OnApplicationQuit() {
       sp.Close();
//SerialPortThread.Abort();//终止进程
    }

/*************Update方法********************/
//一帧之调用多次
    void FixedUpdate() {
        //定期回收垃圾
        if (Time.frameCount % 120 == 0) System.GC.Collect(); 


        //处理数据的线程停止,那么重启该线程
        if (!tPort.IsAlive) {
            tPort = new Thread(DealData);
            tPort.Start();


        }
        if (!tPortDeal.IsAlive) {
            tPortDeal = new Thread(ReceiveData);
            tPortDeal.Start();
        }
        // StartCoroutine(DealData());
//Debug.Log( SerialPortThread.ThreadState);
 }


/// <summary>
/// 数据接收 进程, 使用队列来存储数据,避免数组的上锁和解锁问题
/// 设计模式  多线程+数据池
/// </summary>
/// <returns>
/// The data.
/// </returns>
    /*****************数据接收*************************/
private void ReceiveData () {
//读取数据,同时存入数据池中
//newline结束标记,同时使用readIlne()方法 
try {

//从输入缓存区中去读取第一次出现"AA"时的内容
             Byte[] buf=new Byte[120];
           if (open) sp.Read(buf,0,120);
             //如果没有接到到数据,就返回
             if (buf.Length ==0) {
                 return;
             }
            string sbReadline2str = string.Empty;
            if (buf != null)
            {
                for (int i = 0; i < buf.Length; i++)
                {
                    sbReadline2str += buf[i].ToString("X2");
                }
            }         
           //sbReadline2str = System.Text.Encoding.Default.GetString(buf, 0,buf.Length);
        //  Debug.LogError(sbReadline2str.ToString());
         // sbReadline2str = buf.ToString();
            // sbReadline2str = sbReadline.ToString();
                 //提取完整的一个数据包,压入数据池中(队列中)
        if (sbReadline2str.StartsWith("DB90"))
          {       //分组数据包
              string[] _str = Regex.Split(sbReadline2str, "55AA", RegexOptions.IgnoreCase);
              foreach (string s in _str)
              {
                  if (s.Length == 16)
                  {
                      //数据进入队列
                      queueDataPool.Enqueue(s + "55AA");
                      //Debug.LogError(s+"55AA");
                  }
                 
              }
          }
          else {
             
             sbReadline2str.Remove(0,sbReadline2str.IndexOf("DB90")+1);
              string[] _str = Regex.Split(sbReadline2str, "55AA", RegexOptions.IgnoreCase);
              foreach (string s in _str)
              {
                  if (s.Length == 16)
                  {
                      //数据进入队列
                      queueDataPool.Enqueue(s + "55AA");
                     // Debug.LogError(s+"55AA");
                  }
                 // Convert.ToByte(s,16);
                  
              }
          }

}catch (Exception ex) {
Debug.LogError(ex);
}
//yield return null;
}


     /***************处理出队的数据***********************/
     private void DealData()
     {
         while (queueDataPool.Count != 0)
         {//循环检测队列
             //队列中有数据


             //处理出队的数据
             //循环队列,首先加载第一包数据,只执行一次
             if (flag)
             {    //初始化第一组数据
                 for (int i = 0; i < 7; i++)
                 {   //出队列,同时移除这个数据
                     strOutPool = queueDataPool.Dequeue();
                     float[] jiaodu = DealReceivedDatanow(strOutPool);
                     AVEFangXiangJiao.Enqueue(jiaodu[0]);
                     AVEYangJiao.Enqueue(jiaodu[1]);
                     AVEXuanZhuanJiao.Enqueue(jiaodu[2]);


                 }
                 FangXiangJiao = Caulation(AVEFangXiangJiao);
                 YangJiao = Caulation(AVEYangJiao);
                 XuanZhuanJiao = Caulation(AVEXuanZhuanJiao);
                 flag = false;


             }
             else
             {/*采用7点平滑滤波,消除抖动现象.注意特殊点的处理360到0之间的数据*/
                 
               
                  //没有存在跳变的现象,那就直接存储数据
                     for (int i = 0; i < 7; i++)
                     {
                         //出队列,同时移除这个数据。
                         //加载7包数据,
                         strOutPool = queueDataPool.Dequeue();
                         //组合加载的数据,低字节在前,高字节在后,return 三个角度值
                         float[] jiaodu = DealReceivedDatanow(strOutPool);
                         //角度循环队列更新最新进入的数据
                         AVEFangXiangJiao.Dequeue();
                         //角度循环队列在队尾插入新的数据
                         AVEFangXiangJiao.Enqueue(jiaodu[0]);
                         //计算更新后的循环队列中的7个数据平均值
                         /*****************处理特殊数据************************/
                         int[] IntPanDuan = CheackDataTuBian(AVEFangXiangJiao);
                         //如果存在着跳变的现象
                         if (IntPanDuan[0] == 1)
                         {//保留队列前面的数据,移除后面的数据
                             float[] FXj = AVEFangXiangJiao.ToArray();
                             float sum = 0f;
                             //获取要移除数据的索引值
                             int indexOfRemovingData = IntPanDuan[2];
                              for (int j = 0; j < indexOfRemovingData; j++)
                             {
                                 sum += FXj[j];
                             }
                             //计算平均值
                             FangXiangJiao = sum / indexOfRemovingData;
                         }
                         FangXiangJiao = Caulation(AVEFangXiangJiao);
                         /******************处理特殊数据结束*************************/


                         /*******************************************/
                         /*同上*/
                         AVEYangJiao.Dequeue();
                         AVEYangJiao.Enqueue(jiaodu[1]);
                         YangJiao = Caulation(AVEYangJiao);
                         /*同上*/
                         AVEXuanZhuanJiao.Dequeue();
                         AVEXuanZhuanJiao.Enqueue(jiaodu[2]);
                         XuanZhuanJiao = Caulation(AVEXuanZhuanJiao);
                         /******************************************/
                     }
                 
             }


         }


     }


     /************************检验特殊值***************************/
     private int[] CheackDataTuBian(Queue<float> cheackQueue)
     {
         float[] cheackDataArrary = cheackQueue.ToArray();
  
         //flag =0表示false;  flag=1表示true
         int flag = 0; 
         int[] BoolAndIndexOfNext=new int[2];
         for (int count = 0; count < 6; count++ )
         {float Previous =cheackDataArrary[count];
             float Next = cheackDataArrary[count+1];
             if (Mathf.Abs(Previous - Next)>350.0F && Mathf.Abs(Previous -Next)<360.0f) {
                 flag = 1;
                 BoolAndIndexOfNext[0] = flag;
                 BoolAndIndexOfNext[1] = count + 1; break;
             }
         }


         return BoolAndIndexOfNext;
     }




     /*****************计算平均值****************************/
         private float Caulation(Queue<float> AVEf) {
         
         float _f=0.0f;
          foreach (float f in AVEf) {
              _f+=f;
          }
         return _f/7;
     }
     
     /*****************处理来自数据池的数据**************************/
private float[] DealReceivedDatanow (string DataBuf) {
//this is a whole frame data package,then convert the 
//First convert the Byte type into Char type
// Debug.Log("--starting");
         //Debug.Log("databuf----"+DataBuf);
         /*把16进制字符串转换成字节数组*/
         byte[] returnBytes = new byte[DataBuf.Length / 2];
         for (int i = 0; i < returnBytes.Length; i++)
             returnBytes[i] = Convert.ToByte(DataBuf.Substring(i * 2, 2), 16);
         /*消除抖动,因为眼镜传感器会不断的发送数据,即使是不动的状态下,也会有这样的现象*/
         float[] jiaoduCollection =new float[3];
         _FangXiangJiao = (float)((byte)returnBytes[2] + ((sbyte)returnBytes[3] << 8)) / 10;
jiaoduCollection[0] =_FangXiangJiao;
  // Debug.Log("fx-->"+FangXiangJiao);
         _YangJiao = (float)((byte)returnBytes[4] + ((sbyte)returnBytes[5] << 8)) / 10;
jiaoduCollection[1] =_YangJiao; 
         // Debug.Log("yj-->"+YangJiao);
         _XuanZhuanJiao = -(float)((byte)returnBytes[6] + ((sbyte)returnBytes[7] << 8)) / 10;
jiaoduCollection[2]=_XuanZhuanJiao;
         //Debug.Log("xz-->"+XuanZhuanJiao);
//Debug.Log("--ending");




         return jiaoduCollection;
}


     /********************处理来自数据池的数据**************************/
private void DealReceivedData (string dataOutPool) 
{


        //读取串口的数据, 如果没有反应就会出现timeout 异常
// 数据格式:DB  90  AF  0C  A2  FF  2C  00  55  AA 
//包头是DB 90  包尾:55 AA 
//一帧发送一个数据包 总大小10个字节,
             //int DataNumber;   
 
        try { 
//读取数据

              byte tempB;
tempB= (byte)sp.ReadByte();

//Time.time;
//Read the header first
if (tempB ==219)
{  
//Debug.Log("i get the 0XDB"+(byte)0xDB);
DataBuf[0]=tempB;
tempB= (byte)sp.ReadByte();

if (tempB ==144)
DataBuf[1]=tempB;
int DataNumber=2;
while(DataNumber<10){
tempB= (byte) sp.ReadByte();

DataBuf[DataNumber]=tempB;

DataNumber++;
  }

}

//read out the input data 
//cheack the header and tail for a data package.


//this is a whole frame data package,then convert the 
//First convert the Byte type into Char type
// Debug.Log("--starting");
    FangXiangJiao=(float)(DataBuf[2]+((sbyte)DataBuf[3]<<8))/10;
/* while(NumberCount <7) {
//_FangXiangJiao =
}*/
 
  // Debug.Log("fx-->"+FangXiangJiao);
YangJiao = (float)(DataBuf[4]+((sbyte)DataBuf[5]<<8))/10;
 // Debug.Log("yj-->"+YangJiao);
 XuanZhuanJiao = (float)(DataBuf[6]+((sbyte)DataBuf[7]<<8))/10;
  // Debug.Log("xz-->"+XuanZhuanJiao);
//Debug.Log("--ending");


        } 
        catch (Exception e) {
Debug.LogError(e);
        }
//Debug.Log("starting");
foreach(byte b in DataBuf) {
//Debug.LogError(Convert.ToString(b,16));
}
//Debug.Log("ending");
// yield return null;

}


     /*********************************************/
     //在没有开启串口设备电源的时候,显示消息窗口,开启电源之后,点击确定按钮
     private void OnGUI() {
         if (showMessage)
         {
             GUI.BeginGroup(new Rect(Screen.width/2-100,Screen.height-60,400,400));
             GUI.Label(new Rect(Screen.width / 2 - 80, Screen.height - 60,200,150), "请打开串口设备电源!然后点击确定");
             if (GUI.Button(new Rect(Screen.width / 2 -80, Screen.height +95 , 100, 100), "确定"))
             {
                 open = true;
             }
             GUI.EndGroup();
         }


     }

 
}

3、结束语:

Enjoy!