0

I've recently implemented a small program which reads data coming from a sensor and plotting it as diagram.

The data comes in as chunks of 5 bytes, roughly every 500 µs (baudrate: 500000). Around 3000 chunks make up a complete line. So the total transmission time is around 1.5 s.

As I was looking at the live diagram I noticed a severe lag between what is shown and what is currently measured. Investigating, it all boiled down to:

SerialPort.ReadLine();

It takes around 0.5 s more than the line to be transmitted. So each line read takes around 2 s. Interestingly no data is lost, it just lags behind even more with each new line read. This is very irritating for the user, so I couldn't leave it like that.

I've implemented my own variant and it shows a consistent time of around 1.5 s, and no lag occurs. I'm not really proud of my implementation (more or less polling the BaseStream) and I'm wondering if there is a way to speed up the ReadLine function of the SerialPort class. With my implementation I'm also getting some corrupted lines, and haven't found the exact issue yet.

I've tried changing the ReadTimeout to 1600, but that just produced a TimeoutException. Although the data arrived.

Any explanation as of why it is slow or a way to fix it is appreciated.

As a side-note: I've tried this on a Console application with only SerialPort.ReadLine() as well and the result is the same, so I'm ruling out my own application affecting the SerialPort.


I'm not sure this is relevant, but my implementation looks like this:

LineSplitter lineSplitter = new LineSplitter();
async Task<string> SerialReadLineAsync(SerialPort serialPort)
{
    byte[] buffer = new byte[5];
    string ret = string.Empty;

    while (true)
    {
        try
        {
            int bytesRead = await serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
            byte[] line = lineSplitter.OnIncomingBinaryBlock(this, buffer, bytesRead);
            if (null != line)
            {
                return Encoding.ASCII.GetString(line).TrimEnd('\r', '\n');
            }
        }
        catch
        {
            return string.Empty;
        }
    }
}

With LineSplitter being the following:

class LineSplitter
{
    // based on: http://www.sparxeng.com/blog/software/reading-lines-serial-port
    public byte Delimiter = (byte)'\n';
    byte[] leftover;


    public byte[] OnIncomingBinaryBlock(object sender, byte[] buffer, int bytesInBuffer)
    {
        leftover = ConcatArray(leftover, buffer, 0, bytesInBuffer);
        int newLineIndex = Array.IndexOf(leftover, Delimiter);
        if (newLineIndex >= 0)
        {
            byte[] result = new byte[newLineIndex+1];
            Array.Copy(leftover, result, result.Length);
            byte[] newLeftover = new byte[leftover.Length - result.Length];
            Array.Copy(leftover, newLineIndex + 1, newLeftover, 0, newLeftover.Length);
            leftover = newLeftover;

            return result;
        }
        return null;
    }

    static byte[] ConcatArray(byte[] head, byte[] tail, int tailOffset, int tailCount)
    {
        byte[] result;
        if (head == null)
        {
            result = new byte[tailCount];
            Array.Copy(tail, tailOffset, result, 0, tailCount);
        }
        else
        {
            result = new byte[head.Length + tailCount];
            head.CopyTo(result, 0);
            Array.Copy(tail, tailOffset, result, head.Length, tailCount);
        }

        return result;
    }
}
Arsenal
  • 333
  • 3
  • 15
  • ReadLine isn't so great. Better to use BaseStream directly: http://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport – Dax Fohl Jul 05 '17 at 13:23
  • This is interesting. When you look at the reference source, how does your code differ? https://referencesource.microsoft.com/#System/sys/system/io/ports/SerialPort.cs,b9e124ee9bc580dd – Andrew Diamond Jul 05 '17 at 13:27
  • @DaxFohl yeah, I've read that article, but it only mentions errors and synchronous being a problem, not that it is slow as hell as well. – Arsenal Jul 05 '17 at 13:35
  • @AndrewDiamond I've added my code. Maybe it helps someone with either an answer or with something else. – Arsenal Jul 06 '17 at 06:57

1 Answers1

-1

I ran into this issue in 2008 talking to GPS modules. Essentially the blocking functions are flaky and the solution is to use APM.

Here are the gory details in another Stack Overflow answer: How to do robust SerialPort programming with .NET / C#?

You may also find this of interest: How to kill off a pending APM operation

Peter Wone
  • 17,965
  • 12
  • 82
  • 134
  • Link-only answers are not useful on Stack Overflow. Add pertinent details to your answer so that the actionable information the OP requires Is contained entirely within your answer, or remove the answer. – Peter Duniho Jul 06 '17 at 03:59
  • Link only answers are not ideal because a day may come when the link no longer resolves. But this is a link to another Stack Overflow answer. The dead link problem couldn't happen unless some rule obsessed badge collector decided to remove useful information on the basis that it doesn't conform to some arbitrary notion of propriety. – Peter Wone Jul 06 '17 at 08:43
  • _"But this is a link to another Stack Overflow answer"_ -- if the other link is the _answer_ to the original question, then posting an answer here is inappropriate. The original question should be closed as a duplicate and your post here amounts to a comment, not an answer. Frankly, I don't see how the link you offered actually _answers_ the question anyway (so I'm not going to be the one to close the original question as a duplicate), but that's a wholly separate matter. – Peter Duniho Jul 06 '17 at 09:29
  • It provides an alternate way to solve the problem that doesn't suffer from performance or reliability issues, both of which were listed as concerns. It's not a duplicate _question_ but the same answer applies because using APM instead of blocking functions solves many problems, these being two. Frankly I think the serial blocking functions should be deprecated, they're poor quality and they encourage poor practices. – Peter Wone Jul 07 '17 at 03:34