Go Down

Topic: Due native USB bidirectional I/O performance [Now with specific example] (Read 3814 times) previous topic - next topic

exedor

Hello all,
 I have an application I'm testing that uses bi-directional communication on the native USB port of the Due.  It sends 32 to 64 byte packets back and forth at 2400 times per second both directions.  I don't have any problems going from the Due to the pc at even 4800 times per second.  However, as soon as I start sending any data even at slower speeds like 1200 times per second from the PC to the Due while the Due is also sending at any speed, I get massive errors, data loss, and after about 5 seconds or so it will even hang the Due and it must be reset to recover.  Here are a few things I've already tried:

- Making sure data being sent/received is isolated so I'm not running into any concurrency issues (although I'm not sure how that would be a problem anyway since this thing doesn't really appear to be multi-threaded)

- Currently output from the Due is on a timer (TC1, ch2 so TC5_Handler) but I've tried just processing it in the main loop "as fast as it can" as well but I see the same behavior.

- I tried synchronizing the I/O so that the Due doesn't send it get's the response from the PC and the PC doesn't send until it gets a response from the Due.  That solves the problems, makes it stable and run fine, but us WAAAY too slow at around 250 packets per second.

- I have standard packet sizes.  I've tried both 32 byte and 64 byte packets and I get the exact same results.

I'm about ready to give using SerialUSB as the read line and Serial as the write line a try to see if I can make that work.  Ugly as heck but I figured it was worth a try.  Can anyone help me understand why as soon as I try to go bi-directional with the USB I/O everything goes to crap?

Thanks!

Paul Stoffregen

Could you post the code to run this test?

I personally can't help much, but I'd really like to give this test a try on Teensy3 to see if it works.

Paul Stoffregen

Ok.  I'm fine with ugly code and having to change some names of stuff.  Teensy does indeed have native USB, which is part of the reason I'd like to try running your benchmark.  Like Leonardo's native USB, it's called "Serial", not "SerialUSB".  That's pretty easy to change.

It does take a LOT of time to do good benchmarks, make the code nice to use, and write up the results well.  I've done a lot of benchmarks, but published very few so far because of how much work it is to go from ugly but useful info to something worth of publishing to a wide audience.

Don't worry... I'm only interested to run it and see how simultaneously data flow, perhaps in ways I've not anticipated, actually works.  So ugly code is fine, really.

Paul Stoffregen

#3
Jun 21, 2013, 08:46 am Last Edit: Jun 21, 2013, 08:59 am by Paul Stoffregen Reason: 1
Linux is not an issue.  I use all 3 systems and Ubuntu is my primary desktop.

But if you're got a lot of complex stuff going on with timers, perhaps this isn't a good benchmark that shows Due's actual native USB performance?   Maybe the problem is unrelated to the USB speed at all.  Complicated timing issues are pretty common with programs that directly manipulate the timers and other hardware.

If you do decide to post some code, I'll take a look.

Paul Stoffregen

If you post some code, I'll give it a try here on a couple other boards.  I have a collection of every Arduino compatible board which currently supports native USB: Arduino Leonardo & Due, every Teensy, Maple, and Fubarino Mini.

It could at least be a sanity check.  But until you actually post something, nobody could even begin to guess why you're experiencing these speed issues.

Paul Stoffregen

I'm about to give this a try, but first, other than one bad result at the end, I'm failing to see what is so unexpected?


Tweaking the above code produces the following times when running test program above on Linux
Time to read 2400 packets of 64 bytes from native USB only:        1 second (or less)
Time to write 2400 packets of 64 bytes to native USB only:           1 second (or less)
Time to read 2400 packets of 32 bytes from native USB only:        1 second (or less)
Time to write 2400 packets of 32 bytes to native USB only:           1 second (or less)


This seems to confirm the native port is indeed fast.  Is that right?  Where is the problem you mentioned?

Quote

Time to write 2400 packets of 64 bytes to programming port:       13 seconds
Time to read 2400 packets of 64 bytes from programming port:    13 seconds


13 seconds is exactly as expected.  The programming port is a hardware serial port that communicates to the 16u2 chip.  You set the baud rate to 115200, which gives 11.52 kbytes/sec.   Sending 153600 bytes at that speed should take 13.3 seconds.

Quote

Time to write 2400 packets of 32 bytes to programming port:       77 seconds
Time to read 2400 packets of 32 bytes from programming port:    77 seconds


This is the one result I do not understand.  You're transmitting half as much data in exactly the same way, but the time increases by a factor of 6.

Quote

I've tried changing port speed to 230400.  It makes no difference whatsoever.


Did you change it on the Arduino side?  On the programming port, the rate Arduino is sending controls the pace.


Quote

I would use the native USB port for both read and write but as soon as I start doing async read/write against the native USB port, I'm seeing extreme data corruption and data loss.


Again, I would give this a try here, but you didn't post any code which attempts to demonstrate the problem.

Paul Stoffregen

I've run the test.  I can confirm similar results.

Code: [Select]

paul@preston:/tmp/t > ./test
Succfully opened /dev/ttyACM0 for reading
Total time to run test with 2400 packets of size 64 was 13 seconds


Code: [Select]

paul@preston:/tmp/t > ./test
Succfully opened /dev/ttyACM0 for reading
Total time to run test with 2400 packets of size 32 was 81 seconds


Something is clearly very wrong with the 32 byte test.

Paul Stoffregen

I believe the problems you've been seeing are caused by incorrect baud rate settings on the programming port.

Using "stty" to set the baud rate is not reliable.  Here is a copy of your code using the proper termios functions to set the baud rate after the port is open.  This version always receives the 2400 32-byte packets in 6 or 7 seconds.

Code: [Select]

#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>

#define PKTS           2400
#define PKT_SIZE       32
#define SER_PORT_IN    "/dev/ttyACM0"  // On my system this is programming                                                                   
#define SER_PORT_OUT   "/dev/ttyACM1"  // On my system this is native USB                                                                   
#define SER_PORT_SPEED 115200

#define TEST_READ     1
#define TEST_WRITE    0

int main() {

  time_t deltaT = 0;
  int i = 0, bytes = 0, totalBytes = 0, fdIn = 0, fdOut = 0;
  char buf[PKT_SIZE];
  struct termios t;

#if !(TEST_READ || TEST_WRITE)
  printf("Nothing to test, change TEST_READ to 1 or TEST_WRITE to 1\n");
  return(0);
#endif

#if TEST_READ
  // this stty command does not seem to set the baud rate in some cases
  snprintf(buf, sizeof(buf)-1, "stty -F %s sane raw -echo %d", SER_PORT_IN, SER_PORT_SPEED);
  errno = 0;
  fdIn = open(SER_PORT_IN, O_RDWR | O_NOCTTY);
  if (fdIn < 0)  {
    printf("ERROR: Unable to open port %s, err(%d): %s\n",
           SER_PORT_IN, errno, strerror(errno));
    return(-1);
  }
  // using the termios functions after the port is open is very reliable
  tcgetattr(fdIn, &t);
  cfsetispeed(&t, B115200);
  tcsetattr(fdIn, TCSANOW, &t);
  printf("Succfully opened %s for reading\n", SER_PORT_IN);
#endif

#if TEST_WRITE
  snprintf(buf, sizeof(buf)-1, "stty -F %s sane raw -echo", SER_PORT_OUT);
  system(buf);

  // Time for device to reset and native USB dev node to show up again                                                                       
  sleep(4);

  errno = 0;
  fdOut = open(SER_PORT_OUT, O_RDWR | O_NOCTTY);
  if (fdOut < 0)  {
    printf("ERROR: Unable to open port %s, err(%d): %s\n",
           SER_PORT_OUT, errno, strerror(errno));
    return(-1);
  }
  printf("Succfully opened %s for writing\n", SER_PORT_OUT);
#endif

  deltaT = time(NULL);
  for (i = 0; i < PKTS; i++) {
#if TEST_WRITE
    // Ensure full packet is delivered                                                                                                       
    bytes = 0; totalBytes = 0;
    while (totalBytes < sizeof(buf)) {
      errno = 0;
      bytes = write(fdOut, buf+totalBytes, sizeof(buf) - totalBytes);
      if (bytes < 0) {
        printf("ERROR: Unable to write to  port %s, err(%d): %s\n",
               SER_PORT_OUT, errno, strerror(errno));
        return(-1);
      }
      totalBytes += bytes;
    }
#endif

#if TEST_READ
    bytes = 0; totalBytes = 0;
    while (totalBytes < sizeof(buf)) {
      errno = 0;
      bytes = read(fdIn, buf+totalBytes, sizeof(buf) - totalBytes);
      if (bytes < 0) {
        printf("ERROR: Unable to read from port %s, err(%d): %s\n",
               SER_PORT_IN, errno, strerror(errno));
        return(-2);
      }
      //printf("read %d\n", bytes);
      totalBytes += bytes;
    }
#endif
  }
  printf("Total time to run test with %d packets of size %d was %d seconds\n",
         PKTS, (int)sizeof(buf), (int)(time(NULL) - deltaT));
  return 0;
}

stimmer

exedor: Try packet sizes of 36 and 43, they might help you see where your bug is ;)

When using that stty hack I find it better having open(...) before system("stty ..."). It's always been reliable for me but even so I wouldn't use it in production code or code for profiling.
Due VGA library - http://arduino.cc/forum/index.php/topic,150517.0.html

Paul Stoffregen

I really don't think I can keep following this thread.  You posted code, I tried to help.  You could have posted code demonstrating the actual bidirectional problem, but didn't.  I just can't keep wasting time on this.

peebee

I have had a similar problem http://forum.arduino.cc/index.php?topic=186118.0

I thought it might have been caused by binary data, but reading this thread, I see that I also was doing bidirectional comms.  The exact same sketch works 100% if I switch to the Programming port, at 57600 baud.  It seems the SerialUSB on the Due, and the Serial on the Leonardo, are too flaky to be used for real life two way comms.  :smiley-roll-blue:

My sketch is a biggish system, and uses MavLink to third party software, so its hard to extract a simple test.  Exedor - did you get a stripped down test written?

garygid

Does just sending data TO the PC using SerialUSB.print
at speeds 115200 or higher work flawlessly?
Thanks, Gary
Cheers, Gary
Due for controlling Electric car charging.
Nissan LEAF - Mini Quick Charge (mQC)

peebee

I guess that needs a Due sketch sending out incrementing values and a processing sketch to read and verify.  I don't have the time to develop this right now unfortunately...

Paul Stoffregen

#13
Sep 18, 2013, 09:53 am Last Edit: Sep 18, 2013, 09:56 am by Paul Stoffregen Reason: 1

Does just sending data TO the PC using SerialUSB.print
at speeds 115200 or higher work flawlessly?


It did work when I tested several months ago.  The speed was in the hundreds of kbytes/sec range, which is dramatically faster than 11 kbytes/sec, but far less than Due's hardware ought to be capable of providing.

Maybe I ought to re-run these tests?  Now I have a nice collection of all the Arduino compatible boards with native USB (each with their own USB stacks that have vastly different performance): Arduino Due & Leonardo, Maple, Teensy 2.0 & 3.0, and Fubarino Mini.

EDIT: I had hoped to adapt the OP's test into a bidirectional bandwidth benchmark..... but after many messages he never actually posted even a test program that demonstrated the problem, and has since deleted almost all his prior writings.

Paul Stoffregen

I just dug out an old transmit benchmark program, ported it to Arduino and ran it.

Using Arduino 1.5.4, Due's speed has improved about 10X since I tested many months ago.  On my Linux machine, I measured 4700.86 kbyte/sec transmit-only speed from Arduino Due.  That's 408 times faster than using hardware serial at 115200 baud.

Here's the benchmark test, if you'd like to try running it on your own system:

Quote

// USB Serial Transmit Bandwidth Test
// Written by Paul Stoffregen, paul@pjrc.com
// This benchmark code is in the public domain.
//
// Within 5 seconds of opening the port, this program
// will send a message as rapidly as possible, for 10 seconds.
//
// To run this benchmark test, use serial_read.exe (Windows) or
// serial_listen (Mac, Linux) program can read the data efficiently
// without saving it.
// http://www.pjrc.com/teensy/serial_listen.c
// http://www.pjrc.com/teensy/serial_read.c
// http://www.pjrc.com/teensy/serial_read.exe
//
// You can also run a terminal emulator and select the option
// to capture all text to a file.  However, some terminal emulators
// may limit the speed, depending upon how they update the screen
// and how efficiently their code processes the imcoming data.  The
// Arduino Serial Monitor is particularly slow.  Only use it to
// verify this sketch works.  For actual benchmarks, use the
// efficient receive tests above.
//
// Full disclosure: Paul is the author of Teensyduino.
//
// Results can vary depending on the number of other USB devices
// connected.  For fastest results, disconnect all others.


//#define USBSERIAL Serial       // for Leonardo, Teensy, Fubarino
#define USBSERIAL SerialUSB  // for Due, Maple

void setup()
{
 USBSERIAL.begin(115200);
}

void loop()
{
 // wait for serial port to be opened
 while (!USBSERIAL) ;
 
 // give the user 5 seconds to enable text capture in their
 // terminal emulator, or do whatever to get ready
 for (int n=5; n; n--) {
   USBSERIAL.print("10 second speed test begins in ");
   USBSERIAL.print(n);
   USBSERIAL.println(" seconds.");
   if (!USBSERIAL) break;
   delay(1000);
 }
 
 // send a string as fast as possible, for 10 seconds
 unsigned long beginMillis = millis();
 do {
   USBSERIAL.print("USB Fast Serial Transmit Bandwidth Test, capture this text.\r\n");
 } while (millis() - beginMillis < 10000);
 USBSERIAL.println("done!");
 
 // after the test, wait forever doing nothing,
 // well, at least until the terminal emulator quits
 while (USBSERIAL) ;
}

Go Up