USB transfer speed from teensy 3.6 to host pc

Status
Not open for further replies.

HeinzR

New member
Hello,

I'm working on a kind of combined generator for periodic functions with a PID loop controller and some very fast limit functions to stop the control loop on configurable conditions. Something like:
- Stop if the sampled signal is longer than x milliseconds out of a given range
or
- Stop if the sampled signal is out of a given range for a given number of times

In this project my fastest generation and control loop cycle time is 100us (10 KHz rate).
Since I want to send some internal data for every control loop (every 100us) to the PC, I tested the transfer speed to the PC.
From what I read, I suspected the transfer speed to be in the range of 1 MB/s - so I started to send a buffer of 64 bytes every 100us (to produce something like 640KB/s).
The call to Serial.write itself takes 5-6 us (at the beginning), as long as my program stays "in time". But after a few hundred or a few thousands cycles my program "collapses" - it takes a couple of thousands us for the call for Serial.write and it happens, when Serial.availableForWrite is not 64 for the first time.
In the source code below I send only every second "tick" (5000 times per second) and it takes approx. half a minute to fill up. That makes me believe, that I'm close to my max. transfer rate.
If I send only every 10th tick, it runs infinite (at least as long as I let it run).

The effect is not related to the serial monitor - with a little program, that just reads the data from the com port as fast as possible and does nothing else, I get comparable results.
So my program is able to transfer a little less than 300 KByte/s to the PC (didn't figure out the exact numbers). Is this reasonable or am I missing something important ?

BTW: Changing CDC_TX_SIZE from 64 to 128 didn't change anything.
I'm using Teensy 3.6 (and 3.5) with 1.38b2 and tried as well with the Arduino IDE, as with MS VC 2014 with VisualMicro. OS is Windows 10 pro, 64 bit, version 1703.

Please excuse my bad english ( and formatting like TurboPascal :) ).

Thanks for any suggestion to enhance transfer speed (or for lowering my expectation, if this is as good as it gets),
Heinz

Small part of the code, that isolates my question:

Code:
const uint32_t BASE_TICK_uS = 100;

uint32_t LastTick_us;
uint32_t ThisBaseTickCount, LastBaseTickCount;
uint8_t ResultBuf[64];

IntervalTimer BaseTickTimer100us;
volatile uint32_t BaseTickCount;

void BaseTickOp (void)
{
   BaseTickCount++;
}


void setup() 
{
   Serial.begin (115200);     // Waits 2.5s, if no receiver there
   while (!Serial);           // Waits (infinite) unil receiver there

   // Some stupid line to see progress
   memcpy (ResultBuf, (uint8_t*)"--------10--------20--------30--------40--------50--------60--", 62);
   ResultBuf[62] = 13;
   ResultBuf[63] = 10;
   
   // Start 100us irq timed function
   BaseTickTimer100us.begin (BaseTickOp, BASE_TICK_uS);

   // Initialization
   LastBaseTickCount = 0;
   LastTick_us = micros ();
}


void loop() 
{
   
   // Avoid call of yield 
   while (true)
   {
      noInterrupts ();     // Is this necessary, since it is atomic ?
      ThisBaseTickCount = BaseTickCount;
      interrupts ();

      if (ThisBaseTickCount != LastBaseTickCount)
      {
         // Check irq jitter in us
         uint32_t ThisTick_us = micros ();
         uint32_t Diff = ThisTick_us - LastTick_us;
         LastTick_us = ThisTick_us;
         uint32_t Avail = Serial.availableForWrite ();

         if (ThisBaseTickCount % 2 == 0)
            Serial.write (ResultBuf, 64);

         if ((Diff < 90) || (Diff > 110))
         {
            Serial.println ();
            Serial.print ("IRQ not in time on Sample: ");
            Serial.println (ThisBaseTickCount);
            Serial.print ("""availableForWrite"" was: ");
            Serial.println (Avail);
            Serial.print ("dt (in us) between IRQs was: ");
            Serial.println (Diff);
            Serial.println ();

            BaseTickTimer100us.end ();
            return;
         }

         LastBaseTickCount = ThisBaseTickCount;
      }
   }
}
 
Hello,

I'm working on a kind of combined generator for periodic functions with a PID loop controller and some very fast limit functions to stop the control loop on configurable conditions. Something like:
- Stop if the sampled signal is longer than x milliseconds out of a given range
or
- Stop if the sampled signal is out of a given range for a given number of times

In this project my fastest generation and control loop cycle time is 100us (10 KHz rate).
Since I want to send some internal data for every control loop (every 100us) to the PC, I tested the transfer speed to the PC.
From what I read, I suspected the transfer speed to be in the range of 1 MB/s - so I started to send a buffer of 64 bytes every 100us (to produce something like 640KB/s).
The call to Serial.write itself takes 5-6 us (at the beginning), as long as my program stays "in time". But after a few hundred or a few thousands cycles my program "collapses" - it takes a couple of thousands us for the call for Serial.write and it happens, when Serial.availableForWrite is not 64 for the first time.
In the source code below I send only every second "tick" (5000 times per second) and it takes approx. half a minute to fill up. That makes me believe, that I'm close to my max. transfer rate.
If I send only every 10th tick, it runs infinite (at least as long as I let it run).

The effect is not related to the serial monitor - with a little program, that just reads the data from the com port as fast as possible and does nothing else, I get comparable results.
So my program is able to transfer a little less than 300 KByte/s to the PC (didn't figure out the exact numbers). Is this reasonable or am I missing something important ?

BTW: Changing CDC_TX_SIZE from 64 to 128 didn't change anything.
I'm using Teensy 3.6 (and 3.5) with 1.38b2 and tried as well with the Arduino IDE, as with MS VC 2014 with VisualMicro. OS is Windows 10 pro, 64 bit, version 1703.

Please excuse my bad english ( and formatting like TurboPascal :) ).

Thanks for any suggestion to enhance transfer speed (or for lowering my expectation, if this is as good as it gets),
Heinz

Small part of the code, that isolates my question:

Code:
const uint32_t BASE_TICK_uS = 100;

uint32_t LastTick_us;
uint32_t ThisBaseTickCount, LastBaseTickCount;
uint8_t ResultBuf[64];

IntervalTimer BaseTickTimer100us;
volatile uint32_t BaseTickCount;

void BaseTickOp (void)
{
   BaseTickCount++;
}


void setup() 
{
   Serial.begin (115200);     // Waits 2.5s, if no receiver there
   while (!Serial);           // Waits (infinite) unil receiver there

   // Some stupid line to see progress
   memcpy (ResultBuf, (uint8_t*)"--------10--------20--------30--------40--------50--------60--", 62);
   ResultBuf[62] = 13;
   ResultBuf[63] = 10;
   
   // Start 100us irq timed function
   BaseTickTimer100us.begin (BaseTickOp, BASE_TICK_uS);

   // Initialization
   LastBaseTickCount = 0;
   LastTick_us = micros ();
}


void loop() 
{
   
   // Avoid call of yield 
   while (true)
   {
      noInterrupts ();     // Is this necessary, since it is atomic ?
      ThisBaseTickCount = BaseTickCount;
      interrupts ();

      if (ThisBaseTickCount != LastBaseTickCount)
      {
         // Check irq jitter in us
         uint32_t ThisTick_us = micros ();
         uint32_t Diff = ThisTick_us - LastTick_us;
         LastTick_us = ThisTick_us;
         uint32_t Avail = Serial.availableForWrite ();

         if (ThisBaseTickCount % 2 == 0)
            Serial.write (ResultBuf, 64);

         if ((Diff < 90) || (Diff > 110))
         {
            Serial.println ();
            Serial.print ("IRQ not in time on Sample: ");
            Serial.println (ThisBaseTickCount);
            Serial.print ("""availableForWrite"" was: ");
            Serial.println (Avail);
            Serial.print ("dt (in us) between IRQs was: ");
            Serial.println (Diff);
            Serial.println ();

            BaseTickTimer100us.end ();
            return;
         }

         LastBaseTickCount = ThisBaseTickCount;
      }
   }
}

If you loop continuously (avoiding yield) the serial driver will not get sufficient time to pass on data.
I would try to comment the "while(true)" statement
 
You could try increasing the number of USB buffers. Edit usb_desc.h to increase.

But if your PC fails to keep up, sooner or later the USB buffers on Teensy will all fill up.
 
Thank you for your answers.

WMXZ: To be honest, I don't understand your point. In my understanding "yield" just checks all serial ports for Event functions. Since I have no Event fuctions, my aim was just to save some cpu cycles. Of course I tried your suggestion, but it makes no difference.

Paul: I learned a few things, while playing arond with it.
- It seems, that the buffer size for CDC USB has nothing to do the buffer size I set up for the virtual com port (and is only 16KB in my case, but this needs further investigation).
- You're right - the main problem is, that my program is to slow in fetching the data. On the other hand if I send only, if Serial.availableForWrite tells me, that enough space is there, I'm caught by the usb_serial_flush_callback function. Not sure if this is much better after my fetch program is faster.
- Enhancing the buffers means: Enhancing NUM_USB_BUFFERS and TX_PACKET_LIMIT. Is it correct, that the max. for NUM_USB_BUFFERS is 31 (or 32), because of __builtin_clz inside of usb_malloc ?

Thanks a lot for the help,
Heinz
 
Status
Not open for further replies.
Back
Top