in C#, Programming, Tutorial, Unity

Asynchronous Serial Communication

In a previous post, How To Integrate Arduino With Unity, we have shown how data can be sent and received through the serial port. The main problem encountered was dealing with the latency and the delays that communicating across different devices introduces. The solution proposed in that tutorial used a coroutine; decoupling the device communication from the game logic attenuates the problem, but it doesn’t remove it. The main problem is that, despite the name, Unity coroutines are not really executed in parallel with the rest of the game. Unity, by design, is not thread safe. This means that true parallelism is intentionally avoided by the engine, as it could result in race conditions. To solve the problem of the communication between Unity and Arduino, once and for all, we need actual threads.

At the end of this tutorial, you can find a link to download the Unity package.

Step 1. Creating the Thread

C# supports threads with its System.Threading library. When a new thread is created, a function has to be provided. In the following example, that function is called ThreadLoop .

using UnityEngine;
using System.Threading;

public class ArduinoThread : MonoBheaviour
{
	private Thread thread;

	public void StartThread ()
	{
		// Creates and starts the thread
		thread = new Thread (ThreadLoop);
		thread.Start();
	}

	
	public void ThreadLoop ()
	{
		// The code of the thread goes here...
	}
}

Step 2. Callbacks and Queues

When code is executed in parallel, the traditional pattern of request and response typical of most imperative languages breaks. We cannot expect a function that, once invoked, returns when some data from Arduino is available. A very common approach relies on callbacks. With callbacks, the logic flow of the game is as follow:

  • A MonoBehavior sends a request to the thread;
  • The thread executes the request in the background;
  • When ready, the thread invoked a callback function that the MonoBehavior has provided.

This was exactly the approach used in How To Integrate Arduino With Unity, implemented in the coroutine AsynchronousReadFromArduino.

That approach, unfortunately, won’t work with threads. The thread that communicates with Arduino can complete its request at any moment in time. As a result, its callback could potentially be executed in an environment that is not thread safe. To make it simple: Unity objects cannot be touched or manipulated in a thread.

The solution revolves around the concept of synchronised queues. When a MonoBehavior script wants to send a request to Arduino, it places it in a input queue. The thread reads it, executes it and when its done it places it into an output queue. The thread never accesses any Unity objects. Those two queues are used to safely send and receive messages across threads.

C# has an effective implementation of synchronised queues that are safe to use between different threads:

private Queue outputQueue;	// From Unity to Arduino
private Queue inputQueue;	// From Arduino to Unity

public void StartThread ()
{
	outputQueue = Queue.Synchronized( new Queue() );
	inputQueue  = Queue.Synchronized( new Queue() );

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

Please note that the class Queue, by itself, is not guaranteed to be thread safe. The Queue.Synchronzed wrapper makes an existing queue thread safe (MSDN).

Part 3. Queue Communication

Now that we have two queues, we need some code that allows an external MonoBehavior script to send and receive data. Using the queue in this setup is relatively straightforward:

public void SendToArduino (string command)
{
	outputQueue.Enqueue(command);
}

public string ReadFromArduino ()
{
	if (inputQueue.Count == 0)
		return null;

	return (string) inputQueue.Dequeue();
}

Please, note that now that we are using queues, it is your script’s responsibility to periodically query the queue for incoming messages.

Part 4. Thread Loop

The final part of code necessary is the actual body of the thread. It consists of two parts; a setup, and a loop. The setup opens the serial connection with Arduino. The loop keeps it open, sending and receiving messages through the synchronised queues:

public void ThreadLoop ()
{
	// Opens the connection on the serial port
	stream = new SerialPort(port, baudRate)
	stream.ReadTimeout = 50;
	stream.Open();

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

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

Note that the functions WriteToArduino and ReadFromArduino are taken from the first part of this tutorial, How To Integrate Arduino With Unity.

Part 5. Improvements

The code shown so far is not very reliable, as it doesn’t take into account possible errors and exceptions. A better implementation should always make sure that all the possible exceptions are captures and properly handled.

Another aspect that has not been considered is the closure of the serial port. There are several approaches that can be done, but they all need a way for a Unity script to alter the state of a thread. For instance, by adding a boolean variable called looping that can be used a guard for the thread loop.

To be pedantic, a script should never directly change the variable used by a thread. Given how simple this script is, it’s safe to assume that there are virtually no side effects. If you want to play safe with thread, each access to a shared variable between different threads should be mediated through a lock, like this:

public bool looping = true;

public void StopThread ()
{
	lock (this)
	{
		looping = false;
	}
}

The keyword lock ensures that no two threads can edit the object at the same time. This is who thread safe code is developed in C#. But it can also be the start of an endless stream of drama called deadlocks.

To ensure thread safety, every potentially concurrent access to the variable looping must be enclosed in a lock block. This also includes the access that is done in the guard of the while loop in the ThreadLoop function:

public void IsLooping ()
{
	lock (this)
	{
		return looping;
	}
}

public void ThreadLoop ()
{
	...

	// Looping
	while (	IsLooping()	)
	{
		...
	}

	// Close the stream
	stream.Close();
}

The method StopThread can now be invoked safely from any Unity script.

Conclusion & Download

This post extends the original tutorial on Arduino and Unity. It shows how a thread can be used to avoid delays when waiting for data on the serial port. This post was inspired by Daniel Wilches’ asset SerialCommUnity.

Become a Patron!

You can download the Unity package for this tutorial on Patreon. The package allows connecting Unity and Arduino for efficient and effective two-way communication using threads. It also contains several examples to test your setup.

💖 Support this blog

This website exists thanks to the contribution of patrons on Patreon. If you think these posts have either helped or inspired you, please consider supporting this blog.

Patreon Patreon_button
Twitter_logo

YouTube_logo
📧 Stay updated

You will be notified when a new tutorial is released!

📝 Licensing

You are free to use, adapt and build upon this tutorial for your own projects (even commercially) as long as you credit me.

You are not allowed to redistribute the content of this tutorial on other platforms, especially the parts that are only available on Patreon.

If the knowledge you have gained had a significant impact on your project, a mention in the credit would be very appreciated. ❤️🧔🏻

Write a Comment

Comment

19 Comments

  1. Although I probably never need to do these stuff, I really enjoy the article.
    It great to see an article that doesn’t just touch the surface of a concept, create a working duct-taped prototype and call it a day, which is basically every Unity tutorial.
    By the way, if you use a leonardo you can just send keyboard signal and don’t have to worry about these serial stuff

    • Hey!
      Yeah, I’ve been using that feature lately! 🙂
      Unfortunately you can’t send any data back to Arduino, which is why I have decided to take a different approach.

    • Hey! I think is a good practice to lock your resources in general. This example was very simple, but if you are going to perform a much complex operation, then you know what you have to do.

  2. Thanks you for your explanation. I have some questions:
    1. I notice that the the serial port is constantly opened and closed inside the ThreadLoop () in your example. If I have only one thread to deal the communication between Unity and Arduino, can I just move the stream.open() to be inside the Start() to execute it once, and close the stream inside the update() loop or OnApplicationQuit() once the game is ended?
    2. Once I have two serial ports, say com1, and com2, to read data from 2 Arduino, Should I have two threads: one to deals with com1 the other one deals with com2? or I can use only one thread with 2 separating receiving and 2 sending queues(inQueue1 inputQueue2, outputQueue1 outputQueue2), and one locking variable (looping) so that I can query com1 first and following by query com2 in each iteration of ThreadLoop ()?

    2.1 If two threads are necessarily for two serial ports, when it is timing to call StopThread()? I was wondering if I only need to call it when the game is finished? (E.g., call it at OnApplicationQuit() ?)

    • Hey!
      (1) The thread is started ONCE. So, the serial port is opened only once (i.e. when the thread starts). You can see that the reading / writing bit is stuck in an endless loop. The opening part is not repeated.

      (2) This all depends on how critical your application is. Theoretically speaking, you can avoid threads all together if you don’t need super fast reaction times. The problem with merging two threads is that is harder to handle errors. Because you now have 4 possible states between the two working/non working threads.

      (2.1) Again, this depends on your application. If there are parts of the game where you are not using the serial port, you can close it to release its resources. There’s no issue opening it again and again, if you’re doing properly and sporadically. :p

      • Hi Alan

        thanks so much for this great tutorial! I am very new to Unity, so I have a follow-up question to what shelandy asked.

        I have 2 serial ports (COM2, COM3). I only receive data from COM3, so I assume I only need 1 inputQueue. However, I need to send data to both COM2 and COM3, so similarly to shelandy, I was wondering whether I could still use 1 thread for both ports, but define outputQueue1 (corresponding to COM2) and outputQueue2 (corresponding to COM3).

        Is this possible and how would I need to adapt the code? If this is not possible, what other options are there?
        I need both ports to stay open at all times, the timing needs to be as good as possible. I really need to check once per frame whether sth. has been received or needs to be sent.

        Thanks a lot in advance, I really appreciate it!

  3. Hi Alan, thanks very much for this tutorial. I was just constantly dumping data into the serial buffer before and this approach makes much more sense to me.

    I came across some weird behavior in my Unity implementation and I wondered if you had any advice or insight.

    When using ReadLine(), everything works fine, but I’m reading from the Arduino every frame and I think the amount of data I’m sending through would exceed the limit of the fastest baud rate so instead, I’m sending a buffer of bytes with the arduino Serial.write() command.

    I tested with my Unity app only sending the ping and getting data on a key press and I found that the first two readings would always return junk data. Then after that, each reading would be one reading behind… that is if I change the voltage source from 0v to 5v and take a reading, it will return 0v. If I swing the voltage back to 0v and take another reading, it will return 5v. After another reading without changing the incoming voltage, it will correctly return 0v.

    It’s always accurate when sending via Serial.println() like in your tutorial, but I have this weird lag of one reading when using Serial.write(). Would love to know why because it would seem that I’m always going to be one reading behind this way! Thanks!

  4. Hey Alan, thanks for the tutorial! I learnt a few new things – especially Queue.Synchronized seems useful.

    I was missing one thing though: I think you should add that StopThread() should definitely be called at in OnDestroy(). Otherwise the thread will continue to run in the Unity editor even when you’ve stopped the app, and eventually Unity will crash.

  5. Hi,
    Is all that code in the Arduino thread? How would you periodically check the queue for data? Would you do that in that thread or a separate one?

    I’m getting an error on the line inputQueue.Queue(result); that says System.Queue does not contain a definition for ‘Queue’.

    Is it possible to see a full script with everything in that would read the data?

    Thanks so much, this blog is absolutely awesome and a massive life saver!
    Thanks,
    Ben

    • Thank you for spotting that! The method is actually “Enqueue”, not “Queue”!
      I often refactor to make it simpler for the tutorial; that was a typo!

      Hopefully, I’ll be able to post the full script soon, but that is likely to be on Patreon!

      The best way is to only communicate when you need it. This depends on how fast you need to do it. Since is a thread, it will take some time to do the update. A good approach is to ask for a new data as soon as one becomes available. If you do it like that, you always have a single element in the queue.

      • Ah fab! I’ll keep an eye out for when you release that, I’d definitely pay for it!

        So I’m trying to call the functions Write to Arduino and Read from Arduino from another script, but it says I either have to make them static or create an object instance. When I do either of those things it gives an error for the line stream.WriteLine(message); Object reference not set to instance of an object. (There’s definitely a message).
        What’s the best way to call the read and write functions outside of the thread? Thanks!

        • Hang on I’ve realized I’ve accidentally been working in a separate class when I didn’t need to be -sorry!

          • I am getting the error Cannot convert method group `Dequeue’ to non-delegate type `string’. Consider using parentheses to invoke the method. for the line return(string)inputQueue.Dequeue;

            Thanks

  6. Really excellent – works like a treat. I had to fix a few things, some typos, some issues with the current version of Unity (I think), but it all works, so now I can visualise player’s heartbeat and GSR for them inside a VR experience. THANKS!

    I used to program threads in the distant mists of time (1980s), near their origin, using EPT (Encore Parallel Threads) on an Encore Multimax (16 32-bit NS32032 CPUs in a shared memory architecture) from a remote Sun 3/60 which visualised the results of the computations run on the Multimax with sockets. Things change, but things also stay the same 🙂

Webmentions

  • How to integrate Arduino with Unity - Alan Zucconi April 9, 2021

    […] topic of connecting Arduino to Unity is further expanded in Asynchronous Serial Communication, where you can also download the entire Unity […]