c# - C# SerialPort - RS485 如何在一个部分中接收所有数据
问题描述
我有一个通过 RS485 进行通信的设备,我需要在 C# 中进行环回以接收我的数据并将它们发回。第一个字节包含有关即将到来的数据长度的信息。有时,C# 会分几部分触发一个事件。我需要我只收到一部分,以便我可以一次性寄回。
var buffer = new byte[1000];
port = new SerialPort(ConfigParams.PORT, 115200, Parity.None, 8, StopBits.One);
port.Handshake = Handshake.None;
port.RtsEnable = true;
port.DtrEnable = true;
port.DataReceived += (sender, e) =>
{
var length = port.ReadByte();
Thread.Sleep(10);
var bytes = port.Read(buffer, 1, length);
buffer[0] = (byte)length;
port.Write(buffer, 0, length + 1);
mainForm.Log("Port (" + (length + 1).ToString() +"): " + Encoding.UTF8.GetString(buffer, 0, length + 1)+"\n");
mainForm.Log("Data was sended.\n");
};
第一步,我读取第一个字节以获取即将到来的字节数。然后,我读取了其余的字节。如果我不在那里插入 Thread.Sleep(10) 行,Event DataReceived 有时会分几个部分触发。使用 Thread.Sleep(10) 它每次都能正常工作。
你知道为什么会这样吗?我希望如果我说我想读取 40 个字节,SerialPort 将尝试接收所有这些 40 个字节或发生异常。我试图改变属性 ReadTimeout 但没有改变。
解决方案
这里的问题是,当您调用SerialPort.Read()
它时,它将在缓冲区中等待至少一个字节,然后它将返回它拥有的所有字节,最大为指定长度。
这意味着它有可能返回少于要求的金额。
为了规避这个问题,您可以编写类似的代码(重要的一点在private blockingRead()
方法中):
/// <summary>
/// Attempts to read <paramref name="count"/> bytes into <paramref name="buffer"/> starting at offset <paramref name="offset"/>.
/// If any individual port read times out, a <see cref="TimeoutException"/> will be thrown.
/// </summary>
/// <param name="buffer">The byte array to write the input to. </param>
/// <param name="offset">The offset in buffer at which to write the bytes. </param>
/// <param name="count">The number of bytes to read from the port.</param>
/// <param name="timeoutMilliseconds">
/// The timeout for each individual port read (several port reads may be issued to fulfil the total number of bytes required).
/// If this is -2 (the default) the current <see cref="ReadTimeout"/> value is used.
/// If this is -1 or <see cref="SerialPort.InfiniteTimeout"/>, an infinite timeout is used.
/// </param>
/// <exception cref="TimeoutException">Thrown if any individual port read times out.</exception>
public void BlockingRead(byte[] buffer, int offset, int count, int timeoutMilliseconds = SerialComPortTimeout.UseDefault)
{
if (timeoutMilliseconds < SerialComPortTimeout.UseDefault)
throw new ArgumentOutOfRangeException(nameof(timeoutMilliseconds),timeoutMilliseconds, $"{nameof(timeoutMilliseconds)} cannot be less than {SerialComPortTimeout.UseDefault}." );
int timeoutToRestore = setTimeoutAndReturnOriginal(timeoutMilliseconds);
try
{
blockingRead(buffer, offset, count);
}
finally
{
if (timeoutToRestore != SerialComPortTimeout.UseDefault)
this.ReadTimeout = timeoutToRestore;
}
}
private void blockingRead(byte[] buffer, int offset, int count)
{
while (count > 0)
{
// SerialPort.Read() blocks until at least one byte has been read, or SerialPort.ReadTimeout milliseconds
// have elapsed. If a timeout occurs a TimeoutException will be thrown.
// Because SerialPort.Read() blocks until some data is available this is not a busy loop,
// and we do NOT need to issue any calls to Thread.Sleep().
int bytesRead = _serialPort.Read(buffer, offset, count);
offset += bytesRead;
count -= bytesRead;
}
}
private int setTimeoutAndReturnOriginal(int timeoutMilliseconds)
{
int originalTimeout = this.ReadTimeout;
if ((timeoutMilliseconds != SerialComPortTimeout.UseDefault) && (originalTimeout != timeoutMilliseconds))
{
this.ReadTimeout = timeoutMilliseconds;
return originalTimeout;
}
return SerialComPortTimeout.UseDefault;
}
请注意 .Net 的实现SerialPort
是 flakey - 有关详细信息,请参阅本文。
推荐阅读
- java - 尝试删除 Java AD 身份验证
- cordova - PhoneGap 和 Amazon SNS
- python - Python 3.6.5 中的多处理 PicklingError 在 3.6.1 中不会发生
- javascript - AWS - import JSON file to load Dynamo table
- javascript - 更改轴中刻度的文本
- javascript - How to Make Select All Check Box
- cmake - CMake在定义之前设置变量,在定义后访问它们
- javascript - jest.setMock 不适用于已安装的依赖项
- virtualbox - 在 Virtaul Box 上的 Ubuntu 18.04 上 Steam
- google-colaboratory - 本地运行时 404 GET