首先定义一个串口管理类的脚本,来进行串口管理。
数据定义
public delegate void ProcessMsgDelegate(List<byte> recvMsg_); /// <summary> /// 串口数据包(消息)处理 /// </summary> public event ProcessMsgDelegate OnProcessMsg; /// <summary> /// 数据包长度 /// </summary> private const int dataLength = 8; //!串口资源 SerialPort mSerialPort = null; Thread serialPortThread_ = null; static volatile bool isSerialPortRunning_ = true; static volatile bool isSerialPortFinished_ = true; //!读取的锁 Object readLockObject_ = new Object(); //!在串口线程接收到的消息 List<List<byte>> allRecvMsgs_ = new List<List<byte>>(); //!写入的锁 Object writeLockObject_ = new Object(); //!发送到串口中的消息 List<List<byte>> allSendMsgs_ = new List<List<byte>>(); enum eReadState { //!FF的消息头 eRS_ReadHead_FF, //!AA的消息头 eRS_ReadHead_AA, //!消息的数据 eRS_Msg_Data, //!开始消息读取 eRS_Msg_Start, } private eReadState readState_ = eReadState.eRS_Msg_Start; private volatile string errorMsg_ = ""; //!ReadTimeOut cout /// <summary> /// 计算超时次数 /// </summary> private int readTimeOutCout = 0;
开始监听串口:
/// <summary> /// 开始监听串口 /// </summary> /// <param name="portName"></param> public void StartListning(string portName) { try { mSerialPort = new SerialPort(portName, 115200, Parity.None, 8, StopBits.One);//使用指定的方式进行初始化串口,指定COM口和端口和方式 mSerialPort.ReadTimeout = 1; mSerialPort.WriteTimeout = 1000; if (mSerialPort.IsOpen == false) { mSerialPort.Open(); } serialPortThread_ = new Thread(new ThreadStart(serialPortRunning)); serialPortThread_.Start(); } catch(System.Exception ex) { Debug.LogError("initialize serial port failed:" + ex.ToString()); } }
串口处理:
/// <summary> /// 监听线程 /// 只接受FF AA 开头的数据包 /// 其他为异常数据 /// </summary> void serialPortRunning() { //!读取的数据缓冲区 List<byte> recvMsg_ = new List<byte>(); isSerialPortRunning_ = true; isSerialPortFinished_ = false; while (isSerialPortRunning_) { try { lock (writeLockObject_) { if (allSendMsgs_.Count != 0) { List<byte> sndMsg_ = allSendMsgs_[0]; mSerialPort.Write(sndMsg_.ToArray(), 0, sndMsg_.Count); allSendMsgs_.RemoveAt(0); } } byte rByte_=System.Convert.ToByte(mSerialPort.ReadByte()); if (rByte_ == 0xFF) { readState_ = eReadState.eRS_ReadHead_FF; recvMsg_.Add(rByte_); } else if (rByte_ == 0xAA) { if (readState_ == eReadState.eRS_ReadHead_FF && (recvMsg_.Count <= 2 || recvMsg_.Count > 8))//防止中间数据有FF AA头 { readState_ = eReadState.eRS_ReadHead_AA; recvMsg_ = new List<byte>(); recvMsg_.Add(0xFF); recvMsg_.Add(0xAA); } else { recvMsg_.Add(rByte_); } } else { if (readState_ != eReadState.eRS_Msg_Data) { readState_ = eReadState.eRS_Msg_Data; } recvMsg_.Add(rByte_); } if (recvMsg_.Count > 0x03)//接收到数据头,和数据长度,根据数据长度读取对应长度的消息 { if (recvMsg_.Count == recvMsg_[2] + 2) { readTimeOutCout = 0; if (recvMsg_[0] == 0xFF && recvMsg_[1] == 0xAA)//这个地方本应该还要处理消息的校验,暂时忽略 && CheckOutDate(recvMsg_) { lock (allRecvMsgs_) { allRecvMsgs_.Add(recvMsg_); } recvMsg_ = new List<byte>(); } else { string msg_ = ""; foreach (byte data_ in recvMsg_) { msg_ += string.Format("0x{0:x} ", data_); } errorMsg_ = "invalid msg:" + msg_; UnityEngine.Debug.LogError(errorMsg_); } } } } catch (System.Exception ex) { if ((ex.GetType() != typeof(System.TimeoutException))) //忽略读取数据超时异常 { Debug.Log("serial port exception:" + ex.ToString()); } else { readTimeOutCout++; if(readTimeOutCout>=3000) //串口超时处理 { ReadDataTimeOut(); Debug.Log("serial port exception:" + ex.ToString()); } } } //Debug.Log("循环中"); } isSerialPortFinished_ = true; }
/// <summary> /// 当收到一条完整消息后开始执行消息段 /// </summary> void Update() { lock (allRecvMsgs_) { if (allRecvMsgs_.Count > 0) { foreach (List<byte> recvMsg_ in allRecvMsgs_) { if (OnProcessMsg!=null) OnProcessMsg(recvMsg_); } allRecvMsgs_.Clear(); } } }
向串口发送数据:
/// <summary> /// 向串口发送数据 /// </summary> /// <param name="writeBuffer"></param> public void SendData(byte[] writeBuffer) { lock (writeLockObject_) { try { List<byte> sndMsg_ = new List<byte>(writeBuffer); allSendMsgs_.Add(sndMsg_); } catch(System.ArgumentNullException ex) { UnityEngine.Debug.LogError("SendData to io:" + ex.Message); } } }
/// <summary> /// 停止监听串口,但是有缺陷,会随着线程的阻塞而阻塞主进程(这里还需要改进一下) /// </summary> public void StopListning() { isSerialPortRunning_ = false; while (!isSerialPortFinished_) { } if (mSerialPort != null) { try { mSerialPort.Close(); } catch (System.Exception ex) { Debug.LogError("串口关闭异常:" + ex.Message); } } mSerialPort = null; }
超时处理:
/// <summary> /// 串口超时的处理 /// </summary> public void ReadDataTimeOut() { readTimeOutCout = 0; }
最后的是检验方法,很多时候不大需要
/// <summary> /// 对一条完整的消息进行校验 /// 校验方法:取数据段和的低位和最后一段消息比较,相等即有效消息 /// 消息头两段,命令一段,消息校验一段,内容最少一段,最少大于五段消息 /// </summary> /// <param name="Mes"></param> /// <returns></returns> private bool CheckOutDate(List<byte> Mes) { int count = Mes.Count; string hexadecimalStr = ""; long dateSum = 0; if (count < 6 ) return false; for (int i = 3; i < count - 1; i++) { dateSum += Mes[i]; } hexadecimalStr = System.Convert.ToString(dateSum, 16); if (System.Convert.ToInt32(hexadecimalStr.Substring(hexadecimalStr.Length - 2 > 0 ? (hexadecimalStr.Length - 2) : 0),16) == Mes[count - 1]) { return true; } else { return false; } } /// <summary> /// 生成校验位 /// </summary> /// <returns></returns> private void CreateCheckOutDate(ref byte[] date) { int byteSum = 0; string hexadecimalStr = ""; for (int i = 2; i < date.Length-1; i++) { byteSum += date[i]; } hexadecimalStr = System.Convert.ToString(byteSum, 16); date[date.Length - 1] = (byte) System.Convert.ToInt32(hexadecimalStr.Substring(hexadecimalStr.Length - 2 > 0 ? (hexadecimalStr.Length - 2) : 0), 16); //return date[date.Length - 1]; }