USB Host Serial Crash on cable disconnect/reconnect

tmrob

New member
I have two Teensy 4.1 boards transferring two channels of data. The data transfers are done either over USB or Ethernet depending on what cable connects the two boards. Switching between modes is automatic on a cable switch and works without problem except occasionally, when transferring over USB, one of the boards will crash when the USB cable is pulled and then reconnected. The system continues to run just fine until the USB cable is reconnected.

The board that crashes is configured with two USBSerial_BigBufferl objects. The other board is configured as Dual Serial. One channel of data is 512-byte chunks at 667us and flows in one direction, from the USB host board to the device board (~750kBps). The other channel consists of short character-based messages and responses at approximately one per second.

I've tracked down the crash to USBHost::allocate_Transfer (memory.cpp, USBHost_t36), where on line 100 it appears the host is trying to access a Transfer_t left over from right before the cable disconnect event. It seems as if a remnant timer event from before the cable disconnect fires after the cable reconnects leading to the crash, which flows from a call to queue_Data_Transfer in USBSerialBase::timer_event (line 1246 in serial.cpp, USBHost_t36).

I was able, however, to resolve the crash by bailing early from USBSerialBase::timer_event if device was null with the following snippet:

Code:
if(!device) {
  println("bail");
  debugDigitalWrite(5, LOW);
  return;
}

Here are two short sketches that recreate the problem (this code run with 1.61 and Arduino 2.3.10, but that same occurs on previous versions):

Code:
// DEVICE TEENSY CODE
// Tools > USB Type: "Dual Serial"

#include <SPI.h>

void setup() {
  Serial.begin(115200);
  SerialUSB1.begin(115200);
  delay(1000);
}

void loop() {
  char buf[512];
  static int count = 0;

  if(Serial.available()) {
    char c = Serial.read();
    ++count;
  }
  if(Serial.availableForWrite() > 10 && count > 2) {
    Serial.print("xxxxxx;");
    count = 0;
  }

  if(SerialUSB1.available() >= 512) {
    SerialUSB1.readBytes(buf, 512);
  }
}

Code:
// HOST TEENSY CODE

#include "USBHost_t36.h"
#include <SPI.h>

USBHost myusb;
USBHub hub1(myusb);

USBSerial_BigBuffer stream1(myusb);
USBSerial_BigBuffer stream2(myusb);

elapsedMillis timer0;
elapsedMicros timer1;
char buf[512];

void setup() {
  Serial.begin(9600);
  if(CrashReport) {
    /* print info (hope Serial Monitor windows is open) */
    Serial.print(CrashReport);
  }
  myusb.begin();
  delay(1000);

  for(int i = 0; i < 512; i++) buf[i] = (char)i;
  stream1.begin(9600);
  stream2.begin(9600);
}

void loop() {
  myusb.Task();

  if(stream1) {
    // send ID request every second
    if(timer0 > 1000) {
      timer0 = 0;
      if(stream1.availableForWrite()>10) stream1.print("ID;");
    }

    myusb.Task();

    // get ID reply
    if(stream1.available() >= 7) {
      stream1.readBytes(buf, 8);
    }
  }

  myusb.Task();

  if(stream2) {
    // send data every 667us
    if(timer1 > 667) {
      timer1 = 0;
      if(stream2.availableForWrite() >= 512) {
        stream2.write(buf,512);
      }
    }
  }
}

You need to uncomment USBHOST_PRINT_DEBUG in USBHost_t36.h and monitor the host board Serial port. A upcoming failure is easy to catch on a disconnect as the last item on the serial monitor will be a TX Data (xxx) byte list after the disconnect debug messages. It's easier to catch the Crash Report on reconnection if you comment out a few debug lines, such as println("tx1:"); in serial.cpp.

Of course, my code is much more complex than the above, but the results are the same. No extra amount of qualifying my writes or calling Task before or after each write helps in my code. In fact, it seems some of these extra efforts cause the crash to occur more frequently, but that could be coincidence.

I realize that this is a rather unique case. You'll need to disconnect and reconnect the cable multiple times to recreate the crash. That's not likely in real-life, but even without that effort, the crash can occur randomly on a single disconnect/reconnect. That's what drove me to investigate this (I don't like mystery events). High data flows are needed to catch this more consistently at just the right moment, but I don't think my use, or this test case, have particularly fast data flows.

I'm guessing others have resolved this, but I couldn't find a discussion. Are there better ways than my snippet?
 
Last edited:
I'm guessing others have resolved this, but I couldn't find a discussion. Are there better ways than my snippet?
I resolved it by making my own USB host library instead of using the one that's full of race conditions.
 
Back
Top