You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity读取Arduino串口数据异常 返回0-300随机值问题求助

问题概述
  • 场景:Arduino硬件通过串口与Unity引擎通信
  • 故障表现:Unity侧读取串口仅返回0-300区间随机值,无法获取正确传输数据;相同硬件、接线、波特率配置下,Arduino IDE自带串口监视器可正常读取全部数据,故障仅存在于Unity侧
  • 现有实现:使用System.IO.Ports类操作串口,在MonoBehaviour的Update生命周期中直接调用ReadLine()读取数据,原始代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.IO.Ports;

public class UIcontroler : MonoBehaviour
{
    SerialPort data_stream = new SerialPort("COM3", 9600);
    public string receivedstring;
    public string[] datas;
    public Text text;

    void Start()
    {
        data_stream.Open();
        data_stream.ReadTimeout = 5000;
    }


    void Update()
    {
        receivedstring = data_stream.ReadLine();
        text.text = receivedstring;
    }
}
故障根因
  1. 缺少复位等待逻辑:Arduino在检测到串口连接建立时会自动触发硬件复位,板载bootloader启动需要2-3秒时间,这段时间会向外输出无意义的字节流,数值范围正好落在0-300区间,就是你看到的随机值来源。Arduino IDE串口监视器默认内置了启动等待逻辑,所以不会读到这段乱码。
  2. 串口参数配置不全:仅指定了端口号和波特率,未明确配置校验位、数据位、停止位参数,部分环境下会因为默认参数不匹配出现读值错误。
  3. 读取逻辑阻塞主线程:ReadLine()是阻塞方法,直接放在每帧执行的Update中调用,会和Unity的帧调度、UI渲染产生冲突,既可能造成程序卡顿,也会因为读取时机和数据到达时机不匹配,读到截断的残包、缓冲区残留脏数据。
  4. 缺少资源释放和异常处理逻辑:程序退出时不会主动关闭串口,容易造成端口被持续占用,读取过程中出现超时、传输错误时会直接抛出异常中断程序。
修复方案

替换原有代码为以下实现,核心改动包括:补全串口默认参数、打开串口后增加复位等待时间、清空收发缓冲区脏数据、开启独立后台线程读取串口数据避免阻塞主线程、增加退出时的资源释放逻辑:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.IO.Ports;
using System.Threading;

public class UIcontroler : MonoBehaviour
{
    // 补全串口默认配置:9600波特率、无校验、8数据位、1停止位,匹配Arduino默认串口参数
    SerialPort data_stream = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);
    public string receivedstring;
    public Text text;
    private Thread serialReadThread;
    private bool isPortRunning = false;

    void Start()
    {
        data_stream.Open();
        data_stream.ReadTimeout = 500;
        data_stream.WriteTimeout = 500;
        // 清空打开串口时缓冲区残留的所有脏数据
        data_stream.DiscardInBuffer();
        data_stream.DiscardOutBuffer();
        // 等待Arduino复位完成,避开bootloader输出乱码的阶段
        Thread.Sleep(3000);
        
        isPortRunning = true;
        // 启动独立后台线程读取串口,不阻塞Unity主线程的帧调度和UI渲染
        serialReadThread = new Thread(ReadSerialLoop);
        serialReadThread.IsBackground = true;
        serialReadThread.Start();
    }

    // 后台线程循环读串口逻辑
    void ReadSerialLoop()
    {
        while (isPortRunning && data_stream.IsOpen)
        {
            try
            {
                receivedstring = data_stream.ReadLine().Trim();
            }
            catch (System.TimeoutException)
            {
                // 读超时为正常现象,直接跳过进入下一轮循环即可
                continue;
            }
            catch (System.Exception e)
            {
                Debug.LogError("串口读取异常:" + e.Message);
                break;
            }
        }
    }

    void Update()
    {
        // 主线程仅负责更新UI文本,不执行阻塞的IO操作
        if (!string.IsNullOrEmpty(receivedstring))
        {
            text.text = receivedstring;
        }
    }

    void OnDestroy()
    {
        // 程序退出时安全关闭线程、释放串口资源,避免端口被占用
        isPortRunning = false;
        if (serialReadThread != null && serialReadThread.IsAlive)
        {
            serialReadThread.Join(100);
        }
        if (data_stream != null && data_stream.IsOpen)
        {
            data_stream.Close();
            data_stream.Dispose();
        }
    }
}
注意事项
  • Arduino端发送数据必须使用Serial.println()方法,因为ReadLine()以换行符\n作为单条数据的结束标记,如果用不带换行的Serial.print()发送,会持续阻塞直到超时。
  • 运行Unity前必须完全关闭Arduino IDE的串口监视器、串口绘图器等占用同一COM口的程序,同一时间仅允许一个程序访问串口。
  • 如果使用的是老款Arduino开发板,可适当将复位等待时间从3000毫秒调整为4000-5000毫秒,适配更慢的启动速度。

内容的提问来源于stack exchange,提问作者ICODE

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 03:15:40