Identify Teensy variants from USB connection

jrw

Member
I often have several MCUs and other devices plugged in at the same time. I have a python script that attempts to identify what devices are plugged in to what port. Right now it uses the VID and PID of the USB device to identify it. As I understand it, all Teensys use the same VID:pID. However, the Teensyduino IDE can tell the difference between a Teensy 3.6 and a Teensy 4.1. How? I retrieved the Teensyduino code from GitHub and I can't figure out how it does this.

Thanks,
Jim
 
Each Teensy has a unique Serial number presented over USB.

Perhaps looking at TyCommander source code will give a better idea how it finds that.
 
have also a look into teensyloader_cli, which part of the USB message translates to Teensy model
for example
the HID uses the capabilities description to infer the Teensy model
 
In bootloader mode, the HID usage page indicates Teensy model. The usage number correspond to the "_teensy_model_identifier" numbers in the linker scripts. Teensy Loader checks whether the hardware matches your .elf file by comparing that number from the elf file to the HID usage number from the HID report descriptor. Very old versions of Teensy Loader also looked at other elf symbols, like the stack pointer initialization, to deduce which chip was intended without the "_teensy_model_identifier" symbol.

When running most Arduino compiled programs, the device descriptor version number gives a hint. Look for details in usb_desc.c

https://github.com/PaulStoffregen/c...412517470f58c84c1f7b38/teensy3/usb_desc.c#L93

https://github.com/PaulStoffregen/c...12517470f58c84c1f7b38/teensy4/usb_desc.c#L115

This version hint has only been present for the last 2-3 years. Old versions of Teensyduino didn't have it, so there was no way to tell whether a USB Serial device was Teensy LC, 3.2, 3.6, etc.

I can't comment on how TyCommander works.
 
@Paul: I always wondered if there is a technical reason why the MIDI modes don't use the BCD_DEVICE field to announce the board type? Would be very helpful to get this information for all USB-Modes without the need to switch it to bootloader first.
 
Sorry I don't know if below will help you or not...

As for how TyCommander works, I only know enough to be dangerous... That is did some of the work to help add T4.1 and MMOD be integrated and then the owner of the stuff, fixed it all ;)

I believe it first goes through VID/PID match to know that it is a teensy:

Code:
static const hs_match_spec default_match_specs[] = {
    HS_MATCH_VID_PID(0x16C0, 0x0476, (void *)&_ty_teensy_class_vtable),
    HS_MATCH_VID_PID(0x16C0, 0x0478, (void *)&_ty_teensy_class_vtable),
    HS_MATCH_VID_PID(0x16C0, 0x0482, (void *)&_ty_teensy_class_vtable),
    HS_MATCH_VID_PID(0x16C0, 0x0483, (void *)&_ty_teensy_class_vtable),
    HS_MATCH_VID_PID(0x16C0, 0x0484, (void *)&_ty_teensy_class_vtable),
    HS_MATCH_VID_PID(0x16C0, 0x0485, (void *)&_ty_teensy_class_vtable),
    HS_MATCH_VID_PID(0x16C0, 0x0486, (void *)&_ty_teensy_class_vtable),
    HS_MATCH_VID_PID(0x16C0, 0x0487, (void *)&_ty_teensy_class_vtable),
    HS_MATCH_VID_PID(0x16C0, 0x0488, (void *)&_ty_teensy_class_vtable),
    HS_MATCH_VID_PID(0x16C0, 0x0489, (void *)&_ty_teensy_class_vtable),
    HS_MATCH_VID_PID(0x16C0, 0x048A, (void *)&_ty_teensy_class_vtable),
    HS_MATCH_VID_PID(0x16C0, 0x048B, (void *)&_ty_teensy_class_vtable),
    HS_MATCH_VID_PID(0x16C0, 0x048C, (void *)&_ty_teensy_class_vtable),
    HS_MATCH_VID_PID(0x16C0, 0x04D0, (void *)&_ty_teensy_class_vtable),
    HS_MATCH_VID_PID(0x16C0, 0x04D1, (void *)&_ty_teensy_class_vtable),
    HS_MATCH_VID_PID(0x16C0, 0x04D2, (void *)&_ty_teensy_class_vtable),
    HS_MATCH_VID_PID(0x16C0, 0x04D3, (void *)&_ty_teensy_class_vtable),
    HS_MATCH_VID_PID(0x16C0, 0x04D4, (void *)&_ty_teensy_class_vtable),
    HS_MATCH_VID_PID(0x16C0, 0x04D9, (void *)&_ty_teensy_class_vtable),

    HS_MATCH_TYPE(HS_DEVICE_TYPE_SERIAL, (void *)&_ty_generic_class_vtable)
};
Then if bootloader (Halfkay) it uses another switch statement:
Code:
static ty_model identify_model_halfkay(uint16_t usage)
{
    ty_model model = 0;
    switch (usage) {
        case 0x1A: { model = TY_MODEL_TEENSY_PP_10; } break;
        case 0x1B: { model = TY_MODEL_TEENSY_20; } break;
        case 0x1C: { model = TY_MODEL_TEENSY_PP_20; } break;
        case 0x1D: { model = TY_MODEL_TEENSY_30; } break;
        case 0x1E: { model = TY_MODEL_TEENSY_31; } break;
        case 0x20: { model = TY_MODEL_TEENSY_LC; } break;
        case 0x21: { model = TY_MODEL_TEENSY_32; } break;
        case 0x1F: { model = TY_MODEL_TEENSY_35; } break;
        case 0x22: { model = TY_MODEL_TEENSY_36; } break;
        case 0x23: { model = TY_MODEL_TEENSY_40_BETA1; } break;
        case 0x24: { model = TY_MODEL_TEENSY_40; } break;
        case 0x25: { model = TY_MODEL_TEENSY_41; } break;
        case 0x26: { model = TY_MODEL_TEENSY_MM; } break;
    }

if not it then tries to identify from bcd device:
Code:
    switch (bcd_device) {
        case 0x274: { model = TY_MODEL_TEENSY_30; } break;
        case 0x275: { model = TY_MODEL_TEENSY_31; } break;
        case 0x273: { model = TY_MODEL_TEENSY_LC; } break;
        case 0x276: { model = TY_MODEL_TEENSY_35; } break;
        case 0x277: { model = TY_MODEL_TEENSY_36; } break;
        case 0x278: { model = TY_MODEL_TEENSY_40_BETA1; } break;
        case 0x279: { model = TY_MODEL_TEENSY_40; } break;
        case 0x280: { model = TY_MODEL_TEENSY_41; } break;
        case 0x281: { model = TY_MODEL_TEENSY_MM; } break;
    }

Note: when I am playing with some robotics code (Hexapod) or the like, on SBC boards like RPI or Odroid... I would setup at different times to use multiple microcontrollers (usually teensy), but other times maybe a more specific board... I got tried of worrying about which order they enumerated /dev/ttyACM1 or 0 ditto for /dev/ttyACM0...

So I often setup my code to work off a different device name, like for Dynamixel servos I was using /dev/ttyDXL maybe for remote control I had /dev/ttyXBEE
and then I would try to use UDEV rules to set these up.

At the time most of the time, if I had multiple teensy boards, I used the Serial number.
But doing a quick look on my secondary ubuntu board I see both with the commands:
lsusb -v -d 16c0:0483
it shows both the serial numbers well as the bcdDevice = 2.79 so 279 T4...

Likewise for the other command I used to use to figure out udev rules stuff:
udevadm info --query=property --name=ttyACM0

Code:
urte@kurte-XPS-8300:~$ udevadm info --query=property --name=ttyACM0
DEVPATH=/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.4/1-1.4.1/1-1.4.1:1.0/tty/ttyACM0
DEVNAME=/dev/ttyACM0
MAJOR=166
MINOR=0
SUBSYSTEM=tty
USEC_INITIALIZED=526358026
ID_MM_DEVICE_IGNORE=1
ID_MM_PORT_IGNORE=1
MTP_NO_PROBE=1
ID_BUS=usb
ID_VENDOR_ID=16c0
ID_MODEL_ID=0483
ID_PCI_CLASS_FROM_DATABASE=Serial bus controller
ID_PCI_SUBCLASS_FROM_DATABASE=USB controller
ID_PCI_INTERFACE_FROM_DATABASE=EHCI
ID_VENDOR_FROM_DATABASE=Van Ooijen Technische Informatica
ID_MODEL_FROM_DATABASE=Teensyduino Serial
ID_VENDOR=Teensyduino
ID_VENDOR_ENC=Teensyduino
ID_MODEL=USB_Serial
ID_MODEL_ENC=USB\x20Serial
ID_REVISION=0279
ID_SERIAL=Teensyduino_USB_Serial_10883560
ID_SERIAL_SHORT=10883560
ID_TYPE=generic
ID_USB_INTERFACES=:020201:0a0000:
ID_USB_INTERFACE_NUM=00
ID_USB_DRIVER=cdc_acm
ID_USB_CLASS_FROM_DATABASE=Miscellaneous Device
ID_USB_PROTOCOL_FROM_DATABASE=Interface Association
ID_PATH=pci-0000:00:1a.0-usb-0:1.4.1:1.0
ID_PATH_TAG=pci-0000_00_1a_0-usb-0_1_4_1_1_0
ID_MM_CANDIDATE=1
DEVLINKS=/dev/serial/by-path/pci-0000:00:1a.0-usb-0:1.4.1:1.0 /dev/serial/by-id/usb-Teensyduino_USB_Serial_10883560-if00
TAGS=:systemd:
kurte@kurte-XPS-8300:~$ lsusb -v -d 16c0:0483

Bus 001 Device 006: ID 16c0:0483 Van Ooijen Technische Informatica Teensyduino Serial
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass          239 Miscellaneous Device
  bDeviceSubClass         2 
  bDeviceProtocol         1 Interface Association
  bMaxPacketSize0        64
  idVendor           0x16c0 Van Ooijen Technische Informatica
  idProduct          0x0483 Teensyduino Serial
  bcdDevice            2.79
  iManufacturer           1 Teensyduino
  iProduct                2 USB Serial
  iSerial                 3 10883560
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength       0x004b
    bNumInterfaces          2
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0xc0
      Self Powered
    MaxPower              100mA
    Interface Association:
      bLength                 8
      bDescriptorType        11
      bFirstInterface         0
      bInterfaceCount         2
      bFunctionClass          2 Communications
      bFunctionSubClass       2 Abstract (modem)
      bFunctionProtocol       1 AT-commands (v.25ter)
      iFunction               0 
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         2 Communications
      bInterfaceSubClass      2 Abstract (modem)
      bInterfaceProtocol      1 AT-commands (v.25ter)
      iInterface              0 
      CDC Header:
        bcdCDC               1.10
      CDC Call Management:
        bmCapabilities       0x01
          call management
        bDataInterface          1
      CDC ACM:
        bmCapabilities       0x06
          sends break
          line coding and serial state
      CDC Union:
        bMasterInterface        0
        bSlaveInterface         1 
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x82  EP 2 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0010  1x 16 bytes
        bInterval               5
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass        10 CDC Data
      bInterfaceSubClass      0 
      bInterfaceProtocol      0 
      iInterface              0 
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x03  EP 3 OUT
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x84  EP 4 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0200  1x 512 bytes
        bInterval               0
Device Qualifier (for other device speed):
  bLength                10
  bDescriptorType         6
  bcdUSB               2.00
  bDeviceClass          239 Miscellaneous Device
  bDeviceSubClass         2 
  bDeviceProtocol         1 Interface Association
  bMaxPacketSize0        64
  bNumConfigurations      1
can't get debug descriptor: Resource temporarily unavailable
Device Status:     0x0000
  (Bus Powered)
 
Thank you all for the great information. Extremely helpful, and I realize to get the info I want, I need to poke at it with a bit more finesse. I had been using something similar to this, stripped down for this example.

Code:
#! /usr/bin/env python3

import sys
import re
import serial.tools.list_ports

def main():

    iterator = sorted(serial.tools.list_ports.comports(False));
    for n, (port, desc, hwid) in enumerate(iterator, 1):

        teensy36 = re.match(".*VID:PID=16C0:0483.*", hwid)
        if bool(teensy36):
            print("{:<32}  Teensy 3.6".format(port))
        else:
            print("{:<32}  unknown".format(port))

if __name__ == '__main__':
    main()

And I would get results somewhat like this. Here run on a Mac.

Code:
/dev/cu.Bluetooth-Incoming-Port   unknown
/dev/cu.usbmodem37500701          Teensy 3.6
/dev/cu.usbmodem60665101          Teensy 3.6
/dev/cu.usbmodem93314401          Teensy 3.6
/dev/cu.usbserial-210321AE0ABC    Digilent Analog Discovery 2
/dev/cu.usbserial-FT4ULCN7        FTDI TTL 234X 3V3

Even though it is actually two Teensy 3.6 and one Teensy 4.1. The nice thing about this is that it works on Mac, Linux and Windows. With the following simple proof-of-concept code, I can see more detailed information on the Teensy devices, and can see the correct info to distinguish them. Now I just have to figure out how to merge the two approaches so I get correct device identification, and I get the filesystem device node or COM port associated with the Teensy.

Code:
#! /usr/bin/env python3
#
#

import usb.core
import usb.util

devs = usb.core.find(find_all=True, idVendor=0x16C0, idProduct=0x0483)
if devs is None:
    raise ValueError('Device is not found')
    
for n, dev in enumerate(devs):
    print(dev)
 
Back
Top