Udev rules for Arduino development

Home
Movies
Images
Java
PHP
Micro controllers
Fractals
Vector graphics
Projects
Links
Blog
Meta

Contents

Introduction

Udev is the device manager of the Linux kernel, and it is what creates the device nodes in /dev. By writing udev rules it is possible to, for example, control how devices are created and trigger side effects when a particular piece of hardware is plugged in. The udev rules themselves are located in /etc/udev/rules.d. There is a good (although some minor udevinfo / udevadm syntax has changed since it was written) general
guide to writing udev rules at reactivated.net.

This page will not cover udev rules in general. Instead it will focus creating convenient symlinks to devices, and telling different devices apart, specifically for Arduinos and other pieces of hardware relevant for microcontroller development.

Extracting udev information

To your computer an Arduino is essentially a serial port connected over USB. Older Arduino models use an FTDI chip for the serial connection and those will turn up as /dev/ttyUSBx. Many rs232 and rs485 to USB adapters will also turn up as /dev/ttyUSBx. Newer Arduino models, like the Arduino UNO, uses an Atmel chip for the serial connection and those will turn up as /dev/ttyACMx. Some stk programmers, for example stk500 programmer I have, will also turn up as /dev/ttyACMx. Since there are many different devices that turns up as either /dev/ttyACMx or /dev/ttyUSBx it is not easy to know which device to open, but it is possible to extract the necessary information to tell them apart with udevadm info (on older systems use udevinfo instead).

There are multiple types of udev queries, but the attribute walk query very convenient when we later want to write udev rules. Doing an attribute walk for /dev/ttyACM0 can be done by issuing the following command
udevadm info -a -n /dev/ttyACM0
, which gave me this output (actually it gave me even more, but we will only use the first part of it)
  looking at device '/devices/pci0000:00/0000:00:1d.0/usb6/6-1/6-1:1.0/tty/ttyACM0':
    KERNEL=="ttyACM0"
    SUBSYSTEM=="tty"
    DRIVER==""

  looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb6/6-1/6-1:1.0':
    KERNELS=="6-1:1.0"
    SUBSYSTEMS=="usb"
    DRIVERS=="cdc_acm"
    ATTRS{bInterfaceNumber}=="00"
    ATTRS{bAlternateSetting}==" 0"
    ATTRS{bNumEndpoints}=="01"
    ATTRS{bInterfaceClass}=="02"
    ATTRS{bInterfaceSubClass}=="02"
    ATTRS{bInterfaceProtocol}=="01"
    ATTRS{modalias}=="usb:v15BAp000Cd0104dc02dsc00dp00ic02isc02ip01"
    ATTRS{supports_autosuspend}=="1"
    ATTRS{bmCapabilities}=="6"

  looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb6/6-1':
    KERNELS=="6-1"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{configuration}==""
    ATTRS{bNumInterfaces}==" 2"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bmAttributes}=="80"
    ATTRS{bMaxPower}=="100mA"
    ATTRS{urbnum}=="61"
    ATTRS{idVendor}=="15ba"
    ATTRS{idProduct}=="000c"
    ATTRS{bcdDevice}=="0104"
    ATTRS{bDeviceClass}=="02"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bMaxPacketSize0}=="16"
    ATTRS{speed}=="12"
    ATTRS{busnum}=="6"
    ATTRS{devnum}=="2"
    ATTRS{version}==" 2.00"
    ATTRS{maxchild}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{authorized}=="1"
    ATTRS{manufacturer}=="Olimex Ltd."
    ATTRS{product}=="Olimex AVR-ISP500"
    ATTRS{serial}=="OL2DA9100387624"
As can be seen from the manufacturer and product strings /dev/ttyACM0 is the stk500 programmer. I also had a /dev/ttyACM1 among my devices. Extracting information about the second device with
udevadm info -a -n /dev/ttyACM1
gives the following output
  looking at device '/devices/pci0000:00/0000:00:1d.0/usb6/6-2/6-2:1.0/tty/ttyACM1':
    KERNEL=="ttyACM1"
    SUBSYSTEM=="tty"
    DRIVER==""

  looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb6/6-2/6-2:1.0':
    KERNELS=="6-2:1.0"
    SUBSYSTEMS=="usb"
    DRIVERS=="cdc_acm"
    ATTRS{bInterfaceNumber}=="00"
    ATTRS{bAlternateSetting}==" 0"
    ATTRS{bNumEndpoints}=="01"
    ATTRS{bInterfaceClass}=="02"
    ATTRS{bInterfaceSubClass}=="02"
    ATTRS{bInterfaceProtocol}=="01"
    ATTRS{modalias}=="usb:v2341p0043d0001dc02dsc00dp00ic02isc02ip01"
    ATTRS{supports_autosuspend}=="1"
    ATTRS{bmCapabilities}=="6"

  looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb6/6-2':
    KERNELS=="6-2"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{configuration}==""
    ATTRS{bNumInterfaces}==" 2"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bmAttributes}=="c0"
    ATTRS{bMaxPower}=="100mA"
    ATTRS{urbnum}=="71"
    ATTRS{idVendor}=="2341"
    ATTRS{idProduct}=="0043"
    ATTRS{bcdDevice}=="0001"
    ATTRS{bDeviceClass}=="02"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bMaxPacketSize0}=="8"
    ATTRS{speed}=="12"
    ATTRS{busnum}=="6"
    ATTRS{devnum}=="3"
    ATTRS{version}==" 1.10"
    ATTRS{maxchild}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{authorized}=="1"
    ATTRS{manufacturer}=="Arduino (www.arduino.cc)"
    ATTRS{serial}=="6493633303735190F111"
Again it is the device is most easily recognised by reading the manufacturer string, which reveals that this is the arduino.

Writing udev rules

Now we know how to manually find out which device is which. By writing udev rules it is possible to automatically create suitably named symlinks to tell the devices apart. The udev rules are located in /etc/udev/rules.d, and you will either have to sudo or become root to add or change anything there. The rules themselves consists of one or more properties that should should be matched, and one or more actions that should be performed when a match is found. The property matches use the same form as given as output from udevadm info queries above, and this means the matching part of a udev rule can be copied straight from that output. Wildcards may also be used. In this case the action that we want to perform is to create an extra symlink to the device.

Create /etc/udev/rules.d/72-micro-devel.rules and give it the following contents.
SUBSYSTEM=="tty" ATTRS{manufacturer}=="Arduino*" SYMLINK+="arduino%n"

SUBSYSTEM=="tty" ATTRS{product}=="Olimex AVR-ISP500" SYMLINK+="stk500-programmer%n"
After this rule has been added, plugging in an arduino will automatically create an arduinoX symlink, while plugging in the stk500 programmer will create a stk500-programmerX symlink.

Modifying product and manufacturer strings

My older Arduino uses an FTDI chip for the serial connection, and will turn up as /dev/ttyUSBx. Taking a closer look at /dev/ttyUSB0 with
udevadm info -a -n /dev/ttyUSB0
produces the following information
  looking at device '/devices/pci0000:00/0000:00:1d.0/usb6/6-2/6-2:1.0/ttyUSB0/tty/ttyUSB0':
    KERNEL=="ttyUSB0"
    SUBSYSTEM=="tty"
    DRIVER==""

  looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb6/6-2/6-2:1.0/ttyUSB0':
    KERNELS=="ttyUSB0"
    SUBSYSTEMS=="usb-serial"
    DRIVERS=="ftdi_sio"
    ATTRS{latency_timer}=="1"
    ATTRS{port_number}=="0"

  looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb6/6-2/6-2:1.0':
    KERNELS=="6-2:1.0"
    SUBSYSTEMS=="usb"
    DRIVERS=="ftdi_sio"
    ATTRS{bInterfaceNumber}=="00"
    ATTRS{bAlternateSetting}==" 0"
    ATTRS{bNumEndpoints}=="02"
    ATTRS{bInterfaceClass}=="ff"
    ATTRS{bInterfaceSubClass}=="ff"
    ATTRS{bInterfaceProtocol}=="ff"
    ATTRS{modalias}=="usb:v0403p6001d0400dc00dsc00dp00icFFiscFFipFF"
    ATTRS{supports_autosuspend}=="0"
    ATTRS{interface}=="USB <-> Serial"

  looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb6/6-2':
    KERNELS=="6-2"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{configuration}==""
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bmAttributes}=="80"
    ATTRS{bMaxPower}==" 90mA"
    ATTRS{urbnum}=="12283"
    ATTRS{idVendor}=="0403"
    ATTRS{idProduct}=="6001"
    ATTRS{bcdDevice}=="0400"
    ATTRS{bDeviceClass}=="00"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bMaxPacketSize0}=="8"
    ATTRS{speed}=="12"
    ATTRS{busnum}=="6"
    ATTRS{devnum}=="14"
    ATTRS{version}==" 1.10"
    ATTRS{maxchild}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{authorized}=="1"
    ATTRS{manufacturer}=="FTDI"
    ATTRS{product}=="USB <-> Serial"
This Arduino is old enough to have kept FTDI's default manufacturer and product strings. With the defaults it is difficult to tell this apart from a usb to serial adapter. The strings and other settings are stored on an EEPROM and may be modified. This can be done with the windows program
FT_PROG , or with a bit more work, with libftdi. If the manufacturer string is changed to begin with Arduino the udev rule we wrote earlier will work for this arduino variant too.

I'm sure there are tools to modify manufacturer/product strings for ATmega16U2 (Arduino Uno's serial chip) too, but at this point I am not familiar with them.