Unity串口通信、接受和发送数据、C#
1、串口简介
串行接口(串口)通常指COM接口,是采用串行通信方式的扩展接口。串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。特别适用于远距离通信。
查看串口:右键 我的电脑-管理-设备管理器-端口
选择一个端口,双击查看属性。
这里通过串口属性,可以知道以下数据:
波特率:这是一个衡量符号传输速率的参数。
数据位:这是衡量通信中实际数据位的参数。
停止位:用于表示单个包的最后一位。
奇偶校验:在串口通信中一种简单的检错方式。
对于两个进行通信的端口,这些参数必须匹配。
3、串口通信原理:
串行接口在嵌入式系统中是一种重要的数据通信接口,其本质功能是作为CPU和串行设备间的编码转换器。在发送数据时,数据从CPU经串行端口,字节数据转换为串行的位;在接收数据时,串行的位转换为字节数据。
4、常用的协议:
RS-232: 标准串口,最常用的一种串行通讯接口采取不平衡传输方式,即所谓单端通讯, 是为点对点通讯而设计的。
RS-422: 支持点对多的双向通信。采用单独的发送和接收通道,因此不必控制数据方向,各装置之间任何必须的信号交换均可以按软件方式(XON/XOFF握手)或硬件方式(一对单独的双绞线)实现。
RS-485: 从RS-422基础上发展而来的, RS-485可以采用二线与四线方式,二线制可实现真正的多点双向通信,而采用四线连接时,与RS-422一样只能实现点对多的通信,但它比RS-422有改进,无论四线还是二线连接方式总线上可多接到32个设备。
串口的接口标准规范9针串口:
针脚功能:
1、数据载波检测(DCD)
2、串口接收数据(RXD)
3、串口发出数据(TXD)
4、数据终端就绪(DTR)
5、信号地线(GND)
6、数据发送就绪(DSR)
7、发送数据请求(RTS)
8、清除发送(CTS)
9、铃声指示(RI)
我在现在用的是CH340芯片进行传输
二、使用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/其他
如下图:
或 .net framework
不修改的话是不能进行串口开发的 。
例子一:
修改完之后下面是代码环节,简单通俗易懂
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;#endregionvoid 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");}
通过以上代码,可以完美避开这个坑,第二次就会正常。
每台电脑的串口都是不一样的,比如你从A电脑,插上去设备管理器显示的是COM 2,你程序中定义的是COM2,这样可以实现通讯。
如果你换了台电脑B,但是现在B电脑串口是COM10,你代码中还是COM2,就没办法完成通讯了。
解决方案一:通过读取TXT文档,来设置COM口。
public static string portName = "";//串口名称
void Start(){string path = Application.streamingAssetsPath + "/config.txt";string[] strs = File.ReadAllLines(path);if (strs.Length > 0) portName = strs[0];Debug.Log(strs.Length);}
解决方案二:通过读取电脑注册表,来动态设置COM口。
public string getPortName = "";//串口名称
void Start(){List<string> protReg = GetPortFromReg();if (protReg.Count > 0){getPortName = protReg[2];//0是默认第一接口.}else{Debug.Log("获取不到端口");}Debug.Log("当前COM口配置为:"+getPortName);}
这样就可以动态获取COM口,但是此方法多少还是有局限性,想要准确无误,还是要用解决一方法(通过TXT文本配置)
三步教会你 Unity串口通讯_unity串口通信__橙子先生的博客-CSDN博客
Unity串口通信、接受和发送数据、C#_unity 串口接收_与火星的孩子对话的博客-CSDN博客