I thought this week I would share my adventure into USB enumeration. As I have mentioned in a previous post I am using the following USB/Serial converters
USB to RS232 Cable (Product LinkProduct Link)
USB to RS422 cable (Product LinkProduct Link)
When these cables are plugged in, the system generates a ttyUSBx device in the /dev directory. Serial USB devices are added in the order in which they are recognized, so the first device will be ttyUSB0, the second will be ttyUSB1 and so on. If the cables are plugged in before boot up, it does not seem to cause any issue – the devices are assigned like I need them to be. Although, from what I have read there is no guarantee that it will work like I need it to every time. If they are plugged in after boot and if they are not plugged in in the correct order, they will be assigned differently than I need. Since I need to communicate to a SLC PLC thrugh the RS232 cable and an MTrim controller through the RS422 cable, I need to be able to know which port each is on. If I have written my program to access the RS232 device on /dev/ttyUSB0 and to access the RS422 device on /dev/ttyUSB1 and the port assignments get reversed because of the order they were recognized, my program will no longer work.
Below is an application note from FTDI that describes the USB enumeration process:
I need a way to permanently set the USB ports so that my program can reliably access the correct ports every time. My search led me to Udev. Udev is Linux's device manager which supports persistent device naming that does not depend on the order in which the devices are plugged into the system or the order in which they are recognized at boot. Having persistently named device nodes has the advantage that each device can be accessed by its persistent name every time, e.g. /dev/rs232 and /dev/rs422.
So how do you actually do this? Well,from the udev man page:
The udev daemon, udevd(8), receives device uevents directly from the kernel whenever a device is added or removed from the system, or it
changes its state. When udev receives a device event, it matches its configured set of rules against various device attributes to identify
the device. Rules that match may provide additional device information to be stored in the udev database or to be used to create meaningful
symlink names.
So udev relies on rules that tell it how to name our devices. The following link is an excellent resource in developing rules:
http://reactivated.net/writing_udev_rules.html
Here are the steps that I followed to set up the devices:
1. Plug in the RS232 and RS422 cables
2. Run the following command, to list all USB devices:
lsusb
you should see an output similar to this:
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. Bus 001 Device 004: ID 413c:1005 Dell Computer Corp. Multimedia Pro Keyboard Hub Bus 001 Device 005: ID 046d:c05a Logitech, Inc. Optical Mouse M90 Bus 001 Device 009: ID 0403:6001 Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC Bus 001 Device 010: ID 0403:6001 Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC Bus 001 Device 006: ID 413c:2011 Dell Computer Corp. Multimedia Pro Keyboard
I am interested in Devices 009/010 - the FTDI USB-serial cables. Both devices use FTDI's USB-to-serial converter with the same Vendor and Product ID, so we need some more information to distinguish one from the other.
3. To find that extra bit of information, type the following command
udevadm info --name=/dev/ttyUSB0 --attribute-walk
udevadm starts with the device specified by the devpath and then walks up the chain of parent devices. It prints all the possible attributes in the udev rules key format. A rule to match can be composed by the attributes of the device and the attributes from one single parent device. If you mix and match from multiple parents the rule will not work.
Here is a snippet of the output
looking at device '/devices/platform/bcm2708_usb/usb1/1-1/1-1.5/1-1.5:1.0/ttyUSB0/tty/ttyUSB0': KERNEL=="ttyUSB0" SUBSYSTEM=="tty" DRIVER=="" looking at parent device '/devices/platform/bcm2708_usb/usb1/1-1/1-1.5/1-1.5:1.0/ttyUSB0': KERNELS=="ttyUSB0" SUBSYSTEMS=="usb-serial" DRIVERS=="ftdi_sio" ATTRS{port_number}=="0" ATTRS{latency_timer}=="1" looking at parent device '/devices/platform/bcm2708_usb/usb1/1-1/1-1.5/1-1.5:1.0': KERNELS=="1-1.5:1.0" SUBSYSTEMS=="usb" DRIVERS=="ftdi_sio" ATTRS{bInterfaceClass}=="ff" ATTRS{bInterfaceSubClass}=="ff" ATTRS{bInterfaceProtocol}=="ff" ATTRS{bNumEndpoints}=="02" ATTRS{supports_autosuspend}=="1" ATTRS{bAlternateSetting}==" 0" ATTRS{bInterfaceNumber}=="00" ATTRS{interface}=="USB-RS422 Cable" looking at parent device '/devices/platform/bcm2708_usb/usb1/1-1/1-1.5': KERNELS=="1-1.5" SUBSYSTEMS=="usb" DRIVERS=="usb" ATTRS{bDeviceSubClass}=="00" ATTRS{bDeviceProtocol}=="00" ATTRS{devpath}=="1.5" ATTRS{idVendor}=="0403" ATTRS{speed}=="12" ATTRS{bNumInterfaces}==" 1" ATTRS{bConfigurationValue}=="1" ATTRS{bMaxPacketSize0}=="8" ATTRS{busnum}=="1" ATTRS{devnum}=="9" ATTRS{configuration}=="" ATTRS{bMaxPower}=="300mA" ATTRS{authorized}=="1" ATTRS{bmAttributes}=="80" ATTRS{bNumConfigurations}=="1" ATTRS{maxchild}=="0" ATTRS{bcdDevice}=="0600" ATTRS{avoid_reset_quirk}=="0" ATTRS{quirks}=="0x0" ATTRS{serial}=="FTX25K1F" ATTRS{version}==" 2.00" ATTRS{urbnum}=="16" ATTRS{ltm_capable}=="no" ATTRS{manufacturer}=="FTDI" ATTRS{removable}=="removable" ATTRS{idProduct}=="6001" ATTRS{bDeviceClass}=="00" ATTRS{product}=="USB-RS422 Cable"
There are a number of unique identifiers that I could use in my rule, the most obvious being the ATTR{serial}, but the end product of this project will be used in a production environment and duplicated many times. I don't want to have to build a new rule for every system I put out on the manufacturing floor. There is also the possibility that a cable could go bad and would need to be replaced with another cable with a different ATTR{serial} and the rule would need to be updated. I was able to find a solution that would only require me to label the USB ports, such that the RS232 cable would be attached to only 1 particular USB port and the RS422 cable would be attached to only 1 particular port. To do this we need to look at level 4 of the output. In particular this line:
KERNELS=="1-1.5"
This line shows us that the the cable on tty/USB0 is on port 5 (the 5 in 1.5) of bus 1 (the 1 in 1-1). So if we build our rule with this we can make sure that if the cable is connected to port 5 that the device will be assigned to the RS-422 device. I did the same thing for ttyUSB1 and found it to be on port 4 of bus1 (KERNELS=="1-1.4"). I figured out the ports by connecting the cables to different USB ports on the PI. On the PI2 I found that the ports went from 2-5, with port 2 being on the lower left, port 3 on the top left, port 4 on the top right, and port 5 on the bottom right.
4. Create the rule.
udevd rules are found in /etc/udev/rules.d directory. I created a file in that directory called 99_usbdevices.rules.
KERNELS=="1-1.5", SUBSYSTEMS=="usb", SYMLINK+="rs422" KERNELS=="1-1.4", SUBSYSTEMS=="usb", SYMLINK+="rs232"
Now I can label port 4 for RS232 and port 5 for RS422 and not worry about the order in which the devices are recognized for USB enumeration. My program will access these devices with the new symbolic links /dev/rs422 and /dev/rs232