串口响应EventHandler但不响应ReadExisting或ReadLine?

5
我有一个使用C#从串口读取数据的程序。我需要快速写入端口,从它读取数据,然后关闭它。我不能让它一直开着。我知道串口读写速度很慢,我已经尝试将ReadTimeout和WriteTimeout属性设置得很高,并添加了一个thread.Sleep来延长设备的读写时间。以下是一小部分代码:
我的写入端口方法:
    private void CheckPorts(string testMessage)
    {

        foreach (string s in SerialPort.GetPortNames())
        {
            portNumber = Int32.Parse(s.Remove(0, 3));
            testSerial = new SerialPort(s, baudRate, Parity.None, 8, StopBits.One);
            if (testSerial.IsOpen)
            {
                testSerial.Close();
            }
            testSerial.ReadTimeout = 2000;
            testSerial.WriteTimeout = 1000;
            testSerial.Open();
            if (testSerial.IsOpen)
            {
                string received;
                testSerial.DiscardInBuffer();
                try
                {
                    //testSerial.DataReceived += new SerialDataReceivedEventHandler(testSerialPort_DataReceived);

                    testSerial.Write(testMessage);
                    System.Threading.Thread.Sleep(2000);

                    received = testSerial.ReadExisting();  //EITHER I USE THIS OR EVENT HANDLER, NOT BOTH
                }
                catch (TimeoutException e)
                {
                    testSerial.Close();
                    continue;
                }

               if (received.Length > 0)
                {
                    MessageReceived(received);
                }
                testSerial.Close();
            }
       } 
 }



 private void testSerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        string received = testSerial.ReadExisting();
        int y = received.IndexOf("\r");
        while (y == -1)
        {
            received = received + testSerial.ReadExisting();
            y = received.IndexOf("\r");
        }

        if (testSerial.IsOpen)
        {
            testSerial.Close();
        }

    }

我想知道,如果我必须使用DataHandler,如何保持串口打开时间足够长以读取数据,但在下一个端口需要打开之前关闭串口?
首先,第一种方法会被调用多次,并通过foreach循环迭代,尝试在几个端口上发送消息,然后尝试读取响应。因此,在某些时候,我必须关闭端口,否则下一次它经过时,由于端口仍然打开,它无法正常工作。
这是我的更新代码(仍然无法正常工作):
 private void CheckPorts(string testMessage, int baudRate)
    {

        foreach (string s in SerialPort.GetPortNames())
        {
            var interval = 3000; // ms 
            var timer = new System.Timers.Timer(interval);
            timer.Elapsed += (o, e) =>
            {
                timer.Enabled = false;

                if (testSerial.IsOpen)
                    testSerial.Close();  // may not be necessary with Dispose? 

                testSerial.Dispose();
                timer.Dispose();
            };

            portNumber = Int32.Parse(s.Remove(0, 3));
            testSerial = new SerialPort(s, baudRate, Parity.None, 8, StopBits.One);
            testSerial.ReadTimeout = 2000;
            testSerial.WriteTimeout = 2000;
            if (testSerial.IsOpen)
            {
                testSerial.Close();
            }

            testSerial.Open();
            timer.Enabled = true; 

            if (testSerial.IsOpen)
            {
                string received;
                //testSerial.DiscardInBuffer();
                //autoEvent = new AutoResetEvent(false);
                try
                {
                   // testSerial.DataReceived += new SerialDataReceivedEventHandler(testSerialPort_DataReceived);

                  // autoEvent.Reset();
                    lblPortNum.Content = s;
                    lblPortNum.Refresh();

                    testSerial.Write(testMessage);
                    //System.Threading.Thread.Sleep(2000);

                    //testSerial.NewLine = "\r\n";
                    byte[] rBuff = new byte[2];
                    int rCnt = testSerial.Read(rBuff, 0, 2);
                    System.Text.Encoding enc = System.Text.Encoding.ASCII;
                    received = enc.GetString(rBuff);



                     //received = testSerial.ReadLine();
                }
                catch (TimeoutException e)
                {
                    testSerial.Close();
                    continue;
                }

               if (received.Length > 0)
               {
                    MessageReceived(received, Int16.Parse(s.Remove(0, 3)));
                }
                /*
                if (autoEvent.WaitOne(2000))
                {
                    // the port responded 
                   // testSerial.Close();
                    autoEvent.Dispose();
                    lblPortNum.Content = "HEY I RESPONDED";
                }
                else
                {
                    testSerial.Close();
                    autoEvent.Dispose();
                    continue;
                    // port did not respond within 2 seconds 
                }*/
              //testSerial.Close();
            }
        } 
     }

更新了(仍然无法正常工作)
private void CheckPorts(string testMessage, int baudRate)
    {

        foreach (string s in SerialPort.GetPortNames())
        {
            portNumber = Int32.Parse(s.Remove(0, 3));

            // MUST BE LOCAL 
            var serialOneOfMany = new SerialPort(s, baudRate, Parity.None, 8, StopBits.One);
            serialOneOfMany.ReadTimeout = 2000;
            serialOneOfMany.WriteTimeout = 2000;
            if (serialOneOfMany.IsOpen)
            {
                serialOneOfMany.Close();
            }

            // timer must be defined _after_ serialOneOfMany 
            var interval = 3000; // ms  
            var timer = new System.Timers.Timer(interval);
            timer.Elapsed += (o, e) =>
            {
                timer.Enabled = false;

                if (serialOneOfMany.IsOpen)
                    serialOneOfMany.Close();  // may not be necessary with Dispose?  

                serialOneOfMany.Dispose();
                timer.Dispose();
            };

            if (serialOneOfMany.IsOpen)
            {
                string received;

                try
                {
                    lblPortNum.Content = s;
                    lblPortNum.Refresh();

                    serialOneOfMany.Write(testMessage);
                    byte[] rBuff = new byte[2];
                    int rCnt = serialOneOfMany.Read(rBuff, 0, 2);
                    System.Text.Encoding enc = System.Text.Encoding.ASCII;
                    received = enc.GetString(rBuff);

                }
                catch (TimeoutException e)
                {
                    serialOneOfMany.Close();
                    continue;
                }

                if (received.Length > 0)
                {
                    CheckIfTheMessageMatches(received, Int16.Parse(s.Remove(0, 3)));
                }

            }
        } 

    }

所以,通过这个更新,它只是快速执行代码,我可以逐行调试代码,但它根本不会停止3秒钟。如果我没有任何调试中断地运行它,它只需要不到一秒的时间就能执行完。

更新日期:2011年10月25日

 private void CheckPorts(string testMessage, int baudRate)
    {
        foreach (string s in SerialPort.GetPortNames())
        {
            string received = "";
            testSerial = new SerialPort(s,baudRate, Parity.None, 8, StopBits.One);

            lblStatus.Content = "Scanning...";
            lblStatus.Refresh();

            if (testSerial.IsOpen)
            {
                testSerial.Close();
            }
            else
            {
                testSerial.Open();
            }

            if (testSerial.IsOpen)
            {
                try
                {
                    testSerial.NewLine = "\r";
                    lblPortNum.Content = s;
                    lblPortNum.Refresh();
                    testSerial.WriteTimeout= 500;
                    testSerial.ReadTimeout = 1000;
                    testSerial.WriteLine(testMessage);

                    System.Threading.Thread.Sleep(500);

                    /*THIS DOESN'T WORK
                    byte[] buffer = new byte[testSerial.BytesToRead];
                    int rCnt = testSerial.Read(buffer, 0, buffer.Length);
                    received = enc.GetString(buffer);*/

                    //received = Convert.ToString(testSerial.BaseStream.Read(buffer, 0, (int)buffer.Length));


                    received =  testSerial.ReadLine();


                   int y = received.IndexOf("\r");
                   while (y == -1)
                   {
                       received = received + testSerial.ReadExisting();
                       y = received.Length;
                   }

                   if (lblInfo.Dispatcher.Thread == Thread.CurrentThread)
                   {
                       CheckIfTheMessageMatches(received, s);
                       received = received + lblInfo.Content;
                       lblInfo.Content = received;
                   }
                   else
                   {
                       lblInfo.Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadCheck(threadCheck), received);
                   }
                   if (testSerial.IsOpen)
                   {
                       testSerial.Close();
                   }

                    /*I USE THIS WITH THE sPort.Read() METHOD
                    while (rCnt > 0)
                    {
                        if (lblInfo.Dispatcher.Thread == Thread.CurrentThread)
                        {
                            CheckIfTheMessageMatches(received, s);
                            rCnt = 0;
                            received = received + lblInfo.Content;
                            lblInfo.Content = received;                                
                        }

                        else
                        {
                            lblInfo.Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadCheck(threadCheck), received);
                        }
                    }
                     */

                   if (testSerial.IsOpen)
                   {
                       testSerial.Close();
                   }

                }
                catch (TimeoutException e)
                {
                    testSerial.Close();
                    continue;
                }
                received = null;
            }
        } 

        lblStatus.Content = "Finished Scanning.";
        lblPortNum.Content = "";
    }

更新的代码 这里有一些新的代码,仍然无法工作,DataEventHandler甚至没有被调用一次。我知道它正在接收消息,因为我有另一个可以与串行设备一起使用的程序。

private void CheckPorts(string testMessage, int baudRate)
    {
        foreach (string s in SerialPort.GetPortNames())
        {
            var serialOneOfMany = new SerialPort(s, baudRate, Parity.None, 8, StopBits.One);
            serialOneOfMany.ReadTimeout = 700;
            serialOneOfMany.WriteTimeout = 100;

            var interval = 500; // ms
            var timer = new System.Timers.Timer(interval);
            timer.Elapsed += (o, e) =>
            {
                timer.Enabled = false;

                if (serialOneOfMany.IsOpen)
                    serialOneOfMany.Close();  // may not be necessary with Dispose?

                serialOneOfMany.Dispose();
                timer.Dispose();
            };
            timer.Enabled = true;

            lblStatus.Content = "Scanning...";
            lblStatus.Refresh();

            if (serialOneOfMany.IsOpen)
            {
                serialOneOfMany.Close();
            }
            else
            {
                serialOneOfMany.Open();
            }

            if (serialOneOfMany.IsOpen)
            {
                string received;

                try
                {
                    lblPortNum.Content = s;
                    lblPortNum.Refresh();

                    serialOneOfMany.WriteLine(testMessage);
                    System.Threading.Thread.Sleep(400);
                    serialOneOfMany.DataReceived += new SerialDataReceivedEventHandler(testSerialPort_DataReceived);

                }
                catch (TimeoutException e)
                {
                    serialOneOfMany.Close();
                    continue;
                }
            }
        } 

        lblStatus.Content = "Finished Scanning.";
        lblPortNum.Content = "";
    }

    private void testSerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        SerialPort receivingSerial = sender as SerialPort;
        string received = receivingSerial.ReadExisting();
        int y = received.IndexOf("\r");
        while (y == -1)
        {
            received = received + receivingSerial.ReadExisting();
            y = received.IndexOf("\r");
        }

        if (lblInfo.Dispatcher.Thread == Thread.CurrentThread)
        {
            string name = receivingSerial.PortName;
            received = received + lblInfo.Content;
            lblInfo.Content = received;
            CheckIfTheMessageMatches(received, name);
        }
        else
        {
            lblInfo.Dispatcher.Invoke(DispatcherPriority.Normal, new ThreadCheck(threadCheck), received);
        } 
        if (receivingSerial.IsOpen)
        {
            receivingSerial.Close();
        }

    }

当您使用ReadExisting时会发生什么?TimeoutException会被触发吗? - Ignacio Soler Garcia
实际上什么也没有发生。当我在那里设置断点时,它不会读取任何值。但在事件处理程序中它有效。 - darthwillard
实际上,当我在messagereceived方法处设置断点时,它只有在接收到响应时才会调用该方法。 - darthwillard
你能解释一下为什么不能让串口保持“打开”状态吗?这段代码在你的应用程序中必须满足哪些功能需求? - ChrisBD
7个回答

4

假设这是可以的,您应该能够同时执行这些操作。然后在触发DataReceived事件时关闭它们(删除不必要的代码)。只是不要在CheckPorts中关闭端口。

private void testSerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    SerialPort localSerialPort = sender as SerialPort;

    ... // use localSerialPort instead of global/class variable

    if (localSerialPort.IsOpen)
    {
        localSerialPort.Close();
    }
}
编辑:回应评论。
你可以随时在程序中添加定时器。如果你将下面的代码放在foreach循环中,你将为每个串行端口获得一个计时器,并且这个计时器会在3秒后关闭其对应的串行端口。重要的是,在foreach循环内部声明该计时器。
var interval = 3000; // ms
var timer = new System.Timers.Timer(interval);
timer.Elapsed += (o,e) => 
    {
        timer.Enabled = false;

        if (testSerial.IsOpen)
            testSerial.Close();  // may not be necessary with Dispose?

        testSerial.Dispose();
        timer.Dispose();
    }

timer.Enabled = true;

编辑:代码已更新,因此我将进行更新

范围对于我提供的代码非常重要。您应该摆脱非本地testSerial或在此处使用完全不同的名称。

        portNumber = Int32.Parse(s.Remove(0, 3));

        // MUST BE LOCAL
        var serialOneOfMany = new SerialPort(s, baudRate, Parity.None, 8, StopBits.One);
        serialOneOfMany.ReadTimeout = 2000;
        serialOneOfMany.WriteTimeout = 2000;
        if (serialOneOfMany.IsOpen)
        {
            serialOneOfMany.Close();
        }

        // timer must be defined _after_ serialOneOfMany
        var interval = 3000; // ms 
        var timer = new System.Timers.Timer(interval);
        timer.Elapsed += (o, e) =>
        {
            timer.Enabled = false;

            if (serialOneOfMany.IsOpen)
                serialOneOfMany.Close();  // may not be necessary with Dispose? 

            serialOneOfMany.Dispose();
            timer.Dispose();
        };     

但是,如果我迭代一个完全没有响应的端口,那么它就根本没有关闭,这就是问题所在。我需要一种方法,在如果敲门时没有人回答时关闭该端口,例如发生ReadTimeout。 - darthwillard
我尝试了上述两种方法。如果我分配一个新值,例如localserialport,它不起作用。我尝试了另一种方法(我认为是最好的方法),但是当我试图写入端口时,它会显示端口已关闭。我在方法的开头定义它,并在调用testSerial.Open后立即添加timer.enabled。我是否将它们放在正确的位置? - darthwillard
@darthwillard:您需要发布您的最新实现,以获取有关放置的建议。 - Austin Salonen
@darthwillard:你从未重新打开串口或启用计时器... - Austin Salonen
我应该把timer.enable = true放在哪里? - darthwillard
@darthwillard:在你打开测试连接之后。 - Austin Salonen

1
请看一下这个(我也在答案中使用了,这是darthwillard提出的一个串口相关问题)。所有端口都是依次打开的,DataReceived事件被绑定(你只需要在那里测试传入的消息即可),但不需要等待。计时器事件处理程序可以关闭所有端口或保留您想要使用的端口等。希望能对您有所帮助!
private List<SerialPort> openPorts = new List<SerialPort>();

private void button3_Click(object sender, EventArgs e)
{
    int baudRate = 9600;
    string testMessage = "test";
    txtPortName.Text = "Testing all serial ports";
    foreach (string s in SerialPort.GetPortNames())
    {
        SerialPort newPort = new SerialPort(s, baudRate, Parity.None, 8, StopBits.One);
        if (!newPort.IsOpen)
        {
            try
            {
                newPort.Open();
            }
            catch { }
        }
        if (newPort.IsOpen)
        {
            openPorts.Add(newPort);
            newPort.DataReceived += new SerialDataReceivedEventHandler(serialOneOfMany_DataReceived);
            newPort.Write(testMessage);
        }
        else
        {
            newPort.Dispose();
        }
    }
    txtPortName.Text = "Waiting for response";
    tmrPortTest.Enabled = true;
}

private void serialOneOfMany_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    txtPortName.Text = ((SerialPort)sender).PortName;
}

private void tmrPortTest_Tick(object sender, EventArgs e)
{
    tmrPortTest.Enabled = false;
    foreach (SerialPort port in openPorts)
    {
        if (port.PortName != txtPortName.Text)
        {
            port.Close();
            port.Dispose();
        }
    }
}

1

请查看来自微软的信息:

此方法将串口对象的流和内部缓冲区作为字符串返回。此方法不使用超时。请注意,此方法可能会在内部缓冲区中留下尾随引导字节,这使得 BytesToRead 值大于零。

为什么不使用通常的 Read 方法 SerialPort.Read (Byte[], Int32, Int32)


除了 MSDN 对 SerialPort.Read 的定义外,我找不到一个将字符作为字符串返回的使用示例。有没有其他地方有这样的示例?另外,SerialPort.Read 方法是否会等待缓冲区填满才返回?因为我只需要在每个端口上查找 1 或 2 个字符。 - darthwillard
没事了,我已经搞定了。但是它还不能按照我的想法工作。还是谢谢你。 - darthwillard
当然。要从字节数组创建字符串,只需使用System.Text.ASCII.GetString(bytearray)。 - Ignacio Soler Garcia

0

你可能最好使用BackgroundWorker。例如:

        BackgroundWorker worker=new BackgroundWorker();
        worker.DoWork += (s, dwe) =>
                             {
        // do your serial IO here
        worker.RunWorkerCompleted += (s, rwe) =>
        {
            // check for rwe.Error and respond
        };
        worker.RunWorkerAsync();

0

你不能使用Thread.Sleep。它会阻塞设备的读取。你需要创建一个新线程。


0
尝试在写入端口之前设置事件处理程序,然后看看它是否能够捕获您的断点。

-1

在公共表单1中打开端口,就在InitializeComponent()之后/下面,myport.open,当数据接收完毕后关闭。成功了!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接