MSVS C# SerialPort 收到数据丢失

本文关键字:数据 SerialPort MSVS | 更新日期: 2023-09-27 18:36:25

我正在尝试使用 C# 在 MSVS 上创建一个串行通信工具。 它与光子MCU和蓝牙加密狗通信。

当按下"开始"按钮时,UI向Photon发送一个"1",它首先发送当前时间戳并开始从函数生成器流式传输数据。当按下"停止"按钮时,它首先发送10个"2"(由于光子端的计时器问题),当光子接收到时,它停止传输函数发生器的数据。然后它休眠一秒钟并发送一个"3",它发送另一个当前时间戳。然后,UI 会丢弃 InBuffer 中的数据并停止读取数据。

connectBT与开始按钮连接,disconnectBT与停止按钮连接。

这是我现在拥有的代码:

SerialPort serial = new SerialPort();
string recieved_data;
int startBuffer = 0;
private void connectBT(object sender, RoutedEventArgs e)
{
    startBuffer++; // keep track of BT open counter
    if (serial.IsOpen) Debug.WriteLine("BT Open");
    // first time BT is open and BT is not open
    if (!serial.IsOpen)
    {
        if (startBuffer == 1)
        {
            // COM port properties
            serial.PortName = "COM7";
            serial.BaudRate = 38400;
            serial.Handshake = Handshake.None;
            serial.Parity = Parity.None;
            serial.DataBits = 8;
            serial.StopBits = StopBits.One;
            serial.ReadTimeout = 200;
            serial.WriteTimeout = 50;
            serial.Open();
        }
        startButton.Content = "Recording";
        Send_Data("1"); // tell Photon to start sending data
        serial.DiscardInBuffer(); // discard whatever is in inbuffer
        serial.DataReceived += new SerialDataReceivedEventHandler(Recieve); // start receiving data
    }
    // after BT has been opened and start button has been pressed again
    else if (serial.IsOpen && startBuffer > 1)
    {
        startButton.Content = "Recording";
        Send_Data("1");
        serial.DiscardInBuffer();
        serial.DataReceived += new SerialDataReceivedEventHandler(Recieve);
    }
}
// stop button is pressed
private void disconnectBT(object sender, RoutedEventArgs e)
{
    // send "2" ten times to tell photon to stop transmitting function generator data
    int i = 0;
    while (i < 10)
    {
        Send_Data("2");
        Thread.Sleep(1);
        i++;
    }
    Thread.Sleep(1000);
    Send_Data("3"); // send a 3 to tell photon to send the last time stamp
    Thread.Sleep(1000);
    serial.DiscardInBuffer(); // discard in buffer
    serial.DataReceived -= Recieve; // stop receiving data
    //serial.Close(); // close BT
    startButton.Content = "Start";
}
private void Recieve(object sender, SerialDataReceivedEventArgs e)
{
    recieved_data = serial.ReadLine();
    Debug.WriteLine(recieved_data);
}

我遇到了一个问题,当我按下"停止"按钮时,从蓝牙发送的最后一块数据丢失了。我永远不会收到按下停止按钮时应该收到的最后一个时间戳。根据我们的数学计算,我们应该每秒接收 500 点 (500Hz),但我只收到大约 100 个点。

我的理论是,UI 以较慢(或延迟)的速度接收数据,serial.DiscardInBuffer甚至在数据可以打印到 Debug 输出之前就丢弃接收到的数据。我知道一个事实,由于与数据包相关的计数器值,我收到的第一个和最后一个之间的所有数据都在那里。基本上,如果我接收 1~500 个数据点,我只接收 1~100 个数据点。我也尝试过只用白蚁发送 1、2 和 3 作为 UI 应该的样子,我需要它们时我会得到所有数据。我不是故意关闭BT。

我该怎么做才能防止这种数据丢失?我在代码中做错了什么,我不应该为正确的蓝牙协议做或正在做?这是我第一次编写蓝牙代码,所以我对它相当陌生。

MSVS C# SerialPort 收到数据丢失

不确定这是否是导致您问题的原因,但是您的接收有一个非常大的陷阱。

每个接收事件只读取一行,在一个事件上可以读取多行,然后它们在最后被累积并丢弃。

ReadLine 旨在以同步方式使用,例如在流中读取一行,处理它然后写入,而不是与 DataReceived 事件一起使用。

您有两种选择:使用串行在连续循环读取中旋转新线程。ReadLine()(它将阻塞,直到有新行可用)或更好的方法,在每个接收事件上读取串行缓冲区。

要做到这一点,你可以像这样做

    List<byte> tmpBuffer = new List<byte>();
    static byte newLineB = Encoding.ASCII.GetBytes("'n")[0];
    void Receive(object sender, SerialDataReceivedEventArgs e)
    {
        lock (tmpBuffer)
        {
            while (serial.BytesToRead > 0)
            {
                byte[] segment = new byte[serial.BytesToRead];
                serial.Read(segment, 0, segment.Length);
                tmpBuffer.AddRange(segment);
                ProcessBuffer();
            }
        }
    }
    private void ProcessBuffer()
    {
        int index = 0;
        while ((index = tmpBuffer.IndexOf(newLineB)) > -1)
        {
            string line = Encoding.ASCII.GetString(tmpBuffer.Take(index + 1).ToArray());
            //Do whatever you need to do with the line data
            Debug.WriteLine(line);
            tmpBuffer.RemoveRange(0, index + 1);
        }
    }

如您所见,接收到的数据存储在用作缓冲区的临时列表中(是的,数组和使用 Buffer 函数会更快,但对于小消息和简单起见,在大多数情况下,列表就足够了),然后将接收的数据添加到缓冲区中,当没有更多字节时,将处理列表以搜索字符串行。

另请注意,读取是在循环中,我在执行函数时收到数据并且没有触发接收事件的情况下运行过,因此更好的方法是创建一个循环以在仍有数据时读取。

谢谢大家的回复,他们都帮助我解决了我的问题,但最终修复它的是延迟发送"3"和丢弃我的 inBuffer 和关闭接收连接之间的时间。

async Task DelayBT()
{
    await Task.Delay(100);
}
Thread.Sleep()不起作用,

因为它的性质禁用了线程中的所有操作(我仍然需要),所以这种方法就像一个魅力。我只是打电话给await DelayBT我需要延迟的地方。

希望这可以帮助任何遇到与我相同的问题的人。