Unity串口通信学习过程记录

发布于:2024-04-04 ⋅ 阅读:(128) ⋅ 点赞:(0)

需要准备:虚拟串口软件(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;
    }

}