MSVS C# SerialPort Received Data Loss

2019-09-19 21:06发布

I'm trying to create a Serial Communication tool on MSVS using C#. it communicates with the Photon MCU and a bluetooth dongle.

When the "start" button is pressed, the UI sends a "1" to the Photon which it first sends the current time stamp and starts streaming data from the function generator. When the "stop" button is pressed, It first sends 10 "2"s (due to the timer issue on the photon's end) which the when the Photon receives, it stops transmitting the function generator's data. Then it sleeps for a second and sends a "3" which it sends another current time stamp. Then the UI discards data in the InBuffer and stops reading data.

connectBT is connected with the start button and the disconnectBT is connected with the stop button.

This is the code that I have right now:

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

}

I'm running into an issue where when I press the "stop" button, the last chunk of data that was sent from the bluetooth is lost. I never receive the last time stamp that I'm supposed to have received when the stop button is pressed. According to our math, we're supposed to be receiving 500 points per second (500Hz) but I only receive about 100 of them.

My theory is that the UI is receiving data at a slower (or a delayed) rate and the serial.DiscardInBuffer discard the received data even before that the data can be printed to the Debug output. I know for a fact that all the data between the first and last I receive are all there because of counter values associated with the data packets. Basically if I were to receive 1~500 data points, I only receive 1~100. I've also tried it with just termite with sending 1,2, and 3 as the UI is supposed to be and I get all the data as I need them. I don't close BT on purpose.

What can I do to prevent this data loss? What am I doing wrong in my code that I shouldn't be doing or be doing for the correct bluetooth protocol? This is my first time writing bluetooth code so I'm fairly unfamiliar with it.

2条回答
beautiful°
2楼-- · 2019-09-19 21:38

Not sure if that's the cause of your problem, but your Receive has a very big pitfall.

You only read one line per Receive event, and on one event there can be more than one line to read, then they are being accumulated and discarded at the end.

ReadLine is meant to be used in a synchronous way like an stream where you read one line, process it then you write, not to be used with the DataReceived event.

You have two options: spin a new thread in a continuous loop reading with serial.ReadLine() (it will block until a new line is available) or the better approach, read the serial buffer on each Receive event.

To do it like that you can do smething like this:

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

As you can see, the received data is being stored on a temporal list used as a buffer (yes, an array and using Buffer functions would be faster, but for small messages and for simplicity a list is enough for most cases), then the received data is added to the buffer and when there are no more bytes left the list is processed in search of string lines.

Also note the read is in a loop, I have run in cases where there were data received while the function was being executed and no receive event was fired, so the better for this is to create a loop to read while there is still data.

查看更多
\"骚年 ilove
3楼-- · 2019-09-19 21:50

Thank you all for your response, they all helped me reaching the solution for my issue, but in the end what fixed it was delaying the time between sending the "3" and discarding my inBuffer and closing the Receive connection.

async Task DelayBT()
{
    await Task.Delay(100);
}

Thread.Sleep() didn't work because of its nature disabling all action within the thread (which I still needed) so this method worked like a charm. I just called await DelayBT where I needed the delay.

Hope this helps anyone running into the same issue as me.

查看更多
登录 后发表回答