2017-01-21 13 views
1

Редактировать: Я добавил код отправки и пример полученного вывода, который я получаю.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 
===== 

Я провел пару недель выследить эту проблему, которая первоначально проявилась в странном поведении от продукта в стадии разработки. Я почти уверен, что я должен делать что-то неправильно, но я просто не вижу этого, и в этот момент я очень отчаянно нуждаюсь в каких-либо мыслях или предложениях!

+0

Что такое serialInBuffer? Ваш метод async не должен использовать разделяемое состояние, особенно если это не потокобезопасно. Кроме того, хвостовая рекурсия - плохая идея ... – Lucero

+0

@Lucero Комментарии в верхней части блока кода объясняют, что serialInBuffer является экземпляром класса буфера с байтовым байтом. Я отредактирую, чтобы отметить, что он является потокобезопасным, но это не проблема. Я проверяю полученные данные, создавая событие, прежде чем он войдет в serialInBuffer. Я буду комментировать код, чтобы указать на это. –

+0

@ Lucero почему хвостовая рекурсия плохая идея в этом случае? Я изначально имел это в цикле While (true), но менял его, чтобы он больше походил на то, что опубликовал Бен Вейгт, и сделать логику видимой, не перепрыгивая между блоками кода. –

ответ

1

Наконец-то я получил ответ после прохождения декомпилированного исходного кода для класса .Net SerialPort (с установкой resharper только Rclick on SerialPort->Navigate->Decompiled Sources).

Ответ # 1: Ошибка в байтах из-за ошибки ранее в моей программе. Я отменил и перезапустил цикл readAsync, но я использовал неправильный токен отмены, поэтому было два экземпляра цикла, ожидающих readAsync из последовательного порта. Оба выдавали прерывания, чтобы вернуть полученные данные, но, конечно, это было состояние гонки, в котором он попал первым.

Ответ # 2: Обратите внимание, как я использую метод синхронного чтения: я не использую событие Received (которое работает неправильно) или проверяет количество прочитанных байтов (что ненадежно) или что-то в этом роде. Я просто устанавливаю тайм-аут нуля, пытаюсь читать с большим буфером и проверять, сколько байтов я получил.

При вызове таким образом синхронный SerialPort.Read сначала пытается выполнить запрос чтения из внутреннего кэша [1024] полученных байтов данных. Если он все еще не имеет достаточного количества данных для удовлетворения запроса, он затем выдает запрос ReadAsync к базовому базовому потоку с использованием того же буфера, (скорректированного) смещения и (скорректированного) счета.

Итог: при использовании способа, которым я пользуюсь, синхронный метод SerialPort.Read ведет себя точно так же, как SerialPort.ReadAsync. Я пришел к выводу, что, вероятно, было бы неплохо положить асинхронную оболочку вокруг синхронного метода и просто ждать. Тем не менее, мне не нужно делать это сейчас, чтобы я мог читать из basestream надежно.

Update: я теперь надежно получить более 3Mbps от моего последовательного порта, используя задачу, содержащую цикл, который непрерывно ожидает SerialPort.Basestream.ReadAsync и складывает результаты в кольцевой буфер.

+0

Не будет ли это в нижней строке заключить, что асинхронный обернут вокруг синхронизации, обернутой вокруг асинхронизации? Не было бы более быстрым/надежным просто использовать SerialPort.ReadASync? – Foitn

+0

@Foitn, если вы имеете в виду SerialPort.Basestream.ReadAsync, то да! Вопрос об упаковке был только потому, что Basestream.ReadAsync заставлял меня потерять байты, но это оказалось секретным дополнительным потоком, конкурирующим за тот же базовый поток. Теперь я уверенно получаю около 3 Мбит/с с помощью BaseStream.ReadAsync на последовательном порту. –

+0

Это действительно то, что я имел в виду, приятно, что вы смогли это исправить – Foitn