Are Teensys members of a disfunctional family?

mborgerson

Well-known member
I'm talking about the fact that the USB Serial Host driver does not allow one Teensy to talk to another.

The Serial Host driver will only communicate with devices that are specified in the serial.cpp driver in the USBHost_t36 library. The relevant code starts at line 45:

Code:
USBSerialBase::product_vendor_mapping_t USBSerialBase::pid_vid_mapping[] = {

    // FTDI mappings.
    {0x0403, 0x6001, USBSerialBase::FTDI, 0},
    {0x0403, 0x8088, USBSerialBase::FTDI, 1},  // 2 devices try to claim at interface level
    {0x0403, 0x6010, USBSerialBase::FTDI, 1},  // Also Dual Serial, so claim at interface level

etc., etc.

Until I added it, there was no mapping for the T4.1--or any other Teensy, for that matter.
So I added one for the T4.1 before the FTDI mappings:

{0x16C0, 0x0483, USBSerialBase::TEENSY41, 1}, // interface level Teensy 4.1.

Not so fast--the compiler notified me that TEENSY41 was not defined. So I went to the USBSerialBase section of USBHost_t36 and added TEENSY41 to the enum specifying serial device types and added TEENSY41 at line #1617:

// Added TEENSY41 to enum typedef MJB 12/12/2023
typedef enum { UNKNOWN = 0, CDCACM, FTDI, PL2303, CH341, CP210X, TEENSY41} sertype_t;

Were the Teensys ready to talk to each other? I hoped so. I wrote two simple programs:

1: A T4.1 host program that implements a host serial port and listens for input and prints what it gets.
2: A T4.1 device program that opens its Serial port and sends a short message once per second.

These two programs are included in the following code block:
Code:
// HostSerialListener
// Establish host comms with connected T4.1
// and listen for input.
// MJB 12/12/2023
#include "USBHost_t36.h"
const int pinLED = 13;
#define LEDON digitalWriteFast(pinLED, HIGH);
#define LEDOFF digitalWriteFast(pinLED, LOW);
#define LEDTOGGLE digitalToggleFast(pinLED);
USBHost myusb;
USBHub hub1(myusb);
USBSerial_BigBuffer myHSerial(myusb);  // Teensy 4.1 has 512-byte max transfer
void setup() {
  elapsedMillis serialtimer;
  // Wait for Serial connection--but only for 3 seconds
  pinMode(pinLED, OUTPUT);
  LEDON
  Serial.begin(9600);
  while (!Serial && (serialtimer < 3000)) {
    delay(50);
  }
  LEDOFF
  Serial.println("\n\n Teensy USB Host Serial Listener test.");
  delay(1000);
  // Start USBHost_t36, HUB(s) and USB devices.
  myusb.begin();
  myusb.Task();
  Serial.printf("myusb at: %p\n", &myusb);
  Serial.printf("myHSerial at: %p\n", &myHSerial);
  myHSerial.begin(9600);  // start the USB Host serial
  delay(100);
  Serial.println("Proceeding to Listener loop()");
  delay(100);
}
void loop() {
  char ch;
  myusb.Task();
  LEDON
  if (myHSerial.available()) {
    ch = myHSerial.read();
    Serial.print(ch);
    delay(5);  // longer and brighter flash when receiving
  }
  delay(2);
  LEDOFF
  delay(5);
  myusb.Task();
}


// SerialSend program
//  Simple sketch to send data to serial port once per second.
//  Used to test whether T4.1 USB Host receives data
const int pinLED = 13;
#define LEDON digitalWriteFast(pinLED, HIGH);
#define LEDOFF digitalWriteFast(pinLED, LOW);
#define LEDTOGGLE digitalToggleFast(pinLED);
void setup() {
  elapsedMillis serialtimer;
  pinMode(pinLED, OUTPUT);
  LEDON
  LEDON
  Serial.begin(9600);
  while (!Serial && (serialtimer < 3000)) {
    delay(50);
  }
  delay(1000);  // visual indication that program is running
  LEDOFF
}
void loop() {
  static uint32_t msgnum = 0;
  LEDON
  Serial.print("Teensy 4.1 message #");
  Serial.println(msgnum);
  msgnum++;
  delay(200);  // short flash once per second
  LEDOFF
  delay(800);
}

SerialSender was tested by observing its output with a terminal program. It sends a message about once per second:

Teensy 4.1 message #0
Teensy 4.1 message #1
Teensy 4.1 message #2
etc.

It was time to connect the two family members. I setup the host serial listener to produce debug output, connected the T41. with the sender to the USB Host port and started the listener program. The result was a bit like having my wife tell me a story about two people I don't know attending a master gardeners' function that I've never experienced---while I was watching the last minutes of a close Seattle Seahawks game. After about 30 seconds, nothing was received.

To quote the prison gang captain in Cool Hand Luke: "What we've got here is failure to communicate. "

The output from the listener program is in the following code block. I've added some comments and deleted some of the longer hexadecimal data dumps.


Code:
//  Output from listener test with Host debug enabled
//  Collected 12/12/2023 
//  comments added after //
 Teensy USB Host Serial Listener test.
USB2 PLL running
 reset waited 6
USBHS_ASYNCLISTADDR = 0
USBHS_PERIODICLISTBASE = 20004000
periodictable = 20004000
myusb at: 0x20006760
myHSerial at: 0x200045e0
control callback (serial) 6
port change: 10001803
    connect
  begin reset
port change: 18001205
  port enabled
  end recovery
new_Device: 480 Mbit/sec
new_Pipe
enumeration:
enumeration:
enumeration:
Device Descriptor:
  12 01 00 02 EF 02 01 40 C0 16 83 04 80 02 01 02 03 01
    VendorID = 16C0, ProductID = 0483, Version = 0280  // Note T4.1 vender and product ID
    Class/Subclass/Protocol = 239 / 2 / 1
    Number of Configurations = 1
enumeration:
enumeration:
Manufacturer: Teensyduino
enumeration:
Product: USB Serial
enumeration:
Serial Number: 12229190
enumeration:
Config data length = 98
enumeration:
Configuration Descriptor:
  09 02 62 00 03 01 00 C0 32
    NumInterfaces = 3
    ConfigurationValue = 1
  08 0B 00 02 02 02 01 00
    Interface Association = 0 through 1
    Class / Subclass / Protocol = 2 / 2 / 0  //  Serial device class and subclass
  09 04 00 00 01 02 02 01 00
    Interface = 0                       
    Number of endpoints = 1
    Class/Subclass/Protocol = 2 / 2 / 1
  05 24 00 10 01
  05 24 01 01 01
  04 24 02 06
  05 24 06 00 01
  07 05 82 03 10 00 05
    Endpoint = 2 IN        // Im not sure how the interrupt end point is used
    Type = Interrupt
    Max Size = 16
    Polling Interval = 5
  09 04 01 00 02 0A 00 00 00
    Interface = 1           // Second interface (after control at 0)
    Number of endpoints = 2
    Class/Subclass/Protocol = 10 / 0 / 0   CDC Data interface
  07 05 03 02 00 02 00
    Endpoint = 3 OUT
    Type = Bulk
    Max Size = 512          // 512-byte bulk endpoint, as expected
    Polling Interval = 0
  07 05 84 02 00 02 00
    Endpoint = 4 IN
    Type = Bulk
    Max Size = 512          // 512-byte bulk endpoint, as expected
    Polling Interval = 0
  09 04 02 00 02 FF 6A C7 00
    Interface = 2            //  Third interface
    Number of endpoints = 2  //
    Class/Subclass/Protocol = 255 / 106 / 199  // Vendor Specific Class (for Teensy loader?)
  07 05 81 02 00 02 01
    Endpoint = 1 IN
    Type = Bulk
    Max Size = 512
    Polling Interval = 1
  07 05 01 02 00 02 01
    Endpoint = 1 OUT
    Type = Bulk
    Max Size = 512
    Polling Interval = 1
enumeration:
USBHub memory usage = 960
USBHub claim_device this=20004220
USBSerial(512)claim this=200045E0
vid=16C0, pid=483, bDeviceClass = 239, bDeviceSubClass = 2, bDeviceProtocol = 1  // Miscellaneous
08 0B 00 02 02 02 01 00 09 04 00 00 01 02 02 01 00 05
//hex print deleted for brevity
Serial device wants to map at interface level
Descriptor 11 = IAD
Descriptor 4 = INTERFACE
USBSerial(512)claim this=200045E0
vid=16C0, pid=483, bDeviceClass = 239, bDeviceSubClass = 2, bDeviceProtocol = 1  // Miscellaneous
09 04 00 00 01 02 02 01 00 05 24 00 10 01 05
//hex print deleted for brevity
len = 81
Descriptor 36 =  ???
Descriptor 36 =  ???
Descriptor 36 =  ???
Descriptor 36 =  ???
Descriptor 5 = ENDPOINT
Descriptor 4 = INTERFACE
USBSerial(512)claim this=200045E0
vid=16C0, pid=483, bDeviceClass = 239, bDeviceSubClass = 2, bDeviceProtocol = 1  // miscellaneous
09 04 01 00 02 0A 00 00 00 07 05 03 02 00 02 00 07 05 84 02 00 02 00 09 04 02 00 02 FF 6A C7 00 07 05 81 02 00 02 09 04 01 00 02 0A 00 00 00 07 05 03 02 00 02 00 07 05 84 02 00 02 00 09 04 02 00 02 FF 6A C7 00 07 05 81 02 00 02 01 07
5 01 02 00 02 01
len = 46
USBSerial, rxep=4(512), txep=3(512)
  rx buffer size:1024
  tx buffer size:1024
new_Pipe
new_Pipe
Descriptor 5 = ENDPOINT
Descriptor 5 = ENDPOINT
Descriptor 4 = INTERFACE
USBSerial(512)claim this=200045E0
vid=16C0, pid=483, bDeviceClass = 239, bDeviceSubClass = 2, bDeviceProtocol = 1
09 04 02 00 02 FF 6A C7 00 07 05 81 02 00 02 01 07 05 01 02 00 02 01
len = 23
USBSerial, rxep=1(512), txep=1(512)
  rx buffer size:1024
  tx buffer size:1024
new_Pipe
new_Pipe
Descriptor 5 = ENDPOINT
Descriptor 5 = ENDPOINT
//   Here's something interesting:   Before the program gets to the loop() function,
//   the USB Serial Host receives a data packet from the SerialSend program on the
//   connected T4.1.
rx token: 81E98100 transfer length: 512 len:23 - 54 65
rx: 54 65 65 6E 73 79 20 34 2E 31 20 6D 65 73 73 61 67 65 20 23 30 0D 0A
// Translation: "Teensy 4.1 message #0\n\r"  This the first message from the sender
USBSerialBase::begin timeout6
Proceeding to Listener loop()
// Listener loop receives no more data, even though T4.1 device is sending once per second.

Does anyone have any idea why the host program receives no data after the first transfer?


Backstory:

This test was prompted by some problems I am having with the connection between a T4.1 as a serial host and a FLIR Boson camera. The Boson implements a USB Serial interface for control, status and data transfers. The interface is a straight implementation of a UART SLIP link with checksums. I can send commands and the camera responds (I can hear the shutter click when I request a background calibration operation.) However, when I send a request that requires a return data packet, the packet is always missing the first two bytes and the bad checksum results in a BAD PACKET error. The USBHost driver for that test is greatly modified from the USBHost_t36 code. When adapting existing code I usually remove parts that I think have no function in the target library. In the case of USBSerial Host code, I don't need code for a half dozen serial converters that I will never use while developing the Boson driver. I also don't need all the code to handle serial port handshaking as that is superfluous with USB to USB bulk transfers. I also do my best to reduce the sea of #IFDEFS to a small puddle. I appreciate the utility of one library to control them all and one folder to bind them all, but there's a major difference between USING a library and UNDERSTANDING a library. USBHost_t36.h is 2830 lines of source code. Serial.cpp is 1476 lines. My redacted USBHost_MJB is about half the size of the original and BosonSerial source is about 900 lines. Even so, I'm developing a callous on my index finger from the scroll wheel on my mouse. I need to start using the code-folding capabilities of the IDE more often!
 
They can communicate using the existing SerEMU interface: https://github.com/PaulStoffregen/USBHost_t36/blob/master/SerEMU.cpp

I can agree with you about the USBHost library; it's unwieldy and practically completely undocumented, which is why I've written my own replacement with a much more libusb-style interface. It's still not quite ready for public use though, I need to finish some more example drivers to demonstrate how to properly use it.
 
The Teensy boards should be able to talk to other teensy boards using standard Serial stuff. At least they used to. I may not have tried it in awhile.

However if you plug in a T4.x into it, this will connect at high speed and as such communicate with 512 byte buffers instead of 64:

So it should work with the USBSerial_BigBuffer. Since you only have one Serial object, you might want to change the
define like: SBSerial_BigBuffer myHSerial(myusb, 1);
Which would allow other boards connect at LS or FS to connect with their 64 byte packets.
 
Try looking at the SerialTest.ino example and this case: https://forum.pjrc.com/index.php?th...ial-begin-if-device-already-plugged-in.69329/

don't know anything about that camera but probably shows as a ftdi device. You can always run the deviceinfo sketch to confirm that. The FTDI table is set up to read attached FTDI devices attached.
The Boson camera enumerates as a UVC camera which uses a bulk endpoint instead of the isochronous endpoint used by most USB webcams. It also has an interface for two-way USB CDC coms as part of the enumeration. I think the USB interface is part of the Intel embedded processor inside the camera.

Here's how it enumerates:

Code:
port change: 10001803
    connect
  begin reset
port change: 18001205
  port enabled
  end recovery
new_Device: 480 Mbit/sec
new_Pipe
enumeration:
enumeration:
enumeration:
Device Descriptor:
  12 01 00 03 EF 02 01 40 CB 09 07 40 00 01 01 02 03 01
    VendorID = 09CB, ProductID = 4007, Version = 0100
    Class/Subclass/Protocol = 239 / 2 / 1
    Number of Configurations = 1
enumeration:
enumeration:
Manufacturer: FLIR
enumeration:
Product: Boson
enumeration:
Serial Number: 135651
enumeration:
Config data length = 579
enumeration:
Configuration Descriptor:
  09 02 43 02 04 01 00 C0 FA
    NumInterfaces = 4
    ConfigurationValue = 1
  08 0B 00 02 0E 03 00 04
    Interface Association = 0 through 1
    Class / Subclass / Protocol = 14 / 3 / 4
  09 04 00 00 00 0E 01 00 05
    Interface = 0
    Number of endpoints = 0
    Class/Subclass/Protocol = 14 / 1 / 0
  0D 24 01 00 01 32 00 E8 03 00 00 01 01
  11 24 02 01 01 02 00 00 00 00 00 00 00 00 02 00 00
  09 24 03 02 01 01 00 03 00
  0B 24 05 03 01 00 00 02 00 00 00
  09 04 01 00 01 0E 02 00 06
    Interface = 1
    Number of endpoints = 1
    Class/Subclass/Protocol = 14 / 2 / 0
 // deleted hex dump
  07 05 81 02 00 02 00
    Endpoint = 1 IN
    Type = Bulk
    Max Size = 512
    Polling Interval = 0
  08 0B 02 02 02 02 01 07
    Interface Association = 2 through 3
    Class / Subclass / Protocol = 2 / 2 / 7
  09 04 02 00 01 02 02 01 08   
    Interface = 2
    Number of endpoints = 1
    Class/Subclass/Protocol = 2 / 2 / 1
  05 24 00 10 01
  04 24 02 02
  05 24 06 02 03
  05 24 01 00 00
  07 05 83 03 10 00 08
    Endpoint = 3 IN
    Type = Interrupt
    Max Size = 16
    Polling Interval = 8
  09 04 03 00 02 0A 00 00 09
    Interface = 3
    Number of endpoints = 2
    Class/Subclass/Protocol = 10 / 0 / 0   // USB serial interface
  07 05 82 02 00 02 00
    Endpoint = 2 IN
    Type = Bulk
    Max Size = 512
    Polling Interval = 0
  07 05 01 02 00 02 00
    Endpoint = 1 OUT
    Type = Bulk
    Max Size = 512
    Polling Interval = 0

I had to make some changes to the host usb serial driver to get it to claim the serial interface. The standard serial.cpp claim() function in USBHost_t36 gives up before it gets to CDC interface at the end of the configuration. I think it gives up because of the UVC interfaces, even though I've added 'BOSON' to the enumeration and provided the vendor and product ID entry in the device table.
 
I had to make some changes to the host usb serial driver to get it to claim the serial interface. The standard serial.cpp claim() function in USBHost_t36 gives up before it gets to CDC interface at the end of the configuration. I think it gives up because of the UVC interfaces, even though I've added 'BOSON' to the enumeration and provided the vendor and product ID entry in the device table.
Was afraid you were going to say it uses UVC.... Read that on web about those cameras. Glad you got that far.

Unfortunately you are going to have to come up a your own class for a camera interface and add it to the USBHost_t36 somehow. I did find this on the web - maybe you can make use of it:

A bit out of my league - I may have a web cam somewhere but most are just built into my laptops :)
 
After a few more tests, I found out why adding the TEENSY41 to the recognized host serial types (and the PID and VID to the table) resulted in a failure to communicate. I failed to account for the fact that when a device is recognized, the host needs to do some device-specific setup. When a device is not recognized (sertype = UNKNOWN), the claim function changes the sertype to CDCACM -- a composite device. If the rest of the descriptors include a CDC data interface, the claim function sends a few control packets and returns true and the device is claimed by the driver.

If the sertype is TEENSY41, the serial interface is found, but I did not include the CDCACM setup control function. Without those calls, the Teensy 4.1 device won't send the messages to the host.

If this is a valid hypothesis, adding the CDCACM setup controls should get the Host Serial link to work with the connected Teensy. My conclusion is that Teensys are not members of a dysfunctional family--in reality it seems that they will talk with any device they don't recognize as long as the device speaks the proper language (CDCACM). If the Teensy does recognize the device it has to do some initial setup to get the other device to behave properly.

I'm also hoping that doing the CDCACM setup will solve the issues with the BOSON camera---which is a composite device.
 
recently when I was playing with USBHost code for another processor, I experimented some with the Serial code, where I think I fell back to using the CDC ACM Serial code at the Interface level, probably a little cleaner... Wondering if I should try porting that code back...
 
recently when I was playing with USBHost code for another processor, I experimented some with the Serial code, where I think I fell back to using the CDC ACM Serial code at the Interface level, probably a little cleaner... Wondering if I should try porting that code back...
Couldnt hurt :)
 
In hindsight, we should probably have a 3rd claim step, after whole device but before individual interfaces when the top-level Class/Subclass/Protocol is 239/2/1 and we received Interface Association descriptors.
 
Back
Top