Edited at

RaspberryPiで同種複数のUSB機器を識別する事を考えてみる


はじめに

この記事は、CPS Lab Advent Calendar 2018の13日目の記事です。

12日目の記事は【Web Speech API】Speech Recognition ホームページでブラウザの音声認識を使う(無料)

14日目の記事はLabmartの顔認証を実装してみる


背景

pi1.png

画像のように同種のドライバーのUSB機器を接続したとき、それぞれ挿した順番に/devに認識されてしまいます。

例えば画像のようにArduinoを2つ接続すると両方ともACMのドライバーなので以下のようになります。

pi@raspberrypi:~ $ ls /dev | grep ttyACM*

ttyACM0
ttyACM1

これだとどっちがArduinoMEGAでどっちがArduinoUNOなのかわかりません。

USBカメラを複数個接続したときも/dev/video*で認識されるのでどれがどのカメラかわからなくなります。

そこで今USBポートに接続されている機器を識別する方法をまとめてみました。


環境

RaspberryPi2(RASBIAN JESSIE)


どう認識されてるのかみてみる

まずUSBに接続されている機器を確認するコマンドとしてlsusbコマンドがあります。

先程の画像の状態でコマンドを叩くと

pi@raspberrypi:~ $ lsusb

Bus 001 Device 006: ID 2019:ab2a PLANEX GW-USNano2 802.11n Wireless Adapter [Realtek RTL8188CUS]
Bus 001 Device 005: ID 2341:0043 Arduino SA Uno R3 (CDC ACM)
Bus 001 Device 004: ID 2341:0042 Arduino SA Mega 2560 R3 (CDC ACM)
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

こんな感じでUNO R3MEGA 2560 R3が別のものとしてちゃんと認識されていることがわかります。

よく見るとUNO R3ID 2341:0043MEGA 2560 R3、ID 2341:0042で認識されているのでここで条件分けしてあげれば識別できそうです。

2341のほうがidVendorその右がidProductです。


識別するその1

USB機器が接続されたときにどのようにマウントするかを決めたルールが/etc/udev/rules.d/99-com.rulesに記述されています。

今回はここの末尾にUNO R3MEGA 2560 R3を識別するルールをidVendoridProductをもとに追加します。


/etc/udev/rules.d/99-com.rules

# for UNO

KERNEL=="ttyACM*", ATTRS{idVendor}=="2341", ATTRS{idProduct}=="0043", SYMLINK+="ttyUSB_UNO"
# for MEGA
KERNEL=="ttyACM*", ATTRS{idVendor}=="2341", ATTRS{idProduct}=="0042", SYMLINK+="ttyUSB_MEGA"

このような記述を末尾に追加してリブートすると以下のように別の名前で識別されて、それぞれが対応したttyACM*にリンクされている事がわかります。

pi@raspberrypi:~ $ ls /dev |grep USB

ttyUSB_MEGA
ttyUSB_UNO
pi@raspberrypi:~ $ ls -l /dev |grep USB
lrwxrwxrwx 1 root root 7 Dec 13 19:12 ttyUSB_MEGA -> ttyACM0
lrwxrwxrwx 1 root root 7 Dec 13 19:12 ttyUSB_UNO -> ttyACM1


識別するその2


lsusbで観察

pi2.png

写真のようにesp8266とesp32の開発ボードを接続した場合これらは同じIDのFTDIチップとして認識されてしまいlsusbでは区別できません。

pi@raspberrypi:~ $ ls /dev | grep ttyUSB*

ttyUSB0
ttyUSB1
pi@raspberrypi:~ $ lsusb
Bus 001 Device 006: ID 2019:ab2a PLANEX GW-USNano2 802.11n Wireless Adapter [Realtek RTL8188CUS]
Bus 001 Device 008: ID 0403:6015 Future Technology Devices International, Ltd Bridge(I2C/SPI/UART/FIFO)
Bus 001 Device 007: ID 0403:6015 Future Technology Devices International, Ltd Bridge(I2C/SPI/UART/FIFO)
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

lsusb -vでオプションを付けて観察してみます

Bus 001 Device 008: ID 0403:6015 Future Technology Devices International, Ltd Bridge(I2C/SPI/UART/FIFO)

Couldn't open device, some information will be missing
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x0403 Future Technology Devices International, Ltd
idProduct 0x6015 Bridge(I2C/SPI/UART/FIFO)
bcdDevice 10.00
iManufacturer 1
iProduct 2
iSerial 3
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 32
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 90mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 255 Vendor Specific Subclass
bInterfaceProtocol 255 Vendor Specific Protocol
iInterface 2
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0

Bus 001 Device 007: ID 0403:6015 Future Technology Devices International, Ltd Bridge(I2C/SPI/UART/FIFO)
Couldn't open device, some information will be missing
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x0403 Future Technology Devices International, Ltd
idProduct 0x6015 Bridge(I2C/SPI/UART/FIFO)
bcdDevice 10.00
iManufacturer 1
iProduct 2
iSerial 3
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 32
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 90mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 255 Vendor Specific Subclass
bInterfaceProtocol 255 Vendor Specific Protocol
iInterface 2
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0

この情報の範囲だとユニークな属性は見当たらなさそうです。

そこでudevadmコマンドを使います。


udevadmで観察

udevadmでは以下のように観察したいデバイスのパスを渡してあげます

pi@raspberrypi:~ $ udevadm info -a -p $(udevadm info -q path -n /dev/ttyUSB0)

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all 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.

looking at device '/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.2/1-1.2:1.0/ttyUSB0/tty/ttyUSB0':
KERNEL=="ttyUSB0"
SUBSYSTEM=="tty"
DRIVER==""

looking at parent device '/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.2/1-1.2: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/soc/3f980000.usb/usb1/1-1/1-1.2/1-1.2:1.0':
KERNELS=="1-1.2:1.0"
SUBSYSTEMS=="usb"
DRIVERS=="ftdi_sio"
ATTRS{bInterfaceClass}=="ff"
ATTRS{bInterfaceSubClass}=="ff"
ATTRS{bInterfaceProtocol}=="ff"
ATTRS{bNumEndpoints}=="02"
ATTRS{authorized}=="1"
ATTRS{supports_autosuspend}=="1"
ATTRS{bAlternateSetting}==" 0"
ATTRS{bInterfaceNumber}=="00"
ATTRS{interface}=="FT231X USB UART"

looking at parent device '/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.2':
KERNELS=="1-1.2"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bDeviceProtocol}=="00"
ATTRS{devpath}=="1.2"
ATTRS{idVendor}=="0403"
ATTRS{speed}=="12"
ATTRS{bNumInterfaces}==" 1"
ATTRS{bConfigurationValue}=="1"
ATTRS{bMaxPacketSize0}=="8"
ATTRS{busnum}=="1"
ATTRS{devnum}=="7"
ATTRS{configuration}==""
ATTRS{bMaxPower}=="90mA"
ATTRS{authorized}=="1"
ATTRS{bmAttributes}=="a0"
ATTRS{bNumConfigurations}=="1"
ATTRS{maxchild}=="0"
ATTRS{bcdDevice}=="1000"
ATTRS{avoid_reset_quirk}=="0"
ATTRS{quirks}=="0x0"
ATTRS{serial}=="DN02BCJT"
ATTRS{version}==" 2.00"
ATTRS{urbnum}=="16"
ATTRS{ltm_capable}=="no"
ATTRS{manufacturer}=="FTDI"
ATTRS{removable}=="removable"
ATTRS{idProduct}=="6015"
ATTRS{bDeviceClass}=="00"
ATTRS{product}=="FT231X USB UART"

looking at parent device '/devices/platform/soc/3f980000.usb/usb1/1-1':
KERNELS=="1-1"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bDeviceProtocol}=="02"
ATTRS{devpath}=="1"
ATTRS{idVendor}=="0424"
ATTRS{speed}=="480"
ATTRS{bNumInterfaces}==" 1"
ATTRS{bConfigurationValue}=="1"
ATTRS{bMaxPacketSize0}=="64"
ATTRS{busnum}=="1"
ATTRS{devnum}=="2"
ATTRS{configuration}==""
ATTRS{bMaxPower}=="2mA"
ATTRS{authorized}=="1"
ATTRS{bmAttributes}=="e0"
ATTRS{bNumConfigurations}=="1"
ATTRS{maxchild}=="5"
ATTRS{bcdDevice}=="0200"
ATTRS{avoid_reset_quirk}=="0"
ATTRS{quirks}=="0x0"
ATTRS{version}==" 2.00"
ATTRS{urbnum}=="101"
ATTRS{ltm_capable}=="no"
ATTRS{removable}=="unknown"
ATTRS{idProduct}=="9514"
ATTRS{bDeviceClass}=="09"

looking at parent device '/devices/platform/soc/3f980000.usb/usb1':
KERNELS=="usb1"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bDeviceProtocol}=="01"
ATTRS{devpath}=="0"
ATTRS{idVendor}=="1d6b"
ATTRS{speed}=="480"
ATTRS{bNumInterfaces}==" 1"
ATTRS{bConfigurationValue}=="1"
ATTRS{bMaxPacketSize0}=="64"
ATTRS{authorized_default}=="1"
ATTRS{busnum}=="1"
ATTRS{devnum}=="1"
ATTRS{configuration}==""
ATTRS{bMaxPower}=="0mA"
ATTRS{authorized}=="1"
ATTRS{bmAttributes}=="e0"
ATTRS{bNumConfigurations}=="1"
ATTRS{maxchild}=="1"
ATTRS{interface_authorized_default}=="1"
ATTRS{bcdDevice}=="0404"
ATTRS{avoid_reset_quirk}=="0"
ATTRS{quirks}=="0x0"
ATTRS{serial}=="3f980000.usb"
ATTRS{version}==" 2.00"
ATTRS{urbnum}=="25"
ATTRS{ltm_capable}=="no"
ATTRS{manufacturer}=="Linux 4.4.38-v7+ dwc_otg_hcd"
ATTRS{removable}=="unknown"
ATTRS{idProduct}=="0002"
ATTRS{bDeviceClass}=="09"
ATTRS{product}=="DWC OTG Controller"

looking at parent device '/devices/platform/soc/3f980000.usb':
KERNELS=="3f980000.usb"
SUBSYSTEMS=="platform"
DRIVERS=="dwc_otg"
ATTRS{hnp}=="HstNegScs = 0x0"
ATTRS{srp}=="SesReqScs = 0x1"
ATTRS{regvalue}=="invalid offset"
ATTRS{hsic_connect}=="HSIC Connect = 0x1"
ATTRS{guid}=="GUID = 0x2708a000"
ATTRS{mode}=="Mode = 0x1"
ATTRS{srpcapable}=="SRPCapable = 0x1"
ATTRS{regdump}=="Register Dump"
ATTRS{gpvndctl}=="GPVNDCTL = 0x00000000"
ATTRS{ggpio}=="GGPIO = 0x00000000"
ATTRS{hprt0}=="HPRT0 = 0x00001005"
ATTRS{wr_reg_test}=="Time to write GNPTXFSIZ reg 10000000 times: 520 msecs (52 jiffies)"
ATTRS{driver_override}=="(null)"
ATTRS{hcd_frrem}=="HCD Dump Frame Remaining"
ATTRS{mode_ch_tim_en}=="Mode Change Ready Timer Enable = 0x0"
ATTRS{gnptxfsiz}=="GNPTXFSIZ = 0x01000306"
ATTRS{remote_wakeup}=="Remote Wakeup Sig = 0 Enabled = 0 LPM Remote Wakeup = 0"
ATTRS{busconnected}=="Bus Connected = 0x1"
ATTRS{hcddump}=="HCD Dump"
ATTRS{gotgctl}=="GOTGCTL = 0x001c0001"
ATTRS{spramdump}=="SPRAM Dump"
ATTRS{grxfsiz}=="GRXFSIZ = 0x00000306"
ATTRS{gsnpsid}=="GSNPSID = 0x4f54280a"
ATTRS{gusbcfg}=="GUSBCFG = 0x20001700"
ATTRS{hptxfsiz}=="HPTXFSIZ = 0x02000406"
ATTRS{devspeed}=="Device Speed = 0x0"
ATTRS{fr_interval}=="Frame Interval = 0x1d4c"
ATTRS{rem_wakeup_pwrdn}==""
ATTRS{bussuspend}=="Bus Suspend = 0x0"
ATTRS{buspower}=="Bus Power = 0x1"
ATTRS{hnpcapable}=="HNPCapable = 0x1"
ATTRS{rd_reg_test}=="Time to read GNPTXFSIZ reg 10000000 times: 1410 msecs (141 jiffies)"
ATTRS{enumspeed}=="Device Enumeration Speed = 0x1"
ATTRS{inv_sel_hsic}=="Invert Select HSIC = 0x0"
ATTRS{regoffset}=="0xffffffff"

looking at parent device '/devices/platform/soc':
KERNELS=="soc"
SUBSYSTEMS=="platform"
DRIVERS==""
ATTRS{driver_override}=="(null)"

looking at parent device '/devices/platform':
KERNELS=="platform"
SUBSYSTEMS==""
DRIVERS==""

2つ並べると長いので割愛しますがこの中にあるユニークな属性を使って識別ルールを追加してあげます。

今回はesp8266側ATTRS{serial}=="DN02BCJT"とesp32側ATTRS{serial}=="DN02TAFR"を使います。


/etc/udev/rules.d/99-com.rules

# for esp8266

KERNEL=="ttyUSB*", ATTRS{serial}=="DN02BCJT", SYMLINK+="ttyUSB_esp8266"
# for esp8266
KERNEL=="ttyUSB*", ATTRS{serial}=="DN02TAFR", SYMLINK+="ttyUSB_32"

pi@raspberrypi:~ $ ls -l /dev |grep USB

crw-rw---- 1 root dialout 188, 0 Dec 13 19:50 ttyUSB0
crw-rw---- 1 root dialout 188, 1 Dec 13 19:50 ttyUSB1
lrwxrwxrwx 1 root root 7 Dec 13 19:50 ttyUSB_32 -> ttyUSB1
lrwxrwxrwx 1 root root 7 Dec 13 19:50 ttyUSB_esp8266 -> ttyUSB0

このようにして識別が可能です。


識別するその3


USBを挿した位置による識別

udevadmした結果をみてみるとKERNELS=="1-1.2"のような箇所が見つかると思います。実はここの値で4つあるUSBポートそれぞれで物理的にどこにささっているものか識別することができます。RaspberryPi2の場合、対応は画像のようになっているようです。

pi3.png


参考

こちらを参考にさせてもらいました。ありがとうございます。

https://www.irori.org/doc/usb-console.html

https://askubuntu.com/questions/49910/how-to-distinguish-between-identical-usb-to-serial-adapters