Unity与Arduino串口控制双舵机:同帧双指令失效问题排查
问题排查与解决方案
核心问题是串口数据粘包:Unity在同一个Update里连续发送两个指令时,Arduino会把这两段数据当成一个完整字符串读取,导致无法匹配预设的指令(比如把"clockwise"和"anticlockwise joint 2"拼成一个长串,自然匹配不上任何if条件)。另外Arduino原代码的delay(1000)会阻塞接收逻辑,进一步加剧数据堆积问题。
Arduino端修改方案
修改接收逻辑,用换行符作为指令分隔符,同时优化字符串处理和移除不必要的阻塞延迟:
#include <Servo.h> String input; int count = 0; int countSecond = 0; const int rotationAngleStep = 10; Servo servo1; Servo servo2; void setup() { Serial.begin(9600); servo1.attach(13); servo1.write(0); servo2.attach(10); servo2.write(0); } void loop() { if(Serial.available() > 0){ // 读取到换行符为止,确保每次只处理一个完整指令 input = Serial.readStringUntil('\n'); // 去除字符串首尾的空白(包括换行、空格) input.trim(); Serial.print("Received: "); Serial.println(input); if(input == "anticlockwise") { Serial.println("Servo 1: turn anticlockwise"); count = constrain(count + 1, 0, 18); // 限制角度在0-180度(18*10=180) servo1.write(count*rotationAngleStep); } else if(input == "clockwise") { Serial.println("Servo 1: turn clockwise"); count = constrain(count - 1, 0, 18); servo1.write(count*rotationAngleStep); } else if(input == "anticlockwise joint 2") { Serial.println("Servo 2: turn anticlockwise"); countSecond = constrain(countSecond + 1, 0, 18); servo2.write(countSecond*rotationAngleStep); } else if(input == "clockwise joint 2") { Serial.println("Servo 2: turn clockwise"); countSecond = constrain(countSecond - 1, 0, 18); servo2.write(countSecond*rotationAngleStep); } } // 移除原有的1秒延迟,避免阻塞串口接收 }
关键修改点:
- 用
Serial.readStringUntil('\n')替代readString(),确保每次只读取一个完整指令 - 增加
input.trim()处理,消除指令首尾的空白字符(比如Unity发送的换行符、可能的空格) - 用
constrain()限制舵机角度范围,防止超出0-180度的安全范围 - 移除
delay(1000),避免阻塞串口接收逻辑
Unity端修改方案
发送指令时添加换行符,并在两个指令间增加短暂延迟,确保Arduino有足够时间处理第一个指令:
using System.Collections; using System.Collections.Generic; using UnityEngine; using System.IO.Ports; public class IKManager : MonoBehaviour { public SerialPort serial = new SerialPort("COM4",9600); public Joint m_root; public Joint m_end; public GameObject m_target; public float m_threshold = 20f; private int counter = 2; private float timer; void Start(){ if(!serial.IsOpen){ serial.Open(); // 设置串口读取超时,避免阻塞 serial.ReadTimeout = 100; } } float CalculateSlope(Joint _joint) { float deltaTheta = 10f; float distance1 = Vector3.Distance(m_end.transform.position, m_target.transform.position); _joint.transform.Rotate(0f,0f,deltaTheta,Space.World); float distance2 = Vector3.Distance(m_end.transform.position, m_target.transform.position); _joint.transform.Rotate(0f,0f,-deltaTheta,Space.World); return (distance2 - distance1) / deltaTheta; } async void Update() { timer = Time.realtimeSinceStartup; if (timer > 4*counter) { counter++; if (Vector3.Distance(m_end.transform.position, m_target.transform.position) > m_threshold) { Joint current = m_root; float slope1 = CalculateSlope(current); if (slope1 > 0){ current.transform.Rotate(0f,0f,-10f,Space.World); serial.WriteLine("clockwise"); // 用WriteLine自动添加换行符 Debug.Log("clockwise"); }else if(slope1 < 0){ current.transform.Rotate(0f,0f,10f,Space.World); serial.WriteLine("anticlockwise"); Debug.Log("anticlockwise"); } // 等待100ms,给Arduino处理第一个指令的时间 await new WaitForSeconds(0.1f); current = current.GetChild(); float slope2 = CalculateSlope(current); if (slope2 > 0){ current.transform.Rotate(0f,0f,-10f,Space.World); serial.WriteLine("clockwise joint 2"); Debug.Log("clockwise joint 2"); }else if(slope2 < 0){ current.transform.Rotate(0f,0f,10f,Space.World); serial.WriteLine("anticlockwise joint 2"); Debug.Log("anticlockwise joint 2"); } } } } }
关键修改点:
- 用
serial.WriteLine()替代serial.Write(),自动在指令末尾添加换行符,和Arduino的接收逻辑匹配 - 在发送两个指令之间添加
await new WaitForSeconds(0.1f),确保Arduino有时间处理第一个指令后再接收第二个 - 增加
serial.ReadTimeout = 100,避免Unity端串口读取阻塞
额外建议
- 可以在Arduino串口监视器里查看接收到的指令,确认是否正确分割
- 如果需要更高效的指令传输,可以考虑用更简洁的指令格式(比如用单个字符或数字代替长字符串,减少数据量)
内容的提问来源于stack exchange,提问作者seangoulding
相关产品推荐
相关产品推荐

