Communication speed over USB (PC/Arduino) using SerialPort in C# seems to “clip”

三世轮回 提交于 2021-01-29 06:45:36

问题


Prepare for a lengthy explanation - sorry.

I'm building an application in C#, using Visual Studio 2019, that will communicate with some Arduino MEGA 2560 R3 devices. The connection is through USB Serial (if I'm not mistaken, this is USB CDC, right?).

Communication runs fine over all kinds of baudrates, even up to 2000000.

To make sure that my Arduino can "follow" the command rate sent by my PC, I'm using an ACK-protocol, which is as simple as my Arduino confirming each command with the sequence "A\n".

In an attempt to stress test the communication, I let the application send "0123456789\n" commands as fast as possible, waiting for the "A\n" after each command sent. This means sending 11 characters, and receiving 2 characters, a total of 13 characters.

And here is where the mystery starts.

Starting from 38400 baud, the Tx/Rx cycle seems to clip at 244 per second. If I increase the baudrate up to 2000000, it remains at 244 Tx/Rx cycles per second. What is going here!

Below is the PC code, which is a simple Console application written in Visual Studio 2019:

using System;
using System.Diagnostics;
using System.IO.Ports;


namespace BasicComTest
{
    class Program
    {
        public static SerialPort _serialPort;


        static void Main(string[] args)
        {
            _serialPort = new SerialPort();
            _serialPort.BaudRate = 115200;
            _serialPort.PortName = "COM4";
            _serialPort.Handshake = Handshake.None;
            _serialPort.NewLine = "\n";


            _serialPort.ReadTimeout = 2000;
            _serialPort.WriteTimeout = 2000;


            _serialPort.Open();


            _serialPort.WriteLine("");


            Stopwatch sw = new Stopwatch();
            sw.Start();


            ulong count = 0;


            while (!Console.KeyAvailable)
            {
                _serialPort.WriteLine($"0123456789");
                string ack = _serialPort.ReadLine();


                count++;
                if (count == 1000)
                {
                    sw.Stop();
                    Debug.WriteLine($"1000 commands in {sw.ElapsedMilliseconds} - {1000000/sw.ElapsedMilliseconds} commands/sec");
                    count = 0;
                    sw.Restart();
                }
            }


            _serialPort.Close();
        }
    }
}

And below is the simple Arduino sketch (I'm using an ISR routine in an attempt to increase speed):

volatile uint8_t rcvd = 0;

String sCommandRx;
String sCommand;
bool bCommandRx;

ISR(USART0_RX_vect)
{
  // received data
  rcvd = UDR0;

  if (rcvd == '\n')
  {
    sCommandRx = sCommand;
    bCommandRx = true;
    sCommand = "";
  }
  else
    sCommand += (char)rcvd;
}

void setup()
{
  UBRR0H = 0;
  UBRR0L = 0;

  // USART initialization with 16MHz system clock, 8-none-1, RX interrupt
  UCSR0A = 1<<U2X0 | 0<<MPCM0;
  UCSR0B = 1<<RXCIE0 | 0<<TXCIE0 | 0<<UDRIE0 | 1<<RXEN0 | 1<<TXEN0 | 0<<UCSZ02;
  UCSR0C = 0<<UMSEL01 | 0<<UMSEL00 | 0<<UPM01 | 0<<UPM00 | 0<<USBS0 | 1<<UCSZ01 | 1<<UCSZ00 | 0<<UCPOL0;

  sCommand = "";
}

void loop()
{
  if (bCommandRx)
  {
    if (sCommandRx == "0123456789")
    {
      UDR0 = 'A';
      while((UCSR0A & 1<<TXC0)==0);
      UCSR0A |= 1<<TXC0;
     
      UDR0 = '\n';
      while((UCSR0A & 1<<TXC0)==0);
      UCSR0A |= 1<<TXC0;
    }

    bCommandRx = false;
  }
}

I can already guarantee that the Arduino code works perfect, because I tested it with 2 Arduino's back to back to rule out that they are causing the clipping. If you want to read the full story, you will find it on the Arduino forum here:

https://forum.arduino.cc/index.php?topic=719361.0

If I measure the communication speed with the 2 back-to-back Arduino's, then it goes as fast as 6060 Tx/Rx sets per second at 2000000 baud (you have to count some overhead, which is measured at about 100 msec for 1000 commands - you can read the full report in the Arduino forum).

But using my console application, I get maximum 244 Tx/Rx sets per second. Except (and this makes it even weirder), if I build the "A\n" in the ISR routing in my Arduino, then I'm getting exactly the double of 488 Tx/Rx sets per second:

volatile uint8_t rcvd = 0;


ISR(USART0_RX_vect)
{
    // received data
    rcvd = UDR0;
    
    // reply with "A\n" after receiving "\n"
    if(rcvd == '\n')
    {
        UDR0 = 'A';
        while((UCSR0A & 1<<TXC0)==0);
        UCSR0A |= 1<<TXC0;
        
        UDR0 = '\n';
        while((UCSR0A & 1<<TXC0)==0);
        UCSR0A |= 1<<TXC0;
    }
}

void setup()
{
    // set baud rate (500 Kbps)
    UBRR0H = 0;
    UBRR0L = 3;

    // USART initialization with 16MHz system clock, 8-none-1, RX interrupt
    UCSR0A = 1<<U2X0 | 0<<MPCM0;
    UCSR0B = 1<<RXCIE0 | 0<<TXCIE0 | 0<<UDRIE0 | 1<<RXEN0 | 1<<TXEN0 | 0<<UCSZ02;
    UCSR0C = 0<<UMSEL01 | 0<<UMSEL00 | 0<<UPM01 | 0<<UPM00 | 0<<USBS0 | 1<<UCSZ01 | 1<<UCSZ00 | 0<<UCPOL0;
}

void loop(){
}

But whether I put the transmission of "A\n" in the loop() or in the ISR, the results of back-to-back Arduino's remains the same.

Sorry for this very long and detailed description. But my question is obviously very simple. What is the reason of this clipping?

I have a theory. Although I have read a lot of "bad comments" about SerialPort, I think that the reason has to be found in the fact that this goes over USB (Arduino works with USB 2.0). I have explained the theory on the Arduino forum (I even corrected my explanation slightly below):

If I'm not mistaken, USB 2.0 HID devices (and I guess CDC is the same) have a polling rate of 1 msec. I experienced with some PIC microcontrollers, and built my own USB stack on my PC to communicate with it, and I had some limit of 1000 commands/sec, or 500 Tx/Rx sets per second. As you can read above, the fastest results I could get with SerialPort were 488 Tx/Rx sets per second, which is slightly slower but pretty close. There might of course be some "conversion-lag", creating some de-synchronization between serial and USB, hence missing some USB frames. Putting the "A\n" answer in the loop() (not in the ISR) gave me only the half (244) commands. I think that putting the answer in the ISR made it react so fast, that the data could still "piggy back" on the next frame. If I put the answer in the loop(), it missed the train, and took the some later 1msec frames. Anyway, these are just WILD GUESSES for now, so I could be COMPLETELY wrong of course.

Update:

Just tried to work with some lower level file handling in my C# program, using Kernel32.CreateFile, Kernel32.WriteFile and Kernel32.ReadFile, but got exactly the same results which means 244 Tx/Rx sets per second. Even tried to play with the timings (COMMTIMEOUTS), but nothing changed.

I also tried with sending and receiving larger strings of characters, and it seems that if I use up to 72 + '\n' for both the Tx and Rx portion, it remains stable. Using more characters starts dropping the Tx/Rx sets per second number, until I use 76 + '\n' for both the Tx and Rx portion. Then it remains stable again at 162 Tx/Rx sets per second.

For my application, 244 Tx/Rx sets per second is good enough, but still am curious to understand from where that limitation comes.

来源:https://stackoverflow.com/questions/65415004/communication-speed-over-usb-pc-arduino-using-serialport-in-c-sharp-seems-to

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!