Serial communication timeout on long cable time ou

2019-06-15 08:11发布

问题:

I have an application which reads different hardware via rs232. It has been tested and it was working perfectly. For the final application I needed to introduce a few hunder m long cable which means I have rs485 converters.

When I run my application to read the hardware I get a time out error for System.IO.Ports.SerialStream.Read. I have increased the timeout to 20sec unfortunately it did not resolve the problem

I have tried different applications to read the hardware and they worked even with 1sec reading frequency.

The communication is using modbus protocol, which is in the current stage I assume is irrelevant as I do not get to the stage to receive anything.

My code looks like that: First the serial port opening and initialization:

//get the right modbus data structure element
ModBus MB = (ModBus)s[0].sensorData;

//set up the serial port regarding the data structure's data
SerialPort sp = new SerialPort();
sp.PortName = MB.portName;
sp.BaudRate = Convert.ToInt32(MB.baudRate);
sp.DataBits = MB.dataBits;
sp.Parity = MB.parity;
sp.StopBits = MB.stopBits;
//Set time outs 20 sec for now
sp.ReadTimeout = 20000;
sp.WriteTimeout = 20000;

//add the port to a List which can be accessed by the reader portList.Add(sp); sp.Open();

Read hardware:

//get the right port for com
SerialPort sp = getRightPort();
ModBus MB = getRightModBusStructureelement();
try
   {
     //Clear in/out buffers:
     sp.DiscardOutBuffer();
     sp.DiscardInBuffer();

     //create modbus read message
     byte[] message = createReadModBusMessage();

     try
        {
         sp.Write(message, 0, message.Length);

         // FM.writeErrorLog output included for easier debug
         FM.writeErrorLog(DateTime.Now + ": ModBus Message Sent");
         FM.writeErrorLog(DateTime.Now + ": Read TimeOut = " + sp.ReadTimeout + " Write TimeOut = " + sp.WriteTimeout);

         int offset = 0, bytesRead;
         int bytesExpected = response.Length;

         FM.writeErrorLog(DateTime.Now + ": start read");

         while (bytesExpected > 0 && (bytesRead = sp.Read(response, offset, bytesExpected)) > 0)
            {
              FM.writeErrorLog(DateTime.Now + ": read - " + offset);
              offset += bytesRead;
              bytesExpected -= bytesRead;
            }
        }
        catch (Exception err)
        {
           Console.WriteLine("ERROR Modbus Message to serial port ModBus: " + err);
           FM.writeErrorLog(DateTime.Now + " - " + "ERROR Modbus Message to serial port ModBus: " + err);
         }

  }

After trying the application I got the following output from the ErroLog.txt:

14/01/2016 17:18:17: ModBus Message Sent
14/01/2016 17:18:17: Read TimeOut = 20000 Write TimeOut = 20000
14/01/2016 17:18:18: start read
14/01/2016 17:18:38 - ERROR Modbus Message to serial port ModBus: System.TimeoutException: The operation has timed out.
   at System.IO.Ports.SerialStream.Read(Byte[] array, Int32 offset, Int32 count, Int32 timeout)
   at System.IO.Ports.SerialStream.Read(Byte[] array, Int32 offset, Int32 count)
   at System.IO.Ports.SerialPort.Read(Byte[] buffer, Int32 offset, Int32 count)
   at ProbReader.SensorReader.modbusReading(List`1 mm, Int32 spCounter)
14/01/2016 17:18:38: 0
14/01/2016 17:18:38: 0

I have increased the timeout to 60sec just in case but same error:

15/01/2016 11:11:51: ModBus Message Sent
15/01/2016 11:11:51: Read TimeOut = 60000 Write TimeOut = 60000
15/01/2016 11:11:51: start read
15/01/2016 11:12:51 - ERROR Modbus Message to serial port ModBus: System.TimeoutException: The operation has timed out.
   at System.IO.Ports.SerialStream.Read(Byte[] array, Int32 offset, Int32 count, Int32 timeout)
   at System.IO.Ports.SerialStream.Read(Byte[] array, Int32 offset, Int32 count)
   at System.IO.Ports.SerialPort.Read(Byte[] buffer, Int32 offset, Int32 count)
   at ProbReader.SensorReader.modbusReading(List`1 mm, Int32 spCounter)
15/01/2016 11:12:51: 0
15/01/2016 11:12:51: 0

I have tried a few different ways of reading the serial port, I think the current method looks the best which is the while loop in my reading code.

I did not include the rest of my code as it times out before and I think it is irrelevant.

回答1:

If you are using hundreds of metres of serial cable (which is a hardware engineering problem in itself) then I strongly recommend having a proper transceiver on both ends of the cable. The cable itself should be EMC shielded and high-quality. Long runs of unshielded cable can be victims of induced voltage fluctuations that may damage equipment that is not designed to handle long cable runs.

Even with good cable you'll still have considerable voltage drops and inductive / capacitive effects that may prevent communication at higher baud rates. Run at the lowest baud rate you can get away with.



回答2:

Assuming it is a hardware issue (and I guess it is, I had to solve a similar problem too), I would recommend to replace the hundreds of metres of serial cable by a device server (via ethernet) next to the serial device. The device server can emulate a COM-Port on your PC and therefore keep the serial cable short.

Unfortunately those servers are a little more expensive than a few metres of cable..



回答3:

Please note that a difference between RS232 and RS485 is that RS232 is full duplex while RS485 is only half duplex. Since modbus is a request response type protocol this is not your problem but it's important to know.

Due to this a RS232<->RS485 have to know when to turn on it's RS485 transmitter. This can be done differently on different converters and can also be configurable. This could be done with the additional control lines that RS232 has but RS485 lacks. RTS/CTS. If wrongly configured the responding side's (that wait for a request to respond to) transmitter could be on and then it cannot receive anything.

An example in a manual is http://ftc.beijer.se/files/C125728B003AF839/992C59EC02C66E00C12579C60051484E/westermo_ug_6617-2203_mdw-45.pdf This is a popular model in sweden that has three modes of operation. It can turn on/off the transmitter just by incoming data, controlled by the RTS pin on the DB9-RS232 connector or have the transmitter always on (for RS422 that have separate wires in each direction) http://screencast.com/t/KrkEw13J8

This is hard because some manufactorers doesn't print out how it actually works. It's also extreamly easy to get the wiring wrong because it's not clear what's DCE and DTE.



回答4:

Assuming this is not a hardware/long cable issue, you might be able do something in your code to handle the error:

You need to create a "right" error handler as much as you create getRightPort:

SerialPort sp = getRightPort();

Assuming there is a Collection of serial port items inside and you return the right one, in the case of this SerialPort has error, make sure you re-create the error SerialPort object with the same settings:

catch (Exception err)
{
   Console.WriteLine("ERROR Modbus Message to serial port ModBus: " + err);
   FM.writeErrorLog(DateTime.Now + " - " + "ERROR Modbus Message to serial port ModBus: " + err);
   reinitRightPort(sp); //add this
}

And the method reinitRightPort(); may look similar to how you first initiate, with small differences like:

  1. You do not need to add this in the List<SerialPort> anymore
  2. You do not to declare SerialPort in the method, you get it from the input argument.
  3. It may be best to introduce some input check validity to avoid later known errors
  4. Perhaps you may close the previous connection, just to make sure that the port can be used by the new SerialPort object.

Something like this:

private void reinitRightPort(SerialPort sp){ //get SerialPort from outside of method
    //Add whatever necessary here, to close all the current connections
    //Plus all the error handlings
    if (sp == null) //Read 3.
        return;
    sp.Close(); //Read 4. close the current connections

    //get the right modbus data structure element
    ModBus MB = (ModBus)s[0].sensorData;

    //set up the serial port regarding the data structure's data
    sp = new SerialPort(); //Read 2. sp is from outside the method, but use new keyword still
    sp.PortName = MB.portName;
    sp.BaudRate = Convert.ToInt32(MB.baudRate);
    sp.DataBits = MB.dataBits;
    sp.Parity = MB.parity;
    sp.StopBits = MB.stopBits;
    //Set time outs 20 sec for now
    sp.ReadTimeout = 20000;
    sp.WriteTimeout = 20000;

    //portList.Add(sp); Read 1. no need to add this! It is already there!
    sp.Open();
}

Note: after you do that and make sure that your port is working well, whenever possible, you could also combine the reinitRightPort method above with your real initialization with some little modifications. But the first thing you may want to do is to get the your serial port works under error.

But if the error source comes from hardware/long cable issue (such as cable placing in the RS232 or RS485, or voltage drop due to long cable, or incompatible hardware: that is, in short, nothing to do with coding at all), then, unfortunately the solution cannot come from code as well. You got to find the real hardware issue.



回答5:

As I have mentioned in my question I was able to read the hardware with other software so it needed to be a software error. After investigation all the possible variables I could manipulate at the serial port setting I have come up with the idea of turning of the handshake and letting it to be always accepted.

A little bit of digging around give my the following code with increasing the write and read time out. It solved my issue:

                            sp.ReadTimeout = 60000;
                            sp.WriteTimeout = 60000;

                            sp.DtrEnable = true;
                            sp.RtsEnable = true;
                            sp.Handshake = Handshake.None;

I hope it will helps for others in the future, and thanks for everyone help and effort.