Unity串口通信学习过程记录

news/2024/7/24 4:41:12 标签: unity, 学习, 游戏引擎

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

}


http://www.niftyadmin.cn/n/5468564.html

相关文章

vue项目引入微信sdk: npm install weixin-js-sdk --save报错

网上查到要用淘宝的镜像 同事告知旧 域名&#xff1a;https://registry.npm.taobao.org/已经不能再使用 使用 npm config set registry http://registry.npmmirror.com

【Clang+LLVM+honggfuzz学习】(二)honggfuzz的安装与试用

书接上篇【ClangLLVMhonggfuzz学习】&#xff08;一&#xff09;LLVM简介、安装和第一个Hello Pass 本篇介绍honggfuzz的安装与简单使用 本文架构&#xff0c;PS:可选择观看哦 前言git安装试用编写测试文件demo.c设置环境变量开始fuzzFuzz-ing疑问 前言 漏洞检测做毕设&#…

Linux:logrotate日志轮循分割

比如httpd产生的日志&#xff0c;如果你没做任何设置&#xff0c;他会一直把日志都输出到一个文件中&#xff0c;这个文件会越来越大&#xff0c;httpd就有一个日志切割工具&#xff0c;他可以去分割你的日志&#xff0c;但是无法去轮循日志 日志切割的作用&#xff1a;防止文件…

【AHK v2】数据结构LinkedList实现示例

AutoHotkey v2 是一个功能强大的脚本语言&#xff0c;它支持面向对象的编程范式。下面是一个简单的面向对象的链表&#xff08;LinkedList&#xff09;实现示例&#xff0c;使用AutoHotkey v2编写&#xff1a; #Requires AutoHotkey v2.0 ; 定义节点类 class Node {__New(valu…

elementui 左侧或水平导航菜单栏与main区域联动

系列文章目录 一、elementui 导航菜单栏和Breadcrumb 面包屑关联 二、elementui 左侧导航菜单栏与main区域联动 三、elementui 中设置图片的高度并支持PC和手机自适应 四、elementui 实现一个固定位置的Pagination&#xff08;分页&#xff09;组件 文章目录 系列文章目录…

STM32实现软件SPI对W25Q64内存芯片实现读写操作

先看看本次实验的成果吧&#xff1a; 这么简单的一个程序&#xff0c;我学习了一个星期左右&#xff0c;终于把所有的关节都打通了。所有代码都能什么都不看背着敲出来了。为了使自己的记忆更为清晰&#xff0c;特意总结了一个思维导图&#xff0c;感觉自己即便是日后忘记了看一…

编程实战:自己编写HTTP服务器(系列9:上传文件)

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 本系列的源码位于httpd目录下…

rpc的通信流程

rpc能实现调用远程方法就跟调用本地&#xff08;同一个项目中的方法&#xff09;一样&#xff0c;发起调用请求的那一方叫做服务调用方&#xff0c;被调用的一方叫做服务提供方。 接下来就和大家分享一下调用过程的流程和细节。 传输协议 既然是远程调用那肯定就需要通过网络…