需要准备:虚拟串口软件(VirtualSerialPortDriver 6.9 by Eltima Software),串口助手软件(by BruceOu),VisualStudio2022,Unity3D2022.1。
串口实现过程:
扫描串口(API,注册表信息,试错方式)>打开串口>发送数据(文本和二进制数据)>接收数据(二进制数据)>处理数据==>关闭串口
继承Mono的泛型单例
using UnityEngine;
public class SingletonAutoMono<T>: MonoBehaviour where T: MonoBehaviour
{
private static T instance;
public static T GetInstance()
{
if(instance == null)
{
GameObject obj = new GameObject();
obj.name = typeof(T).ToString();
DontDestroyOnLoad(obj);
instance = obj.AddComponent<T>();
}
return instance;
}
}
创建一个串口管理器单例(继承Mono)
using System.Collections.Generic;
using System.IO.Ports;
using System.Threading;
using UnityEngine;
using System;
using UnityEngine.Events;
using Microsoft.Win32;
using System.Text;
public class SerialPortMgr : SingletonAutoMono<SerialPortMgr>
{
public SerialPort sp = new SerialPort();
public bool isConnected = false;
public Action<string> OnMsgCallback; // 给外部调用处理数据
public Queue<string> receiveQueue = new Queue<string>(); // 存储其它线程接收到的数据
public bool IsConnected { get => isConnected; }
private void Update()
{
// 检查接收队列 receiveQueue 中是否有待处理的数据
if (receiveQueue.Count > 0)
{
if(OnMsgCallback != null)
{
// 如果队列中有数据,调用 OnMsgCallback 委托来处理队首的数据
OnMsgCallback(receiveQueue.Dequeue());
}
}
}
/// <summary>
/// 使用API扫描
/// </summary>
/// <returns></returns>
public string[] GetPorts_API()
{
string[] portList = SerialPort.GetPortNames();
return portList;
}
/// <summary>
/// 使用注册表信息扫描可用串口
/// </summary>
/// <returns>返回扫描到的串口号数组</returns>
public string[] GetPorts_Regedit()
{
RegistryKey keyCom = Registry.LocalMachine.OpenSubKey("Hardware\\DeviceMap\\SerialComm");
string[] SubKeys = keyCom.GetValueNames();
string[] portList = new string[SubKeys.Length];
for (int i = 0; i < SubKeys.Length; i++)
{
portList[i] = (string)keyCom.GetValue(SubKeys[i]);
}
return portList;
}
/// <summary>
/// 试错方式扫描
/// </summary>
/// <returns>返回已扫描到的串口号数组</returns>
public string[] ScanPorts_TryFail()
{
// 创建一个临时的串口列表,用于存储扫描到的串口号
List<string> tempPost = new List<string>();
// 标记是否扫描到了有效的串口
bool mark = false;
// 循环尝试连接COM1到COM10的串口
for (int i = 0; i < 10; i++)
{
try
{
// 创建一个新的串口实例,尝试打开当前迭代的串口
SerialPort sp = new SerialPort("COM" + (i + 1).ToString());
sp.Open();
sp.Close(); // 关闭串口释放资源
// 将成功连接的串口号添加到临时列表中
tempPost.Add("COM" + (i + 1).ToString());
// 标记已经扫描到了有效的串口
mark = true;
}
catch(Exception)
{
continue; // 如果连接失败,则跳过当前迭代,继续尝试下一个串口
}
}
// 判断是否扫描到了有效的串口
if (mark)
{
// 如果扫描到了有效的串口,则将临时列表转换为数组并返回
string[] portList = tempPost.ToArray();
return portList;
}
else
{
// 如果未扫描到有效的串口,则返回null
return null;
}
}
/// <summary>
/// 打开串口
/// </summary>
/// <param name="portName">端口号</param>
/// <param name="baudRate">波特率</param>
/// <param name="parity">校验位</param>
/// <param name="dataBits">数据位</param>
/// <param name="stopBits">停止位</param>
public void OpenSerialPort(string portName,int baudRate,
Parity parity,int dataBits,StopBits stopBits,
UnityAction<bool> connectCallback = null,
UnityAction<string> tipCallback = null)
{
// 避免多次打开
if (isConnected) return;
// 绑定端口
// if(sp == null)
sp = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
string tip = "";
try
{
if(!sp.IsOpen)
{
tip = "打开串口" + portName;
sp.Open();
isConnected = true;
//开启接收数据
ThreadPool.QueueUserWorkItem(ReceivedMsg);
}
}
catch (Exception e)
{
tip = "串口打开失败:" + e.Message;
}
Debug.Log(tip);
connectCallback?.Invoke(isConnected);
tipCallback?.Invoke(tip);
}
/// <summary>
/// 接收数据
/// </summary>
/// <param name="obj"></param>
void ReceivedMsg(object obj) // 运行在一个单独的线程中,通过串口异步地接收数据
{
while(isConnected)
{
if(sp.IsOpen)
{
int count = sp.BytesToRead; // 获取串口接收缓冲区中的字节数
if (count > 0) // 判断接收缓冲区中是否有数据
{
// 创建一个字节数组用于存储从串口接收到的数据
byte[] receiveBytes = new byte[count];
try // 进行一次数据的接收和存储操作
{
// 从串口中读取数据,并将数据存储到 receiveBytes 数组中
sp.Read(receiveBytes, 0, count);
// 将接收到的字节数组转换为字符串,并将字符串添加到 receiveQueue 队列
receiveQueue.Enqueue(Encoding.UTF8.GetString(receiveBytes));
}
catch(Exception e)
{
Debug.Log("数据接收失败:" + e.Message);
}
}
}
Thread.Sleep(10); // 暂停当前线程10毫秒以免过于频繁地检查接收缓冲区
}
}
/// <summary>
/// 发送string数据
/// </summary>
/// <param name="info">string数据</param>
public void SendData(string info,UnityAction<string> tipcallback = null)
{
string tip = "";
try
{
if(sp.IsOpen)
{
sp.WriteLine(info);
}
else
{
tip = "请打开串口";
}
}catch(Exception e)
{
tip = "数据发送失败:" + e.Message;
}
tipcallback?.Invoke(tip);
}
/// <summary>
/// 发送二进制数据
/// </summary>
/// <param name="buffer">byte数据</param>
/// <param name="offset">起始位</param>
/// <param name="count">byte长度</param>
public void SendData(byte[] buffer,int offset,int count)
{
try
{
if(sp.IsOpen)
{
sp.Write(buffer, offset, count);
}else
{
sp.Open();
sp.Write(buffer, offset, count);
}
}
catch(Exception e)
{
Debug.Log("数据发送失败:" + e.Message);
}
}
/// <summary>
/// 关闭串口
/// </summary>
/// <param name="tipCallback"></param>
public void Close(UnityAction<string> tipCallback = null)
{
string tip = "";
try
{
if(sp != null)
{
sp.Close();
tip = "主动断开连接";
sp = null;
isConnected = false;
}
}
catch(Exception e)
{
tip = "关闭串口:" + e.Message;
}
Debug.Log(tip);
tipCallback?.Invoke(tip);
}
private void OnDestroy()
{
Debug.Log("关闭串口");
Close();
}
}
buttonMgr
using System.Collections;
using System.Collections.Generic;
using UnityEditor.VersionControl;
using UnityEngine;
using UnityEngine.Events;
using System.IO.Ports;
using System.Threading;
using UnityEngine.UI;
public class buttonMgr : MonoBehaviour
{
[Header("测试用的UI")]
public Button OpenSerialButton;
public Button SendButton;
public InputField InputData;
public Text ReceiveMessage;
public Text Message;
[Header("端口数据")]
//端口号
public string PortName;
//波特率
public int BaudRate;
//校验位
public Parity Parity;
//数据位
public int DataBits;
//停止位
public StopBits StopBits;
private void Start()
{
// 打印出所有端口
//string currentPortsList = "";
//for (int i = 0; i < SerialPortMgr.GetInstance().GetPorts_API().Length; i++)
//{
// currentPortsList += SerialPortMgr.GetInstance().GetPorts_API()[i].ToString() + " | ";
//}
//Debug.Log(currentPortsList);
//打开串口按钮
OpenSerialButton.onClick.AddListener(() =>
{
if (SerialPortMgr.GetInstance().IsConnected)
{
// 关闭串口
SerialPortMgr.GetInstance().Close((tip) =>
{
Message.text = tip;
});
OpenSerialButton.GetComponentInChildren<Text>().text = "关闭串口";
}
else
{
string[] portArray = SerialPortMgr.GetInstance().ScanPorts_TryFail();
PortName = portArray[0];
// 打开串口
SerialPortMgr.GetInstance().OpenSerialPort(PortName, BaudRate, Parity, DataBits, StopBits,
ConnectState, Tip);
}
});
//接收到消息回调
SerialPortMgr.GetInstance().OnMsgCallback = OnMsgHandle;
//发送信号
SendButton.onClick.AddListener(() => {
SerialPortMgr.GetInstance().SendData(InputData.text, (tip) =>
{
Message.text = tip;
});
InputData.text = "";
});
}
/// <summary>
/// 连接反馈
/// </summary>
private void ConnectState(bool isConnected)
{
if (isConnected)
{
OpenSerialButton.GetComponentInChildren<Text>().text = "打开串口";
}
else
{
OpenSerialButton.GetComponentInChildren<Text>().text = "关闭串口";
}
}
/// <summary>
/// 串口消息
/// </summary>
private void Tip(string tip)
{
Message.text = tip;
}
/// <summary>
/// 消息处理
/// </summary>
private void OnMsgHandle(string msg)
{
Debug.Log("接收到消息:" + msg);
ReceiveMessage.text += "\t" + msg;
}
}