C#:读取串口 - BytesToRead

时间:2015-11-13 05:32:57

标签: c# serial-port

我正在修改一个基于C#的UI,它与一个小型PIC单片机测试仪设备接口。

UI由几个按钮组成,这些按钮通过串行端口连接向微控制器发送“命令”来启动测试。每250毫秒,UI轮询串行接口,查找由PIC的测试结果组成的简短消息。消息显示在文本框中。

我继承的代码如下:

try
{
    btr = serialPort1.BytesToRead;
    if (btr > 0)
        Thread.Sleep(300);
    btr = serialPort1.BytesToRead;
    if (btr > 0)
    {
        Thread.Sleep(300);
        btr = serialPort1.BytesToRead;

        numbytes = serialPort1.Read(stuffchar, 0, btr);
        for (x = 0; x < (numbytes); x++)
        {
            cc = (stuffchar[x]);
            stuff += Convert.ToString(Convert.ToChar((stuffchar[x])));
        }

在最终读取串口之前,前三行包括三次调用BytesToRead和两次300毫秒睡眠调用的理由是什么?除非我错误地解释代码,否则从串口读取任何成功的内容都需要600毫秒以上,这对我来说似乎很奇怪。

3 个答案:

答案 0 :(得分:4)

围绕SerialPort.Read()的行为是一个可怕的黑客攻击。它仅返回实际接收的字节数。通常只有1或2个,串口很慢,现代PC速度很快。因此,通过调用Thread.Sleep(),代码将UI线程延迟足够长的时间以获得Read()调用以返回 more 字节。希望所有这些,无论协议是什么样的。通常有效,但并非总是如此在发布的代码中,它没有工作,程序员只是任意延迟两倍。啊。

当然,最大的痛苦是UI线程在被迫睡眠时非常紧张。非常引人注目,绘画和响应用户输入的速度非常慢。

首先要注意协议,这需要修复。 PIC需要在其响应中发送固定数量的字节,因此您可以简单地将它们计数,或者让PC以某种方式检测是否已收到完整响应。通常通过发送唯一字节作为响应的最后一个字节(SerialPort.NewLine)或通过将响应的长度包含在消息开头的字节值来完成。具体的建议很难给出,你根本没有描述协议。

您可以保留hacky代码并将其移动到工作线程中,这样就不会对UI造成太大影响。您可以从SerialPort.DataReceived事件中免费获得一个。但这往往会产生两个问题,而不是解决核心问题。

答案 1 :(得分:0)

如果该代码最初处于循环中,则可能是等待PIC收集数据的一种方法。

如果你有真正的硬件要测试,我会消化你删除两个睡眠。

答案 2 :(得分:0)

@TomWr你是对的,从我读到的就是这种情况 您的评论下面的代码段:

try
{
    // Let's check how many bytes are available on the Serial Port
    btr = serialPort1.BytesToRead;

    // Something? Alright then, let's wait 300 ms.
    if (btr > 0)
        Thread.Sleep(300);

    // Let's check again that there are some bytes available the Serial Port
    btr = serialPort1.BytesToRead;

    // ... and if so wait (maybe again) for 300 ms 
    // Please note that, at that point can be all cumulated about 600ms 
    // (if we actually already waited previously)
    if (btr > 0)
    {
        Thread.Sleep(300);
        btr = serialPort1.BytesToRead;

        numbytes = serialPort1.Read(stuffchar, 0, btr);
        for (x = 0; x < (numbytes); x++)
        {
            // Seems like a useless overhead could directly use
            // an Encoding and ReadExisting method() of the SerialPort.
            cc = (stuffchar[x]);
            stuff += Convert.ToString(Convert.ToChar((stuffchar[x])));
        }

我的猜测与idstam上面已经提到的相同,基本上可能是检查设备是否发送数据并提取它们

您可以使用适当的SerialPort方法轻松地重构此代码,因为实际上有更好,更简洁的方法来检查串行端口上是否有数据可用。

而不是&#34;我正在检查端口上有多少字节,如果有什么东西,那么我等待300毫秒,然后再重复同样的事情。&#34;那是惨不忍睹的结局 &#34;所以是2次300毫秒= 600毫秒,或者只是一次(取决于是否第一次&#34;或者根本没有任何东西(取决于您通过此UI进行通信的设备,这可能是真的因为Thread.Sleep正在阻止UI ......)。#34;

首先让我们考虑一下你试图保留尽可能多的相同代码库,为什么不等待600毫秒呢?

或者为什么不只是使用ReadTimeout属性并捕获超时异常,不是那么干净,但至少在可读性方面更好,而且你直接得到你的String而不是使用一些Convert.ToChar()调用...... / p>

我感觉代码已经从C或C ++移植(或者至少是背后的理由)来自一个主要是嵌入式软件背景的人。

无论如何,回到可用字节数的检查,我的意思是除非在另一个Thread / BackgroundWorker / Task处理程序中刷新串行端口数据,否则我没有看到任何检查它的原因,特别是它的方式编码。
为了加快速度?不是真的,因为如果串行端口上确实存在数据,则会有额外的延迟。它对我来说没有多大意义。

使您的代码段更好的另一种方法是使用ReadExisting()进行轮询。

否则,您也可以考虑使用SerialPort BaseStream的异步方法。

总而言之,如果不访问其余代码库(即上下文),很难说。

如果您有关于目标/协议的更多信息,可以提供一些有关如何做的提示。否则,我只能说这似乎编码很差,再次脱离了上下文。

我将Hans提到的关于UI响应的内容翻了三倍甚至三倍,我希望你的代码片段在一个不是UI的线程中运行(虽然你在帖子中提到UI正在轮询我仍然希望该片段适用于其他工作者)。

如果它真的是UI线程,那么每当有一个Thread.Sleep调用时它就会被阻止,这会使UI不能真正响应用户交互,并且可能会给最终用户一些挫折感

也可以订阅DataReceived事件并使用处理程序执行您想要/需要的事情(例如使用缓冲区和比较值等)。

请注意,mono仍未实现此事件的触发器,但如果您针对普通的MS .NET实现运行,则无需多线程的麻烦就可以完全正常运行。

简而言之:

  • 检查哪些线程(正在)处理您的代码片段并注意UI响应性
    • 如果是UI,则通过Thread,BackgroundWorker(Threadpool)或Task使用另一个线程。
    • 流异步方法,以避免UI线程同步的麻烦
  • 尝试查看目标是否真的值得双300毫秒线程睡眠方法调用
  • 您可以直接获取String而不是收集,如果后面的检查正在使用,那么执行操作而不是自己收集字节(如果所选的编码可以满足您的需要)。