Using UART Sensors on any Linux

UPDATE: This post has been modified for the 2.0 DKMS package release (2015-02-14).

Thanks to the ev3dev project, it is possible to use any LEGO MINDSTORMS EV3 UART sensor on any kind of Linux with a serial port. I have packaged up the necessary driver from ev3dev as a standalone DMKS package posted them on GitHub. Be sure to read that README.

So now lets talk about how to make this actually work. First, you need a UART sensor. Sorry to say, but most LEGO MINDSTORMS sensors are not UART sensors. But these are…

uart-sensors

Now, we need another UART thing to connect the sensor to. Serial port is a synonym for UART, so anything serial port should do. The most important thing is we need to know what voltage it operates at. The EV3 itself uses 3.3V I/O and can tolerate 5V input levels. The UART sensors can probably handle 5V as well, but if you want to be extra safe, stick with 3.3V. Now, you might be thinking I should just hook this up to the old RS232 serial port on my old PC. Well, you better do your homework first. Voltage levels on RS232 can be as high as +/-15V! That could really damage your sensors. The good news is 3.3V stuff is very common nowadays. There are 3.3V USB UART adapters and even 3.3V Bluetooth UART adapters. (Did I just say Bluetooth? Yes! That means you can turn these sensors into wireless sensors!) There are also lots of chips that can convert other voltages (like RS232) to something we can handle.

The next thing we need is a 5V power supply. Event though the communication signal levels are operating at 3.3V, the sensors expect a 5V power supply because that is what the EV3 brick provides. Again, you can probably get away with a 3.3V power supply, but if you are the extra careful type, 5V is the safe choice.

For my example here, I was lucky enough to find a USB serial cable from FTDI that has exactly what we need: 3.3V logic levels and a 5V power source. It even came with the perfect connector.

ftdi-cable

Connecting the sensor

According to the schematic for the FTDI cable, the connections are…

ftdi-cable-connections

And the connections on the EV3 UART sensors look something like this…

ev3-uart-sensor-schem

Now, we just need to figure out how to connect the two together. Ground and Vcc are easy. Then Rx (receive) on the cable goes to Tx (transmit) on the sensor and Tx on the cable goes to Rx on the sensor. RTS and CTS on the cable are for flow control, which we are not using. So, for my particular cable the connections are as follows. If you are using something different, you will have to figure out the connections for yourself.

Cable (wire) Sensor (pin)
GND/BLACK 3
VCC/RED 4
TXD/ORANGE 5
RXD/YELLOW 6

I’m using a connector from mindsensors.com to provide a jack that I can plug in a standard MINDSTORMS cable to connect to the sensor. You can see here that I have rearranged the wires in the connector that came with the FTDI cable. The RTC and CTS wires are taped back so the are out of the way. I didn’t want to cut them off in case I want to use this cable for something else in the future.

ftdi-to-ev3-connection

Let’s hook up a sensor to the cable and see what happens. The driver is already included in the ev3dev firmware, so I’m going to hook it up to my EV3 running ev3dev first. I’m going to use the ultrasonic sensor since it lights up when it is connected, which will let us know it is working.

Plug it in and… nothing happens. If only it was that easy. What’s going on here?

Loading the driver

First, let’s check that the USB serial cable was recognized.

dmesg | tail
 ...
 ftdi_sio 1-1.1:1.0: FTDI USB Serial Device converter detected
 usb 1-1.1: Detected FT232RL
 usb 1-1.1: Number of endpoints 2
 usb 1-1.1: Endpoint 1 MaxPacketSize 64
 usb 1-1.1: Endpoint 2 MaxPacketSize 64
 usb 1-1.1: Setting MaxPacketSize 64
 usb 1-1.1: FTDI USB Serial Device converter now attached to ttyUSB0

Good, it found it and loaded the FTDI driver. Now we need to tell the ev3-uart-sensor-ld driver to run on top of this tty. The  ev3-uart-sensor-ld driver is implemented as a line discipline, which basically means that it takes over your serial port and makes it look like something else. For example, the Bluetooth chip in the EV3 is actually connected to the processor via UART, so on the hardware level, it just looks like a serial port. However, there is a Bluetooth HCI line discipline that we tell the Linux kernel to use so that it looks like a Bluetooth device instead of a serial port. We’re going to do the same thing here so that our serial port will look like a MINDSTORMS sensor. To do this, we use the ldattach command. Each line discipline has a fixed ID number. For our UART sensor driver, it is 29.

ldattach 29 /dev/ttyUSB0
dmesg | tail
...
lego-sensor sensor1: Bound to device 'ttyUSB0'

Yea, the lights lit up! And we have this lego-sensor thing registered in the kernel, whatever that means. Before I get to that though, I would like to point out that ldattach is running (well sleeping actually) in the background and you should kill it before removing the sensor.

ps ax | grep ldattach
  729 ?        Ss     0:00 ldattach 29 /dev/ttyUSB0
  737 pts/0    S+     0:00 grep ldattach
kill 729

Of course we don’t want to have to do that every time we plug and unplug our cable, so lets automate it. To make thing happen automatically when you attach or remove hardware, you can write udev rules. ev3dev-jessie uses systemd, so let’s look at that first (or skip ahead if you want to get to the fun part).

udev rules and systemd service

systemd does not allow udev rules to run long running processes like ldattach, but it does provide a nice way to bind a systemd service to a hardware device. Here’s my udev rule:

SUBSYSTEM=="tty", ENV{ID_VENDOR_ID}=="0403", ENV{ID_MODEL_ID}=="6001", \
    ENV{ID_SERIAL_SHORT}=="A603UZPK", ACTION=="add", TAG+="systemd", \
     ENV{SYSTEMD_WANTS}="ev3-uart-ldattach@$name.service"

This is saved as /etc/udev/rules.d/99-usb-uart-sensor.rules. I figured out all of the matching rules (the ones with ==) using udevadm.

udevadm info /dev/ttyUSB0
P: /devices/platform/ohci.0/usb1/1-1/1-1.1/1-1.1:1.0/ttyUSB0/tty/ttyUSB0
N: ttyUSB0
S: serial/by-id/usb-FTDI_FT232R_USB_UART_A603UZPK-if00-port0
S: serial/by-path/platform-ohci.0-usb-0:1.1:1.0-port0
E: DEVLINKS=/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A603UZPK-if00-port0 /dev/serial/by-path/platform-ohci.0-usb-0:1.1:1.0-port0
E: DEVNAME=/dev/ttyUSB0
E: DEVPATH=/devices/platform/ohci.0/usb1/1-1/1-1.1/1-1.1:1.0/ttyUSB0/tty/ttyUSB0
E: ID_BUS=usb
E: ID_MODEL=FT232R_USB_UART
E: ID_MODEL_ENC=FT232R\x20USB\x20UART
E: ID_MODEL_FROM_DATABASE=FT232 USB-Serial (UART) IC
E: ID_MODEL_ID=6001
E: ID_PATH=platform-ohci.0-usb-0:1.1:1.0
E: ID_PATH_TAG=platform-ohci_0-usb-0_1_1_1_0
E: ID_REVISION=0600
E: ID_SERIAL=FTDI_FT232R_USB_UART_A603UZPK
E: ID_SERIAL_SHORT=A603UZPK
E: ID_TYPE=generic
E: ID_USB_DRIVER=ftdi_sio
E: ID_USB_INTERFACES=:ffffff:
E: ID_USB_INTERFACE_NUM=00
E: ID_VENDOR=FTDI
E: ID_VENDOR_ENC=FTDI
E: ID_VENDOR_FROM_DATABASE=Future Technology Devices International, Ltd
E: ID_VENDOR_ID=0403
E: MAJOR=188
E: MINOR=0
E: SUBSYSTEM=tty
E: SYSTEMD_WANTS=ev3-uart-ldattach@ttyUSB0.service
E: TAGS=:systemd:
E: USEC_INITIALIZED=567877947

Here’s how I came up with the rules. All serial ports have will match SUBSYSTEM==”tty”. Then I used the environment properties to match the vendor ID, product ID and serial number of my cable. This way, the rule will only take effect for this exact serial cable. It also means you will have to modify the file to match your own cable. The ACTION==”add” rule means we want to run the rule when the cable is plugged in. The rest tells systemd that it should be paying attention and start the ev3-uart-ldattch@.serivce when we plug in the cable. $name passes the name of the tty to the service – in this case “ttyUSB0”.

And here is the service, which is saved as /etc/systemd/system/ev3-uart-ldattach@.service

[Unit]
Description=LEGO MINDSTORMS EV3 UART Sensors
BindsTo=dev-%i

[Service]
Type=simple
ExecStart=/usr/sbin/ldattach -d 29 /dev/%i

BindsTo tells the service to stop when the device is removed. In ExecStart, it is important to use the full path to the command (found with which ldattach). Also, we use the -d option so that ldattach does not try to run in the background. This way systemd can kill the process for us and we don’t have to mess with a pid file to keep track of the process. %i is the parameter passed to us, which again in this case is ttyUSB0.

udev rules without systemd

If you want to use this on a system that does not use systemd, we have to do things a little different. The DKMS Debian package contains a helper script for starting and killing ldattach. It can be found at /usr/sbin/ev3uartattach if you install the Debian package or save it at to /usr/local/sbin/ev3uartattach if you don’t install the Debian package. It is used in the udev rule in place of the systemd service.

My udev rule looks like this:

SUBSYSTEM=="tty", ENV{ID_VENDOR_ID}=="0403", ENV{ID_MODEL_ID}=="6001", \
    ENV{ID_SERIAL_SHORT}=="A603UZPK", ACTION=="add", \
    RUN+="/usr/sbin/ev3uartattach start $name"
SUBSYSTEM=="tty", ENV{ID_VENDOR_ID}=="0403", ENV{ID_MODEL_ID}=="6001", \
    ENV{ID_SERIAL_SHORT}=="A603UZPK", ACTION=="remove", \
    RUN+="/usr/sbin/ev3uartattach.sh stop $name"

This is saved as /etc/udev/rules.d/99-legoev3-uart-sensors.rules. The matching part is the same as the systemd version above, but we need separate add and remove actions. RUN tells it to call our script with the appropriate parameters when the cable is plugged or unplugged.

Permissions

By default, to be able to write to a hardware driver in Linux, you have to be root. In order to use these drivers as a non-root user, you can set up some udev rules to modify the permissions. The DKMS debian package installs these by default. If you aren’t using the Debian package, you can find the rules here. These rules give the ev3dev group permission to write to the drivers. So, you need to add your user to the ev3dev group then log out and back in for the change to take effect.

# The ev3dev group is created by the Debian DKMS package.
# If you need to create the group yourself, run...
sudo groupadd --force --system ev3dev
# Then add yourself to the ev3dev group
sudo usermod -aG ev3dev <username>
# Don't forget to log out and back in

Using the lego-sensor class in sysfs

There is more detailed information on this in the ev3dev docs, so I will just show a few simple things. First, let’s measure some distance. Run watch -n 0.1 cat /sys/class/lego-sensor/sensor0/value0. It should look something like this (press CTRL+C to stop):

Every 0.1s: cat /sys/class/lego-sensor/sensor0/value0     Wed Sep  3 18:27:51 2014

435

My sensor is 43.5 cm away from something! (The docs tell us that there is one decimal point.)

Note: the number N in sensorN will increment every time you attach a sensor. If you have more than one sensor or if you unplug and plug back in, you will need to adjust the number accordingly.

OK, how about color. Here is a script to try out:

#!/bin/bash

if [ $# -ne 1 ]
then
    echo "usage: color.sh N"
    echo "where N is the sensor's sysfs id number"
    exit 1
fi

sensor_path="/sys/class/lego-sensor/sensor$1/"

echo "COL-COLOR" > ${sensor_path}mode

while true
do
    case $(cat ${sensor_path}value0) in
    0)
        echo "none"
        ;;
    1)
        echo "black"
        ;;
    2)
        echo "blue"
        ;;
    3)
        echo "green"
        ;;
    4)
        echo "yellow"
        ;;
    5)
        echo "red"
        ;;
    6)
        echo "white"
        ;;
    7)
        echo "brown"
        ;;
    esac
    sleep 1
done

This will print the sensed color ever 1 second.

Using the DKMS package

OK. So the main point of this post is that you don’t actually have to have an EV3 in order to use these sensors. You can use the same driver on anything the runs Linux. To use the drivers, you need kernel modules. Kernel modules have to be compiled against your exact kernel. This is where Dynamic Kernel Module System (DKMS) comes in. The nice thing about DKMS is that the modules will automatically be rebuilt any time you upgrade your kernel.

You can download a package that contains the source code for these kernel modules from GitHub. Then follow the instructions in the README to install the package. You will also want to create some udev rules on that system too.

Once you have installed the modules, plug in your sensor (if it is already plugged in, unplug it and plug it back in). Your sensors will work just like they do in ev3dev!

 The rest is up to you

So what will you do with your UART sensors? Make a wireless Bluetooth sensor? Use 7 sensors at once on your EV3 (using a USB hub and USB serial converters). Automate your desktop? Do some field data acquisition with your laptop? Make a robot with your Raspberry Pi?

Here is the silly thing that I did…

3 Comments on “Using UART Sensors on any Linux

  1. Awesome!

    How do you know that “The EV3 itself uses 3.3V I/O and can tolerate 5V input levels”?
    (the 5V input part)

    • By reading the schematic and doing a little bit of math. The processor uses 3.3V I/O (according to the datasheet) and there are Schottky diodes and series resistors that protect the processor pins from higher voltages. Also, NXT sensors use 5V I/O, so if they are safe, then 5V UART on the same pins must be safe.