CDC Speed for Blocks [Solved: >140kB/s]

Hello,

I am in the search for missing transfer speed.
As I am understanding, the CDC implementation on the Due uses 512 Byte EPs for IN and OUT. That would allow up to 512kB/s unidirectional transfer speed. (512 Bytes per 1ms frame)

I get that this is only theoretically.

For a project I would need about 100-120kB/s (in one direction at a time) but instead I get just about 41kB/s. Additionally I can't seem to transfer 512 byte blocks.

Does anyone know the reason, or what I implemente wrong here to utilize the best possible speed?

My Test-Sketch (only the loop) will echo the incoming data up to 512 bytes transfer:

byte buffer[512] ;

void loop() {

  int pos = 0 ;
  if (!SerialUSB)
    return ;
  while (SerialUSB.available())
  {
    if (pos >= sizeof(buffer))
      return ;
    buffer[pos++] = SerialUSB.read();
  }
  if (pos)
    SerialUSB.write(buffer, pos);
  return ;
}

The PC side (.NET Core 3.1):

           SerialPort Port;
            Port = new SerialPort(portName, 2000000, Parity.None, 8, StopBits.One);
            Port.Handshake = Handshake.RequestToSend;
            Port.Open();
            Port.DtrEnable = true;
            byte[] buffer = new byte[511];
            for (int i = 0; i < buffer.Length; i++)
                buffer[i] = (byte)i;
            DateTime start = DateTime.Now;
            for (int i = 0; i < 256; i++)
            {
                Port.Write(buffer, 0, buffer.Length);
                int pos = 0;
                byte[] verify = new byte[buffer.Length];
                while (pos < buffer.Length)
                {
                    pos += Port.Read(verify, pos, Math.Min(buffer.Length - pos, Port.BytesToRead));
                }
                
            }
            DateTime end = DateTime.Now;
            TimeSpan ts = end - start;

PS: As you can see I am testing 511 Bytes / transfer. That is because 512 Bytes will not work, as the Due will not ack the RX after 512 Bytes as it waits for the 512th bytes to be placed in the ring buffer, which only can hold 511 Bytes. However the EP is configured as 512 Bytes and thus it is possible to fill that up completely with a single transfer without beeing able to drain that buffer:

 ring_buffer *buffer = &cdc_rx_buffer;
 uint32_t i = (uint32_t)(buffer->head+1) % CDC_SERIAL_BUFFER_SIZE;

 // if we should be storing the received character into the location
 // just before the tail (meaning that the head would advance to the
 // current location of the tail), we're about to overflow the buffer
 // and so we don't write the character or advance the head.
 while (i != buffer->tail) {
 uint32_t c;
 if (!USBD_Available(CDC_RX)) {
 udd_ack_fifocon(CDC_RX);
 break;
 }
 c = USBD_Recv(CDC_RX);
 // c = UDD_Recv8(CDC_RX & 0xF);
 buffer->buffer[buffer->head] = c;
 buffer->head = i;

 i = (i + 1) % CDC_SERIAL_BUFFER_SIZE;
 }

This is because the the difference of head and tail can only be in %512 and 0 is beeing used to signal an empty ring buffer. So the max usage of that buffer is 511 bytes < 512 bytes EP.
Instead the buffer should be able to contain any possible single transfer and set the line status to not ready until the buffer was drained. To prevent these type of lockups.

i've found a faster usb implementation for arduino due on github but i cannot find it atm. something like fast usb serial.

Thanks fr the response phr3ak.

Yes, I have found that while looking for the best approach for the CDC transfer. I found it is already implemented in the current Arduino SAM Core. It achieved better speeds by overloading the Stream class write method, so that it would process multiple bytes per transfer instead of feeding them one by one to the USB Core and thus not filling up the usb transfer frames.

So as far as i know, the fast USB is now used by default.

I will take a look for the reasons (analyse which direction and which path is the slowest and work my way through it) there should be enough room for the transfer speed.

If I got a clean solution for the above mentioned 512 Bytes bug, I will submit a patch to the github repo. But that might take a while as this is just a side-project for me.

Got it!

There is a leftover Delay(10) in the bool operator, checking whether the port is open. That was called once each loop (each 511 bytes)

Without this I get 146.588 Bytes/s. Plenty.

:edit: Opened an issue about that on github: CDC boolean operator speed issue · Issue #106 · arduino/ArduinoCore-sam · GitHub