Main Site Documentation

USB CDC Communication speed problems with USB Host on FEZ Raptor


#1

Hi everybody,

I’m working with the USB Host Module on FEZ Raptor trying to communicate over one of three UsbSerial channels, which get automatically connected when I plug in a custom CDC communication board.

There is no handshake implemented on the client CDC communication board.

After sending a ‘help’ command to the client about 5k chars are expected to be returned.

Using HyperTerminal on Windows 7 64Bit everything works as expected, but unfortunately, not on the FEZ Raptor - only about 60 Bytes will be received in a few, not consecutive, chunks!

Any suggestions how I can speed things up?

Or must I only increase internal buffer sizes - and if, how?

Must I use RLP to solve this issue?

All replies are welcome.

Due to the fact that I’m living in Germany, I’m out of the office in a Minute but I will respond as fast as possible.

Cheers, Rolf


#2

We would happily take a look at your serial device to see if we can solve quickly, if you are interested in sending us your serial device.


#3

Does this shed any light on issue?


#4

Hi everybody,

due to lots of work and tight schedules I just couldn’t reply on your suggestions. Here we go…

@ Mike: Thanks for the info. It made me digging deaper into USB specs…

@ Gus:

I was very pleased for your suggestions sending in the board. Unfortunately it’s a prototype board of a companie’s custom solution using MSP430 F5529 and it’s too late sending in the board, anyway.

So, for your knowledge, here are the hard facts:

Here’s a USB Descriptor Dump of the board attached to Windows 7 USB Host:
[em]
Information for device SDHx (VID=0x2047 PID=0x03F0):

Connection Information:

Connection status: Device connected
Device actual bus speed: Full
Device is hub: No
Device adress: 0x0002
Current configuration value: 0x01
Number of open pipes: 3

Device Descriptor:

0x12 bLength
0x01 bDescriptorType
0x0200 bcdUSB
0x02 bDeviceClass (Communication Device Class)
0x00 bDeviceSubClass
0x00 bDeviceProtocol
0x08 bMaxPacketSize0 (8 Bytes)
0x2047 idVendor
0x03F0 idProduct
0x0200 bcdDevice
0x01 iManufacturer "SCHUNK"
0x02 iProduct "SDHx"
0x03 iSerialNumber "075F994625001400"
0x01 bNumConfigurations

Configuration Descriptor:

0x09 bLength
0x02 bDescriptorType
0x0043 wTotalLength
0x02 bNumInterfaces
0x01 bConfigurationValue
0x04 iConfiguration "SCHUNK SDHx"
0x80 bmAttributes (Bus-powered Device)
0x32 bMaxPower (100 mA)

Interface Descriptor:

0x09 bLength
0x04 bDescriptorType
0x00 bInterfaceNumber
0x00 bAlternateSetting
0x01 bNumEndPoints
0x02 bInterfaceClass (Communication Device Class)
0x02 bInterfaceSubClass (Abstract Control Model)
0x01 bInterfaceProtocol (ITU-T V.250)
0x05 iInterface “SCHUNK SDHx virtual COM Port”

CDC Header Functional Descriptor:

0x05 bFunctionalLength
0x24 bDescriptorType
0x00 bDescriptorSubtype
0x0110 bcdCDC

CDC Call Management Functional Descriptor:

0x05 bFunctionalLength
0x24 bDescriptorType
0x01 bDescriptorSubtype
0x00 bmCapabilities
0x01 bDataInterface

CDC Abstract Control Management Functional Descriptor:

0x04 bFunctionalLength
0x24 bDescriptorType
0x02 bDescriptorSubtype
0x02 bmCapabilities

CDC Union Functional Descriptor:

0x05 bFunctionalLength
0x24 bDescriptorType
0x06 bDescriptorSubtype
0x00 bControlInterface
0x01 bSubordinateInterface(0)

Endpoint Descriptor:

0x07 bLength
0x05 bDescriptorType
0x81 bEndpointAddress (IN Endpoint)
0x03 bmAttributes (Transfer: Interrupt / Synch: None / Usage: Data)
0x0040 wMaxPacketSize (64 Bytes)
0xFF bInterval

Interface Descriptor:

0x09 bLength
0x04 bDescriptorType
0x01 bInterfaceNumber
0x00 bAlternateSetting
0x02 bNumEndPoints
0x0A bInterfaceClass (CDC Data)
0x00 bInterfaceSubClass
0x00 bInterfaceProtocol
0x00 iInterface

Endpoint Descriptor:

0x07 bLength
0x05 bDescriptorType
0x02 bEndpointAddress (OUT Endpoint)
0x02 bmAttributes (Transfer: Bulk / Synch: None / Usage: Data)
0x0040 wMaxPacketSize (64 Bytes)
0xFF bInterval

Endpoint Descriptor:

0x07 bLength
0x05 bDescriptorType
0x82 bEndpointAddress (IN Endpoint)
0x02 bmAttributes (Transfer: Bulk / Synch: None / Usage: Data)
0x0040 wMaxPacketSize (64 Bytes)
0xFF bInterval

String Descriptor Table

Index LANGID String
0x00 0x0000 0x0409
0x01 0x0409 "SCHUNK"
0x02 0x0409 "SDHx"
0x03 0x0409 "075F994625001400"
0x04 0x0409 "SCHUNK SDHx"
0x05 0x0409 "SCHUNK SDHx virtual COM Port"
0xEE 0x0000 Request failed with 0x0000001F


Connection path for device:
Renesas Electronics USB 3.0 Host Controller
Root Hub
Renesas Electronics USB Hub
SDHx (VID=0x2047 PID=0x03F0)

Brought to you by TDD v1.83.0, Mar 7 2014, 14:22:05
[/em]

The important things are:

  • The device uses Bulk operations of max 64Byte PacketSize in 12MBit (FullSpeed) mode
  • It’s sending (I used a S/W protocol analyzer) on the IN Endpoint NOT completely filled Packets.
  • The protocol used, is a very simple ASCII protocol, which uses CR/LF pairs as EOL and as a End of Command/Response separator, no length field, nothing (ugly, I know…) (Side note - I’m a communication engineer…)
  • The Host issues a Command and the Client responds to it. This response seems to be split in lines and each line makes a Packet
  • Five devices are connected to the Host and communicate in parallel

What I did, so far:

I implemented the protocol and the driver for a single device, only.
As soon as the device gets connected as a UsbSerial device, I adjust the WorkerInterval to 1.
I set up internal buffers big enough to handle any data, start a communication thread (ComThread) with Prio AboveNormal (I also tried Highest) and setup DataReceived and Disconnected handlers.
When the Host issues a command, it copies it to an internal (ComThread) shared buffer and awakes the ComThread by signalling an event.
The ComThread Writes the data out over the Port and (directly afterwards) waits for an ResponseReceivedEvent to be signalled.
Each time the DataReveived handler gets invoked, it will append the received data to an internalRcvBuffer .
The ResponseReceivedEvent will eventually be set in the DataReveived handler when all expected data will be successfully received.
The ComThread becomes awakened and copies the internalRcvBuffer to the ReceivedText field.

From my Point of view, there is nothing wrong with this approach but data loss occurs - even if only one out of five devices are connected!

Today, I saw a new firmware version is ready for downloading. I will give it a try and will leave a comment whether it works, or not.

Cheers,

Rolf


#5

Hi everybody,

after trying the USB Host communication with the new firmware, nothing really changed. The problem still remains.
After exploring the USB specs, I really wonder why there is no handshake mechanism between the USB Host driver and the application layer, even there is one on the USB protocol side!?
Any explanations on this?

Cheers

Rolf


#6

@ rolf_seeger -

Not sure about your code,

but I wrote a PC software and test with Raptor,

The speed can be up to 60KB/s.

The way I tested is, send each block 4KB, Rator will send crc of this block back as ACK
Host wait for ACK, if it is OK then send next 4K…

After 100 blocks, calculate time. So the speed I measure can be not accurate but not very slow as only 60-70 bytes / s.


#7

@ Dat,

I’m using the USB Host module on the Raptor, not the USB device DP module.

I also thought about testing the USB Host communication with a USB device programmed by myself, but the PC is not a solution for that - it has only USB Hosts as well and unfortunately no OTG port.

Here is the communication class, I’m using - maybe you see something, I don’t see…


using System;
using System.Text;
using System.Threading;
using System.Reflection;
using Microsoft.SPOT;

using GHI.Usb.Descriptors;
using GHI.Usb.Host;
using System.Runtime.CompilerServices;

namespace TestGadgeteerApp
{

    public enum ComThreadState { None, Idle, SendReceive, Received, Error };

    public class SDHxDevice
    {
        public const ushort VENDOR_ID = 0x2047;
        public const ushort PRODUCT_ID = 0x03F0;

        private const byte INTERFACE_CLASS = 0x0A; // CDC Data
        private const byte INTERFACE_SUBCLASS = 0x00;
        private const byte INTERFACE_PROTOCOL = 0x00;

        public AutoResetEvent ResponseReceivedEvent;

        private byte[] internalRcvBuffer;
        private byte[] internalSndBuffer;
        private int internalRcvCount;
        private StringBuilder internalStringBuilder;

        public int ReadTimeout;
        public int MaxThreadCycleTime;
        public string SendText;
        public string ReceiveText;

        public ComThreadState ComThreadState;
        public Thread ComThread;
        public bool TerminateThread;
        public Exception ComThreadException;

        private AutoResetEvent EnterIdleStateEvent;
        private AutoResetEvent SendReceiveOrTerminateEvent;
        private AutoResetEvent ReceivedOrErrorEvent;

        public SDHxDevice(int readTimeout = 500)
        {
            internalRcvBuffer = new byte[0x2000]; // 8KB
            internalSndBuffer = new byte[0x400]; // 1KB
            internalStringBuilder = new StringBuilder(0x2000); // 8KB
            ReadTimeout = readTimeout;
            MaxThreadCycleTime = 100;
        }

        //
        // Communication State properties
        //

        public bool IsConnected
        {
            get { return UsbSerial != null && UsbSerial.Connected; }
        }

        public bool IsRunning
        {
            get { return ComThreadState != ComThreadState.None && !TerminateThread; }
        }

        //
        // UsbSerial property - handles ComThread creation/releasing
        //

        private UsbSerial _UsbSerial;
        public UsbSerial UsbSerial
        {
            get { return _UsbSerial; }
            set
            {
                if (_UsbSerial != value)
                {
                    if (_UsbSerial != null)
                        StopComThread();
                    _UsbSerial = value;
                    if (value != null)
                        StartComThread();
                }
            }
        }

        //
        // Start/Stop of ComThread - called by assigning to UsbSerial property
        //

        private void StartComThread()
        {
            if (IsRunning || !IsConnected)
                return;
            //
            Array.Clear(this.internalSndBuffer, 0, this.internalSndBuffer.Length);
            Array.Clear(this.internalRcvBuffer, 0, this.internalRcvBuffer.Length);
            internalStringBuilder.Clear();
            //
            EnterIdleStateEvent = new AutoResetEvent(false);
            SendReceiveOrTerminateEvent = new AutoResetEvent(false);
            ReceivedOrErrorEvent = new AutoResetEvent(false);
            TerminateThread = false;
            //
            ResponseReceivedEvent = new AutoResetEvent(false);
            //
            _UsbSerial.Disconnected += _UsbSerial_Disconnected;
            _UsbSerial.DataReceived += _UsbSerial_DataReceived;
            //
            ComThreadState = ComThreadState.None;
            ComThreadException = null;
            ComThread = new Thread(ComThreadExecute);     // create communication thread
            ComThread.Priority = ThreadPriority.AboveNormal;  // act as fast as possible
            ComThread.Start();
        }

        private void StopComThread()
        {
            if (!IsRunning || !IsConnected)
                return;
            //
            ResponseReceivedEvent.Reset();
            //
            _UsbSerial.DataReceived -= _UsbSerial_DataReceived;
            _UsbSerial.Disconnected -= _UsbSerial_Disconnected;
            //
            TerminateThread = true;
            SendReceiveOrTerminateEvent.Set();
            ComThread.Join(500);
            TerminateThread = false;
            ComThreadState = ComThreadState.None;
        }

        //
        // USB Host event handlers
        //

        void _UsbSerial_DataReceived(UsbSerial sender, UsbSerial.DataReceivedEventArgs e)
        {
            Array.Copy(e.Data, 0, this.internalRcvBuffer, internalRcvCount, e.Length);
            internalRcvCount += e.Length;
            if (e.Data.Contains(internalRcvCount, "rc="))
                ResponseReceivedEvent.Set();
        }

        void _UsbSerial_Disconnected(BaseDevice sender, EventArgs e)
        {
            this.UsbSerial = null;
        }

        //
        // ComThread routines
        //

        private void ComThreadExecute()
        {
            var signalEnterIdleStateEventEnabled = false;
            while (!TerminateThread)
            {
                switch (ComThreadState)
                {
                    case ComThreadState.None:
                        ComThreadState = ComThreadState.Idle;
                        break;
                    case ComThreadState.Idle:
                        if (signalEnterIdleStateEventEnabled)
                            EnterIdleStateEvent.Set();
                        SendReceiveOrTerminateEvent.WaitOne();
                        if (!TerminateThread)
                        {
                            ThreadCopyToInternalSndBuffer(SendText);
                            ComThreadState = ComThreadState.SendReceive;
                        }
                        break;
                    case ComThreadState.SendReceive:
                        signalEnterIdleStateEventEnabled = true;
                        if (ThreadSendReceive())
                            ComThreadState = ComThreadState.Received;
                        break;
                    case ComThreadState.Received:
                        ReceiveText = ThreadCopyFromInternalRcvBuffer();
                        ComThreadState = ComThreadState.Idle;
                        ReceivedOrErrorEvent.Set();
                        break;
                    case ComThreadState.Error:
                        ComThreadException = new Exception("Thread terminated due to InternalSendReceive() error.");
                        ComThreadState = ComThreadState.Idle;
                        ReceivedOrErrorEvent.Set();
                        break;
                }
            }
        }

        private bool ThreadSendReceive()
        {
            internalRcvCount = 0;
            byte[] buffer = this.internalSndBuffer;
            _UsbSerial.Write(buffer, 0, SendText.Length);
            // wait for all data being received
            var result = ResponseReceivedEvent.WaitOne(ReadTimeout, false);
            return result;
        }

        private void ThreadCopyToInternalSndBuffer(string text)
        {
            byte[] buffer = this.internalSndBuffer; // no locking required, access managed by signals
            for (int i = 0; i < text.Length; i++)
                buffer[i] = (byte)text[i];
        }

        private string ThreadCopyFromInternalRcvBuffer()
        {
            byte[] buffer = this.internalRcvBuffer; // no locking required, access managed by signals
            internalStringBuilder.Clear();
            for (int i = 0; i < internalRcvCount; i++)
            {
                char ch = (char)buffer[i];
                internalStringBuilder.Append(ch);
            }
            return internalStringBuilder.ToString();
        }

        //
        // External interface routines
        //

        public string SendReceive(string text)
        {
            if (Send(text))
                return Receive();
            return null;
        }

        public bool Send(string text)
        {
            if (!IsRunning || !IsConnected)
                return false;
            SendText = text;
            SendReceiveOrTerminateEvent.Set();
            return true;
        }

        public string Receive()
        {
            if (!IsRunning || !IsConnected)
                return null;
            ReceivedOrErrorEvent.WaitOne(ReadTimeout, false);
            EnterIdleStateEvent.WaitOne(MaxThreadCycleTime, false);
            if (ComThreadException == null)
                return ReceiveText;
            throw ComThreadException;
        }

    }

}

Therefore I ordered another EMBEST SBC8530 board with LINUX or WinCE to make further tests, or to switch to.

Cheers

Rolf


#8

@ rolf_seeger -

Understand now

There are 3 thing:

  • Default usbSerial baudrate is 9600, your problem may be because of different baudrate. You can re-define FEZ Raptor usbSerial baudare.
  • Also, WorkerInterval is 50ms, it may too high, it should be 1 if you want higher speed.
  • As I know, internal buffer is 64 bytes. It means for each 1ms, max is 64 bytes can be received (if WorkerInterval =1). In host, you should send 64 bytes then delay at least 1 ms. This is very important.

As I tested here, I send 1000 blocks * 64 bytes, takes 2 seconds, means about 31 KB/s.

 static void Controller_UsbSerialConnected(object sender, UsbSerial e)
        {
            Debug.Print("UsbSerial connected...");
            e.BaudRate = 460800; // defaullt 9600 => should be corrected to be same with host
            e.WorkerInterval = 1; // defaullt 50 => slow
            e.DataReceived += e_DataReceived;
        }

to test with PC, just need USB Serial module and a usbserial FTDI


#9

@ Dat

Sorry, for coming back so late, but I caught a flew which almost knocked me out.

Unfortunately, your suggestions don’t work - The WorkerInterval of the UsbSerial device was already set to 1ms in the calling Routine - which wasn’t shown in the code snippet.
I also tried different Standard BaudRates without success.

From my point of view, one can’t really compare USB Host and Device functionality. The Raptor as a CDC Device might work as expected, but for me it doesn’t work as a USB Host communicating with a CDC device using Bulk transfer!

The Host sends the IN packets to the device and the device responds with a data packet which in turn the Host acknowledges - a proper handshake.

Why is there no Event mechanism between the Driver and the application layer which would make it possible to collect the contents of the received Data packets.

For Bulk Transfers it doesn’t matter at all whether the Host is waiting until the application collects it’s data…

Polling is definitely not a solution!

Any ideas how to solve my Problem?

Cheers, Rolf


#10

if you have another FEZ Raptor, you can config one as USBSerial, one as CDC. They should communicate together.

Or you can use use only FEZ Raptor, config both CDC and USBH Serial, 2 ports can work together same time. I think.

That is easy way to check!