新开的项目,公司决定用串口通信,因为以前都是用Socket,串口一点不懂,自学研究了2天,好歹算是把功能实现了。
测试可以先用 VSPD 虚拟串口
然后用 ComMonitor 串口调试工具,测试接受和发送
注意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