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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 12:58:20