首页 > 解决方案 > 利用两个串行端口(本机 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());
        }
    }

}

标签: c#windowsserial-portrs485

解决方案


推荐阅读