6

Excuse me, quick question:

I have this hardware setup:

Same machine: "Com3" -> USB -> To Serial -> To USB -> "Com4"

And I followed MSDN SerialPort Class and MSDN SerialPort.ReadLine() to build this routine:

SerialPort SendSerialPort = new SerialPort("Com3", 9600);
SerialPort ReceiveSerialPort = new SerialPort("Com4", 9600);

SendSerialPort.Open();
ReceiveSerialPort.Open();

SendSerialPort.WriteLine("Test");
var message = ReceiveSerialPort.ReadLine(); // control stops here

SendSerialPort.Close();
ReceiveSerialPort.Close();

Console.WriteLine(message);

However, when I tend to ReadLine(), my control stops and just waits. I did not expect that.

I am expecting to receive the string Test and assign it to my var message. Could you please tell me what am I doing wrong here?

EDIT:

I tested my hardware using the Serial Port Utility Application and it worked just fine.

Khalil Khalaf
  • 9,259
  • 11
  • 62
  • 104
  • 1
    IO operations are blocking. You'll need to move at least one of your IO operations to another thread/Task. – rene Sep 02 '16 at 15:24

3 Answers3

4

I've altered from the example you linked:

To actually have both ports running to read and write back and forth you will actually need to implement threading for reading and writing for both.

It can be a good idea to use a timer.

public static void Main()
{
    SerialPort SendSerialPort = new SerialPort("Com3", 9600);
    SerialPort ReceiveSerialPort = new SerialPort("Com4", 9600);

    StringComparer stringComparer = StringComparer.OrdinalIgnoreCase;
    Thread readThread = new Thread(Read);

    // Set the read/write timeouts
    _serialPort.ReadTimeout = 500;
    _serialPort.WriteTimeout = 500;

    SendSerialPort.Open();
    ReceiveSerialPort.Open();
    bool _continue = true;
    readThread.Start();

    Console.Write("Name: ");
    name = Console.ReadLine();

    Console.WriteLine("Type QUIT to exit");

    while (_continue)
    {
        message = Console.ReadLine();

        if (stringComparer.Equals("quit", message))
            _continue = false;
        else
            SendSerialPort.WriteLine(String.Format("<{0}>: {1}", name, message));
    }
    readThread.Join();
    SendSerialPort.Close();
}

public static void Read()
{
    while (_continue)
    {
        try
        {
            string message = ReceiveSerialPort.ReadLine();
            Console.WriteLine(message);
        }
        catch (TimeoutException) { }
    }
}

Usually there will be a beginning and end value within the written data to tell the other port that the message is finished and also for the ports to validate that they are reading data they should be, usually with commands of what to do with that data. (out of scope for this question).

Also lacking and important is the intialisation of your ports.

I prefer to use the default constructor (preference only)

SerialPort Constructor ()

And then set any values like so:

_serialPort.BaudRate = SetPortBaudRate(_serialPort.BaudRate);
_serialPort.Parity = SetPortParity(_serialPort.Parity);
_serialPort.DataBits = SetPortDataBits(_serialPort.DataBits);
_serialPort.StopBits = SetPortStopBits(_serialPort.StopBits);
_serialPort.Handshake = SetPortHandshake(_serialPort.Handshake);

All the constructors will give these values:

This constructor uses default property values when none are specified. For example, the DataBits property defaults to 8, the Parity property defaults to the None enumeration value, the StopBits property defaults to 1, and a default port name of COM1.

Even the handshake has a default value. If you look at the source code.

private const Handshake defaultHandshake = Handshake.None;
  • 1
    I could not reproduce the problem. Hence, I will assume that it was out of randomness since the parameters `DataBits`, `StopBits` and `Parity` [are set by default](http://stackoverflow.com/questions/39297839/are-uninitialised-attributes-given-default-values-as-they-are-when-using-the-def). I want to make sure it doesn't happen anymore, therefore I will use the above recommended method of multiple threads for continuous read and write via Serial Port. – Khalil Khalaf Sep 02 '16 at 17:54
4

The problem with your code is in this line

var message = ReceiveSerialPort.ReadLine();

You block your code to wait for a line, if the line never arrives it will remain here forever or the value set to ReadTimeout

So why does the line never arrive?

The problem can be an error in WriteLine("Test");, you should handle errors, or it can be that your in are blocking your code ReadLine() before the WriteLine("Test") manage to come through, you could insert a Thread.Sleep(100) between, but this is not really improving the code.

Note: Your code will also work as is sometimes, depending on these race conditions.

This synchronized / blocking reading from serial ports seems simple in code just one line; but it creates a lot of negative side effects in your communication protocol's.

A much better solution (considering that you like to Read / Write data from a microcontroller) is to either use a thread as Yvette suggested or use asynchronously reading Stream.BeginRead (Byte[], Int32, Int32, AsyncCallback, Object) which I would prefer.

The asynchronously reading will throw an event when something is incoming on the serial port. The basic idea of this programming strategy is to not do step programming but expecting what ever result and then handle it correctly.

In communications protocol with asynchronously reading the AutoResetEvent is very useful, hence you send something, then you start the AutoResetEvent, if asynchronously the expected result is arriving you will set this event and your code can continue, if it does not arrive the AutoResetEvent will timeout and you can handle this.

Community
  • 1
  • 1
Petter Friberg
  • 21,252
  • 9
  • 60
  • 109
  • Thank you for answering. I have a Q if you don't mind: if "_my line never arrives_" why it never arrives and took that long? My code then should stay at `Read()` for the "_100_" milliseconds, let's say, then and continue once the messages arrives. What am I missing? – Khalil Khalaf Sep 02 '16 at 23:21
  • It could have been an error when sending, it could have been that you blocked the sending (it did not have time to start) before you blocked it in readLine (hence the readLine() is actually blocking your code), while the writeLine is not. – Petter Friberg Sep 02 '16 at 23:28
  • oh, it did not have time to go through at the first place and it got "_interupted_". I see. Thanks for the lesson ! – Khalil Khalaf Sep 02 '16 at 23:32
2

It cannot block when there is data available. What you sent either got stuck in the transmit buffer, got lost due to a wiring mistake, triggered an error or was ignored. If it works with another program then a wiring mistake can't be the problem.

Do keep in mind that just setting the Baudrate is not enough, you must also use set the DataBits, Parity and Stopbits properties to match the device settings. A mismatch can trigger an error, the kind you can only see when you write an event handler for the ErrorReceived event. Never skip that event, confounding problems can occur if you never check.

And above all the Handshake property must be set correctly. The proper value depends on how the ports are wired together, it is too common to not connect them. Start by setting it to Handshake.None so a wrong state for the DSR and CTS signals can't block reception and a wrong state for the DTR and RTS signals can't block transmission. Beware that it is common for another program to enable hardware handshaking, a mismatch is guaranteed to cause communications to stall.

If you use synchronous reads instead of the DataReceived event then you should in general deal with the possibility that a device is not responding. Either because it is powered off, not connected at all or malfunctioning. Use the ReadTimeout property for that so your program cannot hang. Aim high, 10000 milliseconds is a reasonable choice.

Beware the randomness of this problem, putzing around with another program can easily get the port configured correctly and now it will suddenly work. And beware that starting a thread accomplishes nothing, it will now be that thread that gets stuck and the Join() call will deadlock.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • One Q though: Do you recommend the need of multiple threads? It worked on the same thread. I assume I just need to make sure of the correct / right configuration. – Khalil Khalaf Sep 02 '16 at 16:03
  • 1
    I tried to warn you in the last paragraph that using a thread is not a solution. So no. You'll have to focus on configuration, that's your problem here. Rock-hard rule is that you can never take a shortcut on it. – Hans Passant Sep 02 '16 at 16:05
  • @Yvette and Mr Hans, my Mision is to Read / Write data from a microcontroller up to 1000 times / s. It will be on same port though. This was the warm up beginning for me to get familiar with the code and to learn how does this technology work. What is then my best approach and road to go with? – Khalil Khalaf Sep 02 '16 at 16:15
  • 2
    Well, you are warmed up. Configuration matters, never ignore errors. Whether you use synchronous reads or use the DataReceived event depends on what the rest of your program does. – Hans Passant Sep 02 '16 at 16:19
  • 1
    @HansPassant I got curios why "You must also set DataBits, Parity and Stopbits" ?, this should already be done in the constructor http://referencesource.microsoft.com/#System/sys/system/io/ports/SerialPort.cs,605, is it not already using these default values? – Petter Friberg Sep 02 '16 at 16:51
  • @PetterFriberg as I understood, if we did not set them, they will be randomized. Which causes a problem. Using another app (the one I did to test the wire for example) set them correctly and that is why my code worked as is after I tested my wire. The best way would be then, is to explicitly set them when we declare our objects. My code works on another machine with new com numbers once I set them. – Khalil Khalaf Sep 02 '16 at 16:59
  • @FirstStep the source code tells us otherwise, but if that solved your problem maybe Hans know why. – Petter Friberg Sep 02 '16 at 17:05
  • @PetterFriberg Right. I just finished reading the reference you provided. I would like to hear why from Mr Hans. – Khalil Khalaf Sep 02 '16 at 17:10
  • It is not very clear how it is wired on the device side. If you have your MCU just echo what is received then it is important that the configuration matches the MCU settings or you'll get framing or parity errors. Do be sure to not skip ErrorReceived. There is no default for the Handshake property, whatever was used last is what you'll get when you don't set it explicitly. A mismatch is guaranteed to cause a stall. – Hans Passant Sep 02 '16 at 17:15