I have an issue which seems to be a big unknown to anyone on the internet, where my Windows application gets stuck when a serial connection is interrupted by the board’s internal exception.
Here is the simplest example of the issue:
Simplest C# code, opening a COM port connection.
internal static class Program
{
private static SerialPortStream _serialPort;
static void Main()
{
_serialPort = new SerialPortStream("COM6", 9600, 8, Parity.None, StopBits.One);
_serialPort.Open();
}
}
Arduino board code, which will simulate an internal exception after a small delay.
void loop()
{
delay(500); //We wait 0.5 seconds to allow the COM port connection to "Successfully" be established, and so the .Open() call in C# won't throw an exception.
//We create an internal exception, in this instance we try to set an array size to a negative value
int index = -4;
byte* serialData = new byte[index];
}
This is a very simple way to re-create this issue.
Some notable info:
- I am using a Raspberry Pi Pico module (RP2040 Zero)
- The RP2040 is USB-C connected, its serial ports run through USB.
- I am using ‘RJCP.IO.Ports’ for COM port communication on C# side. I also don’t think it’s the problem of the library, because I noticed that Visual Studio Code’s terminal, also gets stuck when it’s trying to establish a serial read communication.
- I’ve tried calling the ‘Open()’ method inside a different Task and BackgroundWorker, in both cases, even though the application is not frozen, I cannot close the application until I reset the board through its onboard switch (Actually, I’ve realised the program will close after around 1 min of me trying to close it).
- I know I can ‘fix’ this in Arduino code, but the point of this question is to be able to handle such case in the C# application (E.G. You will not always be the creator of whatever module you posses). I don’t think a simple board should cease any control within the host.
Thank you to anyone who helps!
2
Answers
There are a few tactics you may use in your C# program to deal with this scenario:
Read/Write Timeouts:
Using read and write timeouts for the serial port is one technique to keep your program from hanging. This allows your application to capture the timeout and deal with the issue if the Arduino doesn’t react within a predetermined amount of time.
Async Communication:
One way to avoid blocking the main thread is to read and write using asynchronous techniques. You can use Task, for Task.Run in order to execute serial readings.
Exceptions:
To deal with exceptions, enclose your Open method and all ensuing reads or writes in try-catch blocks. In this manner, you can log the error and possibly attempt to reopen the connection in case something goes wrong.
Serial Port State:
You can keep an eye on the serial port’s condition and possibly shut it off if it stops responding. In order to determine whether the port is still responsive, a different thread or job must be executed.
Cancellation Tokens:
Use cancellation tokens if you implement any long-running operations (such as reading from the serial port). This enables you to stop activities in the event of an error or if they take longer than expected.
for example:
Let’s look at this problem at various levels.
At the first level (device), an exception happens.
At the second level (serial port, device side) that exception means no data is sent. The exception itself is invisible.
At the third level (serial port, PC side) no data arrives. This is not at all exceptional.
At the fourth level (serial driver, PC side) the Windows I/O subsystem works asynchronously. Every command is accepted almost immediately, and only the biggest problems cause an immediate error. Most errors are detected after a while. Common behavior like "bytes being read" are returned asynchronously, as they happen.
At the fifth layer, (synchronous API) Windows can translate synchronous calls to asynchronous I/O, simply by waiting for I/O to happen. (Assuming it happens at all – not a given for serial ports).
At the sixth layer, your C# program is using a synchronous API to access the serial port, and it’s doing so from the thread that’s also responsible for the UI. Hence, when that thread waits for data, everything stops. Or, you use a background thread, but then that background thread blocks.
The problem here is that you need to cancel the I/O operation. The
CancelIoEx
function does this, but you’ll need to get theHANDLE
from yourSerialPortStream