If I have multiple Arduinos connected over USB to a Linux computer, and they show up as
- /tty/ACM0
- /tty/ACM1
- /tty/ACM2
How can I identify which Arduino is which without connecting to them via serial connection? Is there a serial number or a unique id on the Arduino?
Thank you for your time.
Situation: Uno R3, Mega, Leonardo with /ttyACM[1,2,3]
lsusb output with devices in the order mentioned above:
... Bus 001 Device 011: ID 2341:0043 Bus 001 Device 013: ID 2341:8036 ... Bus 001 Device 014: ID 2341:0042
lsusb -d vendor:device -vvv shows for each one
Uno
Bus 001 Device 014: ID 2341:0042 Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 1.10 bDeviceClass 2 Communications bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 8 idVendor 0x2341 idProduct 0x0042 bcdDevice 0.01 iManufacturer 1 Arduino (www.arduino.cc) iProduct 2 iSerial 220 55330313735351910141 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 62 bNumInterfaces 2 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 100mA 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 10.01 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 0x0008 1x 8 bytes bInterval 255 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 10 CDC Data bInterfaceSubClass 0 Unused bInterfaceProtocol 0 iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x04 EP 4 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 1 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 1 Device Status: 0x0000 (Bus Powered)
Leonardo:
Bus 001 Device 013: ID 2341:8036 Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 0 (Defined at Interface level) bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x2341 idProduct 0x8036 bcdDevice 1.00 iManufacturer 1 Arduino LLC iProduct 2 Arduino Leonardo iSerial 0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 100 bNumInterfaces 3 bConfigurationValue 1 iConfiguration 0 bmAttributes 0x80 (Bus Powered) MaxPower 500mA 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 0 None 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 0x81 EP 1 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 2 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 No Subclass bInterfaceProtocol 0 None iInterface 0 HID Device Descriptor: bLength 9 bDescriptorType 33 bcdHID 1.01 bCountryCode 0 Not supported bNumDescriptors 1 bDescriptorType 34 Report wDescriptorLength 101 Report Descriptor: (length is 101) Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Local ): Usage, data= [ 0x02 ] 2 Mouse Item(Main ): Collection, data= [ 0x01 ] 1 Application Item(Local ): Usage, data= [ 0x01 ] 1 Pointer Item(Main ): Collection, data= [ 0x00 ] 0 Physical Item(Global): Report ID, data= [ 0x01 ] 1 Item(Global): Usage Page, data= [ 0x09 ] 9 Buttons Item(Local ): Usage Minimum, data= [ 0x01 ] 1 Button 1 (Primary) Item(Local ): Usage Maximum, data= [ 0x03 ] 3 Button 3 (Tertiary) Item(Global): Logical Minimum, data= [ 0x00 ] 0 Item(Global): Logical Maximum, data= [ 0x01 ] 1 Item(Global): Report Count, data= [ 0x03 ] 3 Item(Global): Report Size, data= [ 0x01 ] 1 Item(Main ): Input, data= [ 0x02 ] 2 Data Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Report Count, data= [ 0x01 ] 1 Item(Global): Report Size, data= [ 0x05 ] 5 Item(Main ): Input, data= [ 0x03 ] 3 Constant Variable Absolute No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Global): Usage Page, data= [ 0x01 ] 1 Generic Desktop Controls Item(Local ): Usage, data= [ 0x30 ] 48 Direction-X Item(Local ): Usage, data= [ 0x31 ] 49 Direction-Y Item(Local ): Usage, data= [ 0x38 ] 56 Wheel Item(Global): Logical Minimum, data= [ 0x81 ] 129 Item(Global): Logical Maximum, data= [ 0x7f ] 127 Item(Global): Report Size, data= [ 0x08 ] 8 Item(Global): Report Count, data= [ 0x03 ] 3 Item(Main ): Input, data= [ 0x06 ] 6 Data Variable Relative No_Wrap Linear Preferred_State No_Null_Position Non_Volatile Bitfield Item(Main ): End Collection, data=none Item(Main ): End Collection, data=none ...... Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x84 EP 4 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 1 Device Status: 0x0000 (Bus Powered)
And Mega256:
Bus 001 Device 014: ID 2341:0042 Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 1.10 bDeviceClass 2 Communications bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 8 idVendor 0x2341 idProduct 0x0042 bcdDevice 0.01 iManufacturer 1 Arduino (www.arduino.cc) iProduct 2 iSerial 220 55330313735351910141 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 62 bNumInterfaces 2 bConfigurationValue 1 iConfiguration 0 bmAttributes 0xc0 Self Powered MaxPower 100mA 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 10.01 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 0x0008 1x 8 bytes bInterval 255 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 10 CDC Data bInterfaceSubClass 0 Unused bInterfaceProtocol 0 iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x04 EP 4 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 1 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0040 1x 64 bytes bInterval 1 Device Status: 0x0000 (Bus Powered)
8 Answers 8
Assuming your distro is using udev
:
udevadm info --query=all --name=/dev/ttyACM0
You might need root privileges to run this(sudo / su). It will output a list of information like this:
P: /devices/pci0000:00/0000:00:1d.2/usb4/4-2/4-2:1.0/tty/ttyACM0
N: ttyACM0
S: serial/by-id/usb-Dean_Camera_LUFA_USB-RS232_Adapter_74133353537351403012-if00
S: serial/by-path/pci-.&checktime(0000,00,1,':')d.2-usb-0:2:1.0
E: DEVLINKS=/dev/serial/by-id/usb-Dean_Camera_LUFA_USB-RS232_Adapter_74133353537351403012-if00 /dev/serial/by-path/pci-.&checktime(0000,00,1,':')d.2-usb-0:2:1.0
E: DEVNAME=/dev/ttyACM0
E: DEVPATH=/devices/pci0000:00/0000:00:1d.2/usb4/4-2/4-2:1.0/tty/ttyACM0
E: ID_BUS=usb
E: ID_MM_CANDIDATE=1
E: ID_MODEL=LUFA_USB-RS232_Adapter
E: ID_MODEL_ENC=LUFA\x20USB-RS232\x20Adapter
E: ID_MODEL_FROM_DATABASE=Uno R3 (CDC ACM)
E: ID_MODEL_ID=0043
E: ID_PATH=pci-.&checktime(0000,00,1,':')d.2-usb-0:2:1.0
E: ID_PATH_TAG=pci-0000_00_1d_2-usb-0_2_1_0
E: ID_REVISION=0001
E: ID_SERIAL=Dean_Camera_LUFA_USB-RS232_Adapter_74133353537351403012
E: ID_SERIAL_SHORT=74133353537351403012
E: ID_TYPE=generic
E: ID_USB_DRIVER=cdc_acm
E: ID_USB_INTERFACES=:020201:0a0000:
E: ID_USB_INTERFACE_NUM=00
E: ID_VENDOR=Dean_Camera
E: ID_VENDOR_ENC=Dean\x20Camera
E: ID_VENDOR_FROM_DATABASE=Arduino SA
E: ID_VENDOR_ID=2341
E: MAJOR=166
E: MINOR=0
E: SUBSYSTEM=tty
E: UDEV_LOG=3
E: USEC_INITIALIZED=751387324986
This is for an Uno with modified firmware on the atmega16u2(usb to serial). The lines of interest are probably ID_MODEL_ID and ID_MODEL_FROM_DATABASE.
-
Thank you. I am using OpenWrt in this case with hotplug2 by default but I will try to switch to udev and test this.vlad b.– vlad b.2014年08月08日 09:27:03 +00:00Commented Aug 8, 2014 at 9:27
On Ubuntu 16.04 (and maybe previous versions or other distribs too), you can do:
> ls /dev/serial/by-id
which displays (on my box where an Arduino UNO is connected):
usb-Arduino__www.arduino.cc__0043_A4139363931351318241-if00
You can easily spot the Device ID 0043 (UNO) here.
This file is actually a link to /dev/ttyACM0
in my box.
-
Use
ls -la /dev/serial/by-id
to see the linkQinjie– Qinjie2020年12月19日 01:47:35 +00:00Commented Dec 19, 2020 at 1:47
You can add an alias for each. Then you know which is which. Here is a nice tutorial on how to set this up.
Here is a snippet that I wrote based on the tutorial. In the below example, I am using a FTDI RS232RL USB to Serial adapter, which I believe is what the Arduino uses too.
- In order to assign an alias to a USB-Serial device, we need to find some info on the device
- Plug in the usb. This assumes you have already install the drivers for this use be device and the device is visible when typing:
lsusb
- We are going to need the following a. The vendor id b. The product id c. The device serial number
- To do this, it takes a little bit of hunting. All your devices log entries in '/var/log/messages'.
Therefore we can read this file and find the correct USB:
grep "ftdi" /var/log/messages
You can also use "usb"
This is find all messages tagged with ftdi:
Next to ftdi_sio, there is a number like 1-1.2. This is the USB device
grep "usb 1-1.2" /var/log/messages
Or you can use:
dmesg | grep "usb 1-1.3"
- This gives us all the info we need:
USB Alias
- Now with the list of serial numbers in hand let’s create a UDEV ruleset that’ll make a nice symbolic link for each of these devices. UDEV rules are usually scattered into many files in /etc/udev/rules.d. Create a new file called 99-usb-serial.rules and put the following lines in there:
In this example my alias is called 'lcdbox'
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", ATTRS{serial}=="A601ERJJ", SYMLINK+="lcdbox"
- SYMLINK is the name of your alias. In this case my alias is lcdbox.
Save the file and type
sudo udevadm control --reload-rules
Type
ls –l /dev/lcdbox
lrwxrwxrwx 1 root root 7 Jan 1 1970 /dev/lcdbox -> ttyUSB0
- This shows that my lcdbox alias is mapped to ttyUSB0
This is quite easy! You have to customize the ftdi chips firmware and add an udev rule:
First, get ftdi_eeprom
via apt-get
or from the sources. Identify your device via lsusb and get the id:
lsusb Bus 001 Device 005: ID 0403:6001 Future Technology Devices International, Ltd FT232 Serial (UART) IC ....
Prepare a config and make sure, that vendor_id
and product_id
match. Customize the strings in the Strings
section to get a unique id for your device.
vendor_id=0x0403 # Vendor ID product_id=0x6001 # Product ID max_power=50 # Max. power consumption: value * 2 mA. Use 0 if self_powered = true. ########### # Strings # ########### manufacturer="FTDI" # Manufacturer product="Arduino Nano" # Product serial="arduino1" # Serial ########### # Options # ########### self_powered=false # Turn this off for bus powered remote_wakeup=false # Turn this on for remote wakeup feature use_serial=true # Use the serial number string # Normally out don't have to change one of these flags # BM_type_chip=true # Newer chips are all BM type in_is_isochronous=false # In Endpoint is Isochronous out_is_isochronous=false # Out Endpoint is Isochronous suspend_pull_downs=false # Enable suspend pull downs for lower power change_usb_version=false # Change USB Version usb_version=0x0200 # Only used when change_usb_version is enabled ######## # Misc # ######## filename="eeprom.old" # Filename, leave empty to skip file writing cbus0=RXLED# cbus1=TXLED#
Dump the current ftdi-firmware:
ftdi_eeprom --read-eprom
This command creates ${pwd}/eeprom.old
, which contains the current firmware on the ftdi. Backup this file before continuing, because during flash-eeprom
the file gets rewritten. After the backup, flash the ftdi:
ftdi_eeprom --flash-eeprom myconfig.conf
Now, create an udev rule, like so
SUBSYSTEMS=="usb", ATTRS{idProduct}=="6001", ATTRS{idVendor}=="0403", SYMLINK+="$attr{serial}", OWNER="bananapi", GROUP="pi", MODE="0777"
in /etc/udev/rules.d/90-arduino-usb.rules
and restart udev.
service udev restart
Unplug and re-plug your device and try
ls -lah /dev/arduino1
Where arduino1
is the string defined under serial
in the above .conf
.
See also: Risks of ftdi_eeprom? - TX always high after flashing
I would have used a script to make an alias under /dev/
and also set group and rights on it with udev
like the other answers.
But if I didn't have udev
I would pipe lsusb
into grep
or awk
, like lsusb|grep -e "idProduct"
.
Anyway, with udev
rules or with lsusb
and grep
to identify USB devices, use idVendor
, idProduct
and iSerial
in Device descriptor
part of lsusb
to make the proper idenification. The idVendor
tells you the manufaturer, idProduct
should indentify the product from the manufacturer but sometimes they use the same product id for more than one product. Lastly, if needed, the iSerial
should be a unique id for each example of that product.
You can always make some kind of id print via serial in void setup(). After the certain board is connected it will send this id to your USB interface (which you are listening with some kind of daemon app on your linux box). Once you receive id you can map it to it's path 'Arduino1' : '/dev/ttyACM0', 'Arduino2' : '/dev/ttyACM1', 'Arduino3' : '/dev/ttyACM2'...etc
Be aware that when device is disconnected for some reason it can change it's physical path so you might have to remap all of them. For this case it might be good to write separate function ex: get_id() which you can call any time (not only on startup).
I'd be mighty tempted to identify some other way, like having the sketch itself respond to a special identify command, just to avoid USB's odd ways of identifying devices.
Overview
One way as ansi_lumen mentioned in his answer is to flash ftdi chip EEPROM to have unique serial number which then could be identified by UDEV rules.
But turns out it won't work on cheap chinese Arduinos which instead of FTDI has CH340G chip which doesn't have EEPROM to store unique ID (CH340B should work).
- This answer suggests to write UDEV rule with special script which asks Arduino to send its unique ID via serial. Problem with that is that you need to modify Arduino sketch and in general it's quite complex.
Identifying by port
So the easiest way I found was to use UDEV rules and identify Arduinos by the usb port (devpath) so connecting Arduino to the same port (even in nested usb hubs) will create persistent name.
TLDR setup:
Modified version of this
- Find Arduino idVendor and idProduct:
lsusb
You will get something like this:
...
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 124: ID 1a86:7523 QinHeng Electronics HL-340 USB-Serial adapter
Bus 003 Device 123: ID 214b:7000
Bus 003 Device 122: ID 1a86:7523 QinHeng Electronics HL-340 USB-Serial adapter
Bus 003 Device 121: ID 1a86:7523 QinHeng Electronics HL-340 USB-Serial adapter
Bus 003 Device 120: ID 1a40:0101 Terminus Technology Inc. Hub
...
By connecting/disconnecting Arduino find which one it is (I have 3 connected). We are looking for its ID. In my case "...ID 1a86:7523 QinHeng...". So idVendor=1a86, idProduct=7523
- Create new UDEV rules file:
sudo nano /etc/udev/rules.d/99-usb-serial.rules
- Modify idVendor and idProduct values by what you found in previous step on copy paste it in the rules file we have created:
SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="ttyUSB-arduino%s{/devpath}"
- Reload UDEV rules
sudo udevadm control --reload
- Replug Arduinos and now it will have unique name. To test it type:
ls /dev/ttyUSB*
Which will output:
/dev/ttyUSB1 /dev/ttyUSB3 /dev/ttyUSB-arduino2.1 /dev/ttyUSB-arduino2.4
/dev/ttyUSB2 /dev/ttyUSB4 /dev/ttyUSB-arduino2.2 /dev/ttyUSB-arduino3
As you can see we still get /dev/ttyUSBx as before which are always changing depending which one was connected first. But now we also have /dev/ttyUSB-arduino{port} which are always the same for same port and only for Arduinos. To analyze what "..arduino2.4" means: 2 refers to the second port of laptop and 4 to the fourth port on usb hub.
To better see it type:
lsusb -t
Output:
...
/: Bus 03.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 480M
|__ Port 1: Dev 5, If 0, Class=Human Interface Device, Driver=usbhid, 12M
|__ Port 1: Dev 5, If 1, Class=Human Interface Device, Driver=usbhid, 12M
|__ Port 2: Dev 84, If 0, Class=Hub, Driver=hub/4p, 480M
|__ Port 1: Dev 85, If 0, Class=Vendor Specific Class, Driver=ch341, 12M
|__ Port 2: Dev 86, If 0, Class=Vendor Specific Class, Driver=ch341, 12M
|__ Port 3: Dev 87, If 0, Class=Hub, Driver=hub/4p, 480M
|__ Port 4: Dev 88, If 0, Class=Vendor Specific Class, Driver=ch341, 12M
|__ Port 3: Dev 89, If 0, Class=Vendor Specific Class, Driver=ch341, 12M
/: Bus 02.Port 1: Dev 1, Class=root_hub, Driver=ehci-pci/2p, 480M
|__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/6p, 480M
...
By connecting/disconnecting you can see which devices are on which ports
lsusb -vvv
says?