Редактировать: Я добавил код отправки и пример полученного вывода, который я получаю.SerialPort.BaseStream.ReadAsync сбрасывает или скремблирует байты при чтении из последовательного порта USB
Я читаю данные с виртуального последовательного порта USB, подключенного к встроенной системе. Я написал два метода для приема данных, один синхронный и один асинхронный. Синхронный работает, а асинхронный теряет или скрещивает немного входных данных. Я не могу понять, почему второй не удается.
Метод, который работает вызывает SerialPort.Read с таймаутом чтения, установленным на ноль, и запрашивает все в буфере приема. Я проверяю возвращаемое значение, чтобы увидеть, сколько байтов действительно было прочитано, и затем помещать данные в круговой буфер для использования в другом месте. Этот метод вызывается прерыванием таймера, и он отлично работает для получения последовательных данных (обычно со скоростью выше 1,6 Мбит/с без потери данных). Однако таймер опроса стал для меня проблемой, и я предпочел бы получать данные асинхронно по остальной части моего кода.
Метод, который теряет данные ожидает ReadAsync на последовательном порту BaseStream и петли до отмены. Такой подход относится к категории, но часто возвращает ведущий байт пакета не по порядку, довольно часто теряет один байт (примерно один раз каждые несколько тысяч байтов данных) и изредка теряет сотни последовательных байтов из пакета.
Возможно, здесь есть две совершенно разные проблемы, потому что потеря больше фрагменты потери данных, по-видимому, коррелируют с более высокими скоростями передачи данных и более тяжелой активностью системы. Эта конкретная часть проблемы потенциально может быть вызвана переполнениями буфера - возможно, из-за сбоя связи USB, когда планировщик USB встречает задержку, - но пример, который я показываю здесь, имеет только очень небольшое количество данных, передаваемых на 50 msec, и система не работает, за исключением этой тестовой процедуры.
Я заметил, что ReadAsync часто возвращает первый байт пакета на одно чтение, а оставшуюся часть пакета на следующее чтение. Я считаю, что это ожидаемое поведение, потому что MSDN говорит, что если в течение некоторого периода времени данные не доступны, ReadAsync вернется с первым байтом, который он получит. Тем не менее, я думаю, что такое поведение каким-то образом связано с моей проблемой, потому что, когда один байт отсутствует или вышел из строя, он «всегда» имеет первый байт, а остальная часть пакета поступает нормально.
Когда пакеты малы, «отсутствующий» байт с передней стороны пакета часто (но не всегда), кажется, доставляется в следующем чтении после остальной части пакета, и это просто абсолютно не означает смысл для меня. С более крупными пакетами это все равно происходит иногда, но чаще первый байт просто отсутствует, когда пакеты большие.
Я искал по всему миру и прочитал каждый вопрос, который я смог найти на эту тему. Я нашел других людей с тем, что похоже на аналогичную проблему (например: SerialPort.BaseStream.ReadAsync missing the first byte), но никто не принял никаких приемлемых или даже правдоподобных решений.
Бен Фойгт (http://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport) и другие, которые на самом деле, кажется, знают последовательной связи рекомендовали использовать ReadAsync на BaseStream, и команда ВГД от Microsoft также рекомендовал этот подход, так что я должен верить подход должен работу.
Вопрос 1: Почему мой код с использованием ReadAsync на байтах USB с последовательным базовым потоком/скремблированием?
Вопрос 2: Если ReadAsync не может быть сделано, чтобы надежно возвращать все байты, полученные байты в правильном порядке, я могу просто положить асинхронную обертку вокруг традиционного SerialPort.Read и ждать/петель так, чтобы я не нужно опросить таймер? Я читал, что это плохая идея, но я также прочитал, что класс SerialPort является внутренне асинхронным, поэтому, возможно, это делает его в порядке? Или моя единственная альтернатива, чтобы поместить это в рабочий поток и просто позволить ему все время ждать?
Мой код ниже. Я установил serialPort1.ReadTimeout = 0;
и serialPort1.BaseStream.ReadTimeout = 0;
(и я пробовал другие длительности). Я включил RTS и DTR, и поскольку это порт USB_serial, он должен обрабатывать рукопожатие внутри себя, и, похоже, это происходит, когда я читаю синхронно - но, возможно, это неверно, когда я читал из 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);
}
}
Вот второй метод, Под редакцией удалить хвостовую рекурсию, отмеченный @Lucero. Теперь у меня не хватит памяти :), но исходная проблема потери данных, конечно же, остается.
// 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++ от системы отправки (teensy 3.2 с чипом ARM). Он отправляет последовательность байтов от 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);
}
}
Традиционный синхронный SerialPort.Read (вызывается из таймера) получает каждый блок полностью точно так, как и ожидалось, без потери данных. Похоже, что это снова и снова:
=====
32 msec => Received 256 bytes
000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
Теперь вот что получает SerialPort.BaseStream.ReadAsync. В другой версии я добавил номер последовательности терминальных пакетов, чтобы доказать, что когда я вижу нуль, за которым следует еще один ноль, между ними не существует всего недостающего пакета. Все порядковые номера пакетов присутствовали, поэтому ведущий байт действительно кажется отсутствующим или доставленным не в порядке.
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
=====
Я провел пару недель выследить эту проблему, которая первоначально проявилась в странном поведении от продукта в стадии разработки. Я почти уверен, что я должен делать что-то неправильно, но я просто не вижу этого, и в этот момент я очень отчаянно нуждаюсь в каких-либо мыслях или предложениях!
Что такое serialInBuffer? Ваш метод async не должен использовать разделяемое состояние, особенно если это не потокобезопасно. Кроме того, хвостовая рекурсия - плохая идея ... – Lucero
@Lucero Комментарии в верхней части блока кода объясняют, что serialInBuffer является экземпляром класса буфера с байтовым байтом. Я отредактирую, чтобы отметить, что он является потокобезопасным, но это не проблема. Я проверяю полученные данные, создавая событие, прежде чем он войдет в serialInBuffer. Я буду комментировать код, чтобы указать на это. –
@ Lucero почему хвостовая рекурсия плохая идея в этом случае? Я изначально имел это в цикле While (true), но менял его, чтобы он больше походил на то, что опубликовал Бен Вейгт, и сделать логику видимой, не перепрыгивая между блоками кода. –