Salman
Salman

Reputation: 11

High speed serial communication over serial port in C#

I am working with visual studio 2019. I am sending data from microcontroller over uart/serial port to the PC and I want to read it on the PC side.

The baud rate is 9600. I am using the following code. However, its very slow. I need to be able to read the code with very high speed (comparable to the baud rate I use).

At present, I am getting 2-3 packets per second. The timer interval is set at 10ms but even if I change it to 1ms, there is no difference. I cant figure out what am I doing wrong. Help will be greatly appreciated.

Regards, Salman

    public void  readCapsule(SerialPort sp)
    {
        timer1.Enabled = false;

        string headerStart = "";
        string headerEnd = "";
        List<Int32> newCoordinates = new List<Int32>();

        headerStart = sp.ReadLine();

        if (headerStart == "START")
        {               
            for (int i = 0; i < 3; i++)
            {
                Int32 coords = sp.ReadByte();
                newCoordinates.Add(coords);
            }

            tbRead.AppendText("X: " + newCoordinates[0].ToString() + " ");
            tbRead.AppendText("Y: " + newCoordinates[1].ToString() + " ");
            tbRead.AppendText("Z: " + newCoordinates[2].ToString() + Environment.NewLine);

            headerEnd = sp.ReadLine();
            newCoordinates.Clear();                
        }
    }

    private void timer1_Tick(object sender, EventArgs e)
    {
        readCapsule(spCapsule);            
        Application.DoEvents();
        spCapsule.DiscardInBuffer();
        timer1.Enabled = true;
    }

My Microcontroller code is basically incrementing 3 variables and sending them in between the START and END header. I know that there are other lines being executed so the throughput wont be 9600 but the delay I experience is WAY too long to be contributed from the microcontroller side I think. The microcontroller code is below. I am using Atmega328p. I have verified on hyper terminal that the incoming data is way faster than the rate at which I read with my C# code:

while (1) {

    counter++; val1 = val1 + 3; val2 = val2 + 3; val3 = val3 + 3; 

    if(counter >= 100) {

        counter = 0;
        val1 = 1; val2 = 2; val3 = 3;

        }

    transmitUart0('S'); transmitUart0('T'); transmitUart0('A'); transmitUart0('R'); transmitUart0('T');transmitUart0(0x0A);
    transmitUart0(val1);
    transmitUart0(val2);
    transmitUart0(val3);
    transmitUart0('E'); transmitUart0('N'); transmitUart0('D'); transmitUart0(0x0A);

    _delay_ms(10);

}

Upvotes: 0

Views: 4857

Answers (3)

DasKr&#252;melmonster
DasKr&#252;melmonster

Reputation: 6060

You should try to handle all the buffer data at once. So rather than reading byte by byte or line by line, subscribe to the event SerialPort.DataReceived and process the whole chunk at once.

See this example: https://learn.microsoft.com/en-us/dotnet/api/system.io.ports.serialport.datareceived?view=netframework-4.8

private static void DataReceivedHandler(
                    object sender,
                    SerialDataReceivedEventArgs e)
{
    SerialPort sp = (SerialPort)sender;
    string indata = sp.ReadExisting();
    Console.WriteLine("Data Received:");
    Console.Write(indata);
    // Use BeginInvoke for synchonization!
}

For using BeginInvoke, see this other thread.

Upvotes: 1

Marcos G.
Marcos G.

Reputation: 3516

We don't know what your microcontroller is doing but on your C# code you're introducing a huge overhead on your processing by reading bytes one by one.

The solution to reducing this overhead is, obviously, do not read bytes one by one. Just get rid of the for loop, do a second sp.ReadLine() right after you detect the header, work with the bytes you get to store the coordinates and discard the rest.

If this approach is not fixing your problem, then try to square it: read more commands in one go and process them. At this point, it might be easier to change the way the microcontroller works I guess.

EDIT: Now that you have included more details, and as you have already realized, my comments above are not really helpful. ) First off, ReadLine() is known to be very slow, see for instance here.

What you're trying to do is not really that demanding if you work out the numbers. So the solution might be to use any other method, I would advise trying the BaseStream to implement your own way of reading between CRs (something similar to what the question linked above proposes).

Otherwise you can try the DataReceived event.

Finally, note that when you say:

The timer interval is set at 10ms but even if I change it to 1ms, there is no difference...

referring to the delay on your microcontroller. You got that the other way around: if you want to see better performance reading data on your PC you need to increase and not reduce this delay (otherwise you are sending even more data than the amount you can handle). But if ReadLines() is that slow, I doubt you can improve the performance exploiting this, unless you're willing to read one data sample every 3 or 4 seconds.

Upvotes: 1

Salman
Salman

Reputation: 11

I have made some changes to the code as per the feedback. The delays are the same. Please see the code below:

public void readCapsule(SerialPort sp)
{
        timer1.Enabled = false;

        string headerStart = "";
        string headerEnd = "";
        List<Int32> newCoordinates = new List<Int32>();

        headerStart = sp.ReadLine();
        tbRead.AppendText(headerStart + Environment.NewLine);

}


private void timer1_Tick(object sender, EventArgs e)
{
        readCapsule(spCapsule);
        timer1.Enabled = true;
    }

Upvotes: 0

Related Questions