USB for Microcontrollers — Part 3: Host Software and Device Drivers

Manuel Bl.
3 min readOct 1, 2020

--

Photo by Tyler Daviaux on Unsplash

With device and firmware ready, this part implements the application running on a computer and communicating with the MCU — without writing device drivers.

This is part 3 of a 4 part series:

Python Preparation

To have a cross-platform application running on Windows, macOS and Linux, Python is used. The easiest way to use it for this tutorial is to run it from PlatformIO. For USB communication, the PyUSB module is needed. To install it, open a Python terminal from PlatformIO (Command Palette… / Python: Create Terminal) and run:

pip install pyusb

PyUSB builds upon the C library libusb. It needs to be installed too.

On macOS, make sure Homebrew is installed and run:

brew install libusb

On Windows, download the latest stable package (libusb-1.0.nn.7z), unpack it and copy libusb-1.0.dll (from MS64\dll) to a directory that is listed in the environment variable PATH.

On Linux, use your system’s package manager and install libusb-1.0–0, e.g.:

sudo apt-get install libusb-1.0-0

Blink Program

The Blink Program (see blinky.py) is straight-forward:

import usb.core
import time
dev = usb.core.find(idVendor=0xcafe, idProduct=0xcafe) ❶dev.set_configuration() ❷led_on = False
while True:
led_on = not led_on
dev.ctrl_transfer(bmRequestType=0x41, bRequest=0x33, \
wValue=int(led_on), wIndex=0) ❸
time.sleep(0.6) ❹

First, the device is located ❶. To find the correct device among all connected USB devices, the vendor and product ID are specified. Then the first configuration is activated ❷. The first configuration in the USB device descriptor is the default, so no argument is needed in the call to set_configuration.

Now the blink loop starts: It mainly consists of sending the control request to turn the LED on or off ❸ and a delay ❹. The control request uses the parameters described in the previous part.

That’s it. If you run blinky.py from PlatformIO (using the green arrow in the top right corner), the green LED on the Blue Pill should blink. To stop it, press Ctrl+C in the terminal.

What about Device Drivers?

On macOS and Linux, device drivers for USB devices are not an issue. A full set of drivers for all standardized USB classes such keyboard, mouse, camera, external disk etc. exists, and there is a generic driver for all other USB devices. libUSB — and pyusb for that matter — use the generic driver to talk to the USB device.

Software accessing USB devices runs in user-mode. No privileged (kernel-mode) software is needed except for the device drivers provided by the operation system. And there is no need to write device drivers. It is straight-forward and without surprises.

Windows is different. It has device drivers for all standardized USB classes as well and uses them automatically. It even has a generic driver, but it does not use it automatically: it needs to be installed. But why did it work on Windows too — without manually installing any drivers?

The generic driver is called WinUSB. Similar to macOS and Linux, it allows to user-mode software to talk to USB devices directly. However, it must be installed explicitly.

To do so, there are three options:

  1. Provide .inf files so the user can run the installation manually.
  2. Use a software like Zadig to install it manually.
  3. Implement the Windows Compatible ID (WCID) extension on the device so Windows installs the WinUSB driver automatically when the device is first plugged in.

In this project, the WCID extension has been implemented as it is the only one providing a real plug-and-play experience. At the minimum, it consists of two parts:

  1. An additional string in the USB device descriptor.
  2. A vendor specific USB control request.

It is described in detail on the excellent WCID Devices web page. The implementation can be found in the wcid.cpp file. It can be reused without any changes in other projects. To activate it, register_wcid_desc() must be called from the set_config callback.

Blinking an LED is a simple example. Let’s move on to a more challenging example — sending an image to a TFT display connected to the MCU…

Part 4: Handling Large Amounts of Data

--

--