USB CDC with the Raspberry Pi

For my home automation project with openHAB I want to attach Freescale (now NXP) FRDM (Freedom) boards so they can take care about the realtime aspects and to act as gateways to my other systems. One way is to use USB CDC (Serial over USB) as communication channel. USB has the advantage that it powers the board, plus I can attach multiple devices: up to four on the Raspberry Pi 2 and even more with using a USB hub. In a standard configuration with a USB WiFi and a USB HID (mouse plus keyboard) dongle I still can attach two Freescale (ahem, NXP) Freedom boards to the Raspberry Pi:

FRDM-K22F and FRDM-K64F attached to Raspberry Pi 2

FRDM-K22F and FRDM-K64F attached to Raspberry Pi 2

Outline

This article describes how to use the Raspberry Pi with devices connected to it using USB CDC. USB CDC is a device class which implements a ‘serial over USB’ protocol. I’m using USB CDC (see “Tutorial: USB CDC with the KL25Z Freedom Board“) in many of my projects, so this is an elegant way how to communicate between the Raspberry Pi and an embedded microcontroller.

Where is my USB port?

After connecting the USB CDC device to the Raspberry, the first problem is to know which device it is using. To see which USB devices are attached I can use the lsusb command:

lsusb

which shows something this:

pi@raspberrypi ~ $ lsusb
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp.
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp.
Bus 001 Device 004: ID 7392:7811 Edimax Technology Co., Ltd EW-7811Un 802.11n Wireless Adapter [Realtek RTL8188CUS]
Bus 001 Device 008: ID 1366:0105 SEGGER
Bus 001 Device 005: ID 046d:c52b Logitech, Inc. Unifying Receiver

With

Bus 001 Device 008: ID 1366:0105 SEGGER

It shows the Segger OpenSDA which has a combined USB CDC implemented.

If plugging in a Freescale USB CDC (see “USB CDC with the FRDM-K64F, finally!“) device, then it shows as

Bus 001 Device 007: ID 2504:0300

💡 The vendor string is empty because I’m using a ‘generic’ VID and PID, not registered with the USB consortium. See http://ubuntuforums.org/showthread.php?t=2234649

Where 2504 is the Vendor ID (VID) and 0300 the Product ID (PID) reported by the USB device. These are the values I had specified in the USB device implementation:

USB CDC Configuration

USB CDC Configuration

So which Linux device is it using?

  1. Plug in the USB cable
  2. Use dmesg command

The dmesg command prints kernel debug/log information:

dmesg

I should see something like this for my USB CDC implementation on the FRDM board:

[23621.397305] usb 1-1.3: New USB device found, idVendor=2504, idProduct=0300
[23621.397337] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[23621.397354] usb 1-1.3: Product: FSL CDC DEVICE
[23621.397371] usb 1-1.3: Manufacturer: FREESCALE INC.
[23621.398717] cdc_acm 1-1.3:1.0: ttyACM0: USB ACM device

Or for the Segger USB CDC (OpenSDA):

[  226.413679] usb 1-1.3: new full-speed USB device number 8 using dwc_otg
[  226.519865] usb 1-1.3: New USB device found, idVendor=1366, idProduct=1015
[  226.519890] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[  226.519908] usb 1-1.3: Product: J-Link
[  226.519924] usb 1-1.3: Manufacturer: SEGGER
[  226.519940] usb 1-1.3: SerialNumber: 000000123456
[  226.524041] cdc_acm 1-1.3:1.0: ttyACM0: USB ACM device
[  226.527545] usb-storage 1-1.3:1.3: USB Mass Storage device detected
[  226.527989] scsi host0: usb-storage 1-1.3:1.3
[  227.525152] scsi 0:0:0:0: Direct-Access     SEGGER   MSD Volume       1.00 PQ: 0 ANSI: 4
[  227.527831] sd 0:0:0:0: [sda] 21829 512-byte logical blocks: (11.1 MB/10.6 MiB)
[  227.528693] sd 0:0:0:0: [sda] Write Protect is off
[  227.528721] sd 0:0:0:0: [sda] Mode Sense: 0b 00 00 08
[  227.529304] sd 0:0:0:0: [sda] No Caching mode page found
[  227.529327] sd 0:0:0:0: [sda] Assuming drive cache: write through
[  227.551467]  sda:
[  227.555561] sd 0:0:0:0: [sda] Attached SCSI removable disk
[  227.562302] sd 0:0:0:0: Attached scsi generic sg0 type 0
[  237.448489] usb 1-1.3: USB disconnect, device number 8
[  237.533960] FAT-fs (sda): unable to read boot sector to mark fs as dirty
[  245.623749] usb 1-1.3: new full-speed USB device number 9 using dwc_otg
[  245.729928] usb 1-1.3: New USB device found, idVendor=1366, idProduct=0105
[  245.729954] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[  245.729971] usb 1-1.3: Product: J-Link
[  245.729988] usb 1-1.3: Manufacturer: SEGGER
[  245.730003] usb 1-1.3: SerialNumber: 000621000000
[  245.732360] cdc_acm 1-1.3:1.0: ttyACM0: USB ACM device

Because the OpenSDA Segger J-Link is a composite (multiple) USB device, it shows multiple devices: a MSD (Mass Storage Device), a J-Link and USB CDC (ACM). Again it is using ttyACMx as device.

This tells me that the device has been recognized and it is handled by the USB CDC ttyACM0 driver.

💡 See https://www.rfc1149.net/blog/2013/03/05/what-is-the-difference-between-devttyusbx-and-devttyacmx/ about the difference between ACMx and USBx.

The command udevadm shows me details for a device:

udevadm info -a -n /dev/ttyACM0

The ‘-a’ option prints all the information up the device chain, and the ‘-n’ option is used to specify the device (/dev/ttyACM0) here:

the details of the device, and it shows my USB CDC device details:

  looking at parent device '/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.3':
    KERNELS=="1-1.3"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{devpath}=="1.3"
    ATTRS{idVendor}=="2504"
    ATTRS{speed}=="12"
    ATTRS{bNumInterfaces}==" 2"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bMaxPacketSize0}=="16"
    ATTRS{busnum}=="1"
    ATTRS{devnum}=="9"
    ATTRS{configuration}==""
    ATTRS{bMaxPower}=="100mA"
    ATTRS{authorized}=="1"
    ATTRS{bmAttributes}=="c0"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{maxchild}=="0"
    ATTRS{bcdDevice}=="0002"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{version}==" 2.00"
    ATTRS{urbnum}=="13"
    ATTRS{ltm_capable}=="no"
    ATTRS{manufacturer}=="FREESCALE INC.              "
    ATTRS{removable}=="removable"
    ATTRS{idProduct}=="0300"
    ATTRS{bDeviceClass}=="02"
    ATTRS{product}=="FSL CDC DEVICE"

Which device/port (/dev/ttyACM0, /dev/ttyACM1, …) depends on the order how I plug them in. Below is an example with both the Segger OpenSDA J-Link and USB CDC connected where it is using ttyACM0 for the Segger USB CDC and ttyACM1 for the native USB CDC:

[ 3753.941466] usb 1-1.3: Product: J-Link
[ 3753.941482] usb 1-1.3: Manufacturer: SEGGER
[ 3753.941499] usb 1-1.3: SerialNumber: 000621000000
[ 3753.943825] cdc_acm 1-1.3:1.0: ttyACM0: USB ACM device
[ 3763.815366] usb 1-1.2: new full-speed USB device number 9 using dwc_otg
[ 3763.919642] usb 1-1.2: New USB device found, idVendor=2504, idProduct=0300
[ 3763.919671] usb 1-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 3763.919709] usb 1-1.2: Product: FSL CDC DEVICE
[ 3763.919727] usb 1-1.2: Manufacturer: FREESCALE INC.
[ 3763.921320] cdc_acm 1-1.2:1.0: ttyACM1: USB ACM device

Running

udevadm info -a -n /dev/ttyACM1

shows me the following information for the Segger OpenSDA J-Link:

  looking at parent device '/devices/platform/soc/3f980000.usb/usb1/1-1/1-1.3':
    KERNELS=="1-1.3"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{bDeviceSubClass}=="02"
    ATTRS{bDeviceProtocol}=="01"
    ATTRS{devpath}=="1.3"
    ATTRS{idVendor}=="1366"
    ATTRS{speed}=="12"
    ATTRS{bNumInterfaces}==" 3"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{busnum}=="1"
    ATTRS{devnum}=="8"
    ATTRS{configuration}=="Configuration"
    ATTRS{bMaxPower}=="100mA"
    ATTRS{authorized}=="1"
    ATTRS{bmAttributes}=="80"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{maxchild}=="0"
    ATTRS{bcdDevice}=="0100"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{serial}=="000621000000"
    ATTRS{version}==" 2.00"
    ATTRS{urbnum}=="586"
    ATTRS{ltm_capable}=="no"
    ATTRS{manufacturer}=="SEGGER"
    ATTRS{removable}=="removable"
    ATTRS{idProduct}=="0105"
    ATTRS{bDeviceClass}=="ef"
    ATTRS{product}=="J-Link"

We will need this information later.

Minicom Terminal Program

Now as I know the device, I need a terminal program to talk to the device. I have found the minicom terminal program a very easy one to use, and it works both from a remote SSH and locally on the Raspberry Pi.

If minicom is not already installed, use the following command to install it:

sudo apt-get install minicom

Run minicom with

minicom -D /dev/ttyACM0 -b 38400

Where -D specifies the device to be used, and -b the baud of the device. This starts the terminal program:

Started minicom session

Started minicom session

As indicated on the bottom of the screen, I can press ‘CTRL+A’ (‘CTRL’ pressed, then press ‘a’ (lower case) key, release both keys and then press ‘z’ on the keyboard) to show a help menu:

Minicom Help

Minicom Help

I can use that menu e.g. to turn on/off local echo. Without the menu I simply would use ‘CTRL+E’ followed by ‘e’:

Local Echo On

Local Echo On

Now I can use minicom as a normal terminal program to talk to my applications using the Shell (shown below talking to my quadrocopter drone project on a FRDM-K22F):

minicom terminal session

minicom terminal session

To leave minicom, I use CTRL+A followed by x. This will hang up the session.

Persistent Device Names for USB CDC Devices

One problem remains: Which device is used depends on the order the device is recognized. Later on, when I want to use the board with openHAB, I need to make sure that the board always is attached to the same device. To assign a fixed device, I can use udev rules. With these rules I can specify what shall happen when a new device is attached. See as well this Hackaday article how to write udev rule files.

In /etc/udev/rules.d I create a new file named

99-usb-serial.rules
99-usb-serial.rules

99-usb-serial.rules

The file needs to have the .rules extension. The number 99 is because rules >=90 should run last (see http://hackaday.com/2009/09/18/how-to-write-udev-rules/).

Now I need to find attributes to differentiate between multiple USB CDC devices. For this I use the udevadm command I have used earlier.

To create a dedicated device for the Segger J-Link I can add the following line to the 99-usb-serial.rules:

SUBSYSTEM=="tty", ATTRS{idVendor}=="1366", ATTRS{product}=="J-Link", ATTRS{idProduct}=="0105", ATTRS{serial}=="000621000000", SYMLINK+="ttyUSB50"

Basically I try to use a set of matching attributes to identify the device and then to create a symbolic link for it with the SYMLINK+= command.

The same way I add the following line to identify my USB CDC implementation:

SUBSYSTEM=="tty", ATTRS{idVendor}=="2504", ATTRS{product}=="FSL CDC DEVICE", ATTRS{manufacturer}=="FREESCALE INC.              ", SYMLINK+="ttyUSB51"

That means that the device will be still used as \dev\ttyACMx, but a symbolic link /dev/ttyUSB50 or /dev/ttyUSB51 will be created.

I use the following command to test the rules:

udevadm test /dev/ttyACM0

This will not create the links, but might be useful to see what is going on.

Udev rules are in effect immediately, but won’t trigger if the device is already attached. To test my new rules, I have to disconnect and re-connect the USB devices. Or use

udevadm trigger

With

ls -l /dev

I should see that a link has been created:

lrwxrwxrwx 1 root root           7 Dec 26 19:23 ttyUSB50 -> ttyACM0
lrwxrwxrwx 1 root root           7 Dec 26 19:30 ttyUSB51 -> ttyACM1

With this, I can have multiple boards connected to the Raspberry Pi, and they will use a fixed device:

FRDM Boards connected to Raspberry Pi

FRDM Boards connected to Raspberry Pi

Note that if I have no attribute differences between devices, I cannot distinguish them. This is for example a problem e.g. with the FSL USB CDC device as long as I’m not changing the default attributes. I’m thinking about adding a serial number to the USB device descriptor so I can easily distinguish different devices that way.

Summary

With some work, I can easily work with USB CDC devices attached to the USB ports of the Raspberry Pi. With lsusb I see which devices are attached, with udevadm info I can check the device attributes, and with udev rules I can assign persistent device names based on attributes. And with minicom I have a good terminal program I can use to talk to my USB CDC devices.

Happy ttying 🙂

Links

3 thoughts on “USB CDC with the Raspberry Pi

  1. Pingback: McuOnEclipse Release 10-Jan-2016 | MCU on Eclipse

  2. Pingback: Controlling NXP Freedom Board RGB LED with openHAB and Raspberry Pi | MCU on Eclipse

  3. Pingback: Hexiwear with Raspberry Pi and OpenHAB Home Automation | MCU on Eclipse

What do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.