gdsports / USB_Host_Library_SAMD

USB host library 2.0 for Zero/M0/SAMD
126 stars 39 forks source link

USB Host with a SAMD21: receiving and sending data issues #28

Open iKK001 opened 1 year ago

iKK001 commented 1 year ago

I am using an Seeed Xiao SAMD21 controller as an USB Host.

The USB counter part I would like to read and write to is a custom Controller from a 3rd-party company that normally connects via USB-FTDI COM port to a computer.

I would like to directly connect this 3rd-party Controller to my Xiao SAMD21 USB-Host.

By the below code example1, I achieved to read from the 3rd-party controller !! I used the existing example for a PL2303 from the given USB_Host_Library_SAMD library. And this PL2303 example seemed to work best to interact with my 3rd-party Controller.

However, there are still two problems:

  1. about every 10th reading is only partially received (i.e. letters in receiving-string are missing)
  2. I cannot send yet (I can only receive)

As for the first problem: erroneous readings: Some readings are cut-off parts of the receiving string. Most receiving-communication works. There is just about every 10th cmd from the 3rd-party controller that is only partially transmitted (i.e. partially received). I tried to change the delay in the receiving routine as can be seen in example1 below. But without success.

Do you have any idea what I could do in order to get 100% successful data readings ?

Example1: Receiving data from the 3rd-party Controller to a SAMD21 USB-Host:

#include <usbhub.h>
#include <cdcacm.h>
#include <cdcprolific.h>

class PLAsyncOper : public CDCAsyncOper {
    public:
        uint8_t OnInit(ACM *pacm);
};

uint8_t PLAsyncOper::OnInit(ACM *pacm) {

    uint8_t rcode;

    // Set DTR = 1
    rcode = pacm->SetControlLineState(1);

    if (rcode) {
        ErrorMessage<uint8_t>(PSTR("SetControlLineState"), rcode);
        return rcode;
    }

    LINE_CODING lc;
    lc.dwDTERate = 115200;
    lc.bCharFormat  = 0;
    lc.bParityType  = 0;
    lc.bDataBits    = 8;

    rcode = pacm->SetLineCoding(&lc);

    if (rcode) {
        ErrorMessage<uint8_t>(PSTR("SetLineCoding"), rcode);
    }

    return rcode;
}

USBHost         UsbH;
PLAsyncOper     AsyncOper;
PL2303          Pl(&UsbH, &AsyncOper);

void setup() {

  Serial1.begin(115200); // receiving from 3rd-party controller and sending to Serial1
  delay(2000);

  if (UsbH.Init()) {
      Serial1.println("USB host did not start");
  }

  delay( 200 );
}

void loop() {

    UsbH.Task();

    if( UsbH.getUsbTaskState() == USB_STATE_RUNNING ) {

        uint8_t rcode;

        uint8_t  buf[32];
        uint16_t rcvd = 32;
        rcode = Pl.RcvData(&rcvd, buf);
        if (rcvd) { // more than zero bytes received
            String receivedStr = "";
            for (uint16_t i=0; i < rcvd; i++ ) {
                receivedStr += (char)buf[i];
                delay(3);
                // delay(5);       // ...other delays did not have an effect, unfortunately
                // delay(10);    // ... but without any delay, every cmd is only received partailly. So I think there must be a delay even tough it makes the entire reading slower....
                // delay(20);
                // delay(100);
            }
            Serial1.write(receivedStr.c_str());
        }
    }
}

Also, I tried to write to the 3rd-party Controller - but this did not work at all. See code-example 2

Example2: Sending data to the 3rd-party Controller from a SAMD21 USB-Host:

#include <usbhub.h>
#include <cdcacm.h>
#include <cdcprolific.h>

class PLAsyncOper : public CDCAsyncOper {
    public:
        uint8_t OnInit(ACM *pacm);
};

uint8_t PLAsyncOper::OnInit(ACM *pacm) {

    uint8_t rcode;

    // Set DTR = 1
    rcode = pacm->SetControlLineState(1);

    if (rcode) {
        ErrorMessage<uint8_t>(PSTR("SetControlLineState"), rcode);
        return rcode;
    }

    LINE_CODING lc;
    lc.dwDTERate = 115200;
    lc.bCharFormat  = 0;
    lc.bParityType  = 0;
    lc.bDataBits    = 8;

    rcode = pacm->SetLineCoding(&lc);

    if (rcode) {
        ErrorMessage<uint8_t>(PSTR("SetLineCoding"), rcode);
    }

    return rcode;
}

USBHost         UsbH;
PLAsyncOper     AsyncOper;
PL2303          Pl(&UsbH, &AsyncOper);

void setup() {

  Serial1.begin(115200); // receiving from 3rd-party controller and sending to Serial1
  delay(2000);

  if (UsbH.Init()) {
      Serial1.println("USB host did not start");
  }

  delay( 200 );
}

void loop() {

    UsbH.Task();

    if( UsbH.getUsbTaskState() == USB_STATE_RUNNING ) {

        uint8_t rcode;

        // sending to the 3rd-party controller
        if (Serial1.available()) {                    
            uint8_t data= Serial1.read();
            rcode = Pl.SndData(1, &data);
        }
    }
}

There was no data transmission at all. What could be improved ??

Questions:

  1. What can I do to improve the data-receiving ? (i.e. how to get rid of missing receive-strings ?)
  2. What can I do to get data-sending working at all ?
  3. Which example-library do you suggest to work best for my USB-FTDI counter-part (see USB-specs below) ?

Here is a complete printout of the 3rd-party USB chip that my 3rd-party Controller contains:

String Descriptors:
Manufacturer:           Prolific Technology Inc. 
Product:                USB-Serial Controller D

Device descriptor: 
Descriptor Length:      12
Descriptor type:        01
USB version:            0110
Device class:           00
Device Subclass:        00
Device Protocol:        00
Max.packet size:        40
Vendor  ID:             067B
Product ID:             2303
Revision ID:            0400
Mfg.string index:       01
Prod.string index:      02
Serial number index:    00
Number of conf.:        01

Configuration descriptor:
Total length:           0027
Num.intf:               01
Conf.value:             01
Conf.string:            00
Attr.:                  80
Max.pwr:                32

Interface descriptor:
Intf.number:            00
Alt.:                   00
Endpoints:              03
Intf. Class:            FF
Intf. Subclass:         00
Intf. Protocol:         00
Intf.string:            00

Endpoint descriptor:
Atr:oint address03      81
                  axpktsie              00
olin iteva:     0

ndpin dsrito:
npontaddes:02
             At.:               0
Polig ntrvl     000
Enpintdecrpor
Atr.t ddes:     0
Polig ntrvl:0   000
Adr1(.01)
unltdltd commented 1 year ago

Hi iKK001,

I was wondering how you progress was going? We are trying to do the same type of communication using a SAMD21 based board (currently Adafruit Feaather M0 Proto) as a USB Host to configure another SAMD21 based board (our custom built controller). I wrote a simple CLI on our custom board so I could change some parameters via the serial monitor on arduino without re-programming. Now we want to allow customers to do the same but not need a full computer. We have been successful in creating a link between the two and the USB host sends data and we see on the receiver 100% of data sent from host to slave is received. Where we see issues is missing data sent from slave to host.

this is our HOST code:

//#define ARDUINO_MAIN
//#include "variant.h"
//#include "Arduino.h" 
//#include <stdio.h>
#include "Adafruit_GFX.h"
#include "Adafruit_SH110X.h"
#include <UnLtd_Debounce.h>
//#include <SPI.h>
Adafruit_SH1107 display = Adafruit_SH1107(64, 128, &Wire);

#include <cdcacm.h>
#include <usbhub.h>

#include "pgmstrings.h"

#define BUTTON_A  9
#define BUTTON_B  6
#define BUTTON_C  5

#define BTN_APin BUTTON_A
#define BTN_A buttonBit9
#define BTN_BPin BUTTON_B
#define BTN_B buttonBit6
#define BTN_CPin BUTTON_C
#define BTN_C buttonBit5

#define numRows 8
#define charPerRow 21
#define lineHeight 8
#define charWidth 6

uint8_t displayRowIndex = 0;
uint8_t displayCharIndex = 10;
uint8_t dataPacket = 0;
char displayBuffer[numRows][charPerRow] = {};

// On SAMD boards where the native USB port is also the serial console, use
// Serial1 for the serial console. This applies to all SAMD boards except for
// Arduino Zero and M0 boards.
#if (USB_VID==0x2341 && defined(ARDUINO_SAMD_ZERO)) || (USB_VID==0x2a03 && defined(ARDUINO_SAM_ZERO))
#define SerialDebug SERIAL_PORT_MONITOR
#else
#define SerialDebug Serial1
#endif

class ACMAsyncOper : public CDCAsyncOper
{
  public:
    uint8_t OnInit(ACM *pacm);
};

uint8_t ACMAsyncOper::OnInit(ACM *pacm)
{
  uint8_t rcode;
  // Set DTR = 1 RTS=1
  rcode = pacm->SetControlLineState(3);

  if (rcode)
  {
    ErrorMessage<uint8_t>(PSTR("SetControlLineState"), rcode);
    return rcode;
  }

  LINE_CODING   lc;
  lc.dwDTERate  = 115200;
  lc.bCharFormat    = 0;
  lc.bParityType    = 0;
  lc.bDataBits  = 8;

  rcode = pacm->SetLineCoding(&lc);

  if (rcode)
    ErrorMessage<uint8_t>(PSTR("SetLineCoding"), rcode);

  return rcode;
}

USBHost     UsbH;
//USBHub     Hub(&UsbH);
ACMAsyncOper  AsyncOper;
ACM           AcmSerial(&UsbH, &AsyncOper);

void setup()
{
  display.begin(0x3C, true); // Address 0x3C default
  delay(250); // wait for the OLED to power up

  // Clear the buffer.
  display.clearDisplay();
  display.display();
  display.setRotation(1);

  // text display tests
  display.setTextSize(1);
  display.setTextColor(SH110X_WHITE);
  display.setCursor(0,0);

  display.println("Awake");
  display.display();
  delay(1000);

  display.println("Initialize USB Host...");
  display.display();
  if (UsbH.Init()){
    display.clearDisplay();
    display.setCursor(0,0);
    display.print("USB host failed to initialize");
    display.display();
  }
  else{
    display.clearDisplay();
    display.setCursor(0,0);
    display.print("USB Host init OK");
    display.display();
  }

    // put your setup code here, to run once:
  pinMode(BTN_APin, INPUT_PULLUP);
  Buttons.ButtonServiced(BTN_A);

  pinMode(BTN_BPin, INPUT_PULLUP);
  Buttons.ButtonServiced(BTN_B);

  pinMode(BTN_CPin, INPUT_PULLUP);
  Buttons.ButtonServiced(BTN_C);

  delay( 2000 );

}

void loop()
{
  Buttons.DoDebounce();
  if (Buttons.ButtonPressed(BTN_A)){
    display.clearDisplay();
    display.setCursor(0,10);
    display.print("BTN_A Button Pushed!");
    display.display();
    delay(250);
    Buttons.ButtonServiced(BTN_A);
  }
  if (Buttons.ButtonPressed(BTN_B)){
    display.clearDisplay();
    display.setCursor(0,10);
    display.print("BTN_B Button Pushed!");
    display.display();
    delay(250);
    Buttons.ButtonServiced(BTN_B);
  }
  if (Buttons.ButtonPressed(BTN_C)){
    uint8_t rcode2;
    int bytesOut = 5;
    char bufOut[64]= {'\0'};
    itoa(dataPacket,bufOut,10);
    bufOut[bytesOut-1] = '\n';
    display.clearDisplay();
    display.setCursor(0,0);
    display.print("buffer[]:");
    for (int i=0;i< bytesOut;i++){
      display.print(bufOut[i]);
    }

    rcode2 = AcmSerial.SndData(bytesOut, (uint8_t*)bufOut);
    if (rcode2){
      ErrorMessage<uint8_t>(PSTR("SndData"), rcode2);
      display.print("|rcode2|");
    }

    Buttons.ButtonServiced(BTN_C);
    display.print("dataPacket:");
    display.println(dataPacket);
    printNumberUSB(dataPacket, 10);
    display.display(); 
    ++dataPacket;
  }

  UsbH.Task();

  if( AcmSerial.isReady()) {
    uint8_t rcode;
    int bytesIn;
    char buf[64];

    /* reading USB CDC ACM */
    /* buffer size must be greater or equal to max.packet size */
    /* it it set to 64 (largest possible max.packet size) here, can be tuned down
       for particular endpoint */
    uint16_t rcvd = sizeof(buf);
    rcode = AcmSerial.RcvData(&rcvd, (uint8_t *)buf);
    if (rcode && rcode != USB_ERRORFLOW){
      ErrorMessage<uint8_t>(PSTR("Ret"), rcode);
      display.print("|rcode|");
    }

    if( rcvd ) { //more than zero bytes received
        display.println();
        display.print("rcv:");
        display.write(buf, rcvd);

          if(++displayRowIndex == numRows){
            displayRowIndex = 0;
          }
          display.display();
    }
  }
}

size_t printNumberUSB(unsigned long n, uint8_t base)
{
  char buf[8 * sizeof(long) + 1]; // Assumes 8-bit chars plus zero byte.
  char *str = &buf[sizeof(buf) - 1];

  *str = '\0';

  // prevent crash if called with base == 1
  if (base < 2) base = 10;

  do {
    char c = n % base;
    n /= base;

    *--str = c < 10 ? c + '0' : c + 'A' - 10;
  } while(n);
  display.print("printNumber");
  display.print(str);
  //return writeUSB(str);
}

Our setup is nearly identical to yours with some debug being displayed on a I2C screen since the usb serial is occupied.

We got some OTG micro usb to micro usb cables that work for this purpose as I read you needed to OTG cable to work with this setup.

Just scanning your code it looks like you are setting rcvd to 32 each time rather than using the receive buffer to see if something is received so you'd always be receiving when you check and not aligned with an actual received packet. This could be the issue of jumbled received data? When we get data it is always as intended, the issue is we either get it all or nothing, never partial.

As for sending data it looks as if you are using the SndData() function same as we are. Maybe you are not using an OTG cable? Or maybe you've figured it out by now and maybe you can help us figure out the receive issues?

Cheers

iKK001 commented 1 year ago

I have it working in both directions.

Mostly, on the receiving side, I had to introduce a 75ms delay !! (otherwise it would not work) - see code below and search for the delay(75) to find it.

And in my case, I made a second mistake: I had confused Serial vs Serial1. (i.e. the SAMD21 USB Port seems Serial and the RX/TX-pins seem Serial1). And in my case, I had a helper-board to send the cmd-strings to RX/TX-pins. So I had to change Serial.available to Serial1.available to make it work.

So make sure you have the "easy" ports set up correctly everywhere - sometimes one is hunting too far for the mistake.

And, you are right, in my original post, I did not care about exact receiving-buffer-length and only set it to 32 (since I knew that my cmds would be shorter than that).

It is probably best if I give you here my working example:

Example :

The loop contains both, sending and receiving methods to make it work bi-directional.

/*
*  After startup the XIAO-SAMD21 listens to USB-port inputs and redirects these signals to the Serial1-port (3rd-party Ctrler inputs)
 * As well, it listens on the Serial1-port and redirects these signals to the USB-port (3rd-party Ctrler outputs)
 * 
 * Board: Seeed Studio XIAO SAMD21 (https://wiki.seeedstudio.com/Seeeduino-XIAO/)
 *
 */

#include <usbhub.h>
#include <cdcacm.h>
#include <cdcprolific.h>

class PLAsyncOper : public CDCAsyncOper {
    public:
        uint8_t OnInit(ACM *pacm);
};

uint8_t PLAsyncOper::OnInit(ACM *pacm) {

    uint8_t rcode;    
    rcode = pacm->SetControlLineState(1); // Set DTR = 1

    if (rcode) {
        ErrorMessage<uint8_t>(PSTR("SetControlLineState"), rcode);
        return rcode;
    }

    LINE_CODING    lc;
    lc.dwDTERate = 115200;
    lc.bCharFormat  = 0;
    lc.bParityType  = 0;
    lc.bDataBits    = 8;

    rcode = pacm->SetLineCoding(&lc);
    if (rcode) {
        ErrorMessage<uint8_t>(PSTR("SetLineCoding"), rcode);
    }

    return rcode;
}

USBHost         UsbH;
PLAsyncOper     AsyncOper;
PL2303          Pl(&UsbH, &AsyncOper);

void setup() {

    Serial1.begin(115200); // receiving from 3rd-party controller and sending to Serial1  
    delay(2000);

    if (UsbH.Init()) {
        Serial1.println("USB host did not start");
    }

    delay(200);
}

void loop() {

    UsbH.Task();

    if ((UsbH.getUsbTaskState() == USB_STATE_RUNNING) && Pl.isReady()) {

        uint8_t rcode;

        // USB-Host gets new data from the Serial1-port and writes it to the 3rd-party Controller
        if (Serial1.available()) { 
            int availableBytes = Serial1.available();
            char sendStr[availableBytes];
            for(int i=0; i<availableBytes; i++) {
                sendStr[i] = Serial1.read();                    
            }
            uint8_t len = strlen(sendStr);
            uint8_t buf[32];
            for (int i=0; i<len; i++) {
                buf[i] = (char)sendStr[i];
            }
            rcode = Pl.SndData(len, buf);
        }

        // USB-Host gets new data from the 3rd-party Controller and writes it to the Serial1 port
        // Buffer size must be greater or equal to max.packet size
        // It is set to 32 here since I know the packets are always shorter (i.e. 64 being the largest possible max.packet size)
        uint8_t buf[32];
        uint16_t rcvd = 32;
        rcode = Pl.RcvData(&rcvd, buf);
        delay(75); // necessary delay (otherwise receive-buffer is incomplete sometimes)
        if ((rcvd) && (rcode == 0)) { // more than zero bytes received
            char rcvStr[rcvd];
            for(int i=0; i<rcvd; i++) {
                rcvStr[i] = (char)buf[i];
            }            
            Serial1.write(rcvStr);           
        }
    }
}

Hope that helps you with your problem.

unltdltd commented 1 year ago

Hi,

Thank you for your reply. I attempted to use the delay as you did and I got some different results. I got less missing data packets but still was missing packets.

I started to dive deeper into the bus itself on my scope to try and understand what is happening. I found out a few things and why your delay helped.

First, when you call rcode = Pl.RcvData(&rcvd, buf); a request sent on the USB bus so without the delay it is spamming the USB (in my case) every 44us. So sometimes the data packet is is followed by some requests that nuke the data packet (will get to that in a moment). So the delay made the code poll the USB bus every 75ms so the bus wasn't overwhelmed with requests. This made the reception of the data packet more reliable as the bus wasn't receiving so many requests.

Second, the delay made the reception of a single packet more reliable but in my case I was sending a test string immediately followed by a number from memory. With the delay the 2nd packet was almost always lost. But I didn't know why until I looked deeper into the USB bus on my scope. I can see the effect of the delay to calm down the bus. I can see all the data is put onto the bus. In my case I see 3 packets (the text string, the number and the LF). Sometimes the host sees these as separate receives, sometimes as in the case I just captured all 3 are received in 1 call to rcode = Pl.RcvData(&rcvd, buf);. So on the USB bus I see my rcv flag go high, the bus is polled 3 times and 3 packets are transferred but when the buffer is read it only contains the last packet and the first packet is overwritten. This seems to be the root cause of the lost data is that if 2 packets are received the first packet is overwritten by the 2nd. The interesting thing I've noticed is that singe I technically have 3 packets being sent sometimes it's only displaying the 2nd packet and not the first or the 3rd. The lost packets always happen when a single call to rcode = Pl.RcvData(&rcvd, buf); results in 3 packets being transferred and the resulting buffer not containing the sum of all 3 packets but some piece of it.

This is why when rcode = Pl.RcvData(&rcvd, buf); was called without any sort of delay the loss of a packet was because there was a poll after the reception of the data packet which was a "nack" since there was no data to receive but this was what nuked the data since the "last" receive was nothing. This is why the delay helped it stopped the spurious "nack" receives immediately following the actual data packet but that seems to just be a symptom of something else where if multiple packets are received in a single call to rcode = Pl.RcvData(&rcvd, buf); only one of the packets survives and in my case it's the 2nd one. The text sting at the beginning and the "CR/LF" that is the 3rd packet is gone. Not that I am not intentionally sending 3 packets. This is just how the SAMD21 serial.println() puts out the variable and the "CR/LF" on the bus.

If anyone has any idea how to deal with this or if it's something that needs to be fixed in the USB Pl.RcvData(&rcvd, buf) function? I don't know how or why it is polling 3 times for a single call to Pl.RcvData(&rcvd, buf).

Apologies for the lengthy post, but I suspect I am not the only one who may be having issues and I am new to USB Host programming and maybe there is something I have overlooked, but I have confirmed the data has made it's way onto the bus when the Pl.RcvData(&rcvd, buf) function is called but the entirety of the data that was on the bus is not in the buf[] array.

Without having done a deep dive into the code I cannot be sure, but I speculate this is a DMA transaction and the SAMD21 is doing the multiple polls by itself.

On my scope the DATA is the blue sections. Purple are the Sync frame and CRC frame. Yellow are the Commands or addresses (In, Ack, Address, D1 etc).

Scope output showing the start of Pl.RcvData(&rcvd, buf) IRQ2/RST line toggling H-L-H 1st Packet contains "Current DMX Address is: " call RcvData

Second screen showing the second packet being received without the IRQ2/RST line going L 2nd Packet contains a variable from 0 -512 (this packet was 110) rcv flag still high receiving second packet

Third screen showing 3rd packet being received then IRQ2/RST line going L then toggling L-H 3 times to signify 3 bytes received. This is the 2nd packet which was '110' 3rd packet contains "00h" and "0Ah" receive flag going low after receiving 3rd packet

iKK001 commented 1 year ago

Thank you @unltdltd , for your deep-dive ! I will need a bit more time to thoroughly look at this (since very busy with day-job, this might take a few days). In the meantime, let's hope for other folks to explain what is going on exactly with these package-delays. Again, thank you very much for your valuable measurements!

unltdltd commented 1 year ago

Thank you, I appreciate any insight you may be able to provide.

Some additional observations today in my testing. I am getting less of the 3 packets-in-one but I am getting a lot of the 3 read attempts when reading the first packet where after it reads the data it does 2 additional read attempts but gets the "NAK" response since there is no more data to read and subsequently nukes the actual buffer of data. I have slowed the USB polling to 20ms intervals and have a delay of 50ms between sending the text string and the number. So the reception of each packet is definitely separated and not overlapping but the triple read is still happening on the first packet. Very interestingly, if I reset the HOST and send 0-99 it's flawless. As soon as I send 100-255 I get the triple read on the receive. If I press the button to transmit enough times to force the rollover from 255-1 it works again until 99 and as soon as I send 100 it fails. Could this somehow be related to what was transmitted? I am currently always transmitting 5 bytes so sending a 2 would be 2;00h;00h;00h;0Ah and a 100 is 1;0;0;00h;0Ah. So the number of bytes sent is always 5 but the receive breaks when I send 100-255? So bizarre.

Another interesting observation is when I see a poll on the bus (and no data available) from executing Pl.RcvData(&rcvd, buf) it is ALWAYS a double back to back NAK tek00010

It is precisely this double NAK that I see after I read the string when I have sent 100-255.

I recall when we were writing some DMA code to write to some SPI peripherals we were getting what I called a "double clutch" where it would send the data twice due to some DMA interrupt flags that we needed to disable while acting on the DMA request. I feel like there is something like this going on here as it is almost always reading the bus twice, and sometimes 3 times.

I tried some additional troubleshooting steps:

  1. changed Serial.println(inNum+1) to Serial.print(inNum+1) followed by Serial.print("!") to replicate the 3 bytes but take the CR/LF away with the 10ms delay between packets and it works fine, no missed data.
  2. same as above but removed the delay and started missing data because of the same issue as before where all data is read in one go but only middle packet survived the read.

Not sure why removing the Serial.println(inNum+1) caused no data to be lost. Could be because there is always a delay between all packets whereas Serial.println(inNum+1) is always a back-to-back packet as when delay is removed back to missing data.

I even tried throwing more packets (5 in my test) and the first read of the bus was a double read and the 2nd packet survived, the second read of the bus was a single read and the third was a double read but it was the first packet that survived!? Now it almost seems random.

So I guess the question to be answered is whether the issue is that it doesn't hold all the packets it reads when it reads the bus twice or if it is not supposed to read the bus twice and this is similar to the "double clutch" issue we encountered on another project and it should only read 1 packet per bus poll?

iKK001 commented 1 year ago

hmmm - really hard problem. I did not find anything. And quite frankly, my knowledge is not enough to really debug this USBHost-code.

unltdltd commented 1 year ago

@iKK001 Thank you for your help. I had to take a break from this and move onto other projects but I had some time in the last couple days to do a VERY deep dive to better understand what is going on and if maybe someone will see this that can shed some light. I dug into the library and found the functions that put the data on the bus.

In cdcacm.cpp ins the function to receive data: RcvData()

This function then calls inTransfer() from Usb.cpp which in turn calls InTransfer() which calls dispatchPkt() which calls UHD_Pipe_Send() from the samd21_host.c in the "cores" folder from the board package.

Phew!

Anyway, I added some flags to try and see where the code is when the data is coming in on the bus. I can see the 2 blips inside InTransfer() before it calls dispatchPkt(). Then inside dispatchPkt() I set a pin and clear it when it is done calling UHD_Pipe_Send().

Then there's a delay while the data it transferred off the bus and then I see more blips after it has completed the data reception process inside dispatchPkt(). I see 3 blips if it was successful or 4 blips if there was an error. Then inside InTransfer() I do 2 blips when that is done, unless there's an error.

As expected InTransfer() only loops once but I see a second packet on the bus. I see this 2nd request go on the bus basically just as dispatchPkt() is registering a successful packet and exiting. So it seems the issue is in the root code that is initiating the bus transfer and the hardware is doing a 2nd or sometimes 3rd read on the bus that the code is not aware of. You can even see this on a "NAK" response where the host polls for data and there is none. The Host polls, gets a NAK, I see 4 blips to signal a NAK but even before I have registered the NAK with the 4 blips another poll has gone on the bus that also gets a NAK but the code doesn't know this has occurred.

tek00011

I don't know if this is a hardware configuration issue or what, but as you can see in the image the NAK signals happen as the 2nd unexpected packet is coming in and was requested by the HOST while dealing with the 1st packet.

iKK001 commented 1 year ago

I am still struggling with using the SAMD21 as USB Host:

As @unltdltd already described, I have now also huge problems in getting the Pl.SndData(..) working. (i.e. receiving form Serial and sending the data to the 3rd-party USB-Client. I do have one other type of 3rd-party Controller (with PL2303 Chip on it as well) that actually worked. But it seems that the Pl.SndData(...) is really edgy for some reason. Here more explanations:

  1. exactly as @unltdltd described, the Pl.SndData(availableBytes, sendStr); does not work for some 3rd-party controllers !! I am only using 3rd-party Controllers with the PL2303 FTDI-Chip from Profilic on them. So of course, I mostly follow the pl2303 examples. See code-example below for more details on my current non-working solution....

IMPORTANT OBSERVATION:

With the following steps, I can get it to work:

a) plug the 3rd-party Controller to my Computer (i.e. Computer is now the USB-Host) b) then I send a few cmds to the 3rd-party Controller. Everything works (in fact, I don't even necessarily need to send a few command. The pure fact to OPEN the port on the Computer makes the miracle !) c) then I disconnect from the Computer and connect the 3rd-party Controller to my Arduino-SAMD21 USB-Host d) sending cmds from there, IT STILL WORKS !!

However, after power-up, if I never connect the 3rd-party Controller to my Computer in the first place, THEN IT NEVER WORKS !!

--> The Computer Connection must do something that the ACM.Init() does not !!!!!!!!!! But what is it ??????

  1. And of course, I still don't understand why I have to introduce 75ms delay when receiving from 3rd-party and sending to Serial. However, this problem is much less important since as a workaround it works at least.
        uint8_t buf[32];
        uint16_t rcvd = 32;
        rcode = Pl.RcvData(&rcvd, buf);
        delay(75); // necessary delay (otherwise receive-buffer is incomplete sometimes)
        if ((rcvd) && (rcode == 0)) { // more than zero bytes received
            Serial1.write(buf, rcvd);
        }

Problem Nr 1 is a killer !!

I have the following example-code:

What is wrong with it ? Why is the Pl.SndData(availableBytes, sendStr); not working ?

And why is it suddenly working if I connect the 3rd-party Controller to a Computer first ??

#include <usbhub.h>
#include <cdcacm.h>
#include <cdcprolific.h>

class PLAsyncOper : public CDCAsyncOper {
    public:
        uint8_t OnInit(ACM *pacm);
};

uint8_t PLAsyncOper::OnInit(ACM *pacm) {

    uint8_t rcode;   

    // ....I tried all three of them...no change to the issue.....
    // rcode = pacm->SetControlLineState(0); // Set DTR=0, RTS=0
    rcode = pacm->SetControlLineState(1); // Set DTR=1, RTS=0
    // rcode = pacm->SetControlLineState(3); // set DTR=1, RTS=1

    if (rcode) {
        ErrorMessage<uint8_t>(PSTR("SetControlLineState"), rcode);
        return rcode;
    }

    LINE_CODING    lc;
    lc.dwDTERate = 115200;
    lc.bCharFormat  = 0;
    lc.bParityType  = 0;
    lc.bDataBits    = 8;

    rcode = pacm->SetLineCoding(&lc);

    if (rcode) {
        ErrorMessage<uint8_t>(PSTR("SetLineCoding"), rcode);
        return rcode;
    }

    return rcode;
}

USBHost         UsbH;
PLAsyncOper     AsyncOper;
PL2303          Pl(&UsbH, &AsyncOper);

void setup() {

    Serial1.begin(115200); // receiving from 3rd-party controller and sending to Serial1  
    delay(2000);

    if (UsbH.Init()) {
        Serial1.println("USB host did not start");
    }

    delay(200);
}

void loop() {

    UsbH.Task();

     if ((UsbH.getUsbTaskState() == USB_STATE_RUNNING) && Pl.isReady()) {

        uint8_t rcode;

        // USB-Host gets new data from the Serial1-port and writes it to the 3rd-party Controller
        uint16_t availableBytes = Serial1.available();
        if (availableBytes > 0) {
            uint8_t sendStr[availableBytes] = {};
            for(int i=0; i<availableBytes; i++) {
                sendStr[i] = (uint8_t)Serial1.read();                    
            }                        

            // Killer-PROBLEM Nr 1:  The following SendData never works unless USB-Client was connected to computer first
            rcode = Pl.SndData(availableBytes, sendStr);

            // for debug: echo same text...
            // Serial1.write(sendStr, availableBytes);
            // The debug-echo works fine - therefore I know that all data is in the correct format and lengths are o.k.
        }

        // USB-Host gets new data from the 3rd-party Controller and writes it to the Serial1 port
        // Buffer size must be greater or equal to max.packet size
        // It is set to 64 (largest possible max.packet size) here, can be tuned down
        uint8_t buf[32];
        uint16_t rcvd = 32;
        rcode = Pl.RcvData(&rcvd, buf);

        // PROBLEM 2 (not so important since the 75ms workaround does the job....)
        delay(75); // necessary delay (otherwise receive-buffer is incomplete sometimes)

        if ((rcvd) && (rcode == 0)) { // more than zero bytes received
            Serial1.write(buf, rcvd);
        }
    }
}
unltdltd commented 3 months ago

Hi,

Sorry it's been a while since I've commented on this. After extensive debugging and gaining a better understanding of how USB Host works we found our issue to be that the SAMD21 (M0) is not fast enough. When the pipe is unfrozen to receive the next packet it does not always re-freeze the pipe fast enough so another packet gets sent that the library does not know about and overwrites the data that was received. We tried experimenting freezing the pipe sooner but too soon and you kill the current transaction. So we upgraded to a SAMD51 (M4) and have had much better success. I don't know if there some optimization where the freezing can be interrupt driven to accommodate using the slower SAMD21 as it's got plenty of horsepower for the task, it's just a timing issue.

Just thought I'd share our results for someone else who may come across this.

Cheers

iKK001 commented 3 months ago

Thank you - it all makes sense now. Thanks a lot for explaining this here !