-1

Kindly bear with me for this confusing question. I'm finding it as hard to describe as it is involving and tiresome. Read it and you'll know why.

I've been hounding this issue for over a month now without much progress. I'm using an STM32 (STM32F103C8 mounted on a BluePill board) to communicate with a C# app through an FT232r Serial-USB converter. The complete communication protocol is a bit complex. I'm writing here a simplistic version of the code that explains my problem quite accurately.

STM32 does the following.

In the initial setup,

  1. Serial.begin at 2000000 (Yes it's very high but I've analyzed it using an oscilloscope and the signal is very healthy; impedance matching and clock jitter is very accurate).

  2. Waits for a command from the C# end to enter the loop

In the loop, it does the following.

  1. TX a byte buffer of length N on the serial port. Packet structure is 0xAA, N bytes, 1 byte checksum.
  2. repeat the loop

And on the C# side (Pseudo code),

  1. new Thread(()=>{while(true) IOTick(); Thread.Sleep(30); }).Start();
IOTick() is defined as:
{
  while(SerialPortObject.BytesToRead > 1)
  {
    header = read();
    if (header != 0xAA) continue;
    byte [] buffer = new byte[N + 1];
    receivedBytes = readBytes(buffer, N + 1, Timeout = 500ms); // receivedBytes is never less than N + 1 for timeout greater than 120)
    use the N=16 bytes. Check Nth byte to compare checksum. Doen't take too much CPU time. 
    Send a packet received software event.
  }
}

readBytes is defined as

 int readBytes(byte[] buffer, int count, int timeout)
    {
        var st = DateTime.Now;
        for (int i = 0; i < count; i++)
        {
            var b_ = read(timeout);
            if (b_ == -1)
                return i;
            buffer[i] = (byte)b_;
            timeout -= (int)(DateTime.Now - st).TotalMilliseconds;
        }
        return count;
    }


    int buffer2ReadIndex = 0;
    byte[] buffer2= new byte[0];

    int read(int timeout)
    {
        DateTime start = DateTime.Now;

        if (buffer2.Length == 0)
        {
            while (SerialPortObject.BytesToRead <= 0)
            {
                if ((DateTime.Now - start).TotalMilliseconds > timeout)
                    return -1;
                System.Threading.Thread.Sleep(30);
            }
            buffer2 = new byte[SerialPortObject.BytesToRead];
            sp.Read(buffer2, 0, buffer2.Length);
        }
        if (buffer2.Length > 0)
        {
            var b = buffer2[buffer2ReadIndex];
            buffer2ReadIndex++;
            if (buffer2ReadIndex >= buffer2.Length)
            {
                buffer2ReadIndex = 0;
                buffer2 = new byte[0];
            }
            return b;
        }
        return -1;
    }

Now, everything is working as expected. The packet received software event is triggered not later than every ~30ms (the windows tick time). The problem starts if I have to wait between each packet TX at the STM side. First, I suspected that the I2C I was using for some tasks between each packet TX was causing some HW or software conflict with serial data which gets corrupted. But then I noticed that only if I introduce a delay of 1 millisecond using Arduino delay() between each packet TX, the same thing happens. Almost, 1K packets should be received every second now. Almost 1 out of 10 packets after a successful header exception get either not delivered completely or delivered with corrupted checksum, causing the C# app to lose the packet Header. The new header trace obviously requires flushing some bytes, losing some packets in the communication. Even this doesn't sound too bad for an app that can afford 5% data packet loss, strangely though, when this anomaly occurs, the packet received software interrupt waits for more than 1 second after every couple hundred of consecutive events.

I'm completely blind here. Even tried it with 115200 baud rate, does the same loss with a slightly lesser loss ratio. It should be noted that at 9600 baud, the issue doesn't happen. This is the only hint I've got right now.

Umar Hassan
  • 192
  • 3
  • 11
  • That `Thread.Sleep(30);` is a bit worrisome. You can chew up all the processor time you want in each loop, so long as the processor yields when some other work needs to be done; try `Thread.Sleep(1);` – Robert Harvey Oct 06 '20 at 18:57
  • 1
    Alternatively, rewrite your code so that it uses hardware interrupts instead of polling to retrieve the data from the serial port (this is probably a better approach). See [here](https://stackoverflow.com/a/1243078/102937) for an example. – Robert Harvey Oct 06 '20 at 18:58
  • @RobertHarvey, In my understanding, Thread.Sleep(1) won't be a lot better than Sleep(30). Isn't it the windows Tick time? i.e. windows don't give back the context before 25-30ms once a thread has yielded? – Umar Hassan Oct 06 '20 at 19:10
  • @RobertHarvey. About hardware interrupts, it says on MS docs here (https://learn.microsoft.com/en-us/dotnet/api/system.io.ports.serialport.datareceived) that serial port doesn't guarantee an event at each byte received. Plus it requires an EOF character that doesn't go along my protocol. – Umar Hassan Oct 06 '20 at 19:15
  • `... doesn't guarantee an event at each byte received` -- That is correct. You have to read a little bit farther: `Use the BytesToRead property to determine how much data is left to be read [from] the buffer.` – Robert Harvey Oct 06 '20 at 19:19
  • As far as the EOF character is concerned, simply write your code in a way that doesn't require it. – Robert Harvey Oct 06 '20 at 19:22
  • I believe I'm doing a similar thing in the read functions. kindly also look into read() and readBytes(). I'm also doing a second level buffer. The original serai.read is too slow and even keeps on accumulating data when reading 16-32 bytes at a time at a rate of 2Mbps baud, i.e ~200KBps. So this way, if my packet is 16-32 bytes long, I still read data fro the serial in chunks of 4000-5000 bytes which is way faster. – Umar Hassan Oct 06 '20 at 19:24
  • Yes. Serial Port communication is an unwieldy beast; it's hard on the processor and relies on ancient technology (by today's standards). – Robert Harvey Oct 06 '20 at 19:26
  • That's why your best course of action is to probably do it the way Microsoft says to do it. – Robert Harvey Oct 06 '20 at 19:26
  • Thanks for corroborating my difficulty, lemme get back on this. Back to the docs!! – Umar Hassan Oct 06 '20 at 19:28
  • I read the MS doc again. EoF is not an issue, I see. Although I'm not too hopeful about using data received event because it's using another system defined thread, which, I doubt, will be bound by the same windows tick limitations, I still think I can implement it to write directly on by 2nd level of buffer. Will get back on it in inn 8-10 hours. Need to test on HW. – Umar Hassan Oct 06 '20 at 19:36
  • use the queues to communicate with worker serial thread which shoud be async. No need for any sleep. BTW `sleep` indicates serious problem in the program. – 0___________ Oct 06 '20 at 19:38
  • Coupled with the previous idea of events, I think queues can be very helpful. Lemme test it on HW and get back on this.... – Umar Hassan Oct 06 '20 at 19:41

1 Answers1

0

It looks like I've found an answer.

After digging deep into SerialPort and SerialPort.base stream class and after doing some document reading and benchmarking, here is what I've observed: SerialPort.BytesToRead updates are not uniform. DataReceived event seems to be following it. When bytes are coming at ~200kHz, (baud = 2Mbps), It is updated almost instantaneously (or within 30ms, worst case). When they are coming at ~20kHz or slower (evenly spaced on time using a micrcontroller), the SerialPort.BytesToRead can take up to 400ms to update. This happens only after a dozen 30ms updates.

So, observing this, I can say that SerialPort.BytesToRead is updated on two conditions. Some amount of time has passed since the data arrived (and this time is not constrained to 30ms) or the data is coming too fast.

This is a strange behavior. No data is lost when this anomaly is occurring. Not to surprise, 0.06% of bytes are lost when working at full bandwidth (200KBps at baud of 2Mbps).

Umar Hassan
  • 192
  • 3
  • 11