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!
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!
Andrew's instructions mean we have
dtoverlay=dwc2 on a single line on the Pi Zero's
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> 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:
usb0 are arbitrary names we've picked.
The documentation lists all the available functions and the configuration properties. The USB gadgets are the various
When you create an instance, you magically get files created in the directory that gives you read and write access to the gadgets properties.
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
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
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
[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
We don't need the
g_ether config we used at the beginning because our new script will do that for us.
Reboot the Pi. After a short delay, you should be able to
ssh firstname.lastname@example.org 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.
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
$ 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.
cat functions/midi.usb0/id returns nothing so maybe we should set the
$ 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
$ sudo su $ echo "" > UDC && ls /sys/class/udc > UDC $ exit
After doing this I could see the MIDI device in MIDI monitor. Hurray!
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!
$ 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.