4

我正在实现一个 C# 应用程序,它使用 USB 串行适配器(FTDI FT232H)以高波特率(8 MegaBaud)从微控制器读取二进制数据。问题是当流包含 0x1A 时,有时会在这个字节之后丢失一大块数据(数千字节)。

我在论坛上发现 0x1A 是一个特殊字符 (EOFCHAR),Windows 对此进行了特殊处理。但是,这种反编译SerialStream和更改EOFCHAR为另一个字节值的解决方案对我没有帮助,我需要使用整个字节范围(0..255)。

我创建了一个小型测试应用程序来重现我的问题,重复发送相同的字节,使用连接到同一台计算机的 2 个串行适配器和连接到 FT232H-RX 的 FT232R-TX

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();
        }
    }
}

测试结果(所有测试的 BUFF_SIZE = 1000000):

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)

此外,对于 2、3、4 等测试,CPU 上的负载(4 GHz 的 i7-4770k)很高(超过 30%),但测试 1 的负载很低(3%)。对于测试 1,我尝试了所有其他测试一个字节的模式(0x00..0x19,0x1B..0xFF)并且没有丢失。

您知道是否有解决方案吗?非常感谢!

4

2 回答 2

4

首先,我无法解释为什么接收该字符会导致字节丢失。虽然您可能认为问题在于设置 EOF 字符,但这并不完全合理。

(设备控制块)结构文档DCB表明这EofChar是“用于表示数据结束的字符的值”,但没有说明这意味着什么。EofChar我在其他任何地方都找不到其他关于神秘的参考。此外,同一页面对fBinary成员这样说:“如果此成员为 TRUE,则启用二进制模式。Windows 不支持非二进制模式传输,因此此成员必须为 TRUE。”

有什么联系?好吧,kb101419说明了它过去在 16 位 Windows 中的工作方式:

    fBinary - 如果 fBinary 设置为零,则接收 EofChar
        字符表示输入流的结束。读取通信()
        不会返回 EofChar 之后的任何字符。如果有任何字符
        在 EofChar 之后收到,将被视为溢出
        接收队列(CE_RXOVER)。EofChar 的接收是
        在 COMSTAT 状态标志 CSTF_EOF 中指示。如果 fBinary 是
        设置为 1,EofChar 字符没有特殊含义。

换句话说,只有在为零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始终将 to 设置EvtChar0x1A,但这并不重要,因为将其更改为不同的字节只会移动问题而不是解决问题。我相信实际问题是由设置位( )调用SetCommMask引起的。不幸的是,无论您是否在监听事件,这个标志总是被设置,这意味着驱动程序总是需要发出信号。EV_RXFLAG0x0002

我怀疑您可以通过SetCommMask清除该位来解决您的问题。的默认SerialStream值为0x1fb(除 之外的所有位EV_TXEMPTY)。既然EV_RXFLAG0x002,你可以传入0x1F9清除它。

P/Invoke 签名为SetCommMask

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领域sp1_FT232R.BaseStream

var _handle = (SafeFileHandle)sp1_FT232R.BaseStream.GetType()
              .GetField("_handle", BindingFlags.NonPublic | BindingFlags.Instance)
              .GetValue(sp1_FT232R.BaseStream);
SetCommMask(_handle, 0x1F9);
于 2014-03-31T15:38:31.733 回答
0

您可以考虑使用不同的编码器。我喜欢使用 windows-1252 编码器,因为我经常处理来自 8 位 MCU 的小(100 字节)数据流。

http://msdn.microsoft.com/en-us/library/aa332096(v=vs.71).aspx

于 2014-03-31T02:12:51.060 回答