问题描述
我已经添加了发送代码和我收到的输出示例.
I've added the sending code and an example of the received output I'm getting.
我正在从连接到嵌入式系统的 USB虚拟"串行端口读取数据.我写了两种接收数据的方法,一种是同步的,一种是异步的.同步的工作,异步的会丢失或打乱一点输入数据.我不知道为什么第二个失败了.
I am reading data from a USB "virtual" serial port connected to an embedded system. 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.
有效的方法调用 SerialPort.Read 并将读取超时设置为零,并请求接收缓冲区中的所有内容.我检查返回值以查看实际读取了多少字节,然后将数据放入循环缓冲区以供其他地方使用.此方法由定时器中断调用,它可以完美地接收串行数据(通常以高于 1.6 Mbps 的速率,不会丢失数据).但是,轮询计时器对我来说已经成为一个问题,我更愿意在我的其余代码中异步接收数据.
The method that works calls SerialPort.Read with a read timeout set to zero, and it requests everything in the receive buffer. 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). 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.
丢失数据的方法在串口 BaseStream 上等待 ReadAsync 并循环直到取消.这种方法有点有效,但它通常会乱序返回数据包的前导字节,相当频繁地丢失一个字节(大约每几千个数据字节一次),偶尔会丢失数百个连续字节从一个数据包.
The method that loses data awaits ReadAsync on the serial port BaseStream and loops until cancelled. 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.
这里可能存在两个完全不同的问题,因为丢失更大块数据似乎与更高的数据速率和更重的系统活动相关.问题的特定部分可能是由于缓冲区溢出造成的——可能是由于 USB 调度程序遇到延迟时 USB 握手失败——但我在这里展示的示例只有非常少量的数据在 50毫秒间隔,系统空闲,除了这个测试例程.
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.
我观察到 ReadAsync 经常在一次读取时返回数据包的第一个字节,并在下一次读取时返回数据包的其余部分.我相信这是预期的行为,因为 MSDN 说如果在一段时间内没有数据可用,ReadAsync 将返回它收到的第一个字节.但是,我认为这种行为在某种程度上与我的问题有关,因为当单个字节丢失或乱序时,它始终"是第一个字节,其余数据包正常到达.
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. 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. 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.
我进行了广泛的搜索,并阅读了我能找到的关于这个主题的每一个 SO 问题.我发现其他人似乎有类似的问题(例如:SerialPort.BaseStream.ReadAsync 缺少第一个字节),但没有人有任何可接受甚至合理的解决方案.
I've searched far and wide, and have read every SO question I could find on this topic. 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.
本·沃伊特 (http://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport) 和其他似乎真正了解串行通信的人建议在 basestream 上使用 ReadAsync,以及微软的 IOT 团队也推荐了这种方法,所以我必须相信这种方法应该有效.
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.
问题 1: 为什么我的代码在 USB 串行 BaseStream 上使用 ReadAsync 会丢弃/加扰字节?
Question 1: Why is my code using ReadAsync on a USB Serial BaseStream dropping /scrambling bytes?
问题 2: 如果 ReadAsync 无法可靠地以正确的顺序返回所有接收到的字节,我可以在传统的 SerialPort.Read 周围放置一个异步包装器并等待/循环它吗我不必从计时器轮询?我读到这是一个坏主意,但我也读到 SerialPort 类在内部是异步的,所以也许这样可以吗?还是我唯一的选择是把它放在工作线程上,让它把所有时间都花在等待上?
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? 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? Or is my only alternative to put this on a worker thread and just let it spend all its time waiting?
我的代码如下.我设置了 serialPort1.ReadTimeout = 0;
和 serialPort1.BaseStream.ReadTimeout = 0;
(并且我尝试了其他持续时间).我启用了 RTS 和 DTR,因为这是一个 USB_serial 端口,它应该在内部处理握手,而且当我同步读取时它确实会这样做 - 但当我从 BaseStream 读取时可能不是这样?
My code is below. I have set serialPort1.ReadTimeout = 0;
and 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?
这里是第一种方法:
// 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);
}
}
这是第二种方法,Edited 删除@Lucero 指出的尾递归.现在我不会耗尽内存 :) 但原来的数据丢失问题当然仍然存在.
Here is the second method, Edited to remove the tail recursion noted by @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;
}
}
}
这是来自发送系统的 C++ 代码(带有 ARM 芯片的 teensy 3.2).它发送从 00 到 FF 的字节序列,每 50 毫秒重复一次.
Here is C++ code from the sending system (teensy 3.2 with an ARM chip). It sends a sequence of bytes from 00 through FF, repeated every 50 msec.
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);
}
}
传统的同步 SerialPort.Read(从计时器调用)完全按预期接收每个块,没有数据丢失.它看起来像这样,一遍又一遍:
The traditional synchronous SerialPort.Read (called from a timer) receives each block completely exactly as expected, with no data loss. It looks like this, over and over:
=====
32 msec => Received 256 bytes
000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
现在这是 SerialPort.BaseStream.ReadAsync 接收的内容.在另一个版本中,我附加了一个终端数据包序列号,以证明当我看到一个零后跟另一个零时,它们之间并没有真正丢失一个完整的数据包.数据包序列号都存在,所以前导字节确实似乎丢失或乱序传递.
Now here is what SerialPort.BaseStream.ReadAsync receives. 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!
推荐答案
在单步调试.Net SerialPort 类的反编译源代码后,我终于想出了一个答案(安装了resharper 刚刚Rclick on SerialPort->导航->反编译的源代码
).
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
).
答案 #1: 字节乱序问题是由于我的程序之前的错误造成的.我已经取消并重新启动了 readAsync 循环,但我使用了错误的取消令牌,因此有两个循环副本都在等待来自串行端口的 readAsync.两者都发出中断以返回接收到的数据,但当然这是一个竞争条件,即谁先到达那里.
Answer #1: The bytes out of order problem was due to an error earlier in my program. 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. Both were issuing interrupts to return the received data, but of course it was a race condition as to which one got there first.
答案 #2: 请注意我使用同步读取方法的方式:我不使用 Received 事件(无法正常工作)或检查要读取的字节数(这是不可靠的)或类似的东西.我只是将超时设置为零,尝试使用大缓冲区读取,并检查我返回了多少字节.
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. I merely set a timeout of zero, attempt to read with a large buffer, and check how many bytes I got back.
当以这种方式调用时,同步 SerialPort.Read 首先尝试从内部缓存[1024] 接收到的数据字节来完成读取请求.如果它仍然没有足够的数据来满足请求,那么它会使用完全相同的缓冲区、(调整的)偏移量和(调整的)计数对底层 BaseStream 发出 ReadAsync 请求.
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. 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.
底线:按照我的使用方式使用时,同步 SerialPort.Read 方法的行为与 SerialPort.ReadAsync 完全相同.我的结论是,在同步方法周围放置一个异步包装器可能会很好,然后等待它.但是,现在我可以可靠地从基本流中读取数据,因此我不需要这样做.
Bottom line: When used the way I am using it, the synchronous SerialPort.Read method behaves exactly like 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.
更新:我现在使用包含循环的任务可靠地从我的串行端口接收超过 3Mbps,该循环不断等待 SerialPort.Basestream.ReadAsync 并将结果添加到循环缓冲区.
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.
这篇关于从 USB 串行端口读取时,SerialPort.BaseStream.ReadAsync 丢弃或加扰字节的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!