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

