Apparent IO.Ports.SerialPort Flaw in C# or Possibl

2019-08-17 00:12发布

Please forgive me as this is going to be quite a long post. I'm currently using the SerialPort class in C# to write an application to communicate with a device called a Fluke 5500A. I've, in the past, had many problems as the amount of time the device takes to issue a command and return whatever it outputs in unpredictable at best. I asked a question yesterday here: System.Timers.Timer Usage The answer to the question is wonderful and most of the time appears to work perfectly. As an example my the class I use to connect to a SerialPort now looks like this:

public class SerialPortConnection
{
    private SerialPort serialPort;
    private string ping;
    double failOut;
    bool isReceiving;

    public SerialPortConnection(string comPort = "Com1", int baud = 9600, System.IO.Ports.Parity parity = System.IO.Ports.Parity.None, int dataBits = 8, System.IO.Ports.StopBits stopBits = System.IO.Ports.StopBits.One, string ping = "*IDN?", double failOut = 2)
    {
        this.ping = ping;
        this.failOut = failOut * 1000;

        try
        {
            serialPort = new SerialPort(comPort, baud, parity, dataBits, stopBits);
            serialPort.NewLine = ">";
            serialPort.ReadTimeout = 1000;
        }
        catch (Exception e)
        {
            serialPort = null;
        }
    }

    //Open Serial Connection. Returns False If Unable To Open.
    public bool OpenSerialConnection()
    {
        //Opens Initial Connection:
        try
        {
            serialPort.Open();
            serialPort.Write("REMOTE\r");
        }
        catch (Exception e)
        {
            return false;
        }

        serialPort.Write(ping + "\r");
        var testReceived = "";

        try
        {
            testReceived += serialPort.ReadLine();
            return true;
        }
        catch
        {
            return false;
        }
    }

    public string WriteSerialConnection(string SerialCommand)
    {
        serialPort.Write(String.Format(SerialCommand + "\r"));
        var received = "";

        try
        {
            received += serialPort.ReadLine();
            return received;
        }
        catch
        {
            received = "Error: No Data Received From Device";
            return received;
        }
    }

    public bool CloseSerialConnection()
    {
        try
        {
            serialPort.Write("LOCAL\r");
            serialPort.Close();
            return true;
        }
        catch (Exception e)
        {
            return false;
        }
    }
}

As you can see, when I open the a connection to, in this case, Com1 I test the connection by writing a command *IDN? to the SerialPort. The return for this command looks like so:

FLUKE,5500A,8030005,2.61+1.3+2.0+*
66>

In the class I've set ">" as the NewLine property so that SerialPort.ReadLine() doesn't finish till it finds that token. I've never once had the class itself throw an exception but I've noticed while debugging that sometimes testReceived won't catch that returned data properly, despite the fact that no exceptions are thrown and the code continues executing properly, and instead received will catch the returned string:

FLUKE,5500A,8030005,2.61+1.3+2.0+*
66>

whenever I pass my first command via SerialPort.Write(); Something important to know is that commands can be executed without that data being fully returned. My concern is that the initial ReadLine() appears to be skipping past that occasionally without catching the entire return. My thought is that there's an inherent flaw in the device I'm communicating with causing this but I'd prefer to be entirely sure before continuing.

My command order looks like so:

First I submit a command on startup:

REMOTE

This disables interaction with the device's manual interface and allows me to submit commands via the Serial Port.

Then I issue *IDN?, in this case, to check that the device is connected:

*IDN?

If nothing is return, the application is set to display an error in a message box and then FailFast. If all goes well a command can be submitted like so:

STBY
OUT 30MV,60HZ
OPER

The only command submitted here manually is OUT 30,MV,60HZ. STBY and OPER are set in the app.config as they only add an unnecessary step to the usage of the application. The STBY command puts the machine in standby for safety reasons. The OPER command puts it in operating mode and the device begins operating under the set parameters.

The application then waits for a technician to enter a result into a textbox and submit it. The content of these results aren't particularly pertinent but upon hitting the result button the machine is put back in standby:

STBY

Finally, two more commands are submitted when the application is closed:

*RST
LOCAL

First *RST resets the machine to ensure that it's in the same state as when it was powered on (I.E. It's not operating and no parameters are set). Then LOCAL sets the re-enables the manual interface for user interaction and disables access via the Serial Port till REMOTE is issued once more.

As you can see, a command is issued after *IDN? and before the first manual command that's sent (In this case we assume the command is OUT 30MV,60HZ). The problem is, sometimes I receive the output of *IDN whenever I check what the output of OUT 30MV,60HZ is yet I can see no problems within my code or the procedure I'm using to operate the machine. Is there any reason this could be happening?

As I've said, the error is extremely hard to reproduce (I've seen it twice in maybe forty runs). Even so, any error at all of this type is not acceptable in a production environment and the error needs to be fixed before I can begin testing my application in its entirety. I'll keep trying to reproduce the error so I can provide an example and hopefully provide further clarification as to what the problem might be.

EDIT:

I'd also like to clarify that I'm fairly certain the bug is not located somewhere within my application itself as the code is somewhat simplistic in nature:

public string SubmitCommand()
    {
        if (_command_Input != "No further commands to input.")
        {
            string received;
            serialPort.WriteSerialConnection("STBY");
            received = serialPort.WriteSerialConnection(_command_Input);
            serialPort.WriteSerialConnection("OPER");

            //Controls Enabled:
            _input_IsEnabled = false;
            _user_Input_IsEnabled = true;
            _results_Input_IsEnabled = false;
            RaisePropertyChanged("Input_IsEnabled");
            RaisePropertyChanged("User_Input_IsEnabled");
            RaisePropertyChanged("Results_Input_IsEnabled");

            return received;
        }
        else
            return "";
    }

received is then manipulated like so:

public bool SetOutput()
    {
        string inter1 = SubmitCommand();

        try
        {

            string[] lines = inter1.Split(Environment.NewLine.ToCharArray()).ToArray();
            _results_Changed = lines[2];
            RaisePropertyChanged("Results_Changed");
        }
        catch
        {
            _results_Changed = inter1;
            RaisePropertyChanged("Results_Changed");
        }
        return true;
    }

I can provide further code if need be but I can't currently see any other code that might be pertinent to the question at hand.

1条回答
男人必须洒脱
2楼-- · 2019-08-17 01:01

You made this hard to diagnose, the response you don't like looks exactly like the one you do like.

In general, you need to ensure that your program is in sync with the device. A possible failure mode is when the driver still has unread data in the receive buffer from a previous connection. Stale data could also exist in the device's transmit buffer. When you start back up, you'll read that stale data and assume it was a response to your command. It wasn't. You'll now be permanently out of sync, always reading stale data that was the response to the previous command.

It is also rather odd that this works without taking care of handshaking, device normally do pay attention to that.

To avoid accidents, initialize your program like this:

  • Call the Open() method to open the port
  • Set the RtsEnable and DtrEnable properties to true so that the device always sees a good signal that allows it to transmit data
  • Sleep for about 100 msec to allow the device to send any data that it still had buffered from the previous connection but could not send because the handshake was off
  • Call DiscardInBuffer() to throw away any stale response bytes.

You have now a reasonable guarantee you'll be in sync.

查看更多
登录 后发表回答