Тайм-аут последовательной связи при длительном тайм-ауте кабеля
У меня есть приложение, которое читает различные аппаратные средства через RS232. Это было проверено, и это работало отлично. Для окончательного применения мне нужно было представить кабель длиной несколько метров, что означает, что у меня есть преобразователи RS485.
Когда я запускаю свое приложение для чтения оборудования, я получаю ошибку тайм-аута для System.IO.Ports.SerialStream.Read. Я увеличил тайм-аут до 20 секунд, к сожалению, это не решило проблему
Я пробовал разные приложения для чтения оборудования, и они работали даже с частотой считывания 1сек.
Для связи используется протокол Modbus, который на текущем этапе, я полагаю, не имеет значения, так как я не попадаю на сцену, чтобы что-то получить.
Мой код выглядит так: сначала открытие и инициализация последовательного порта:
//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;
// добавляем порт в список, к которому может обращаться ридер portList.Add(sp); sp.Open();
Читать аппаратное обеспечение:
//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);
}
}
После попытки применения я получил следующий вывод из файла 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
Я увеличил тайм-аут до 60 секунд на всякий случай, но та же ошибка:
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
Я пробовал несколько разных способов чтения последовательного порта, я думаю, что текущий метод выглядит лучше, чем цикл while в моем коде чтения.
Я не включил остальную часть своего кода, как это было раньше, и я думаю, что это не имеет значения.
5 ответов
Как я уже упоминал в своем вопросе, я мог читать аппаратное обеспечение с другим программным обеспечением, поэтому оно должно было быть программной ошибкой. После исследования всех возможных переменных, которыми я мог манипулировать при настройке последовательного порта, мне пришла в голову идея повернуть рукопожатие и позволить его всегда принимать.
Немного покопавшись, приведу следующий код с увеличением времени записи и чтения. Это решило мою проблему:
sp.ReadTimeout = 60000;
sp.WriteTimeout = 60000;
sp.DtrEnable = true;
sp.RtsEnable = true;
sp.Handshake = Handshake.None;
Я надеюсь, что это поможет другим в будущем, и спасибо всем за помощь и усилия.
Если вы используете сотни метров последовательного кабеля (что само по себе является проблемой аппаратного обеспечения), тогда я настоятельно рекомендую иметь подходящий трансивер на обоих концах кабеля. Сам кабель должен быть ЭМС-экранированным и качественным. Длинные отрезки неэкранированного кабеля могут стать жертвами вызванных колебаний напряжения, которые могут повредить оборудование, не предназначенное для обработки длинных отрезков кабеля.
Даже при наличии хорошего кабеля у вас все равно будут значительные падения напряжения и индуктивные / емкостные эффекты, которые могут помешать обмену данными с более высокой скоростью передачи. Работайте с самой низкой скоростью передачи, с которой вы можете сойти.
Предполагая, что это аппаратная проблема (и я думаю, что мне пришлось решать аналогичную проблему), я бы рекомендовал заменить сотни метров последовательного кабеля сервером устройств (через Ethernet) рядом с последовательным устройством. Сервер устройств может эмулировать COM-порт на вашем компьютере и, следовательно, держать короткий кабель последовательного порта.
К сожалению, эти серверы немного дороже, чем несколько метров кабеля.
Обратите внимание, что разница между RS232 и RS485 заключается в том, что RS232 является дуплексным, а RS485 - только полудуплексным. Поскольку Modbus - это протокол типа ответа на запрос, это не ваша проблема, но важно знать.
В связи с этим RS232<->RS485 должен знать, когда включать передатчик RS485. Это может быть сделано по-разному на разных конвертерах, а также может быть конфигурируемым. Это можно сделать с помощью дополнительных линий управления, которые есть у RS232, но нет у RS485. RTS/CTS. Если неправильно настроен передатчик отвечающей стороны (ожидающий ответа), он может быть включен, и тогда он не сможет ничего получить.
Примером в руководстве является http://ftc.beijer.se/files/C125728B003AF839/992C59EC02C66E00C12579C60051484E/westermo_ug_6617-2203_mdw-45.pdf Это популярная в Швеции модель с тремя режимами работы. Он может включать / выключать передатчик только по поступающим данным, управляется выводом RTS на разъеме DB9-RS232 или всегда держать передатчик включенным (для RS422, которые имеют отдельные провода в каждом направлении) http://screencast.com/t/KrkEw13J8
Это сложно, потому что некоторые производители не распечатывают, как это на самом деле работает. Также очень легко ошибиться в проводке, потому что не ясно, что такое DCE и DTE.
Предполагая, что это не проблема аппаратного / длинного кабеля, вы могли бы сделать что-то в своем коде для обработки ошибки:
Вам нужно создать "правильный" обработчик ошибок столько, сколько вы создаете getRightPort
:
SerialPort sp = getRightPort();
Предполагая, что есть Collection
элементов последовательного порта внутри, и вы возвращаете правильный, в случае этого SerialPort
есть ошибка, убедитесь, что вы воссоздаете ошибку SerialPort
object
с такими же настройками:
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
}
И метод reinitRightPort();
может выглядеть примерно так, как вы начинаете, с небольшими отличиями, такими как:
- Вам не нужно добавлять это в
List<SerialPort>
больше не - Вы не должны объявлять
SerialPort
в методе вы получаете его из входного аргумента. - Может быть лучше ввести некоторую достоверность проверки ввода, чтобы избежать более поздних известных ошибок
- Возможно, вы можете закрыть предыдущее соединение, просто чтобы убедиться, что порт может использоваться новым
SerialPort
object
,
Что-то вроде этого:
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();
}
Примечание: после того, как вы это сделаете и убедитесь, что ваш порт работает нормально, по возможности вы также можете объединить reinitRightPort
метод выше с вашей реальной инициализацией с небольшими изменениями. Но первое, что вы можете сделать, это заставить ваш последовательный порт работать при ошибке.
Но если источник ошибки происходит из-за проблем с оборудованием / длинным кабелем (например, из-за размещения кабеля в RS232 или RS485, или из-за падения напряжения из-за длинного кабеля, или из-за несовместимого оборудования: в общем, это никак не связано с кодированием), тогда, к сожалению, решение не может прийти и из кода. Вы должны найти реальную аппаратную проблему.