c# - 利用两个串行端口(本机 RS485 + USB RS232)的应用程序接收延迟响应
问题描述
我正在使用一个遗留应用程序(MSVC19,Windows 10 上带有 .NET 4.5.2 的 C#),它用作一个无趣设备的前端。为了方便与设备的通信,建立了两个串行连接。一个串行 RS485(115200 波特 8N1)到 MCU 板和一个连接第三方传感器平台的 USB FTDI RS232 到 TTL(9600 波特 8N1)适配器。
我试图诊断的具体问题是,在另一个串行端口上发生任何通信后不久,应用程序正在经历来自通过 USB 串行端口连接的传感器平台的延迟响应时间。通常,传感器平台的响应时间在 10 到 100 毫秒之间是一致的。在性能不佳的设置中,响应延迟可能长达 20 秒。
为了隔离问题,我已将所有通信提取到一个最小的应用程序中。我还用基于 arduino 系统的诱饵替换了 MCU 和传感器平台,以确保 MCU 或传感器平台都不是原因。
在采取这些预防措施之后,我可以通过本机 RS485 端口连接设备的 MCU 诱饵来触发我的测试代码问题。我可以通过 USB RS485 收发器连接 MCU 诱饵来防止这个问题。以下是显示该问题的日志摘录。
良好的设置(USB RS485、USB RS232)
[27.11.2019 11:21:19] SENSOR: TX: Q
[27.11.2019 11:21:19] SENSOR: RX: [Z 00086 z 00083]
[27.11.2019 11:21:19] BaseMessage: Message completed: ( 15,6 ms) [Q]
[27.11.2019 11:21:20] MCU: TX: $;version;*
[27.11.2019 11:21:20] MCU: RX: [$;version;pass;MCU;Proto4;Hw2;*]
[27.11.2019 11:21:20] BaseMessage: Message completed: ( 15,6 ms) [$;version;*]
[27.11.2019 11:21:21] SENSOR: TX: Q
[27.11.2019 11:21:21] SENSOR: RX: [Z 00086 z 00089]
[27.11.2019 11:21:21] BaseMessage: Message completed: ( 15,6 ms) [Q]
[27.11.2019 11:21:22] SENSOR: TX: Q
[27.11.2019 11:21:22] SENSOR: RX: [Z 00085 z 00076]
[27.11.2019 11:21:22] BaseMessage: Message completed: ( 31,2 ms) [Q]
[27.11.2019 11:21:23] SENSOR: TX: Q
[27.11.2019 11:21:23] SENSOR: RX: [Z 00085 z 00089]
[27.11.2019 11:21:23] BaseMessage: Message completed: ( 31,2 ms) [Q]
[27.11.2019 11:21:24] SENSOR: TX: Q
[27.11.2019 11:21:24] SENSOR: RX: [Z 00085 z 00079]
[27.11.2019 11:21:24] BaseMessage: Message completed: ( 15,6 ms) [Q]
[27.11.2019 11:21:25] MCU: TX: $;temp;*
[27.11.2019 11:21:25] MCU: RX: [$;temp;pass;55;0;0;0;0;0;*]
[27.11.2019 11:21:25] BaseMessage: Message completed: ( 15,6 ms) [$;temp;*]
[27.11.2019 11:21:26] SENSOR: TX: Q
[27.11.2019 11:21:26] SENSOR: RX: [Z 00086 z 00094]
[27.11.2019 11:21:26] BaseMessage: Message completed: ( 31,2 ms) [Q]
性能不佳的设置(本机板载 RS485、USB RS232)
[27.11.2019 11:19:56] SENSOR: TX: Q
[27.11.2019 11:19:56] SENSOR: RX: [Z 00088 z 00084]
[27.11.2019 11:19:56] BaseMessage: Message completed: ( 15,6 ms) [Q]
[27.11.2019 11:19:57] MCU: TX: $;version;*
[27.11.2019 11:19:57] MCU: RX: [$;version;pass;MCU;Proto4;Hw2;*]
[27.11.2019 11:19:57] BaseMessage: Message completed: ( 0,0 ms) [$;version;*]
[27.11.2019 11:19:58] SENSOR: TX: Q
[27.11.2019 11:20:06] SENSOR: RX: [Z 00088 z 00095]
[27.11.2019 11:20:06] BaseMessage: Message completed: ( 7997,9 ms) [Q]
[27.11.2019 11:20:07] SENSOR: TX: Q
[27.11.2019 11:20:07] SENSOR: RX: [Z 00088 z 00090]
[27.11.2019 11:20:07] BaseMessage: Message completed: ( 31,2 ms) [Q]
[27.11.2019 11:20:08] SENSOR: TX: Q
[27.11.2019 11:20:08] SENSOR: RX: [Z 00089 z 00097]
[27.11.2019 11:20:08] BaseMessage: Message completed: ( 15,6 ms) [Q]
[27.11.2019 11:20:09] SENSOR: TX: Q
[27.11.2019 11:20:09] SENSOR: RX: [Z 00089 z 00089]
[27.11.2019 11:20:09] BaseMessage: Message completed: ( 31,2 ms) [Q]
[27.11.2019 11:20:10] MCU: TX: $;temp;*
[27.11.2019 11:20:10] MCU: RX: [$;temp;pass;55;0;0;0;0;0;*]
[27.11.2019 11:20:10] BaseMessage: Message completed: ( 0,0 ms) [$;temp;*]
[27.11.2019 11:20:11] SENSOR: TX: Q
[27.11.2019 11:20:16] SENSOR: RX: [Z 00089 z 00091]
[27.11.2019 11:20:16] BaseMessage: Message completed: ( 4967,8 ms) [Q]
[27.11.2019 11:20:17] SENSOR: TX: Q
[27.11.2019 11:20:17] SENSOR: RX: [Z 00089 z 00092]
[27.11.2019 11:20:17] BaseMessage: Message completed: ( 15,6 ms) [Q]
很明显,在性能不佳的设置中,每当向 MCU 平台发送消息时,传感器平台的下一个响应都会延迟几秒钟。我通过检查我的 arduino 诱饵的输出来验证是否及时收到了对传感器平台(“Q”)的请求消息,并立即发送了响应。因此,延迟必须发生在 PC 的接收路径或我的应用程序中。
我还尝试仅从应用程序连接传感器平台,然后使用终端执行与 MCU 平台的手动通信。在这种情况下,不会发生延迟。
由于这是我第一次连接基于 RS485 的串口,有什么需要注意的吗?我检查了计算机的 BIOS 中是否有任何其他设置,但除了协议(RS232/RS485)和通常的地址和中断号之外,什么都没有。
Window 对串行端口的处理是否存在任何已知错误?由于 USB 转换器的流行,很难用谷歌搜索这个问题。我不相信问题出在我的代码中,因为它在性能良好的开发设置中完美运行。
测试应用程序的来源如下;如果有人感兴趣,我也可以提供诱饵的来源。不幸的是,为了不超过帖子长度,我不得不删除很多评论和日志输出。如果有必要,我可以找到一个不同的地方来上传片段。
更新 1
我已经将最小样本修剪得更小,同时仍然表现出这种行为。我更改了 PC BIOS 设置,使 PC 现在在所有 COM 端口上使用 RS232,并使用外部 RS232<->RS485 桥将 MCU 连接到 COM1。然后我将一个 RS232<->TTL 转换器连接到 COM2 并将传感器连接到它。最终结果是不再涉及 USB 转换器,只是普通的旧板载 COM 端口。
我希望在 BIOS 中接近“默认”配置可以正常工作。但似乎并非如此。即使这样,延迟的响应仍然存在。
我还将所有连接的比特率更改为一致的 9600,以确保这不是比特率转换问题。
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
namespace MultiSerial
{
public class MultiSerialTest
{
public string TAG { get { return "Test"; } }
const int ciQueryInterval = 1000;
LogCollector logger;
SerialComm mcucomm;
COMSettings mcuComSettings;
SerialStreamDecoder mcuDecoder;
SerialComm Sensorcomm;
COMSettings SensorComSettings;
SerialStreamDecoder SensorDecoder;
ManualResetEvent evtSensorConnected;
ManualResetEvent evtMCUConnected;
public void run(string mcu_port, string sensor_port)
{
evtSensorConnected = new ManualResetEvent(false);
evtMCUConnected = new ManualResetEvent(false);
logger = new LogCollector();
logger.InitLog("CommTest.log");
mcuComSettings = new COMSettings(9600, Parity.None, 8, StopBits.One);
mcuDecoder = new SerialStreamDecoder(
MCUCommMessage.preamble,
MCUCommMessage.delimiter,
MCUCommMessage.terminator
);
mcucomm = new SerialComm(
mcuComSettings,
mcuDecoder,
VerifyMCU,
false,
logger,
mcu_port,
"MCU"
);
SensorComSettings = new COMSettings(9600, Parity.None, 8, StopBits.One);
SensorDecoder = new SerialStreamDecoder(
SensorCommMessage.preamble,
SensorCommMessage.delimiter,
SensorCommMessage.terminator
);
Sensorcomm = new SerialComm(
SensorComSettings,
SensorDecoder,
VerifySensor,
false,
logger,
sensor_port,
"SENSOR"
);
Sensorcomm.ConnectDevice();
mcucomm.ConnectDevice();
int counter = 0;
while (true)
{
if (counter == 5)
{
VersionRequest verReq = new VersionRequest(mcucomm);
verReq.Send();
verReq.AwaitCompletion();
counter = 0;
}
else
{
FetchRequest fetchReq = new FetchRequest(Sensorcomm);
fetchReq.Send();
fetchReq.AwaitCompletion();
}
counter++;
System.Threading.Thread.Sleep(ciQueryInterval);
}
}
void VerifySensor(SerialComm comm, out bool bSuccess)
{
comm.Log(TAG, "Servicing verification request for SensorComm");
FetchRequest fetchMessage = new FetchRequest(comm);
fetchMessage.Send();
fetchMessage.AwaitCompletion();
if (fetchMessage.result == 0)
{
comm.Log(TAG, "Verification for SensorComm successful");
bSuccess = true;
}
else
{
comm.Log(TAG, "Verification for SensorComm failed");
bSuccess = false;
}
return;
}
void VerifyMCU(SerialComm comm, out bool bSuccess)
{
comm.Log(TAG, "Servicing verification request for mcuComm");
VersionRequest versionMessage = new VersionRequest(comm);
versionMessage.Send();
versionMessage.AwaitCompletion();
if (versionMessage.result == 0)
{
comm.Log(TAG, "Verification for mcuComm successful");
bSuccess = true;
}
else
{
comm.Log(TAG, "Verification for mcuComm failed");
bSuccess = false;
}
return;
}
}
class Program
{
static void Main(string[] args)
{
MultiSerialTest test = new MultiSerialTest();
test.run(args[0], args[1]);
}
}
public class FetchRequest : SensorCommMessage
{
new string TAG { get { return "SensorComm:FetchRequest"; } }
// Request = "Q\r\n"
// Response = [<identifier><whitespace><value>]^n
public FetchRequest(SerialComm comm)
: base(comm,fetch_instruction)
{
}
protected override bool ParseReplyContents(List<string> tokens)
{
// Dummy validation
if (tokens.Count < 2 ||
tokens.Count % 2 != 0) { return false; }
return true;
}
}
public abstract class SensorCommMessage : BaseMessage
{
new string TAG { get { return "SensorCommMessage "; } }
public const char preamble_ch = 'Z';
public const char delimiter_ch = ' ';
public static readonly string preamble = preamble_ch.ToString();
public static readonly char delimiter = delimiter_ch;
public static readonly string terminator = "\r\n";
public const string fetch_instruction = "Q";
private string instructionToken;
private List<string> userTokens;
public SensorCommMessage(SerialComm _comm, string _instructionToken)
: base(_comm)
{
instructionToken = _instructionToken;
}
public override string ToWireString()
{
string message =
instructionToken;
if (userTokens != null)
{
foreach (string token in userTokens)
{
message += token;
message += delimiter;
}
}
return message;
}
public override bool ParseReplyFrame(List<string> replyTokens)
{
bool bHandled = false;
bHandled = ParseReplyContents(replyTokens);
if (bHandled) { MessageCompleted(); }
return bHandled;
}
protected abstract bool ParseReplyContents(List<string> replyContentTokens);
}
public class VersionRequest : MCUCommMessage
{
public string DeviceId { get { return strDeviceId; } }
string strDeviceId;
public string ProtocolVersion { get { return strProtocollVersion; } }
string strProtocollVersion;
public string HardwareVersion { get { return strHardwareVersion; } }
string strHardwareVersion;
// Request = $;version;*
// Response = $;version;pass;DeviceId;ProtocolVersion;HardwareVersion;*
public VersionRequest(SerialComm comm)
: base(comm,version_instruction)
{
}
protected override bool ParseReplyContents(List<string> tokens)
{
// Expect precisely 3 remaining tokens
if (tokens.Count != 3)
{
return false;
}
strDeviceId = tokens[0];
strProtocollVersion = tokens[1];
strHardwareVersion = tokens[2];
return true;
}
}
public class ParseHelper
{
public static bool ParseFloatToken(string strToken, float fMin, float fMax, out float fValue)
{
fValue = float.NaN;
if (float.TryParse(strToken, out fValue) &&
fValue >= fMin &&
fValue <= fMax)
{
return true;
}
return false;
}
public static bool ParseIntToken(string strToken, int iMin, int iMax, out int iValue)
{
iValue = 0;
if (int.TryParse(strToken, out iValue) &&
iValue >= iMin &&
iValue <= iMax)
{
return true;
}
return false;
}
}
public class SerialStreamDecoder
{
public string TAG { get { return "SerialStreamDecoder"; } }
public string preamble;
public char delimiter;
public string terminator;
public SerialStreamDecoder(string _preamble, char _delimiter, string _terminator)
{
preamble = _preamble;
delimiter = _delimiter;
terminator = _terminator;
}
public bool DecodeMessageFromStream(SerialComm comm, out string message, out List<string> tokens)
{
string strRx = "";
message = "";
tokens = null;
// Read until terminator
strRx = comm.usart.ReadTo(terminator);
// Re-append terminator because it does not get included in the read
strRx += terminator;
// If a preamble is set, scan input sequence for preamble
int preampleIndex = 0;
if ( preamble != "" &&
( preampleIndex = strRx.IndexOf(preamble)) > 0)
{
strRx = strRx.Substring(preampleIndex, strRx.Length - preampleIndex);
}
message = strRx;
tokens = strRx.Split(delimiter).ToList<string>();
return true;
}
}
public abstract class MCUCommMessage : BaseMessage
{
public string TAG { get { return "MCUCommMessage "; } }
public const char preamble_ch = '$';
public const char delimiter_ch = ';';
public const char terminator_ch = '*';
public static readonly string preamble = preamble_ch.ToString();
public static readonly char delimiter = delimiter_ch;
public static readonly string terminator = terminator_ch.ToString();
public const string version_instruction = "version";
public const string okay_result = "pass";
public const string fail_result = "fail";
private string instructionToken;
private List<string> userTokens;
public MCUCommMessage(SerialComm _comm, string _instructionToken )
: base(_comm)
{
instructionToken = _instructionToken;
}
public override string ToWireString()
{
string message = preamble + delimiter + instructionToken + delimiter;
if (userTokens != null)
{
foreach (string token in userTokens)
{
message += token;
message += delimiter;
}
}
message += terminator;
return message;
}
public override bool ParseReplyFrame(List<string> replyTokens)
{
bool bHandled = false;
string lastToken = replyTokens.ElementAt(replyTokens.Count - 1);
if (replyTokens.Count < 4 ||
replyTokens[0] != preamble ||
replyTokens[1] != instructionToken ||
lastToken != terminator)
{
return false;
}
if (!bHandled &&
replyTokens[2] == fail_result &&
replyTokens.Count == 6
)
{
ParseHelper.ParseIntToken(replyTokens[4], int.MinValue, int.MaxValue, out result);
bHandled = true;
}
if (!bHandled &&
replyTokens[2] == okay_result)
{
List<string> payloadTokens = replyTokens.GetRange(3, replyTokens.Count - 4);
bHandled = ParseReplyContents(payloadTokens);
}
if (bHandled)
{
MessageCompleted();
}
return bHandled;
}
protected abstract bool ParseReplyContents(List<string> replyContentTokens);
public void AddTokens(IEnumerable<string> tokens)
{
if (userTokens == null) userTokens = new List<string>();
foreach (string token in tokens)
{
userTokens.Add(token);
}
}
}
public abstract class BaseMessage
{
public string TAG
{
get { return "BaseMessage"; }
}
public ManualResetEvent evtMessageRelease;
public delegate void MessageCompletedEventHandler();
public event MessageCompletedEventHandler EvtMessageCompleted;
protected SerialComm comm;
DateTime timestampRequest;
DateTime timestampReply;
public int result;
public BaseMessage(
SerialComm _comm
)
{
comm = _comm;
evtMessageRelease = new ManualResetEvent(false);
}
public int Send()
{
timestampRequest = DateTime.Now;
comm.TransmitSync(this);
return result;
}
public void AwaitCompletion()
{
evtMessageRelease.WaitOne();
}
public abstract string ToWireString();
protected void MessageCompleted()
{
timestampReply = DateTime.Now;
TimeSpan duration = timestampReply - timestampRequest;
comm.Log(TAG, "Message completed: (" + string.Format("{0,8:F1} ms", duration.TotalMilliseconds) + ") [" + this.ToWireString() + "]");
if (EvtMessageCompleted != null)
EvtMessageCompleted();
CleanupMessage();
}
private void CleanupMessage()
{
evtMessageRelease.Set();
}
public abstract bool ParseReplyFrame(List<string> tokens);
}
public class LogCollector
{
StreamWriter streamwriter;
public void InitLog(string file)
{
Directory.CreateDirectory(".\\logs");
FileStream filestream = new FileStream(".\\logs\\" + file, FileMode.Create);
streamwriter = new StreamWriter(filestream);
streamwriter.AutoFlush = false;
}
public void Log(string tag, string message)
{
string logMessage = "[" + DateTime.Now.ToString() + "] " + tag + ": " + message;
streamwriter.WriteLine(logMessage);
streamwriter.Flush();
Console.WriteLine(logMessage);
}
}
public class COMSettings
{
public int baudrate;
public Parity parity;
public int databits;
public StopBits stopbits;
public COMSettings(int _baudrate, Parity _parity, int _databits, StopBits _stopbits)
{
baudrate = _baudrate;
parity = _parity;
databits = _databits;
stopbits = _stopbits;
}
}
public class SerialComm
{
public string TAG { get { return strTag; } }
string strTag;
LogCollector logger;
public void Log(string tag, string message)
{
if (logger != null)
logger.Log(tag, message);
}
COMSettings comSettings;
public SerialPort usart;
private string preferredPort;
private BaseMessage currentMessage = null;
private SerialStreamDecoder decoder;
public delegate void VerifyDeviceResponseDelegate(SerialComm comm, out bool bSuccess);
private event VerifyDeviceResponseDelegate DeviceResponseVerification;
public delegate void MessageReceivedDelegate();
public event MessageReceivedDelegate EvtMessageReceived;
Thread connectThread;
public SerialComm(
COMSettings _comSettings,
SerialStreamDecoder _decoder,
VerifyDeviceResponseDelegate d,
bool _bReconnectEnabled,
LogCollector _logger,
string _preferredPort,
string tag = "SerialComm"
)
{
comSettings = _comSettings;
decoder = _decoder;
DeviceResponseVerification = d;
logger = _logger;
preferredPort = _preferredPort;
strTag = tag;
}
public bool ConnectDevice()
{
bool bDeviceVerified = false;
try
{
usart =
new SerialPort(
preferredPort,
comSettings.baudrate,
comSettings.parity,
comSettings.databits,
comSettings.stopbits
);
usart.Open();
usart.DataReceived += new SerialDataReceivedEventHandler(OnDataReceived);
DeviceResponseVerification(this, out bDeviceVerified);
}
catch (Exception e)
{
bDeviceVerified = false;
}
if (!bDeviceVerified)
{
if (usart != null)
{
usart.Close();
usart.DataReceived -= OnDataReceived;
usart = null;
}
}
return usart != null;
}
/* OnDataReceived
* Handles received serial communication
*/
private void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
// Read until terminator
if (usart == null) return;
List<string> tokens = null;
string message = "";
if (!decoder.DecodeMessageFromStream(this, out message, out tokens))
{
return;
}
Log(TAG, "RX: [" + message.Trim() + "]");
bool bHandled = false;
// Check if we received a response to the current message
if (!bHandled && this.currentMessage != null)
{
bHandled = currentMessage.ParseReplyFrame(tokens);
}
if (bHandled)
{
if (EvtMessageReceived != null)
EvtMessageReceived();
}
}
catch (Exception ex)
{
}
}
public void TransmitSync(BaseMessage message)
{
currentMessage = message;
WriteCurrentMessage();
}
public void WriteCurrentMessage()
{
usart.Write(currentMessage.ToWireString() + "\r\n");
Log(TAG, "TX: " + currentMessage.ToWireString());
}
}
}
解决方案
推荐阅读
- pandas - 如何设置 Spark 以使用由 anaconda 管理的 pandas?
- mongodb - 如何将 MongoDB 图表嵌入到应用程序中
- haskell - Haskell:将整数列表的字符串表示形式转换为整数列表
- r - 如何在 R 数据框中的列中添加双引号和逗号?
- android - Android Studio 3.2.1:加载旧项目后,布局预览不适用于所有项目
- java - Jasper 在运行批量请求时报告错误
- c - 阅读编译错误报告格式:format_io.c:3:10:
- reactjs - 导入 css 和 sass 文件 nextjs 7
- java - Android 预览版无法转换为模拟器
- python - “用户名被复制!” 在 text.after(3000, ...) 之前未显示 text.configure(text="Username 已复制!")