使用C#和Unity进行串口编程
在对串口进行编程时候,我们要向串口发送指令,然后我们解析串口返回的指令。从.NET Framework 2.0开始,C#提供了SerialPort类用于实现串口控制。命名空间:System.IO.Ports。详细信息可以参看(MSDN技术文档)
1、 常用的字段:
PortName:获取或设置通信端口
BaudRate:获取或设置串行波特率
DataBits:获取或设置每个字节的标准数据位长度
Parity:获取或设置奇偶效验检查协议
StopBits:获取或设置每个字节的标准停止位数
2、 常用方法:
Close:关闭端口连接,将IsOpen属性设置false,并释放内部Stream对象
GetPortNames:获取当前计算机的串行端口名称数组
Open:打开一个新的串行端口连接
Read:从SerialPort输入缓冲区中读取
Write:将数据写入串行端口输出缓冲区
3、常用事件:
DataReceived:表示将处理SerialPort对象的数据接收事件的方法
DisPosed:通过调用释放组件时发生Dispose方法(继承自Component)
4、SerialPort 控件使用流程
流程是设置通信端口号及波特率、数据位、停止位和校验位,再打开端口连接、发送数据、接收数据,最后关闭端口连接步骤。
首先Unity是支持串口通信的,只不过Unity采用的是Mono .NET 2.0。之前版本对COM支持不是很好,所以导致Unity在串口通信方面有些问题。
小编用的版本是2018.4.0
首先想使用Unity开发串口通信,必须要做的 一点就是 要使用Mono.NET 2.0/4.0/其他
或者
代码案例部分
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO.Ports;
using System.Threading;
using System;
using System.Text;
/// <summary>
/// 串口通信
/// </summary>
public class SerialPortControl
{
public static string portName;//串口号
public static int baudRate;//波特率
public static Parity parity;//校验位
public static int dataBit;//数据位
public static StopBits stopBit;//停止位
static SerialPort sp = new SerialPort();
/// <summary>
/// 串口号
/// </summary>
/// <param name="portname"></param>
/// <returns></returns>
public static string PortName(string portname)
{
portName = portname;
return portName;
}
/// <summary>
/// 波特率
/// </summary>
/// <param name="baud"></param>
/// <returns></returns>
public static int BauRate(int baud)
{
baudRate = baud;
return baudRate;
}
/// <summary>
/// 校验位
/// </summary>
/// <param name="paritys"></param>
/// <returns></returns>
public static Parity Paritys(Parity paritys)
{
parity = paritys;
return parity;
}
/// <summary>
/// 数据位
/// </summary>
/// <param name="dataBits"></param>
/// <returns></returns>
public static int DataBits(int dataBits)
{
dataBit = dataBits;
return dataBit;
}
/// <summary>
/// 停止位
/// </summary>
/// <param name="stopBits"></param>
/// <returns></returns>
public static StopBits StopBitss(StopBits stopBits)
{
stopBit = stopBits;
return stopBit;
}
/// <summary>
/// 字节流转字符串
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static string BytesTohexString(byte[] bytes)
{
if (bytes == null || bytes.Length < 1)
{
return string.Empty;
}
var count = bytes.Length;
var cache = new StringBuilder();
cache.Append("0x");
for (int ii = 0; ii < count; ++ii)
{
var tempHex = Convert.ToString(bytes[ii], 16).ToUpper();
cache.Append(tempHex.Length == 1 ? "0" + tempHex : tempHex);
}
return cache.ToString();
}
/// <summary>
/// 字符串转字节流
/// </summary>
/// <param name="hexStr"></param>
/// <returns></returns>
public static byte[] HexStringToBytes(string hexStr)
{
if (string.IsNullOrEmpty(hexStr))
{
return new byte[0];
}
if (hexStr.StartsWith("0x"))
{
hexStr = hexStr.Remove(0, 2);
}
var count = hexStr.Length;
var byteCount = count / 2;
var result = new byte[byteCount];
for (int ii = 0; ii < byteCount; ++ii)
{
var tempBytes = Byte.Parse(hexStr.Substring(2 * ii, 2), System.Globalization.NumberStyles.HexNumber);
result[ii] = tempBytes;
}
return result;
}
//字符串转字节流 推荐======
public static byte[] Convert16(string strText)
{
strText = strText.Replace(" ", "");
byte[] bText = new byte[strText.Length / 2];
for (int i = 0; i < strText.Length / 2; i++)
{
bText[i] = Convert.ToByte(Convert.ToInt32(strText.Substring(i * 2, 2), 16));
}
return bText;
}
/// <summary>
/// 打开端口,连接串口
/// </summary>
public static void OpenPort()
{
sp = new SerialPort(portName, baudRate, parity, dataBit, stopBit);
sp.ReadTimeout = 1000;
try
{
sp.Open();
Debug.Log("打开端口成功");
}
catch (Exception e)
{
Debug.Log(e.Message);
}
}
/// <summary>
/// 关闭端口
/// </summary>
public static void ClosePort()
{
try
{
sp.Close();
sp.Dispose();
Debug.Log("关闭端口");
}
catch (Exception e)
{
Debug.Log(e.Message);
}
}
byte[] buffer = new byte[5];
int bytes = 0;
/// <summary>
/// 交互:接收端口数据
/// </summary>
public void DataReceiveFun()
{
while (true)
{
if (sp != null && sp.IsOpen)
{
try
{
bytes = sp.Read(buffer, 0, buffer.Length);
sp.DiscardOutBuffer();
if (bytes == 0)
{
continue;
}
else
{
for (int i = 0; i < buffer.Length; i++)
{
Debug.Log(buffer[i].ToString("x8"));
}
}
}
catch (Exception e)
{
Debug.Log(e);
}
}
Thread.Sleep(500);
}
}
/// <summary>
/// 交互:发送数据
/// </summary>
/// <param name="dataStr"></param>
public static void SendData(byte[] dataStr)
{
sp.Write(dataStr, 0, dataStr.Length);
Debug.LogWarning("发送成功");
}
}
一些其他便捷功能:
//十进制转二进制
Console.WriteLine(Convert.ToString(69, 2));
//十进制转八进制
Console.WriteLine(Convert.ToString(69, 8));
//十进制转十六进制
Console.WriteLine(Convert.ToString(69, 16));
//二进制转十进制
Console.WriteLine(Convert.ToInt32("100111101", 2));
//八进制转十进制
Console.WriteLine(Convert.ToInt32("76", 8));
//16进制转换10进制
Console.WriteLine(Convert.ToInt32("FF", 16));
/// <summary>
/// 字符串转字节流
/// </summary>
/// <param name="hexStr"></param>
/// <returns></returns>
public static byte[] HexStringToBytes(string hexStr)
{
if (string.IsNullOrEmpty(hexStr))
{
return new byte[0];
}
if (hexStr.StartsWith("0x"))
{
hexStr = hexStr.Remove(0, 2);
}
var count = hexStr.Length;
var byteCount = count / 2;
var result = new byte[byteCount];
for (int ii = 0; ii < byteCount; ++ii)
{
var tempBytes = Byte.Parse(hexStr.Substring(2 * ii, 2), System.Globalization.NumberStyles.HexNumber);
result[ii] = tempBytes;
}
return result;
}
//第二种 常用转换方式 推荐
public static byte[] Convert16(string strText)
{
strText = strText.Replace(" ", "");
byte[] bText = new byte[strText.Length / 2];
for (int i = 0; i < strText.Length / 2; i++)
{
bText[i] = Convert.ToByte(Convert.ToInt32(strText.Substring(i * 2, 2), 16));
}
return bText;
}
Mono版本
using UnityEngine;
using System.Collections;
using System.IO.Ports;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Text;
public class PortControl : MonoBehaviour
{
#region 定义串口属性
public GUIText gui;
//public GUIText Test;
//定义基本信息
public string portName = "COM3";//串口名
public int baudRate = 9600;//波特率
public Parity parity = Parity.None;//效验位
public int dataBits = 8;//数据位
public StopBits stopBits = StopBits.One;//停止位
SerialPort sp = null;
Thread dataReceiveThread;
//发送的消息
string message = "";
public List<byte> listReceive = new List<byte>();
char[] strchar = new char[100];//接收的字符信息转换为字符数组信息
string str;
#endregion
void Start()
{
OpenPort();
dataReceiveThread = new Thread(new ThreadStart(DataReceiveFunction));
dataReceiveThread.Start();
}
void Update()
{
}
#region 创建串口,并打开串口
public void OpenPort()
{
//创建串口
sp = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
sp.ReadTimeout = 400;
try
{
sp.Open();
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
}
#endregion
#region 程序退出时关闭串口
void OnApplicationQuit()
{
ClosePort();
}
public void ClosePort()
{
try
{
sp.Close();
dataReceiveThread.Abort();
}
catch (Exception ex)
{
Debug.Log(ex.Message);
}
}
#endregion
/// <summary>
/// 打印接收的信息
/// </summary>
void PrintData()
{
for (int i = 0; i < listReceive.Count; i++)
{
strchar[i] = (char)(listReceive[i]);
str = new string(strchar);
}
Debug.Log(str);
}
#region 接收数据
void DataReceiveFunction()
{
#region 按单个字节发送处理信息,不能接收中文
//while (sp != null && sp.IsOpen)
//{
// Thread.Sleep(1);
// try
// {
// byte addr = Convert.ToByte(sp.ReadByte());
// sp.DiscardInBuffer();
// listReceive.Add(addr);
// PrintData();
// }
// catch
// {
// //listReceive.Clear();
// }
//}
#endregion
#region 按字节数组发送处理信息,信息缺失
byte[] buffer = new byte[1024];
int bytes = 0;
while (true)
{
if (sp != null && sp.IsOpen)
{
try
{
bytes = sp.Read(buffer, 0, buffer.Length);//接收字节
if (bytes == 0)
{
continue;
}
else
{
string strbytes = Encoding.Default.GetString(buffer);
Debug.Log(strbytes);
}
}
catch (Exception ex)
{
if (ex.GetType() != typeof(ThreadAbortException))
{
}
}
}
Thread.Sleep(10);
}
#endregion
}
#endregion
#region 发送数据
public void WriteData(string dataStr)
{
if (sp.IsOpen)
{
sp.Write(dataStr);
}
}
void OnGUI()
{
message = GUILayout.TextField(message);
if (GUILayout.Button("Send Input"))
{
WriteData(message);
}
string test = "AA BB 01 12345 01AB 0@ab 发送";//测试字符串
if (GUILayout.Button("Send Test"))
{
WriteData(test);
}
}
#endregion
}
一些问题的解决方案
1、没有将Unity3D的API平台切换成.NET2.0,这时Unity编写SerialPort类会报错。
解决方法:将Unity3D的API平台切换成.NET2.0,切换方法: “Edit–>project Setting–>Player–>Other Setting –>Api Compatibility level”。在这里将“.NET2.0 Subset”切换为“.NET2.0”
2、Unity的目标平台没有切换为Windows平台,会提示该命名空间不支持SystemIO,提示你切换工具。
解决方法:把目标平台切换为Windows平台,否则是其他平台会报错误。
3、串口发送的信息不能正常解析
解决方法:把串口发送的消息使用字节流进行转换。(字符流转换)
4、串口接收信息的缺失问题
(1)接收字符串(string):port.ReadLine()
数据接收可能错误,数据丢失,数据接收不到
(2)接收字节数组(byte[]):port.Read()
接收数据断层,会分两次接收完整数据
(3)接收单个字节(byte):port.ReadByte()
将接收到的数据进行组合
5.第一次发送数据的时候,往往会丢失数据尾(和知名大佬总结得出,最后会在文章底部@大佬)
比如发送:64 64 80 89 第一次接收的可能是64 80 89 0
//40405059 暂停
if (paramByte[0] == (byte)64 && paramByte[1] == (byte)64 && paramByte[2] == (byte)80 && paramByte[3] == (byte)89)
{
Debug.Log("AA");
}
else if (paramByte[0] == (byte)64 && paramByte[1] == (byte)80 && paramByte[2] == (byte)89)
{
Debug.Log("AA");
}
通过以上代码解决