我正在实现一个C#应用程序,它使用USB串行适配器(FTDI FT232H)以高波特率(8兆波特)从微控制器读取二进制数据。问题是当流包含0x1A时,在此字节之后有时会丢失大块数据(数千字节)。
我在论坛上发现0x1A是一个特殊字符(EOFCHAR),Windows正在对此进行特殊处理。但是SerialStream
反编译EOFCHAR
并将using System;
using System.IO.Ports;
using System.Collections.Generic;
using System.Text;
namespace SerialPort_0x1A_Loss_Test
{
class Program
{
static void Main(string[] args)
{
const byte BYTE_FILL = 0x1A; // 0x1A is EOFCHAR
const int BAUD_RATE = 3000000; // 3 MegaBaud
const int BUFF_SIZE = 1000000;
SerialPort sp1_FT232R =
new SerialPort("COM3", BAUD_RATE, Parity.None, 8, StopBits.One);
SerialPort sp2_FT232H =
new SerialPort("COM6", BAUD_RATE, Parity.None, 8, StopBits.One);
sp1_FT232R.Encoding = Encoding.GetEncoding(1252);
sp1_FT232R.WriteBufferSize = 20000000;
sp1_FT232R.Open();
sp2_FT232H.Encoding = Encoding.GetEncoding(1252);
sp2_FT232H.ReadBufferSize = 20000000;
sp2_FT232H.ReceivedBytesThreshold = 20000000;
sp2_FT232H.Open();
byte[] bufferTx = new byte[BUFF_SIZE];
for (int i = 0; i < BUFF_SIZE; i++)
{
bufferTx[i] = BYTE_FILL;
}
Console.WriteLine("Sending ...");
sp1_FT232R.Write(bufferTx, 0, BUFF_SIZE);
Console.WriteLine("Sending finished. " +
"Press a key to view status, ESC to exit.");
// Receiving maybe not yet finished,
// query the status with a keypress
while (Console.ReadKey(true).Key != ConsoleKey.Escape)
{
Console.WriteLine("TOTAL RX = " + sp2_FT232H.BytesToRead);
}
// A second test, using .Read() call
// This will be executed after pressing ESC for the previous test
int totalRX_Read_Call = 0;
var listBufferRx = new List<byte>();
int btr; // BytesToRead
while ( (btr = sp2_FT232H.BytesToRead) > 0)
{
var bufferRx = new byte[btr];
totalRX_Read_Call += sp2_FT232H.Read(bufferRx, 0, btr);
listBufferRx.AddRange(bufferRx);
Console.WriteLine("totalRX_Read_Call = " + totalRX_Read_Call +
"; listBufferRx.Count = " + listBufferRx.Count);
}
Console.ReadKey();
sp1_FT232R.Close();
sp2_FT232H.Close();
}
}
}
更改为另一个字节值对我没有帮助,我需要使用整个字节范围(0..255)。
我创建了一个小型测试应用程序来重现我的问题,重复发送相同的字节,使用连接到同一台计算机的2个串行适配器和连接到FT232H-RX的FT232R-TX
1. BYTE_FILL = 0x55; BAUD_RATE = 3000000; TOTAL RX = 1000000 ( no loss)
2. BYTE_FILL = 0x1A; BAUD_RATE = 3000000; TOTAL RX = 333529 (66% loss)
3. BYTE_FILL = 0x1A; BAUD_RATE = 2000000; TOTAL RX = 627222 (37% loss)
4. BYTE_FILL = 0x1A; BAUD_RATE = 1000000; TOTAL RX = 1000000 ( no loss)
测试结果(所有测试的BUFF_SIZE = 1000000):
{{1}}
此外,对于测试1的测试,CPU(i7-4770k,4 GHz)的负载高(超过30%),但测试1低(3%)。对于测试1,我尝试过与一个字节的所有其他模式(0x00..0x19,0x1B..0xFF)并没有任何损失。
您知道是否有解决方案吗? 非常感谢你!
答案 0 :(得分:3)
首先,我无法解释为什么接收该字符会导致字节丢失。虽然您可能认为问题在于设置EOF字符,但这并没有多大意义。
documentation for the DCB
(device control block) structure表示EofChar
是&#34;用于表示数据结束的字符的值&#34;,但没有说明这意味着什么。在我能找到的其他任何地方都没有其他的神秘EofChar
参考。此外,同一页面说明fBinary
成员:&#34;如果此成员为TRUE,则启用二进制模式。 Windows不支持非二进制模式传输,因此该成员必须为TRUE。&#34;
连接是什么?好吧,kb101419说明了它在16位Windows中的工作原理:
fBinary - If fBinary is set to zero, reception of the EofChar character indicates the end of the input stream. ReadComm() will not return any characters past EofChar. If any characters are received after EofChar, it will be treated as overflowing the receive queue (CE_RXOVER). The reception of EofChar is indicated in the COMSTAT status flag CSTF_EOF. If fBinary is set to one, the EofChar character has no special meaning.
换句话说,EofChar
仅在fBinary
为零时使用,但Windows不再支持该模式,因此似乎忽略了EofChar
。
那么是什么导致0x1A
被视为特殊字符? DCB有另一个名为EvtChar
的成员,定义为The value of the character used to signal an event
。在端口上收到此字符时,将发出端口事件信号,并为该端口设置EV_RXFLAG
位。 SerialData
枚举定义Eof = NativeMethods.EV_RXFLAG
,这就是为什么EOF意味着什么的混淆。
好的,但这并不能解释为什么该字符会导致数据丢失。我无法找到有关它的任何文档,但我的猜测是,当在端口上收到EvtChar
时,将发出事件信号,并且在该事件被清除之前不再缓冲数据。在低数据速率下,事件在收到另一个字节之前被清除,因此之前没有人注意到这个问题。在高数据速率下,在此期间可能会收到数千个字节。如果这实际上是串行端口驱动程序的一个方面,则行为可能很难在其他系统上重现。
现在的问题是如何禁用此行为。 SerialStream
类始终将EvtChar
设置为0x1A
,但这并不重要,因为将其更改为其他字节只会移动问题而不是修复问题。我认为实际问题是由EV_RXFLAG
位(0x0002
)设置调用SetCommMask引起的。不幸的是,无论您是否正在监听事件,都会设置此标志,这意味着驱动程序始终需要发出信号。
我怀疑您可以通过调用SetCommMask
并清除该位来解决您的问题。 SerialStream
的默认值为0x1fb
(所有位均为EV_TXEMPTY
)。由于EV_RXFLAG
为0x002
,您可以传递0x1F9
来清除它。
SetCommMask
的P / Invoke签名是:
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
[DllImport("Kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
static extern bool SetCommMask(
SafeFileHandle hFile,
int dwEvtMask
);
要获得hFile
,您必须使用反射来获取_handle
的{{1}}字段:
sp1_FT232R.BaseStream
答案 1 :(得分:0)
您可以考虑使用其他编码器。我喜欢使用windows-1252编码器,因为我经常处理来自8位MCU的小型(100字节)数据流。
http://msdn.microsoft.com/en-us/library/aa332096(v=vs.71).aspx