Fixing a busy CP210x serial device on Ubuntu

April 5, 2020

I had the opportunity to reorganize my local machines. As part of that, I wanted to plug my firewall’s serial port into USB serial and pop that into my server. I have a CP2104 serial device that I bought with my PC Engines apu2 which I use for a firewall.

The USB serial device worked fine when plugged into my Windows 8 laptop, but I want my server to be able to connect to my firewall even when the network is down. Because the apu2 is headless, it’s nice to have something that’s plugged into a monitor when I need to fix things.

I plugged the USB serial port in, and tried to connect to my firewall with minicom. I got this instead:

$ minicom 
minicom: cannot open /dev/ttyUSB0: Device or resource busy

So, time to look at who has /dev/ttyUSB0 open:

$ sudo lsof | grep ttyUSB0
gpsd 416 root 3u CHR 188,0 0t0 176 /dev/ttyUSB0

Ok, why is gpsd holding /dev/ttyUSB0 open? It’s true I have a GPS attached to my server, but that runs as /dev/ttyACM0 and has nothing to do with /dev/ttyUSB0. Hmm… time to search and find this in the gpsd FAQ: Why does GPSD open non-GPS USB devices?

That made me suspicious. See, gpsd is trying to be too friendly – and to do that, it opens a whole bunch of possibly GPS devices even if they’re not GPS devices! Could that be my problem?

$ lsusb
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 008 Device 003: ID 10c4:ea60 Cygnal Integrated Products, Inc. CP210x UART Bridge / myAVR mySmartUSB light
Bus 008 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 007 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 006 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 040: ID 1546:01a7 U-Blox AG 
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub

Hmm… let’s take a peek in /lib/udev/rules.d/60-gpsd.rules:

... blah blah blah...
# Cygnal Integrated Products, Inc. CP210x Composite Device (Used by Holux m241 and Wintec grays2 wbt-201) [linux module: cp210x]
ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="gps%n", TAG+="systemd", ENV{SYSTEMD_WANTS}="gpsdctl@%k.service"
... more blah...

So in an effort to detect the Holux m241 and Wintec grays2 wbt-201, it’s matching the vendor and product ID of my CP2104 serial device as well! Luckily, I don’t have any of those GPS devices, so a quick snip:

# Cygnal Integrated Products, Inc. CP210x Composite Device (Used by Holux m241 and Wintec grays2 wbt-201) [linux module: cp210x]
# commented out because it interferes with Andrew's PC Engines 2104 USB serial cable
#ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="gps%n", TAG+="systemd", ENV{SYSTEMD_WANTS}="gpsdctl@%k.service"

was all it took. Now my USB serial device shows up as a serial device, and is not held open by a GPS daemon.

I have to disagree with the GPSD FAQ’s statement, “It’s not a problem we can solve with clever programming, the devices simply don’t yield enough information about themselves to avoid conflicts.” Err, no… clever programming would have the user run through an install procedure which involved plugging the device in, and detecting the device. Then they could update the udev rules so that only the device that a user owned was stolen by gpsd, and not all serial devices on the planet that happened to match a vendor/product ID that they knew about.


Mapping a USB volume knob into a keyboard on Linux for SDR

December 16, 2019

I recently discovered the existence of USB volume knobs. A Reddit user posted an article about reflashing the firmware on one to convert it to a keyboard.

 

IMG_20191216_010138

Inspired, I picked one up for $18 on eBay (“USB Volume Controller Knob Adjuster Switcher for Tablet PC Speaker Audio“) and thought that I might be able to do something similar.

It turns out, under Linux, this is pretty easy.

First, I plugged in the volume knob and saw that Linux detected it correctly and used it to adjust the volume. That was a promising start. I could see it show the “HDMI / DisplayPort” volume – and it went up when I turned the knob to the right, down when I turned the knob to the left, and muted when I pressed the knob.

Next, I wanted to see what events were being generated. I found some very useful instructions at https://yulistic.gitlab.io/2017/12/linux-keymapping-with-udev-hwdb/ and did them:

$ cat /proc/bus/input/devices
...
I: Bus=0003 Vendor=0483 Product=572d Version=0111
N: Name="STMicroelectronics USB Volume Control"
P: Phys=usb-0000:00:1d.0-1.7.2.4.3.1/input0
S: Sysfs=/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.7/2-1.7.2/2-1.7.2.4/2-1.7.2.4.3/2-1.7.2.4.3.1/2-1.7.2.4.3.1:1.0/0003:0483:572D.0008/input/input14
U: Uniq=2070363C4250
H: Handlers=kbd event8 
B: PROP=0
B: EV=13
B: KEY=3800000000 e000000000000 0
B: MSC=10
...

This showed me a few useful things:

  • The device vendor for my device is 0483 (the “I:” line)
  • The product ID for my device is 572d (also on the “I:” line)
  • The device is attached on /dev/input/event8 (on the “H:” line)

So now I could scan the events that came across when I moved the knob:

$ sudo evtest /dev/input/event8
Input driver version is 1.0.1
Input device ID: bus 0x3 vendor 0x483 product 0x572d version 0x111
Input device name: "STMicroelectronics USB Volume Control"
Supported events:
  Event type 0 (EV_SYN)
  Event type 1 (EV_KEY)
    Event code 113 (KEY_MUTE)
    Event code 114 (KEY_VOLUMEDOWN)
    Event code 115 (KEY_VOLUMEUP)
    Event code 163 (KEY_NEXTSONG)
    Event code 164 (KEY_PLAYPAUSE)
    Event code 165 (KEY_PREVIOUSSONG)
  Event type 4 (EV_MSC)
    Event code 4 (MSC_SCAN)
Properties:
Testing ... (interrupt to exit)
Event: time 1576479720.245227, type 4 (EV_MSC), code 4 (MSC_SCAN), value c00e9
Event: time 1576479720.245227, type 1 (EV_KEY), code 115 (KEY_VOLUMEUP), value 1
Event: time 1576479720.245227, -------------- SYN_REPORT ------------
Event: time 1576479720.253248, type 4 (EV_MSC), code 4 (MSC_SCAN), value c00e9
Event: time 1576479720.253248, type 1 (EV_KEY), code 115 (KEY_VOLUMEUP), value 0
Event: time 1576479720.253248, -------------- SYN_REPORT ------------
Event: time 1576479722.325231, type 4 (EV_MSC), code 4 (MSC_SCAN), value c00ea
Event: time 1576479722.325231, type 1 (EV_KEY), code 114 (KEY_VOLUMEDOWN), value 1
Event: time 1576479722.325231, -------------- SYN_REPORT ------------
Event: time 1576479722.333224, type 4 (EV_MSC), code 4 (MSC_SCAN), value c00ea
Event: time 1576479722.333224, type 1 (EV_KEY), code 114 (KEY_VOLUMEDOWN), value 0
Event: time 1576479722.333224, -------------- SYN_REPORT ------------
Event: time 1576479724.381251, type 4 (EV_MSC), code 4 (MSC_SCAN), value c00e2
Event: time 1576479724.381251, type 1 (EV_KEY), code 113 (KEY_MUTE), value 1
Event: time 1576479724.381251, -------------- SYN_REPORT ------------
Event: time 1576479724.389251, type 4 (EV_MSC), code 4 (MSC_SCAN), value c00e2
Event: time 1576479724.389251, type 1 (EV_KEY), code 113 (KEY_MUTE), value 0
Event: time 1576479724.389251, -------------- SYN_REPORT ------------

Neat, even more useful things. In particular:

  • When I turn the knob to the right, I get an MSC_SCAN event of type c00e9 (along with a KEY_VOLUMEUP event)
  • When I turn the knob to the left, I get an MSC_SCAN event of type c00ea (along with a KEY_VOLUMEDOWN event)
  • When I push on the knob, I get an MSC_SCAN event of type c00e2 (along with a KEY_MUTE event)
  • Apparently the firmware supports KEY_NEXTSONG, KEY_PREVIOUSSONG and KEY_PLAYPAUSE as well. Huh.

I want to map those MSC_SCAN events to different key codes. In particular, I want a cursor-left key when I turn the knob to the left, a cursor-right key when I turn the knob to the right, and something useful (say, pressing the “m” key) when I press the knob. So I created a hwdb file for my device:

$ cat /etc/udev/hwdb.d/99-usb-knob.hwdb
evdev:input:b*v0483p572D*
 KEYBOARD_KEY_c00ea=left
 KEYBOARD_KEY_c00e9=right
 KEYBOARD_KEY_c00e2=m

You’ll recognize the vendor (0483) and the device (572d) that I found earlier. It’s important to use uppercase hex codes for vendor and product in the hwdb file – but not for the scan codes, which should be lowercase. The values on the right have to be lowercase, and correspond to the KEY_LEFT, KEY_RIGHT and KEY_M values from /usr/include/linux/input-event-codes.h. (You can pick any of the KEY_ values from there.) Then a quick bit of Linux magic to update the hardware database:

$ sudo systemd-hwdb update
$ sudo udevadm trigger

…and… exactly the same as before. I got the volume control displayed when I turned the knob.

After scratching my head and doing some searching, I happened on https://catswhisker.xyz/log/2018/8/27/use_vecinfinity_usb_foot_pedal_as_a_keyboard_under_linux/ which gave me the clue I needed. My knob was being detected, but not as a keyboard – so it wasn’t being used as a keyboard input device.

So I created this file:

$ cat /etc/udev/rules.d/99-usb-knob.rules
ACTION=="add|change", KERNEL=="event[0-9]*", 
 ATTRS{idVendor}=="0483", ATTRS{idProduct}=="572d",
 ENV{ID_INPUT_KEYBOARD}="1"

(That’s all on one line on my machine.) You’ll recognize the vendor and product ID from earlier, using lowercase for the hex this time. I added ID_INPUT_KEYBOARD to the list of attributes for this device.
Unplug the device, plug it back in, and hooray! I’m doing what I wanted to! When I turn the knob left, I go left. When I turn the knob right, I go right. When I press the knob, “m” shows up on the screen.

Now I just need to install an SDR program… and an SDR….


Upgrading the Arduino Uno 8U2 using Fli

April 14, 2011

The instructions on the Arduino DFU update page are pretty vague when it comes to updating to the latest firmware using the Atmel Flip utility. Here’s what I did to get my Uno 8U2 firmware updated using Windows XP:

  1. Install Flip from the Atmel webpage. If you don’t already have a JRE, you probably want the 20 megabyte package that includes one.
  2. Download the 8U2 firmware. That’s the “Arduino-usbserial-uno.hex” file. The “UNO-dfu_and_usbserial_combined.hex” is the Arduino Uno bootloader firmware for the 328p. If you try installing the 328p firmware onto the 8U2 in Flip, you’ll get an error dialog:
    class com.atmel.flipGui.FileMenuHandler
    Address is out of range.
    
  3. I ended up cutting & pasting the hex file contents into a text editor because I couldn’t figure out how to download a single file from github, and I didn’t want the whole 200 megabyte wad.
  4. Hook up wires and touch them to the right spots on the Uno at the right time as described in this Arduino forum post by pluggy.

    Update: Pluggy’s image has disappeared. Here it is:
    flasheo8u2-1-300x216
    I found it at www.ardumania.es/reflashear-el-8u2/.

    You’ll know you did it right when Windows detects a new device. It took me a few tries to keep the first wire steady enough when touching the second wire. Make sure you’re going to the right pads – you could short out your Uno if you touch the wrong place!

  5. After Windows detects the new device, you don’t have to hold the wires in place any more. Move them to the side so you don’t accidentally short out something.
  6. When the Windows device installer comes up, select “have disk” and install the Atmel USB driver. By default the Atmel USB driver is installed in C:\Program Files\Atmel\Flip 3.4.2\usb\atmel_usb_dfu.inf
  7. Notice that the driver is AT90USB82, not ATmega8U2
  8. Although it tells you that you have to reboot XP when you install the driver, I didn’t bother.
  9. Start Flip from the Program menu
  10. File->Load HEX file->Arduino-usbserial-uno.hex
  11. Device->Select->AT90USB82
  12. Settings->Communication->USB and press Open
  13. At this point, you should be ready to program. Press the “Run” button on the main screen.
  14. Programming is quick, taking about 4 seconds. After it’s done, remove the two wires you put in earlier, then unplug the USB cord and reinsert it.

Once you’ve done that, your Uno should be out of DFU mode and back into normal mode.

You can go to My Computer->Manage->Device Manager->Ports (COM & LPT), right-click on “Arduino UNO” and select “Properties”. Under the Details tab, Firmware Revision should now be 00.01.

Update: Someone mentioned they couldn’t find the atmel_usb_dfu.inf file. Looks like there’s a copy here.