gpt4 book ai didi

c# - 当流包含 0x1A 时,SerialPort 接收到的数据在高波特率下丢失

转载 作者:行者123 更新时间:2023-11-30 16:12:34 41 4
gpt4 key购买 nike

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

我在论坛上发现0x1A是一个特殊字符(EOFCHAR),Windows对此做了特殊处理。然而this solution反编译 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)

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

请问有解决办法吗?非常感谢!

最佳答案

首先,我无法解释为什么接收到那个字符会导致字节丢失。虽然您可能认为问题出在 EOF 字符的设置上,但这不太合理。

documentation for the DCB (device control block) structure表示 EofChar 是“用于表示数据结束的字符的值”,但没有说明这意味着什么。我在其他任何地方都找不到对神秘的 EofChar 的其他引用。此外,同一页还提到了 fBinary 成员:“如果该成员为 TRUE,则启用二进制模式。Windows 不支持非二进制模式传输,因此该成员必须为 TRUE。”

有什么关系?嗯,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.

In other words, the EofChar is only used when fBinary is zero, yet Windows no longer supports that mode, thus it seems that EofChar is ignored.

So what is causing 0x1A to be treated as a special character? The DCB has another member called EvtChar, defined as The value of the character used to signal an event. When this character is received on the port, the port's event is signaled and the EV_RXFLAG bit is set for that port. The SerialData enum defines Eof = NativeMethods.EV_RXFLAG, which is why there is some confusion as to what EOF means.

OK, but that doesn't explain why that character causes data loss. I couldn't find any documentation about it, but my guess is that when the EvtChar is received on the port, the event is signaled and no more data is buffered until that event is cleared. At low data rates the event is cleared before another byte is received, so nobody ever noticed the problem before. At high data rates, potentially thousands of bytes may be received during this period. And if this is actually an aspect of the serial port driver, the behavior may be hard to reproduce on other systems.

Now the problem is how to disable this behavior. The SerialStream class always sets the EvtChar to 0x1A, but that doesn't matter because changing it to a different byte just moves the problem rather than fixing it. I believe the actual problem is caused by calling SetCommMask with the EV_RXFLAG bit (0x0002) set. Unfortunately this flag is always set regardless of whether you are listening on the event, meaning that the driver will always need to signal it.

I suspect that you can solve your problem by calling SetCommMask with that bit cleared. The default for SerialStream is 0x1fb (all bits but EV_TXEMPTY). Since EV_RXFLAG is 0x002, you can pass in 0x1F9 to clear it.

The P/Invoke signature for SetCommMask is:

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,您必须使用反射来获取 sp1_FT232R.BaseStream_handle 字段:

var _handle = (SafeFileHandle)sp1_FT232R.BaseStream.GetType()
.GetField("_handle", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(sp1_FT232R.BaseStream);
SetCommMask(_handle, 0x1F9);

关于c# - 当流包含 0x1A 时,SerialPort 接收到的数据在高波特率下丢失,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22745933/

41 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com