3. Two things at once
Published on 17 June 2016
A series of posts exploring the possibilities of using the Pi Zero as a USB MIDI device. The previous post was "talk to me".
The last post used a convoluted setup of the USB cable to act as the USB gadget and a USB-TTL serial cable to give us an SSH connection into the Pi Zero using a PPP connection.
This post does away with that and provides a way to enable both ethernet and MIDI over the same cable. It's a miracle!
Never rush in
I spent many hours last week grappling with PPP on Linux and OS X and grappling with Mac OS X packet filtering and forwarding.
Then I saw this great writeup by Tobias Girstmair. He proposes a way to configure multiple USB gadget functions through a single USB cable using libcomposite
. This allows configuration of the gadget functionality using configfs a way of configuring the kernel by modifying things that look like files. Confusing? Yes!
Get ethernet access
I started by following Andrew Mulholland's super-quick instructions to ethernet access to the Pi Zero. I also enabled internet access to the Pi.
Andrew's instructions mean we have dtoverlay=dwc2
on a single line on the Pi Zero's /boot/cmdline.txt
.
Configuring a gadget
This document describes how to use configfs to configure Linux USB Gadgets.
A "gadget" can have multiple "functions". A function is something like ethernet or MIDI. And it seems that you can have multiple instances of each function.
To create a gadget you create a folder in /sys/kernel/config/usb_gadget/<some-gadget-name>/
. And then each function is created in the functions
subdirectory with the name <function>.<instance>
.
<function>
is the thing that determines which bit of code is loaded and <instance>
is just a unique name for that instance of the function.
So a full path for an ethernet function might be: /sys/kernel/config/usb_gadget/some-gadgets/functions/ecm.usb0
where some-gadgets
and usb0
are arbitrary names we've picked.
The documentation lists all the available functions and the configuration properties. The USB gadgets are the various configfs-usb-gadget*
files.
When you create an instance, you magically get files created in the directory that gives you read and write access to the gadgets properties.
Configure a gadget and ethernet function
To get started, I adapted the script from iSticktoit's blog post (see below).
This script creates a gadget, sets it's vendor ID and product ID and gives it a human-readable name. The Linux USB website has a full list of Vendor IDs and Device IDs.
I found that putting modprobe libcomposite
into the script meant that nothing needed to be put into /etc/modules
.
The script also creates an ethernet function so that we can access the Pi Zero via USB, just like we have now.
I created a file in /boot/create-usb-gadgets
so I could edit it by inserting the SD card into my Mac.
/boot/create-usb-gadgets
has the following contents:
#!/bin/sh
# Load libcomposite
modprobe libcomposite
# Create a gadget called usb-gadgets
cd /sys/kernel/config/usb_gadget/
mkdir -p usb-gadgets
cd usb-gadgets
# Configure our gadget details
echo 0x1d6b > idVendor # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB # USB2
mkdir -p strings/0x409
echo "fedcba9876543210" > strings/0x409/serialnumber
echo "Pi Zero USB Gadget" > strings/0x409/manufacturer
echo "Pi Zero USB Gadget" > strings/0x409/product
mkdir -p configs/c.1/strings/0x409
echo "Config 1: ECM network" > configs/c.1/strings/0x409/configuration
echo 250 > configs/c.1/MaxPower
# Ethernet gadget
mkdir -p functions/ecm.usb0
# first byte of address must be even
HOST="48:6f:73:74:50:43" # "HostPC"
SELF="42:61:64:55:53:42" # "BadUSB"
echo $HOST > functions/ecm.usb0/host_addr
echo $SELF > functions/ecm.usb0/dev_addr
ln -s functions/ecm.usb0 configs/c.1/
# End functions
ls /sys/class/udc > UDC
We need to make this script executable so that we can run it:
$ chmod +x /boot/create-usb-gadgets
Run on boot
The configuration is not permanent. Restarting the Pi Zero will remove it so we need to make sure that script runs on boot.
We can create a systemd service that will do this. I created /etc/systemd/system/create-usb-gadgets.service
:
[Unit]
Description=Create USB gadgets
[Service]
Type=oneshot
ExecStart=/boot/create-usb-gadgets
StandardInput=tty
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
Then to enable it:
$ sudo systemctl daemon-reload
$ sudo systemctl enable create-usb-gadgets
Remove the existing g_ether
config
We don't need the g_ether
config we used at the beginning because our new script will do that for us.
Remove modules-load=dwc2,g_ether
from cmdline.txt
.
Reboot the Pi. After a short delay, you should be able to ssh pi@raspberrpi.local
to access the Pi Zero.
The Mac will think the ethernet adapter is different because we've changed it's name and ID so we can re-bridge the Mac's internet connection via the "Sharing" → "Internet Sharing" preferences pane. The Ethernet gadget will appear as whatever you've configured the name to be in the script above.
MIDI
Now that we're back on the ethernet, we need to figure out how to configure other Linux USB Gadget functions.
I'm interested in the MIDI gadget interface and the configuration document is quite sparse:
What: /config/usb-gadget/gadget/functions/midi.name
Date: Nov 2014
KernelVersion: 3.19
Description:
The attributes:
index - index value for the USB MIDI adapter
id - ID string for the USB MIDI adapter
buflen - MIDI buffer length
qlen - USB read request queue length
in_ports - number of MIDI input ports
out_ports - number of MIDI output ports
Since the script above configured the ethernet interface, it's useful to compare the two:
What: /config/usb-gadget/gadget/functions/ecm.name
Date: Jun 2013
KernelVersion: 3.11
Description:
The attributes:
ifname - network device interface name associated with
this function instance
qmult - queue length multiplier for high and
super speed
host_addr - MAC address of host's end of this
Ethernet over USB link
dev_addr - MAC address of device's end of this
Ethernet over USB link
Here's where using configfs becomes useful. You can create "files" on what appears to be a "filesystem", which are then magically turned into USB driver calls. This allows us to iteratively try out the config on the Pi Zero live to see what happens.
So, I booted the Pi Zero with the Ethernet configuration and ran variations of the commands below until I worked out what did the trick.
For example, I tried created a gadget function midi
with the instance name usb0
:
$ cd /sys/kernel/config/usb_gadget/usb-gadgets
$ sudo mkdir -p functions/midi.usb0
$ ls functions/midi.usb0/
buflen id index in_ports out_ports qlen
Creating this directory automatically exposes some "files" that map to the items in the MIDI documentation above.
$ cat functions/midi.usb0/in_ports
1
This suggests that the function is initialised with some defaults.
However cat functions/midi.usb0/id
returns nothing so maybe we should set the id
item:
$ echo "pi" > functions/midi.usb0/id
Then, you need to link the function so it's available in the same configuration as the ethernet:
$ ln -s functions/midi.usb0 configs/c.1/
At this point nothing happened, because I needed to disable and re-enable the gadget by writing to /sys/class/UDC
.
$ sudo su
$ echo "" > UDC && ls /sys/class/udc > UDC
$ exit
After doing this I could see the MIDI device in MIDI monitor. Hurray!
Create MIDI device
Now that it works, we could turn that into config for the /boot/create-usb-gadgets
script. I added this after the Ethernet config but before the # End functions
lines:
# MIDI gadget
mkdir -p functions/midi.usb0
# id - ID string for the USB MIDI adapter
echo "pi-zero-midi" > functions/midi.usb0/id
ln -s functions/midi.usb0 configs/c.1/
Reboot the Pi Zero and when it boots you'll be able to SSH into it and it will appear as a USB MIDI device and it will be powered by the same USB cable!
Demo
So, after ssh pi@raspberrypi.local
I downloaded a nice MIDI file of Titanic - My Heart Will Go On (Techno Mix) from MIDI World onto the Pi.
$ curl http://www.midiworld.com/download/3861 > techno.midi
Then I found out what the MIDI device is listed as:
pi@raspberrypi:~ $ aplaymidi --list
Port Client name Port name
14:0 Midi Through Midi Through Port-0
20:0 f_midi f_midi
It seems to have moved to port 20
, I reckon those config items could change that.
Then, I can play the MIDI file over the USB interface into the Web MIDI synth running on Google Chrome like this:
$ aplaymidi --port 20:0 techno.midi
The result? Behold the awful sound. 🙉
The terminal window at the top is the Pi Zero playing the MIDI and the Chrome window on the bottom is on the Mac.
Now we have a much better development setup with a single cable allowing us to access the Pi and to access the MIDI device. There are many other supported USB gadget functions so this is really promising.
Next, I need to work out how to send custom MIDI commands, rather than just MIDI files.