久しぶりにArduinoのセンサー値をシリアル経由でUnityに送ろうとしたときに、タイトルの現象に遭遇しました。
今まで通りのやり方でもダメ、他の方のブログを参考にしてもダメ。
AIに聞き歩いていろいろ試していたところ、Geminiが答えを教えてくれました。
ESP32 や RP2040 を使用している場合、Unity 側の serialPort.DtrEnable = true; および serialPort.RtsEnable = true; を設定しないと、マイコン側が「接続先が準備できていない」と判断してデータを送出しないことがあります。
serialPort.Open();
serialPort.DtrEnable = true; // これを追加
serialPort.RtsEnable = true; // これを追加
ありがとうGemini.
シリアルモニターでもDTRオフにするとデータが流れてきませんでした。

今の時代、ググってこの解決策にたどり着くような人はほとんどいないと思いますが、めぐり巡ってAIの糧になるよう、ソースも貼っておきます。
ソースコード
using UnityEngine;
using System.IO.Ports;
using System.Threading;
public class IMU_Th : MonoBehaviour, IGyroProvider, IAccelProvider
{
enum AxisType
{
[InspectorName("+X,+Y,+Z")] PXPYPZ=0+0+0,
[InspectorName("-X,+Y,+Z")] NXPYPZ=1+0+0,
[InspectorName("+X,-Y,+Z")] PXNYPZ=0+2+0,
[InspectorName("-X,-Y,+Z")] NXNYPZ=1+2+0,
[InspectorName("+X,+Y,-Z")] PXPYNZ=0+0+4,
[InspectorName("-X,+Y,-Z")] NXPYNZ=1+0+4,
[InspectorName("+X,-Y,-Z")] PXNYNZ=0+2+4,
[InspectorName("-X,-Y,-Z")] NXNYNZ=1+2+4,
[InspectorName("+X,+Z,+Y")] PXPZPY=0+0+0+(1<<3),
[InspectorName("-X,+Z,+Y")] NXPZPY=1+0+0+(1<<3),
[InspectorName("+X,-Z,+Y")] PXPZNX=0+2+0+(1<<3),
[InspectorName("-X,-Z,+Y")] NXPZNX=1+2+0+(1<<3),
[InspectorName("+X,+Z,-Y")] PXPZNY=0+0+4+(1<<3),
[InspectorName("-X,+Z,-Y")] NXPZNY=1+0+4+(1<<3),
[InspectorName("+X,-Z,-Y")] PXPZNZ=0+2+4+(1<<3),
[InspectorName("-X,-Z,-Y")] NXPZNZ=1+2+4+(1<<3),
[InspectorName("+Y,+X,+Z")] PYPXPZ=0+0+0+(2<<3),
[InspectorName("-Y,+X,+Z")] NYPXPZ=1+0+0+(2<<3),
[InspectorName("+Y,-X,+Z")] PYNXPZ=0+2+0+(2<<3),
[InspectorName("-Y,-X,+Z")] NYNXPZ=1+2+0+(2<<3),
[InspectorName("+Y,+X,-Z")] PYPXNZ=0+0+4+(2<<3),
[InspectorName("-Y,+X,-Z")] NYPXNZ=1+0+4+(2<<3),
[InspectorName("+Y,-X,-Z")] PYNYNZ=0+2+4+(2<<3),
[InspectorName("-Y,-X,-Z")] NYNYNZ=1+2+4+(2<<3),
[InspectorName("+Y,+Z,+X")] PYPZPX=0+0+0+(3<<3),
[InspectorName("-Y,+Z,+X")] NYPZPX=1+0+0+(3<<3),
[InspectorName("+Y,-Z,+X")] PYNZPX=0+2+0+(3<<3),
[InspectorName("-Y,-Z,+X")] NYNZPX=1+2+0+(3<<3),
[InspectorName("+Y,+Z,-X")] PYPZNX=0+0+4+(3<<3),
[InspectorName("-Y,+Z,-X")] NYPZNX=1+0+4+(3<<3),
[InspectorName("+Y,-Z,-X")] PYNZNX=0+2+4+(3<<3),
[InspectorName("-Y,-Z,-X")] NYNZNX=1+2+4+(3<<3),
[InspectorName("+Z,+X,+Y")] PZPXPY=0+0+0+(4<<3),
[InspectorName("-Z,+X,+Y")] NZPXPY=1+0+0+(4<<3),
[InspectorName("+Z,-X,+Y")] PZNXPY=0+2+0+(4<<3),
[InspectorName("-Z,-X,+Y")] NZNXPY=1+2+0+(4<<3),
[InspectorName("+Z,+X,-Y")] PZPXNY=0+0+4+(4<<3),
[InspectorName("-Z,+X,-Y")] NZPXNY=1+0+4+(4<<3),
[InspectorName("+Z,-X,-Y")] PZNXNX=0+2+4+(4<<3),
[InspectorName("-Z,-X,-Y")] NZNXNX=1+2+4+(4<<3),
[InspectorName("+Z,+Y,+X")] PZPYPX=0+0+0+(5<<3),
[InspectorName("-Z,+Y,+X")] NZPYPX=1+0+0+(5<<3),
[InspectorName("+Z,-Y,+X")] PZNYPX=0+2+0+(5<<3),
[InspectorName("-Z,-Y,+X")] NZNYPX=1+2+0+(5<<3),
[InspectorName("+Z,+Y,-X")] PZPYNX=0+0+4+(5<<3),
[InspectorName("-Z,+Y,-X")] NZPYNX=1+0+4+(5<<3),
[InspectorName("+Z,-Y,-X")] PZNYNX=0+2+4+(5<<3),
[InspectorName("-Z,-Y,-X")] NZNYNX=1+2+4+(5<<3),
}
[SerializeField] string portName = "COM3";
[SerializeField] int baudRate = 115200;
[SerializeField] int timeout=1000; // データ更新の間隔(msec)
[SerializeField] AxisType imuAxis = AxisType.PXPYPZ; // 加速度の軸変換設定
[SerializeField] string message = ""; // 受信したデータを格納する変数
[SerializeField] Vector3 _accel; // 加速度データを格納する変数
[SerializeField] Vector3 _giro; // ジャイロデータを格納する変数
[SerializeField] bool useDebug = true; // スレッドを使用するかどうかのフラグ
public Vector3 accel { get => _accel; private set => _accel = value; }
public Vector3 giro { get => _giro; private set => _giro = value; }
[System.Serializable]
private class IMUData
{
public float ax;
public float ay;
public float az;
public float gx;
public float gy;
public float gz;
}
private SerialPort serialPort;
private Thread serialThread;
private bool isRunning = false;
private bool isDataReceived = false; // データ受信フラグ
void Start()
{
try
{
serialPort = new SerialPort(portName, baudRate);
serialPort.DataBits = 8;
serialPort.StopBits = StopBits.One;
serialPort.Parity = Parity.None;
serialPort.ReadTimeout = timeout;
serialPort.DtrEnable = true;
serialPort.RtsEnable = true;
serialPort.Open();
Debug.Log("Serial port opened: " + portName);
// スレッドを開始
isRunning = true;
serialThread = new Thread(SerialReadThread);
serialThread.Start();
}
catch (System.Exception e)
{
Debug.LogError("Error opening serial port: " + e.Message);
}
}
void Update()
{
// データ受信フラグが立っている場合、ここで処理を行う
if (isDataReceived)
{
try
{
var json = JsonUtility.FromJson<IMUData>(message);
_accel = new Vector3(json.ax, json.ay, json.az);
_giro = new Vector3(json.gx, json.gy, json.gz);
// accelAxisに合わせて_accelの軸を変換
_accel = ConvertAxis(_accel, imuAxis);
// giroAxisに合わせて_giroの軸を変換
_giro = ConvertAxis(_giro, imuAxis);
if (useDebug)
{
Debug.Log($"gx: {json.gx}, gy: {json.gy}, gz: {json.gz}");
}
}
catch
{
if (useDebug)
{
Debug.LogWarning("Failed to parse JSON: " + message);
}
}
isDataReceived = false; // フラグをリセット
}
}
private void SerialReadThread()
{
while (isRunning && serialPort != null && serialPort.IsOpen)
{
try
{
string line = serialPort.ReadLine();
message = line; // 受信したデータを変数に格納
isDataReceived = true; // データ受信フラグを立てる
//Debug.Log("Received: " + line);
}
catch (System.TimeoutException)
{
// タイムアウトは正常な動作なので、何もしない
continue;
}
catch (System.Exception ex)
{
// こちらも同様に、エラーログを直接出力します。
Debug.LogError("Error reading from serial port: " + ex.Message);
break;
}
}
}
void OnDestroy()
{
// スレッドの停止
isRunning = false;
if (serialPort != null && serialPort.IsOpen)
{
serialPort.Close();
Debug.Log("Serial port closed.");
}
if (serialThread != null && serialThread.IsAlive)
{
serialThread.Join(1000); // 1秒待機
if (serialThread.IsAlive)
{
serialThread.Abort(); // 強制終了(推奨されませんが、フォールバック)
}
}
}
/// <summary>
/// 軸変換を行う関数。AxisTypeの設定に基づいて、入力されたVector3の各軸を反転させたり入れ替えたりします。
/// </summary>
/// <param name="input"></param>
/// <param name="axisType"></param>
/// <returns></returns>
Vector3 ConvertAxis(Vector3 input, AxisType axisType)
{
int config = (int)axisType;
Vector3 output = Vector3.zero;
// 軸の入れ替え
int swapConfig = (config >> 3) & 7; // 上位3ビットで入れ替え設定を取得
Vector3 swappedOutput = input;
switch (swapConfig)
{
case 0: swappedOutput = new Vector3(swappedOutput.x, swappedOutput.y, swappedOutput.z); break; // XYZ
case 1: swappedOutput = new Vector3(swappedOutput.x, swappedOutput.z, swappedOutput.y); break; // XZY
case 2: swappedOutput = new Vector3(swappedOutput.y, swappedOutput.x, swappedOutput.z); break; // YXZ
case 3: swappedOutput = new Vector3(swappedOutput.y, swappedOutput.z, swappedOutput.x); break; // YZX
case 4: swappedOutput = new Vector3(swappedOutput.z, swappedOutput.x, swappedOutput.y); break; // ZXY
case 5: swappedOutput = new Vector3(swappedOutput.z, swappedOutput.y, swappedOutput.x); break; // ZYX
default: break; // デフォルトはXYZのまま
}
// X軸の変換
swappedOutput.x = ((config & 1) == 0) ? swappedOutput.x : -swappedOutput.x;
// Y軸の変換
swappedOutput.y = ((config & 2) == 0) ? swappedOutput.y : -swappedOutput.y;
// Z軸の変換
swappedOutput.z = ((config & 4) == 0) ? swappedOutput.z : -swappedOutput.z;
return swappedOutput;
}
// インターフェースの実装
Vector3 IGyroProvider.giro => giro;
Vector3 IAccelProvider.accel => accel;
}
// インターフェースを定義
public interface IGyroProvider
{
Vector3 giro { get; }
}
public interface IAccelProvider
{
Vector3 accel { get; }
}