Setting Up Multiple FTDI Devices

Background

I’m building a test jig for a device that needs both RS-232 and RS-485 transceivers. Since this is a test jig, I prefer to build it using off-the-shelf components—ideally ones that can be delivered overnight.

Inside the jig, I have two USB-to-serial dongles (RS-232 and RS-485) and a LabJack U3, all connected through a USB hub. The hub connects to a USB isolator, which then to a bulkhead-mounted connector for easy disconnection. The jig is controlled using a laptop running Ubuntu with a test suite is written in Python.

The issue I’m encountering is identifying which dongle is which. Both the RS-232 and RS-485 dongles use identical FTDI chips with the same vendor ID, model, and revision. The /dev/ttyUSB* assignment depends on the enumeration order, which can vary between boots or plug events. To distinguish them, I considered the following options:

  1. Measuring each device’s output (impractical for automation)
  2. Keeping the USB hub port assignments fixed
  3. Using each device’s unique serial number
  4. Powering the devices on in sequence

Tools

Useful Unix tools for USB device management include:

  1. udevadm — udev management tool (dynamic device metadata)
  2. lsusb — list connected USB devices
  3. lshw — list hardware configuration
  4. usb-devices — print detailed USB info

Finding Your Device

In my case, both dongles show up as serial ports:

$ ls /dev/ttyUSB*
/dev/ttyUSB0  /dev/ttyUSB1



```sh
> udevadm info --query=property /dev/ttyUSB1
DEVPATH=/devices/pci0000:00/0000:00:14.0/usb3/3-1/3-1.1/3-1.1:1.0/ttyUSB1/tty/ttyUSB1
DEVNAME=/dev/ttyUSB1
MAJOR=188
MINOR=1
SUBSYSTEM=tty
USEC_INITIALIZED=199589964479
ID_BUS=usb
ID_VENDOR_ID=0403
ID_MODEL_ID=6001
ID_PCI_CLASS_FROM_DATABASE=Serial bus controller
ID_PCI_SUBCLASS_FROM_DATABASE=USB controller
ID_PCI_INTERFACE_FROM_DATABASE=XHCI
ID_VENDOR_FROM_DATABASE=Future Technology Devices International, Ltd
ID_MODEL_FROM_DATABASE=FT232 Serial (UART) IC
ID_VENDOR=FTDI
ID_VENDOR_ENC=FTDI
ID_MODEL=FT232R_USB_UART
ID_MODEL_ENC=FT232R\x20USB\x20UART
ID_REVISION=0600
ID_SERIAL=FTDI_FT232R_USB_UART_AG0JP1WN
ID_SERIAL_SHORT=AG0JP1WN
ID_TYPE=generic
ID_USB_INTERFACES=:ffffff:
ID_USB_INTERFACE_NUM=00
ID_USB_DRIVER=ftdi_sio
ID_PATH=pci-0000:00:14.0-usb-0:1.1:1.0
ID_PATH_TAG=pci-0000_00_14_0-usb-0_1_1_1_0
ID_MM_CANDIDATE=1
ID_FOR_SEAT=tty-pci-0000_00_14_0-usb-0_1_1_1_0
DEVLINKS=/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_AG0JP1WN-if00-port0 /dev/serial/by-path/pci-0000:00:14.0-usb-0:1.1:1.0-port0
TAGS=:seat:uaccess:systemd:snap_cups_cupsd:snap_cups_ippeveprinter:

The output are key value pairs separated by ‘=’, using this output with python is trivial but lets use shell commands for now.

Serial Number

> udevadm info --query=property /dev/ttyUSB1 | grep ID_SERIAL= | sed s/ID_SERIAL=//
FTDI_FT232R_USB_UART_AG0JP1WN

> udevadm info --query=property /dev/ttyUSB0 | grep ID_SERIAL= | sed s/ID_SERIAL=//
FTDI_FT232R_USB_UART_A50285BI

Port

> udevadm info --query=property /dev/ttyUSB1 | grep DEVPATH= | sed s/DEVPATH=//
/devices/pci0000:00/0000:00:14.0/usb3/3-1/3-1.1/3-1.1:1.0/ttyUSB1/tty/ttyUSB1

> udevadm info --query=property /dev/ttyUSB0 | grep DEVPATH= | sed s/DEVPATH=//
/devices/pci0000:00/0000:00:14.0/usb3/3-1/3-1.2/3-1.2:1.0/ttyUSB0/tty/ttyUSB0

The ports differ by the number of the port in the hub. Moving USB1 from the first port to the third gives:

> udevadm info --query=property /dev/ttyUSB1 | grep DEVPATH= | sed s/DEVPATH=//
/devices/pci0000:00/0000:00:14.0/usb3/3-1/3-1.3/3-1.3:1.0/ttyUSB1/tty/ttyUSB1

Using horrific sed to separate out the port number:

> udevadm info --query=property /dev/ttyUSB1 | grep DEVPATH= | sed s/DEVPATH=// | sed s/"\/"/\\n/g | sed '7!d' | sed s/"\."/\\n/ | sed '2!d'
3

> udevadm info --query=property /dev/ttyUSB0 | grep DEVPATH= | sed s/DEVPATH=// | sed s/"\/"/\\n/g | sed '7!d' | sed s/"\."/\\n/ | sed '2!d'
2

My preference is to use the port number of the hub for identification. This way assembly instructions can be used with no software changes. As long as the device are always hooked up the same way then no configuration needs to be done.