Arduino periodically resetting (in Unity, Mux 8-channel, 3 IMU's)

Hello, this is going to be a long post, but I hope someone can help as it is quite specific I guess.

I set up an Arduino Uno with the 8-channel Mux Breakout.
I plugged in 3 IMU with the I2C pullup resistorssolders removed (hookup guide says only one should be present - it is on the Mux Breakout).

Tests on the Arduino alone were perfect - the mux returned every rotation vector succesfully in turn.

I then set up a SerialPort stream reading using System.Thread in Unity, as in this guide :
December 1, 2016 - Alan Zucconi … ion/#step1
…which uses https://github.com/scogswell/ArduinoSerialCommand

Basically, I send a command “PING” to Arduino which then returns me the IMU’s rotation vectors.
I then process the data and send it the PlayerController.

Here it got spicy - the Arduino gets periodically reset, with no apparent reason.
What I know :

  • It works for a certain amount of cycles, then gets reset.
  • The thread isn’t Stopped/Started again (it would print info each time, and it only does once)
  • The port is Open in the ThreadLoop, which is called only once. So it isn’t the Unity script that re-opens the port, it comes from the Arduino. but the reset might be caused by Unity (as the Arduino worked well alone).

The SerialPort reader class is as follows :

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

public class ArduinoThread : MonoBehaviour
{
    public string port = "COM3";
    public int baudrate = 9600;
    public int timeout = 50;
    public bool looping = true;

    private SerialPort stream;
    private Thread thread;
    private Queue outputQueue; // Unity => Arduino
    private Queue inputQueue; // Arduino => Unity

    public string[] imuData; // Used for separating ID, x, y, z, w values from the single string we get from the Arduino
    private int imuID; // The ID of the IMU we get data from
    private Quaternion imuRotation; // The Quaternion of the IMU we get data from

    private PlayerController player;

    private void Start()
    {
        if(player == null) player = FindObjectOfType<PlayerController>();
        StartThread();
    }

    public void StartThread()
    {
        Debug.Log("Started thread !");
        outputQueue = Queue.Synchronized(new Queue());
        inputQueue = Queue.Synchronized(new Queue());

        thread = new Thread(ThreadLoop);
        thread.Start();
    }

    // ThreadLoop() will execute in an endless while() loop IN PARALLEL, so no freezing.
    public void ThreadLoop()
    {
        Debug.Log("Thread loop called initiated !");
        stream = new SerialPort(port, baudrate);
        stream.ReadTimeout = 50;
        stream.Open();

        // Loop
        while (IsLooping())
        {
            // Send command to Arduino
            if(outputQueue.Count != 0)
            {
                string command = (string) outputQueue.Dequeue();
                WriteToArduino(command);
            }

            // Read from Arduino
            string result = ReadFromArduino(timeout);
            if (result != null) inputQueue.Enqueue(result);
        }
        stream.Close();
    }

    // Call this with desired command as parameter, it will add to Queue. Then ThreadLoop() will handle communication.
    public void SendToArduino(string command)
    {
        outputQueue.Enqueue(command);
    }
    public string GetFromArduino()
    {
        //Debug.Log("Queue count = " + inputQueue.Count);
        if (inputQueue.Count == 0) return null;
        return (string)inputQueue.Dequeue();
    }

    public void WriteToArduino(string message)
    {
        // Send the request
        stream.WriteLine(message);
        stream.BaseStream.Flush();
    }
    public string ReadFromArduino(int timeout = 0)
    {
        stream.ReadTimeout = timeout;
        try
        {
            return stream.ReadLine();
        }
        catch (System.TimeoutException)
        {
            return null;
        }
    }

    private void Update()
    {
        SendToArduino("PING");
        string rawInput;
        rawInput = GetFromArduino();
        if (rawInput != null)
        {
            imuData = new string[] { };
            imuData = rawInput.Split(' ');
            ExtractRotation(imuData); // This simply sends each IMU's ID and Quaternion to PlayerController class.
        }
    }
    private void ExtractRotation(string[] words)
    {
        for (int i = 0; i < words.Length; i++)
        {
            Debug.Log("words is : " + words[i]);
        }
        float id = float.Parse(words[0]);
        imuID = (int)System.Math.Round(id);

        float[] quat = new float[4];
        int x = 0;
        for (int i = 1; i < words.Length; i++)
        {
            if (words[i] != ' '.ToString())
            {
                quat[x] = float.Parse(words[i]);
                x++;
            }
            if (x == 4) break;
        }
        imuRotation = new Quaternion(quat[0], quat[1], quat[2], quat[3]);
        player.imuInputs[imuID] = imuRotation;
    }

    // Loop stop handling
    public bool IsLooping()
    {
        lock (this)
        {
            return looping;
        }
    }
    public void StopThread()
    {
        Debug.Log("Stopped thread !");
        lock (this)
        {
            looping = false;
        }
    }
    private void OnDestroy()
    {
        StopThread();
    }
}

The Arduino code (works without the SerialCommands part for Unity) :

#include <SerialCommand.h>
#include <Wire.h>
#include "SparkFun_BNO080_Arduino_Library.h"

SerialCommand sCmd;
BNO080 imu;
int imuCount;

void setup()
{  
  imuCount = 0;
  Serial.begin(9600);
  while (!Serial);

  // SerialCommand setup
  sCmd.addCommand("PING", pingHandler);
  sCmd.addCommand("ECHO", echoHandler);
  
  // I2C setup
  Wire.begin();
  Wire.setClock(400000); //Increase I2C data rate to 400kHz

  //Initialize all the sensors
  for (byte x = 0 ; x < 8 ; x++)
  {
    enableMuxPort(x); //Tell mux to connect to port X
    if (imu.begin() == false)
    {
      Serial.println("BNO080 not detected at default I2C address. Skipping this port.");
    }
    else
    {
      imuCount++;
      Serial.println(String("An IMU was detected at port : ") + x + ". IMU count is : " + imuCount);
    }

    imu.enableRotationVector(50); //Send data update every 50ms
    disableMuxPort(x);
  }

  Serial.println("Mux Shield online");
}

void loop()
{
  if (Serial.available() > 0) sCmd.readSerial();

  delay(50);
}

void pingHandler ()
{  
Serial.println();
    for (byte x = 0 ; x < imuCount ; x++)
  {
    enableMuxPort(x); //Tell mux to connect to this port, and this port only
    //Look for reports from the IMU
    if (imu.dataAvailable() == true)
    {
      float quatI = imu.getQuatI();
      float quatJ = imu.getQuatJ();
      float quatK = imu.getQuatK();
      float quatReal = imu.getQuatReal();
      float quatRadianAccuracy = imu.getQuatRadianAccuracy();
      Serial.print(x);
      Serial.print(" ");
      Serial.print(quatI, 2);
      Serial.print(" ");
      Serial.print(quatJ, 2);
      Serial.print(" ");
      Serial.print(quatK, 2);
      Serial.print(" ");
      Serial.print(quatReal, 2);
      Serial.print(" ");
      Serial.print(quatRadianAccuracy, 2);
      Serial.println(); 
    }
    else {
      Serial.println("IMU_NOT_AVAILABLE");
}
    disableMuxPort(x); //Tell mux to disconnect from this port
  }
 
}

void echoHandler ()
{
    // Not used.
}

C++ String objects are a bit of a disaster in a limited RAM environment. Try replacing this:

Serial.println(String("An IMU was detected at port : ") + x + ". IMU count is : " + imuCount);

With something like this:

Serial.print("An IMU was detected at port : ");
Serial.print(x);
Serial.print(". IMU count is : ");
Serial.println(imuCount);

Thank you a lot for your answer !
Did a witchhunt for String before but left this one out :stuck_out_tongue:
It didn't solve the problem, but while I was looking for it I noticed I was sending the degree of radian accuracy, but didn't use it on Unity side.

I commented that out and now it works without resetting itself, but only one IMU data is being received.
I guess I can figure out the rest, so I'll keep the post updated !

So is it like saturation of the RAM on Arduino which caused resetting? I'm not an engineer so sorry if I'm being silly.

Cheers !

As it turns out, the problem was caused on arduino's script in the loop in pinghandler, where I get through all the ports of the Mux breakout.

When I query all the ports in separate PINGs, I get the data without problem. It is less efficient because every request carries only one IMU's data, but at least it works...

Is there an issue with sending more than one Serial.println() per request?

Hoping to get a more efficient solution sometime (I'll end up with 10 IMU's).
Thank you