新开的项目,公司决定用串口通信,因为以前都是用Socket,串口一点不懂,自学研究了2天,好歹算是把功能实现了。


测试可以先用 VSPD 虚拟串口

UNITY 串口 HEX string unity串口通信协议_串口


然后用 ComMonitor 串口调试工具,测试接受和发送

UNITY 串口 HEX string unity串口通信协议_串口_02


注意Unity .NET 框架必须设置成 .NET 2.0,而不是默认的.NET 2.0 Subset
下面是代码

/******************************串口协议************************************
* 协议规定
* 1、协议格式必须为 | 字头 | 长度 | 地址 | 内容 | 检验 | 字尾 |
**************************************************************************/
using System;
using System.IO.Ports;
using UnityEngine;

[Serializable]
public class SerialPortConfig
{
    [Header("SerialPort option")]
    public string portName = "COM1";
    public int baudRate = 9600;
    public Parity parity = Parity.None;
    public int dataBits = 8;
    public StopBits stopBits = StopBits.One;

    [Header("Read bytes")]
    public int readBufferSize = 1024;
    public int readTimeout = 500;
    public byte[] readHead;
    public byte[] readTail;
    public int readCount = 10;
    public int readCycle = 250;

    [Header("Write bytes")]
    public int writeBufferSize = 1024;
    public int writeTimeout = 500;
    public byte[] writeHead;
    public byte[] writeTail;
    public int writeCount = 10;//仅指令的长度
    public int writeCycle = 250;

    public SerialPortConfig()
    {
        //全部在无参构造函数里面修改
        portName = "COM1";
        baudRate = 57600;
        parity = Parity.None;
        dataBits = 8;
        stopBits = StopBits.One;

        readBufferSize = 1024;
        readTimeout = 500;
        readHead = new byte[] { 0xEB, 0x2F };
        readTail = new byte[] { 0x24, 0xFF };
        readCount = 35;
        readCycle = 25;

        writeBufferSize = 1024;
        writeTimeout = 500;
        writeHead = new byte[] { 0x90, 0x2F };
        writeTail = new byte[] { 0x5F, 0xFF };
        writeCount = 1;
        writeCycle = 250;
    }

    public SerialPortConfig(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits)
    {
        this.portName = portName;
        this.baudRate = baudRate;
        this.parity = parity;
        this.dataBits = dataBits;
        this.stopBits = stopBits;

        //协议相关在这里修改
        readBufferSize = 1024;
        readTimeout = 500;
        readHead = new byte[] { 0xEB, 0x2F };
        readTail = new byte[] { 0x24, 0xFF };
        readCount = 35;
        readCycle = 25;

        writeBufferSize = 1024;
        writeTimeout = 500;
        writeHead = new byte[] { 0x90, 0x2F };
        writeTail = new byte[] { 0x5F, 0xFF };
        writeCount = 1;
        writeCycle = 250;
    }
}
/******************************串口协议************************************
*1.实时数据查询指令:
*|  字头 | 长度 | 地址 | PC查询控制指令 | 校验 | 字尾  |
*| 0 | 1 |  2   |  3   |        4       |   5  | 6 | 7 |
*| 90| 2F|  06  |  01  | 0B停止/0A开始  |  CHK | 5F| FF|
*
* 90 2F 06 01 0A 0D 5F FF / 90 2F 06 01 0B 0C 5F FF
* 
*2.回送数据:(模拟量高位在前)
*|  字头 | 长度 | 地址 | 指令 | 反馈电流 | X轴角度 | Y轴角度 |  ADC3  |  ADC4  |  ADC5  |  ADC6  |  ADC7  | 标头 |  航向  |  仰角  |  横角  | 标尾 | 针头行程 | 开关量 | 校验 |  字尾  |
*| 0 | 1 |  2   |  3   |  4   |  5 | 6   |  7 |  8 |  9 | 10 | 11| 12 | 13| 14 | 15| 16 | 17| 18 | 19| 20 |  21  | 22| 23 | 24| 25 | 26| 27 |  28  |  29 | 30 |   31   |  32  | 33| 34 |
*| 90| 2F|  21  |  01  |  0C  | XX | XX  | XX | XX | XX | XX | XX| XX | XX| XX | XX| XX | XX| XX | XX| XX |  AA  | XX| XX | XX| XX | XX| XX |  55  |  XX | XX |   XX   |  XX  | 24| FF |
* 
* EB 2F 21 01 0C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 AA 00 00 00 00 00 00 55 00 00 00 D3 24 FF
**************************************************************************/
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Threading;
using UnityEngine;

public class SerialPortController
{
    public SerialPortConfig config;
    private SerialPort serialPort;

    private Thread readThread;
    private Thread writeThread;
    private Thread dealThread;

    private byte[] readBytes;
    private byte[] writeBytes;

    //*********************************************************************
    public float angleX;            //X轴角度
    public float angleY;            //Y轴角度
    public float header;            //标头
    public float heading;           //航向
    public float elevationAngle;    //仰角
    public float rollAngle;         //横角
    public float trailer;           //标尾
    public float needleStroke;      //针头行程
    public int switchingValue = 7;      //开关量

    public float startPoint;          //行程起点
    public float feedbackStartPoint;  //反馈起点
    public float feedbackEndPoint;    //反馈终点

    public float resistance;          //阻力
    //*********************************************************************

    /// <summary>
    /// 从串口读取的字节
    /// </summary>
    public byte[] ReadBytes { get { return readBytes; } }

    /// <summary>
    /// 写入串口的字节
    /// </summary>
    public byte[] WriteBytes
    {
        set
        {
            if (value == null)
                Debug.LogWarning("Value of WriteBytes is null.");
            else if (value.Length != config.writeCount)
                Debug.LogWarning("Value of WriteBytes is not match config length.");
            else
            {
                writeBytes = new byte[config.writeCount];
                value.CopyTo(writeBytes, 0);
            }
        }
        get { return writeBytes; }
    }

    public bool IsOpen { get { return serialPort.IsOpen; } }
    public bool IsReading { get { return readThread.IsAlive; } }
    public bool IsWriting { get { return writeThread.IsAlive; } }
    public bool IsDealing { get { return dealThread.IsAlive; } }
    public bool IsReadTimeout { protected set; get; }
    public bool IsWriteTimeout { protected set; get; }

    #region 测试用
    public void Print()
    {
        if (ReadBytesIsEmpty()) return;

        string str = string.Empty;
        for (int i = 0; i < readBytes.Length; i++)
        {
            str += readBytes[i].ToString("X2");
            str += " ";
        }

        Debug.Log(str);
    }
    #endregion

    private void OnDestroy()
    {
        CloseSerialPort();
    }

    /// <summary>
    /// 初始化串口
    /// </summary>
    public void InitializeSerialPort()
    {
        //config = new SerialPortConfig("COM3", 57600, Parity.None, 8, StopBits.One);
        config = new SerialPortConfig();
        serialPort = new SerialPort("\\\\.\\" + config.portName, config.baudRate, config.parity, config.dataBits, config.stopBits)
        {
            ReadBufferSize = config.readBufferSize,
            ReadTimeout = config.readTimeout,
            WriteBufferSize = config.writeBufferSize,
            WriteTimeout = config.writeTimeout
        };

        //初始化读写线程
        readThread = new Thread(ReadBytesFromBuffer);
        writeThread = new Thread(WriteBytesToBuffer);
        dealThread = new Thread(DealBytesFromBuffer);

        //初始化读写字节数组
        readBytes = new byte[config.readCount];
        writeBytes = new byte[config.writeCount];

        Debug.Log("Initialize succeed.");
    }

    /// <summary>
    /// 从serialport缓冲区读取字节
    /// </summary>
    private void ReadBytesFromBuffer()
    {
        byte[] readBuffer = new byte[serialPort.ReadBufferSize];
        int _readCount = 0;
        int index = 0;

        //帧数据长度
        var frameLength = config.readCount;
        List<byte> frameBuffer = new List<byte>();

        while (true)
        {
            try
            {
                _readCount = serialPort.Read(readBuffer, 0, readBuffer.Length);
            }
            catch (TimeoutException te)
            {
                Debug.Log(te.Message);
                ClearReadBytes();
                IsReadTimeout = true;
                Thread.Sleep(config.readCycle);
                continue;
            }
            catch (Exception e)
            {
                Debug.LogError(e.Message);
                readThread.Abort();
                ClearReadBytes();
                IsReadTimeout = false;
                break;
            }

            //清除读取超时的标志
            IsReadTimeout = false;

            //Calculate the last index of double frame bytes to avoid delay.
            //Under normal circumstances, double frame bytes affirm contain a intact frame bytes.
            index = _readCount - 2 * frameLength;
            index = index > 0 ? index : 0;

            //添加过滤器字节到frameBuffer
            for (; index < _readCount; index++)
            {
                frameBuffer.Add(readBuffer[index]);
            }

            //检查frameBuffer是否足够用于帧字节
            while (frameBuffer.Count >= frameLength)
            {
                var tempBuff = frameBuffer.ToArray();
                if (MatchHead(ref tempBuff))
                {
                    int valueLength = Convert.ToUInt16(tempBuff[config.readHead.Length]) + config.readHead.Length;
                    //将完整的字节保存为readBytes
                    if (MatchTail(ref tempBuff, valueLength))
                    {
                        //异或校验
                        int validLength = valueLength - config.readHead.Length - config.readTail.Length - 1;
                        if (CalculateCheckValue(ref tempBuff, config.readHead.Length, validLength) == frameBuffer[valueLength - config.readTail.Length - 1])
                        {
                            readBytes = frameBuffer.GetRange(0, valueLength).ToArray();
                            StartDealData();
                        }
                        else Debug.Log("XOR checks failed");
                    }

                    //删除过时或无效的frame bytes
                    frameBuffer.RemoveRange(0, frameLength);
                    Debug.Log("Discard invalid frame bytes");
                }
                else frameBuffer.RemoveAt(0);
            }
            Thread.Sleep(config.readCycle);
        }
    }

    /// <summary>
    /// 将字节写入serialport缓冲区
    /// </summary>
    private void WriteBytesToBuffer()
    {
        //字头 + 写入长度 + 检验 + 字尾
        int bufLength = config.writeHead.Length + config.writeCount + 1 + config.writeTail.Length;
        byte[] writeBuffer = new byte[bufLength];
        //添加头字节
        for (int h = 0; h < config.writeHead.Length; h++)
            writeBuffer[h] = config.writeHead[h];
        //添加尾字节
        for (int t = 0; t < config.writeTail.Length; t++)
            writeBuffer[bufLength - t - 1] = config.writeTail[config.writeTail.Length - t - 1];
        //添加写入字节
        for (int w = 0; w < config.writeCount; w++)
            writeBuffer[config.writeHead.Length + w] = writeBytes[w];
        do
        {
            //检验位
            writeBuffer[bufLength - config.writeTail.Length - 1] = CalculateCheckValue(ref writeBuffer, config.writeHead.Length, config.writeCount);
            try
            {
                //将writeBuffer写入serialport
                serialPort.Write(writeBuffer, 0, writeBuffer.Length);
            }
            catch (TimeoutException te)
            {
                Debug.Log(te.Message);
                IsWriteTimeout = true;
                Thread.Sleep(config.writeCycle);
                continue;
            }
            catch (Exception e)
            {
                Debug.LogError(e.Message);
                writeThread.Abort();
                IsWriteTimeout = false;
                //break;
            }

            //清除写入超时的标志
            IsWriteTimeout = false;
            //Thread.Sleep(this.writeCycle);
        } while (IsWriteTimeout);
    }

    /// <summary>
    /// 解析读取到的数据
    /// </summary>
    private void DealBytesFromBuffer()
    {
        //字节组拼的三种方法
        //var b = new byte[] { 0xeb, 0x1f };
        //BitConverter.ToInt16(new byte[] { b[1], b[0] }, 0);//默认高位字节在后
        //Convert.ToInt16((b[0].ToString("X2") + b[1].ToString("X2")), 16);
        //Convert.ToUInt16((b[0] << 8) + b[1]);

        while (!ReadBytesIsEmpty())
        {
            if (readBytes.Length == 35)
            {
                angleX = (Convert.ToUInt16((readBytes[7] << 8) + readBytes[8])) / 10f;
                angleY = (Convert.ToUInt16((readBytes[9] << 8) + readBytes[10])) / 10f;
                header = Convert.ToUInt16(readBytes[21]) / 10f;
                heading = (Convert.ToUInt16((readBytes[22] << 8) + readBytes[23])) / 10f;
                elevationAngle = (Convert.ToUInt16((readBytes[24] << 8) + readBytes[25])) / 10f;
                rollAngle = (Convert.ToUInt16((readBytes[26] << 8) + readBytes[27])) / 1000f;
                trailer = Convert.ToUInt16(readBytes[28]) / 10f;
                needleStroke = (Convert.ToUInt16((readBytes[29] << 8) + readBytes[30]));
                switchingValue = Convert.ToUInt16(readBytes[31]);
            }
            else if(readBytes.Length == 8)
            {

            }
            else if (readBytes.Length == 21)
            {
                startPoint = (Convert.ToUInt16((readBytes[5] << 8) + readBytes[6])) / 10f;
                feedbackStartPoint = (Convert.ToUInt16((readBytes[7] << 8) + readBytes[8])) / 10f;
                feedbackEndPoint = (Convert.ToUInt16((readBytes[9] << 8) + readBytes[10])) / 10f;

                resistance = Convert.ToUInt16(readBytes[14]);

                StopRead();
            }
        }
    }

    /// <summary>
    /// 重新启动串口
    /// </summary>
    /// <returns></returns>
    public bool ReInitializeSerialPort()
    {
        if (CloseSerialPort())
        {
            InitializeSerialPort();
            return true;
        }
        else
            return false;
    }

    /// <summary>
    ///打开串口(连接)
    /// </summary>
    /// <returns></returns>
    public bool OpenSerialPort()
    {
        try
        {
            serialPort.Open();
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
            return false;
        }

        Debug.Log("Open SerialPort");
        return true;
    }

    /// <summary>
    /// 关闭串口
    /// </summary>
    /// <returns></returns>
    public bool CloseSerialPort()
    {
        if (IsReading)
        {
            if (!StopRead()) return false;
        }
        if (IsWriting)
        {
            if (!StopWrite()) return false;
        }
        try
        {
            serialPort.Close();
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
            return false;
        }

        Debug.Log("Close SerialPort");
        return true;
    }

    /// <summary>
    /// 启动读取线程,这个方法将尝试打开串口
    /// </summary>
    /// <returns></returns>
    public bool StartRead()
    {
        if (!IsOpen)
        {
            if (!OpenSerialPort()) return false;
        }

        if (!IsReading)
            readThread = new Thread(ReadBytesFromBuffer);
        else
            return false;

        try
        {
            serialPort.DiscardInBuffer();
            readThread.Start();
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
            return false;
        }

        Debug.Log("Start Read");
        return true;
    }

    /// <summary>
    /// 停止读取线程
    /// </summary>
    /// <returns></returns>
    public bool StopRead()
    {
        try
        {
            readThread.Abort();
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
            return false;
        }

        ClearReadBytes();
        IsReadTimeout = false;
        Debug.Log("Stop Read");
        return true;
    }

    /// <summary>
    /// 启动写入线程,这个方法将尝试打开串口
    /// </summary>
    /// <returns></returns>
    public bool StartWrite()
    {
        if (!IsOpen)
        {
            if (!OpenSerialPort()) return false;
        }

        if (!IsWriting)
            writeThread = new Thread(WriteBytesToBuffer);
        else
            return false;

        try
        {
            serialPort.DiscardOutBuffer();
            writeThread.Start();
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
            return false;
        }

        Debug.Log("Start Write");
        return true;
    }

    /// <summary>
    /// 停止写入线程
    /// </summary>
    /// <returns></returns>
    public bool StopWrite()
    {
        try
        {
            writeThread.Abort();
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
            return false;
        }

        IsWriteTimeout = false;
        Debug.Log("Stop Write");
        return true;
    }

    /// <summary>
    /// 启动处理数据线程,这个方法将尝试打开串口
    /// </summary>
    /// <returns></returns>
    public bool StartDealData()
    {
        if (!IsOpen)
        {
            if (!OpenSerialPort()) return false;
        }

        if (!IsDealing)
            dealThread = new Thread(DealBytesFromBuffer);
        else
            return false;

        try
        {
            dealThread.Start();
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
            return false;
        }

        Debug.Log("Start DealData");
        return true;
    }

    /// <summary>
    /// 停止处理数据线程
    /// </summary>
    /// <returns></returns>
    public bool StopDealData()
    {
        try
        {
            dealThread.Abort();
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
            return false;
        }

        Debug.Log("Stop DealData");
        return true;
    }

    /// <summary>
    /// 将ReadBytes的元素清除为默认值(0)
    /// </summary>
    private void ClearReadBytes()
    {
        Array.Clear(readBytes, 0, readBytes.Length);
    }

    private bool ReadBytesIsEmpty()
    {
        for(int i = 0;i< readBytes.Length;i++)
        {
            if (readBytes[i] != 0x00)
                return false;
        }

        return true;
    }

    /// <summary>
    /// 将WriteBytes的元素清除为默认值(0)
    /// </summary>
    private void ClearWriteBytes()
    {
        Array.Clear(writeBytes, 0, writeBytes.Length);
    }

    /// <summary>
    /// 匹配字头
    /// </summary>
    /// <param name="array"></param>
    /// <returns></returns>
    private bool MatchHead(ref byte[] array)
    {
        for (int i = 0; i < config.readHead.Length; i++)
        {
            if (array[i] != config.readHead[i])
                return false;
        }
        return true;
    }

    /// <summary>
    /// 匹配字尾
    /// </summary>
    /// <param name="array"></param>
    /// <returns></returns>
    private bool MatchTail(ref byte[] array, int readCount)
    {
        for (int i = 1; i <= config.readTail.Length; i++)
        {
            if (array[readCount - i] != config.readTail[config.readTail.Length - i])
                return false;
        }
        return true;
    }

    /// <summary>
    /// 计算校验码(异或)
    /// </summary>
    /// <param name="array"></param>
    /// <param name="start"></param>
    /// <param name="length"></param>
    /// <returns></returns>
    private byte CalculateCheckValue(ref byte[] array, int start, int length)
    {
        if (start < 0 || start >= array.Length) return 0x00;
        if (length <= 1) return array[start];

        byte chk = (byte)(array[start] ^ array[start + 1]);
        for (int i = 0; i < length - 2; i++)
        {
            chk ^= array[start + i + 2];
        }
        return chk;
    }
}
using UnityEngine;

public class SerialPortProxy : MonoBehaviour
{
    public static SerialPortProxy Instance { get; set; }
    private void Awake()
    {
        serialPort = new SerialPortController();
        serialPort.InitializeSerialPort();

        Instance = this;
    }

    public SerialPortController serialPort;

    /// <summary>
    /// X轴角度
    /// </summary>
    public float angleX { get { return serialPort.angleX; } }
    /// <summary>
    /// Y轴角度
    /// </summary>
    public float angleY { get { return serialPort.angleY; } }
    /// <summary>
    /// 标头
    /// </summary>
    public float header { get { return serialPort.header; } }
    /// <summary>
    /// 航向
    /// </summary>
    public float heading { get { return serialPort.heading; } }
    /// <summary>
    /// 仰角
    /// </summary>
    public float elevationAngle { get { return serialPort.elevationAngle; } }
    /// <summary>
    /// 横角
    /// </summary>
    public float rollAngle { get { return serialPort.rollAngle; } }
    /// <summary>
    /// 标尾
    /// </summary>
    public float trailer { get { return serialPort.trailer; } }
    /// <summary>
    /// 针头行程
    /// </summary>
    public float needleStroke { get { return serialPort.needleStroke; } }
    /// <summary>
    /// 1 针头开关
    /// 2 触诊按键
    /// 3 穿透标志
    /// </summary>
    /// <param name="pos">位</param>
    /// <returns></returns>
    public bool SwitchingValue(int pos)
    {
        return (serialPort.switchingValue >> (pos - 1) & 0x01) == 1;
    }

    /// <summary>
    /// 行程起点
    /// </summary>
    public float startPoint { get { return serialPort.startPoint; } }
    /// <summary>
    /// 反馈起点
    /// </summary>
    public float feedbackStartPoint { get { return serialPort.feedbackStartPoint; } }
    /// <summary>
    /// 反馈终点
    /// </summary>
    public float feedbackEndPoint { get { return serialPort.feedbackEndPoint; } }

    /// <summary>
    /// 阻力
    /// </summary>
    public float Resistance { get { return serialPort.resistance; } }

    private bool startRead = false;
    private void Update()
    {
        if (startRead) serialPort.StartRead();
    }

    /// <summary>
    /// 开启指令
    /// </summary>
    public void OpenHardware()
    {
        serialPort.config.readCount = 35;
        serialPort.config.readTail = new byte[] { 0x24, 0xFF };
        byte[] WriteBytes = new byte[] { 0x06, 0x01, 0x0A };
        serialPort.config.writeCount = WriteBytes.Length;
        serialPort.WriteBytes = WriteBytes;

        serialPort.StartWrite();
        serialPort.StartRead();

        startRead = true;
    }

    /// <summary>
    /// 关闭指令
    /// </summary>
    public void CloseHardware()
    {
        byte[] WriteBytes = new byte[] { 0x06, 0x01, 0x0B };
        serialPort.config.writeCount = WriteBytes.Length;
        serialPort.WriteBytes = WriteBytes;
        serialPort.StartWrite();
        serialPort.StopRead();

        startRead = false;
    }

    /// <summary>
    /// 参数查询
    /// </summary>
    public void ParameterQuery()
    {
        serialPort.config.readCount = 21;
        serialPort.config.readTail = new byte[] { 0x24, 0xFF };
        byte[] WriteBytes = new byte[] { 0x06, 0x01, 0x0F };
        serialPort.config.writeCount = WriteBytes.Length;
        serialPort.WriteBytes = WriteBytes;

        serialPort.StartWrite();
        serialPort.StartRead();

        startRead = false;
    }

    /// <summary>
    /// 功能设置
    /// </summary>
    /// <param name="WriteBytes"></param>
    public void FunctionSetting(byte[] WriteBytes)
    {
        serialPort.config.readCount = 8;
        serialPort.config.readTail = new byte[] { 0x24, 0x2F };
        serialPort.config.writeCount = WriteBytes.Length;
        serialPort.WriteBytes = WriteBytes;

        serialPort.StartWrite();
        serialPort.StartRead();

        startRead = false;
    }
}

参考项目地址:https://gitee.com/Mogoson/mgs-serialport