简体   繁体   English

"从 USB 串行端口读取时,SerialPort.BaseStream.ReadAsync 丢弃或加扰字节"

[英]SerialPort.BaseStream.ReadAsync drops or scrambles bytes when reading from a USB Serial Port

Edit: I've added the sending code and an example of the received output I'm getting.编辑:我添加了发送代码和收到的输出示例。


I am reading data from a USB "virtual" serial port connected to an embedded system.我正在从连接到嵌入式系统的 USB“虚拟”串行端口读取数据。 I have written two methods for receiving the data, one synchronous and one asynchronous.我写了两种接收数据的方法,一种是同步的,一种是异步的。 The synchronous one works, and the asynchronous one loses or scrambles a little bit of the incoming data.同步的工作,异步的会丢失或打乱一点输入数据。 I cannot tell why the second one fails.我不知道为什么第二个失败了。

The method that works calls SerialPort.Read with a read timeout set to zero, and it requests everything in the receive buffer.有效的方法调用 SerialPort.Read 并将读取超时设置为零,并请求接收缓冲区中的所有内容。 I check the return value to see how many bytes were actually read and then put the data into a circular buffer for use elsewhere.我检查返回值以查看实际读取了多少字节,然后将数据放入循环缓冲区以供其他地方使用。 This method is called by a timer interrupt, and it works perfectly to receive serial data (typically at rates above 1.6 Mbps with no data loss).此方法由定时器中断调用,它可以完美地接收串行数据(通常以高于 1.6 Mbps 的速率,不会丢失数据)。 However, the polling timer has become a problem for me and I would prefer to receive the data asynchronously wrt the rest of my code.但是,轮询计时器对我来说已经成为一个问题,我更愿意在我的其余代码中异步接收数据。

The method that loses data awaits ReadAsync on the serial port BaseStream and loops until cancelled.丢失数据的方法在串口 BaseStream 上等待 ReadAsync 并循环直到取消。 This approach sort of works, but it often returns the leading byte of a packet out of order, loses a single byte fairly frequently (approximately once every few thousand data bytes), and occasionally loses hundreds of sequential bytes from a packet.这种方法有点工作,但它通常会乱序返回数据包的前导字节,相当频繁地丢失一个字节(大约每几千个数据字节一次),并且偶尔会丢失一个数据包中的数百个连续字节。

It is possible that there are two completely different problems here, because the loss of larger chunks of data loss seem to be correlated with higher data rates and heavier system activity.这里可能存在两个完全不同的问题,因为较大数据块的丢失似乎与较高的数据速率和较重的系统活动相关。 That particular part of the problem could potentially be due to buffer overruns -- perhaps through a failure of USB handshaking when the USB scheduler encounters a delay -- but the example I am showing here has only a very small amount of data being transferred at 50 msec intervals, and the system is idle except for this test routine.问题的特定部分可能是由于缓冲区溢出造成的——可能是由于 USB 调度程序遇到延迟时 USB 握手失败——但我在这里展示的示例只有非常少量的数据在 50毫秒间隔,系统空闲,除了这个测试例程。

I have observed that ReadAsync frequently returns the first byte of a packet on one read, and the remainder of the packet on the next read.我观察到 ReadAsync 经常在一次读取时返回数据包的第一个字节,并在下一次读取时返回数据包的其余部分。 I believe this is expected behavior because MSDN says that if no data is available for some period of time, ReadAsync will return with the first byte it receives.我相信这是预期的行为,因为 MSDN 说如果在一段时间内没有数据可用,ReadAsync 将返回它收到的第一个字节。 However, I think this behavior is somehow related to my problem because when a single byte is missing or out of order, it is "always" that first byte, with the rest of the packet arriving normally.但是,我认为这种行为在某种程度上与我的问题有关,因为当单个字节丢失或乱序时,它“始终”是第一个字节,其余的数据包正常到达。

When the packets are small, the "missing" byte from the front of the packet often (but not always) appears to be delivered in the next read after the remainder of the packet, and this just makes absolutely no sense to me.当数据包很小时,数据包前面的“丢失”字节通常(但不总是)似乎在数据包其余部分之后的下一次读取中传递,这对我来说绝对没有意义。 With larger packets this still happens occasionally, but more often the first byte is just missing when the packets are large.对于较大的数据包,这仍然偶尔会发生,但更常见的是,当数据包很大时,第一个字节只是丢失了。

I've searched far and wide, and have read every SO question I could find on this topic.我进行了广泛的搜索,并阅读了我能找到的关于这个主题的每一个 SO 问题。 I found other people with what appears to be a similar problem (ex: SerialPort.BaseStream.ReadAsync missing the first byte ), but nobody with any accepted or even plausible solutions.我发现其他人似乎有类似的问题(例如: SerialPort.BaseStream.ReadAsync 缺少第一个字节),但没有人有任何可接受甚至合理的解决方案。

Ben Voigt ( http://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport ) and others who really seem to know serial comms have recommended the use of ReadAsync on the basestream, and Microsoft's IOT team has also recommended this approach, so I have to believe the approach should work. Ben Voigt ( http://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport ) 和其他似乎真正了解串行通信的人建议在基本流上使用 ReadAsync,并且微软的物联网团队也推荐了这种方法,所以我必须相信这种方法应该有效。

Question 1: Why is my code using ReadAsync on a USB Serial BaseStream dropping /scrambling bytes?问题 1:为什么我的代码在 USB 串行 BaseStream 上使用 ReadAsync 会丢弃/加扰字节?

Question 2: If ReadAsync cannot be made to reliably return all the bytes received bytes in the correct order, can I just put an async wrapper around the traditional SerialPort.Read and await / loop it so I don't have to poll from a timer?问题 2:如果 ReadAsync 不能可靠地以正确的顺序返回所有接收到的字节,我可以在传统的 SerialPort.Read 周围放置一个异步包装器并等待/循环它,这样我就不必从计时器轮询? I've read that this is a bad idea, but I've also read that the SerialPort class is internally asynchronous, so perhaps that makes it OK?我读到这是一个坏主意,但我也读到 SerialPort 类在内部是异步的,所以也许这样可以吗? Or is my only alternative to put this on a worker thread and just let it spend all its time waiting?还是我唯一的选择是把它放在工作线程上,让它花所有时间等待?

My code is below.我的代码如下。 I have set serialPort1.ReadTimeout = 0;我已设置serialPort1.ReadTimeout = 0; and serialPort1.BaseStream.ReadTimeout = 0;serialPort1.BaseStream.ReadTimeout = 0; (and I have tried other durations). (我尝试过其他持续时间)。 I have enabled RTS and DTR, and since this is a USB_serial port it should handle handshake internally, and it certainly appears to do so when I read synchronously -- but perhaps that's not true when I read from the BaseStream?我已经启用了 RTS 和 DTR,因为这是一个 USB_serial 端口,它应该在内部处理握手,当我同步读取时它肯定会这样做——但当我从 BaseStream 读取时,这可能不是真的?

Here is the first method:这是第一种方法:

// this method works perfectly when called from a timer.
// SerialPort.ReadTimeout must be set to zero for this to work.
// It handles incoming bytes reliably at rates above 1.6 Mbps.

private void ReadSerialBytes()
{
    if (!serialPort1.IsOpen)
        return;

    if (serialPort1.BytesToRead > 0)
    {
        var receiveBuffer = new byte[serialPort1.ReadBufferSize];

        var numBytesRead = serialPort1.Read(receiveBuffer, 0, serialPort1.ReadBufferSize);
        var bytesReceived = new byte[numBytesRead];
        Array.Copy(receiveBuffer, bytesReceived, numBytesRead);

        // Here is where I audit the received data.
        // the NewSerialData event handler displays the 
        // data received (as hex bytes) and writes it to disk.
        RaiseEventNewSerialData(bytesReceived);

        // serialInBuffer is a "thread-safe" global circular byte buffer 
        // The data in serialInBuffer matches the data audited above.
        serialInBuffer.Enqueue(bytesReceived, 0, numBytesRead);
    }
}

Here is the second method, Edited to remove the tail recursion noted by @Lucero.这是第二种方法,已编辑以删除@Lucero 指出的尾递归。 Now I won't run out of memory :) but the original data loss problem, of course, remains.现在我不会用完内存:) 但原来的数据丢失问题当然仍然存在。

// This method is called once after the serial port is opened,
// and it repeats until cancelled. 
// 
// This code "works" but periodically drops the first byte of a packet, 
// or returns that byte in the wrong order.
// It occasionally drops several hundred bytes in a row.
private async Task ReadSerialBytesAsync(CancellationToken ct)
{
    while((!ct.IsCancellationRequested) && (serialPort1.IsOpen))
    {
        try
        {
            serialPort1.BaseStream.ReadTimeout = 0;
            var bytesToRead = 1024;
            var receiveBuffer = new byte[bytesToRead];
            var numBytesRead = await serialPort1.BaseStream.ReadAsync(receiveBuffer, 0, bytesToRead, ct);

            var bytesReceived = new byte[numBytesRead];
            Array.Copy(receiveBuffer, bytesReceived, numBytesRead);

             // Here is where I audit the received data.
             // the NewSerialData event handler displays the 
             // data received (as hex bytes) and writes it to disk.
             RaiseEventNewSerialData(bytesReceived);

            // serialInBuffer is a "thread-safe" global circular byte buffer 
            // The data in serialInBuffer matches the data audited above.
            serialInBuffer.Enqueue(receiveBuffer, 0, numBytesRead);
        }
        catch (Exception ex)
        {
            MessageBox.Show("Error in ReadSerialBytesAsync: " + ex.ToString());
            throw;
        }
    }
}

Here is C++ code from the sending system (teensy 3.2 with an ARM chip).这是来自发送系统的 C++ 代码(带有 ARM 芯片的 teensy 3.2)。 It sends a sequence of bytes from 00 through FF, repeated every 50 msec.它发送从 00 到 FF 的字节序列,每 50 毫秒重复一次。

 void SendTestData()
 {
    byte asyncTestBuffer[256] = { 0 };
    for (int i = 0; i < 256; i++)
        asyncTestBuffer[i] = i;

    while(true)
    {
    Serial.write(asyncTestBuffer, sizeof(asyncTestBuffer));
    delay(50);
    }
}

The traditional synchronous SerialPort.Read (called from a timer) receives each block completely exactly as expected, with no data loss.传统的同步 SerialPort.Read(从计时器调用)完全按预期接收每个块,没有数据丢失。 It looks like this, over and over:它看起来像这样,一遍又一遍:

=====
32 msec => Received 256 bytes 
000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====

Now here is what SerialPort.BaseStream.ReadAsync receives.现在这是 SerialPort.BaseStream.ReadAsync 收到的内容。 In another version I appended a terminal packet sequence number to prove that when I see a zero followed by another zero, there's not really an entire missing packet between them.在另一个版本中,我附加了一个终端数据包序列号,以证明当我看到一个零后跟另一个零时,它们之间并没有真正丢失一个完整的数据包。 Packet sequence numbers were all present, so the leading byte really does seem to be missing or delivered out of order.数据包序列号都存在,因此前导字节确实似乎丢失或乱序传递。

7 msec => Received 255 bytes 
0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
5 msec => Received 1 bytes 
00
=====
55 msec => Received 1 bytes 
00
=====
4 msec => Received 255 bytes 
0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
42 msec => Received 1 bytes 
00
=====
5 msec => Received 255 bytes 
0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
68 msec => Received 1 bytes 
00
=====
7 msec => Received 255 bytes 
0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
31 msec => Received 255 bytes 
0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
9 msec => Received 1 bytes 
00
=====
33 msec => Received 1 bytes 
00
=====
10 msec => Received 255 bytes 
0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
55 msec => Received 255 bytes 
0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
12 msec => Received 1 bytes 
00
=====
12 msec => Received 1 bytes 
00
=====
15 msec => Received 255 bytes 
0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
68 msec => Received 255 bytes 
0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
16 msec => Received 1 bytes 
00
=====
14 msec => Received 256 bytes 
000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====

I've spent a couple of weeks tracking down this problem, which originally manifested itself in bizarre behavior from a product under development.我花了几个星期来追踪这个问题,它最初表现为正在开发的产品的奇怪行为。 I am pretty sure I must be doing something wrong, but I just can't see it, and at this point I am quite desperate for any thoughts or suggestions!我很确定我一定做错了什么,但我就是看不到它,在这一点上,我非常渴望任何想法或建议!

I finally came up with an answer after stepping through the decompiled source code for the .Net SerialPort class (with resharper installed just Rclick on SerialPort->Navigate->Decompiled Sources ).在逐步完成 .Net SerialPort 类的反编译源代码后,我终于想出了一个答案(安装了Rclick on SerialPort->Navigate->Decompiled Sources只需Rclick on SerialPort->Navigate->Decompiled Sources )。

Answer #1: The bytes out of order problem was due to an error earlier in my program.答案 #1:字节乱序问题是由于我的程序之前的错误造成的。 I had canceled and restarted the readAsync loop, but I was using the wrong cancellation token so there were two copies of the loop both awaiting readAsync from the serial port.我已经取消并重新启动了 readAsync 循环,但是我使用了错误的取消令牌,因此有两个循环副本都在等待来自串行端口的 readAsync。 Both were issuing interrupts to return the received data, but of course it was a race condition as to which one got there first.两者都发出中断以返回接收到的数据,但当然这是一个竞争条件,即谁先到达那里。

Answer #2: Note the way I am using the synchronous read method: I don't use the Received event (which doesn't work correctly) or check the number of bytes to read (which is unreliable) or anything like that.答案 #2:请注意我使用同步读取方法的方式:我不使用 Received 事件(它不能正常工作)或检查要读取的字节数(这是不可靠的)或类似的东西。 I merely set a timeout of zero, attempt to read with a large buffer, and check how many bytes I got back.我只是将超时设置为零,尝试使用大缓冲区读取,并检查我返回了多少字节。

When called in this way, the synchronous SerialPort.Read first attempts to fulfill a read request from an internal cache[1024] of data bytes received.当以这种方式调用时,同步 SerialPort.Read 首先尝试满足来自内部缓存 [1024] 的接收数据字节的读取请求。 If it still doesn't have enough data to meet the request, it then issues a ReadAsync request against the underlying BaseStream using the exact same buffer, (adjusted)offset, and (adjusted)count.如果它仍然没有足够的数据来满足请求,那么它会使用完全相同的缓冲区、(调整后的)偏移量和(调整后的)计数针对底层 BaseStream 发出 ReadAsync 请求。

Bottom line: When used the way I am using it, the synchronous SerialPort.Read method behaves exactly like SerialPort.ReadAsync.底线:按照我的使用方式使用时,同步 SerialPort.Read 方法的行为与 SerialPort.ReadAsync 完全相同。 I conclude that it would probably be fine to put an async wrapper around the synchronous method, and just await it.我的结论是,在同步方法周围放置一个异步包装器可能没问题,然后等待它。 However, I don't need to do that now that I can read from the basestream reliably.但是,我现在不需要这样做,因为我可以可靠地从基流中读取数据。

Update: I now reliably receive more than 3Mbps from my serial port using a Task containing a loop that continuously awaits SerialPort.Basestream.ReadAsync and adds the results to a circular buffer.更新:我现在使用包含持续等待 SerialPort.Basestream.ReadAsync 并将结果添加到循环缓冲区的循环的任务从我的串行端口可靠地接收超过 3Mbps 的数据。

I know it's quite some time since question was asked/solved, but noticed it while searching.我知道距离提问/解决问题已经有一段时间了,但在搜索时注意到了。 I have had same kind of "problems" earlier.我之前也遇到过同样的“问题”。 Nowadays I use a Pipereader over the BaseStream of the serial port to handle reading.现在我在串口的 BaseStream 上使用 Pipereader 来处理读取。 This allows me to only clear the incoming buffers when I have a complete message (and receive several messages at the same time).这允许我仅在收到完整消息(并同时接收多条消息)时才清除传入缓冲区。 And it seems to perform very well.它似乎表现得非常好。

Code is something like this:代码是这样的:

        var reader = PipeReader.Create(serial.BaseStream);
        while (!token.IsCancellationRequested)
        {
            ReadResult result = await reader.ReadAsync(token);

            // find and handle packets
            // Normally wrapped in a handle-method and a while to allow processing of several packets at once 
            // while(HandleIncoming(result))
            // {
                    result.Buffer.Slice(10); // Moves Buffer.Start to position 10, which we use later to advance the reader
            // }

            // Tell the PipeReader how much of the buffer we have consumed. This will "free" that part of the buffer
            reader.AdvanceTo(result.Buffer.Start, result.Buffer.End);

            // Stop reading if there's no more data coming
            if (result.IsCompleted)
            {
                break;
            }
        }

See the documentation for pipelines here: https://docs.microsoft.com/en-us/dotnet/standard/io/pipelines请参阅此处的管道文档: https : //docs.microsoft.com/en-us/dotnet/standard/io/pipelines

I can confirm that the scrambled sequence still persists (or is back?) in NET 6.我可以确认加扰序列在 NET 6 中仍然存在(或者又回来了?)。

I'm writing my first NET 6 desktop application in January 2022 and ran into this problem for the first time.我在 2022 年 1 月编写了我的第一个 NET 6 桌面应用程序,并且第一次遇到了这个问题。 I have been using the SerialPort class for at least 4 or 5 years and never experienced this problem.我已经使用 SerialPort 类至少 4 或 5 年了,从未遇到过这个问题。 I use it in almost every app I wirte to communicate with various devices.我几乎在我编写的每个应用程序中都使用它来与各种设备进行通信。

I'm just learning that this problem existed for a long time!.我只是知道这个问题存在了很长时间! The oldest report I a saw was from 2012, ... and it is still around?, seriously?.我看到的最古老的报告是从 2012 年开始的,......而且它仍然存在?,真的吗?

Until now the serialport apps that I wrote are based on NET Framework 4.7.2 and older.到目前为止,我编写的串口应用程序都是基于 NET Framework 4.7.2 和更早版本的。 In this framework SerialPort was part of System.dll.在这个框架中,SerialPort 是 System.dll 的一部分。 In NET 6 SerialPort is a platform extension moved to System.IO.Ports.dll that has to be installed as a nugget package.在 NET 6 中,SerialPort 是一个移动到 System.IO.Ports.dll 的平台扩展,必须作为 nugget 包安装。 Could it be possible that they ported an old, bugged version?他们有可能移植了一个旧的、有漏洞的版本吗?

In my test I have the old NET Framework 4.7.2 app sending a string every 20 ms over a phisical port COM3 (no USB adapter).在我的测试中,我有旧的 NET Framework 4.7.2 应用程序每 20 毫秒通过一个物理端口 COM3(无 USB 适配器)发送一个字符串。 The NET 6 app reading the strings is in the same desktop listening on a second phisical port (COM4).读取字符串的 NET 6 应用程序在同一个桌面上侦听第二个物理端口 (COM4)。 Both ports are linked by a short NULL modem cable, only TX, RX, GND connected.两个端口由一根短的 NULL 调制解调器电缆连接,仅连接 TX、RX、GND。 No handshake.没有握手。 This is the scrambled result that, as far as I can tell, is random in nature:这是加扰的结果,据我所知,它本质上是随机的:

<-- port open with tranmission running already -->
 fox jumps over the lazy Dog - 123456789]
[The quick brown fox jumps over the lazy Dog - 123456789]
[The quick brown[The quick brown fox jumps over the lazy Dog - 123456789]
[The quick brown fox jumps over the lazy Dog - 123456789]
[The quick brown fox jumps over the lazy Dog - 123456789]
<--- many good lines removed for brevity --->
[The quick brown fox jumps over the lazy Dog - 123456789]
[The quick brown fox jumps over he lazy Dog - 1t23456789]
[The quick brown fox jumps over the lazy Dog - 123456789]
<--- many good lines removed for brevity --->
[The quick brown fox jumps over the lazy Dog - 123456789]
[The quick brownfox jumps over  the lazy Dog - 123456789]
[The quick brown fox jumps over the lazy Dog - 123456789]

Notice that the third line has the first three words of the first line!.请注意,第三行包含第一行的前三个单词!。 After that is only one byte out of place.之后只有一个字节不合适。 It looks like a slopy double buffer implementation ...它看起来像一个草率的双缓冲区实现......

ops!操作! I forgot these bytes!, here you go!我忘记了这些字节!,给你!

If I open a second good old terminal (Net Framework 4.7.2) and listen to the same stream the output is perfect.如果我打开第二个好的旧终端(Net Framework 4.7.2)并收听相同的流,则输出是完美的。

In the NET 6 project I'm using exactly the same class I wrote long time ago to encapsulate the SerialPort functionality to use it from project to the next.在 NET 6 项目中,我使用了与很久以前编写的完全相同的类来封装 SerialPort 功能,以便在项目到下一个项目中使用它。

Until now I subscribed to the SerialPort.DataReceived event and then in the event handler the reading goes like this inside a Task (started but not waited by the event handler):到现在为止,我订阅了 SerialPort.DataReceived 事件,然后在事件处理程序中,读取在 Task 中是这样的(已启动但未由事件处理程序等待):

var bytesToRead = _serialPort.BytesToRead;
byte[] Data = new Byte[bytesToRead];
int received = _serialPort.Read(Data, 0, bytesToRead);
... notify the class user.

I'll test the work arround suggested here ...我将测试这里建议的工作...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM