Making your boards unique on Linux

I don't know about you, but I have a huge pile of different Arduino-like boards here. (I have so many because I need to test UECIDE with them - or that's what I tell the "bank manager"). Many is the time I will have more than one of them plugged in to my computer. Often times I have programmed one of them with some code only to find it's not worked - and why hasn't it worked? Because I have had the wrong serial port selected in the IDE.

All the development boards fall into three categories, and those categories define what the name of the serial port is. On Linux that name isn't fixed - they're allocated on a first-come-first-served basis, and often at boot up the names of boards already attached change order. A bit of a pain.

First you have the FT232-based boards. These all identify as /dev/ttyUSBn (n is a number). The first board that is detected (or plugged in) gets /dev/ttyUSB0. The second /dev/ttyUSB1, etc.

Then you have the CDC/ACM based boards (boards like the Uno that use a microcontroller for the interface chip, or boards like the Leonardo where the USB is directly interfaced with the main MCU). These all come as /dev/ttyACMn and follow the same numbering rules as above.

The third category (which doesn't really concern us at the moment) are those that don't have a serial port at all - the ones with a built-in debugger, for instance. We can ignore those for now.

Windows tends to always allocate the same COM port number to the same board. Mac OS X gives the ports unique names, like /dev/tty.usbmodem.f9878y23, which is made from things like the serial number of the board, etc., (depending on the interface chip and driver capabilities). Linux, though, doesn't. In some ways that's better, in some worse. It would be nice to always know which board is which.

Well, luckily, as in all things Linux, there is a way.

Enter udev - the Dynamic Device Manager.

udev is capable of doing all sorts of things when a new device is attached and detached, and it's able to look at all sorts of different parameters to find out just what it is you have plugged in.

udev comes with a useful little tool: `udevadm``. You can use that to find out all about a device. For instance, plug in an Arduino Uno and run the command:

udevadm info -a -n /dev/ttyACM0

A huge amount of information gets spewed out. We're only interested in the third block, which for my Uno looks like this:

looking at parent device '/devices/pci0000:00/0000:00:1a.1/usb6/6-2':
KERNELS=="6-2"
SUBSYSTEMS=="usb"
DRIVERS=="usb"
ATTRS{bDeviceSubClass}=="00"
ATTRS{bDeviceProtocol}=="00"
ATTRS{devpath}=="2"
ATTRS{idVendor}=="2341"
ATTRS{speed}=="12"
ATTRS{bNumInterfaces}==" 2"
ATTRS{bConfigurationValue}=="1"
ATTRS{bMaxPacketSize0}=="8"
ATTRS{busnum}=="6"
ATTRS{devnum}=="3"
ATTRS{configuration}==""
ATTRS{bMaxPower}=="100mA"
ATTRS{authorized}=="1"
ATTRS{bmAttributes}=="c0"
ATTRS{bNumConfigurations}=="1"
ATTRS{maxchild}=="0"
ATTRS{bcdDevice}=="0001"
ATTRS{avoid_reset_quirk}=="0"
ATTRS{quirks}=="0x0"
ATTRS{serial}=="64934333235351B002E0"
ATTRS{version}==" 1.10"
ATTRS{urbnum}=="11"
ATTRS{ltm_capable}=="no"
ATTRS{manufacturer}=="Arduino (www.arduino.cc)"
ATTRS{removable}=="unknown"
ATTRS{idProduct}=="0001"
ATTRS{bDeviceClass}=="02"
ATTRS{product}=="Arduino Uno"

There we have all the information you could ever want about your Uno. Any of that can be used by udev to identify it.  The best bits there are the USB VID, PID and serial number:

ATTRS{idVendor}=="2341"
ATTRS{idProduct}=="0001"
ATTRS{serial}=="64934333235351B002E0"

Those values, which are unique to that board, can now be fed in to a udev "rule" which tells it what to do when that board is connected. Rules live in /etc/udev/rules.d and I have made a big file called /etc/udev/rules.d/50-boards.rules for all my development boards.

Ok, so let's look at what a rule consists of.

Basically it has two parts. The first is a set of selectors, and the second is a set of actions to perform.  We already have our selectors above - incidentally, that command outputs them in the correct format to paste directly into a udev rule.

So the first part of our rule simply looks like:

ATTRS{idVendor}=="2341", ATTRS{idProduct}=="0001", ATTRS{serial}=="64934333235351B002E0"

Now to add what to do with that board when it's connected. The main thing we want to do is create a symbolic link that we can use instead of /dev/ttyACM0. That link will always be t he same regardless of which tty name the board has been allocated. I place all mine in /dev/board. So if we add a SYMLINK action to the rule, we end up with:

ATTRS{idVendor}=="2341", ATTRS{idProduct}=="0001", ATTRS{serial}=="64934333235351B002E0", SYMLINK+="board/arduino/uno1"

Put that in a rules file (you'll have to do that as root, so use sudo) and restart udev:

$ sudo /etc/init.d/udev restart

Then unplug and plug in the Uno again. Now, magically, you should have a symbolic link /dev/board/arduino/uno1.

The only drawback of this is that the Arduino IDE won't know about those links and won't list them in the Hardware menu. Neither will UECIDE - or not initially. You can, though with UECIDE, add those links as extra serial ports in Preferences.

My second biggest bugbear is FT232 based boards - which is most of the chipKIT boards - since they all just identify themselves as an FT232 board, not what they actually are, such as a chipKIT uC32. In Windows there is a useful tool downloadable from Future Tech that allows you to change those details, but I don't run Windows, so I can't use it. Fortunately, though, there is a small Linux program, ftdi_eeprom, which allows you to do just the same. It's not as easy to use as the Windows one, but with care and attention it's possible to rename all your FT232 boards with it.

First you need to create a configuration file. This tells the program what settings to write into the FT232's EEPROM memory. You have to do this for every board when you want to change its settings. It's not (currently) possible to read-modify-write the settings, you have to write them all in one big chunk. And that means working out what the settings are to begin with.

Fortunately the default settings are pretty easy.  Here is my config file that I used to rename my uC32:

vendor_id=0x0403
product_id=0x6001
max_power=50
self_powered=true
remote_wakeup=false
use_serial=true
max_power=0
cbus0=TXLED
cbus1=RXLED
manufacturer="chipKIT"
product="uC32"
serial="AJV9K1DB"

It is important that you get the vendor_id and product_id correct. In my early experiments I bricked a board by missing out the 0x portion and changing the VID and PID of the board to something that couldn't be recognised by the program. Fortunately I managed to hack the program to let me change them back, but it wasn't easy.

You need to make sure you set the manufacturer and product to what you want, but you should keep the serial number the same as the board is already reporting (so as not to break your carefully crafted udev rules above).

The cbus0 and cbus1 lines define what the TX and RX LEDs (present on most boards) actually do. By default they are assigned to the TX and RX function, but it is possible to change that to others if you really want, but why would you?

Once you have your config file, and you're sure it's correct, you can now go ahead and program the board. One thing to remember with this though: there is no way of specifying which board you're programming, so make sure this is the only board plugged in to the computer at the time or you might be reprogramming the wrong board.

When you're ready:

$ ftdi_eeprom --flash-eeprom myconfigfile.txt

Unplug your board, plug it back in again, and if all went well it should now know what the board is:

(Output from dmesg:)

[ 5258.195696] usb 10-1: new full-speed USB device number 4 using uhci_hcd
[ 5258.388701] usb 10-1: New USB device found, idVendor=0403, idProduct=6001
[ 5258.388706] usb 10-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 5258.388709] usb 10-1: Product: uC32
[ 5258.388712] usb 10-1: Manufacturer: chipKIT
[ 5258.388714] usb 10-1: SerialNumber: AJV9K1DB
[ 5258.393746] ftdi_sio 10-1:1.0: FTDI USB Serial Device converter detected
[ 5258.393795] usb 10-1: Detected FT232RL
[ 5258.393798] usb 10-1: Number of endpoints 2
[ 5258.393801] usb 10-1: Endpoint 1 MaxPacketSize 64
[ 5258.393803] usb 10-1: Endpoint 2 MaxPacketSize 64
[ 5258.393806] usb 10-1: Setting MaxPacketSize 64
[ 5258.395703] usb 10-1: FTDI USB Serial Device converter now attached to ttyUSB0

And of course the nice friendly names in the various IDEs should now show the right things.


Measuring Arduino Internal Pull-up Resistors