1

I have an application that communicates with a microcontroller via serial port and I need to check the controller's status periodically in a Background Worker, and also allow the user to interact with the controller asynchronously (via user interface), by sending commands and receiving responses (also in a Background Worker).

This is my Serial Communication class:

using System;
using System.IO.Ports;
using System.Timers;

namespace GUI_WPF
{
static class SerialCommunication
{
    static SerialPort serialPort = new SerialPort();
    static int readWriteTimeout = 2000; // [ms]
    static string endOfString = "" + char.MinValue;

    static object _commandLock = new object();

    public static void SerialPortInit(string portName)
    {
        if (serialPort.IsOpen == true)
        {
            serialPort.Close();
        }

        serialPort.PortName = portName;
        serialPort.BaudRate = 115200;
        serialPort.Parity = Parity.None;
        serialPort.DataBits = 8;
        serialPort.StopBits = StopBits.One;
        serialPort.Handshake = Handshake.None;

        // Set the read/write timeouts [ms]
        serialPort.ReadTimeout = readWriteTimeout;
        serialPort.WriteTimeout = readWriteTimeout;

        serialPort.Open();
    }

    public static string SerialRead()
    {
        try
        {
            serialPort.DiscardInBuffer();
            return serialPort.ReadTo(endOfString);
        }
        catch (Exception ex)
        {
            throw new Exception("SerialCommunication.SerialRead(): " + ex.Message);
        }
    }

    public static string SerialRead(int numberOfReads)
    {
        try
        { // The controller sends terminator char after each value transmitted, so using this method we can read multiple values
            serialPort.DiscardInBuffer();
            string response = "";
            for (int i = 0; i < numberOfReads; i++)
            {
                response += serialPort.ReadTo(endOfString) + " ";
            }
            return response;
        }
        catch (Exception ex)
        {
            throw new Exception("SerialCommunication.SerialRead(): " + ex.Message);
        }
    }

    public static void SerialWrite(string text)
    {
        try
        {
            serialPort.DiscardOutBuffer();
            serialPort.Write(text);
        }
        catch (Exception ex)
        {
            throw new Exception("SerialCommunication.SerialWrite(): " + ex.Message);
        }
    }

    public static string SerialWriteAndRead(string text)
    {
        lock (_commandLock) // If command is called from a thread while is executed on another thread, the lock forces the calling thread to wait
        {
            SerialWrite(text);
            return SerialRead();
        }
    }

    public static string SerialWriteAndRead(string text, int numberOfReads)
    {
        lock (_commandLock) // If command is called from a thread while is executed on another thread, the lock forces the calling thread to wait
        { // The controller sends terminator char after each value transmitted, so using this method we can read multiple values
            SerialWrite(text);
            return SerialRead(numberOfReads);
        }
    }

    public static string SerialRead(int numberOfReads, int timeoutMillisecods)
    {
        serialPort.ReadTimeout = timeoutMillisecods;
        string response = SerialRead(numberOfReads);
        serialPort.ReadTimeout = readWriteTimeout;
        return response;
    }

    public static string[] GetSerialPorts()
    {
        return SerialPort.GetPortNames();
    }

    public static void ClosePort()
    {
        try
        {
            if (serialPort.IsOpen == true)
            {
                serialPort.Close();
            }
        }
        catch { }
    }
}
}

In a scenario, the monitor interrogates the controller every 2 seconds. Sometimes (most of the times) when the user issues from the GUI a command which waits for a response, the following exception appears: The I/O operation has been aborted because of either a thread exit or an app request.

Why does the "lock()" method not work? How can it be done?

Cristian M
  • 715
  • 2
  • 12
  • 32
  • 1
    You should try domething else, like having a thread monitoring the port and another waiting orders from the UX interface, when the UX one have an order it sends it to the monitoring one, so it'll execute the order. Doing that the port just interactuate with one thread, and you prevent sync problems. – Raskayu Dec 22 '16 at 12:36
  • @Raskayu Can you please give me a code example? – Cristian M Dec 22 '16 at 12:44
  • Where is the code, calling SerialWriteAndRead? You need a non-UI thread to check the controller status (or, at least, use DispatcherTimer,), and keep UI thread for user interface (as its name sugests!) – cyanide Dec 22 '16 at 13:07
  • @cyanide The code that calls SerialWriteAndRead() is in the viewmodel and it is run inside a background worker (on a thread different from the UI thread), e.g., when the user presses a specific button. The controller status is queried from a different thread than these two mentioned above (and these interrogation also calls SerialWriteAndRead()). – Cristian M Dec 22 '16 at 13:16
  • There are some minor advantages of calling SerialPort.DiscardXxxBuffer(), you get to test how well your app deals with random data loss. Well, test failed. Rather best to focus on writing code that works first. Do not randomly throw away data, never call Close(). – Hans Passant Dec 22 '16 at 16:22
  • 2
    You need a state machine, when the user isn't actively doing something it will poll the microcontroller, when the user does something it goes into that state so it isn't fighting the serial port for control. I made a decent example here: http://stackoverflow.com/a/38536373/2009197 – Baddack Dec 23 '16 at 00:20
  • @HansPassant I don't think I understand. Why would clearing the in/out buffers before each write/read randomly throw away data? SerialPort.Close() is called only at app exit. – Cristian M Dec 23 '16 at 16:10
  • @Baddack Thank you for the suggestion. I was intending to do something similar, but it was too much work. Instead, in the controller class I've implemented a wait method before each command. This way, any thread that calls a method from this class (i.e., sends a serial command to the microcontroller), while another serial communication is in progress (generated from another thread), will wait for the initial communication to finish. Basically, this is what I expected lock() to do, but, unfortunately, I does not (or maybe I am missing something). – Cristian M Dec 23 '16 at 16:14
  • You assume that no data is received until you call Read/To(). This is not accurate, it is the serial port driver that receives data. It uses an internal buffer, you set its size with the ReadBufferSize property. All that Read/To() does is copy bytes from that buffer to your own variable. But when you call DiscardInBuffer() then that buffer is emptied. So if your program is ever late calling it (always happens eventually due to (say) a garbage collection or the machine getting busy), then this call is not innocent anymore, it deletes data. What happens next is never good. – Hans Passant Dec 23 '16 at 16:21
  • @HansPassant Thank you for the explanation. So are you suggesting it is better to remove the DiscardBuffer() calls? – Cristian M Dec 23 '16 at 16:29
  • 2
    No, I'm not suggesting it is "better". I'm saying it is **required**. – Hans Passant Dec 23 '16 at 16:35

0 Answers0