USB Programmierung auf dem PC

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

Für einen Mikrocontroller mit USB Anschluss, wird meist ein passendes Programm auf dem PC benötigt, sofern der Mikrocontroller sich nicht als HID Gerät in den Standardklassen (Maus, Tastatur, CDC etc) nutzen lässt.

PyUSB

Eine der vermutlich einfachsten Arten auf dem PC ein plattformunabhängiges Programm zu schreiben ist mit Python. Der hier gezeigte Code wurde mit Python 3.6 und Debian 10 (Linux), sowie Python 3.9 mit Windows 10 getestet. Jeweils in der 64Bit Variante. Für die Kommunikation mit der USB Schnittstelle kommt PyUSB zum Einsatz. Das in den Beispielen verwendete PID/VID Paar ist für interne Tests reserviert.

Vorteile von PyUSB:

  • Platformunabhängig
  • Sehr wenig Code notwendig

Nachteile:

  • Die Dokumentation ist dürftig
  • Sind mehrere Geräte mit der selben PID/VID Kombination angeschlossen, ist nicht einfach auswählbar welches genutzt werden soll

Das Hello-World Programm ist für beide Betriebssysteme gleich. Das Gerät wird geöffnet, und ein Hersteller spezifisches Kommando (Vendor specific request) abgeschickt. Anschließend wird ein weiteres Kommando abgeschickt, welches ein Datenbyte vom USB Gerät als Antwort haben möchte. Die Parameter für ctrl_transfer sind bmRequestType, bmRequest, wValue und wIndex. Danach folgt entweder die Anzahl der erwarteten Daten, oder ein Array mit Daten die zum Gerät geschickt werden sollen. In bmRequest gibt das höchste Bit (0x80) an, in welche Richtung das Kommando geht und wenn das zweit höchste gesetzt ist (0x40), so ist es ein Hersteller spezifisches Kommando. In diesem Fall können bmRequest, wValue und wIndex selbst einer Bedeutung zugewiesen werden. Weitere Beispiele, die eine Control Pipe öffnen, gibt es auf der Seite von PyUSB.

import usb.core
import usb.util
dev = usb.core.find(idVendor=0x1209, idProduct=0x0001)

# was it found?
if dev is None:
	print('Error, device not found')
	sys.exit(1)

# Host to device + vendor request
bmRequestSend = 0x40
# Device to host + vendor request
bmRequestRecv = 0xC0

# Write a control request, bmRequest = 1, wValue = 2, wIndex = 3, no extra data bytes
dev.ctrl_transfer(bmRequestSend, 1, 2, 3, 0)

#control request to read one byte from the USB device, bmRequest = 3, wValue = 2, wIndex = 1
data = dev.ctrl_transfer(bmRequestRecv, 3, 2, 1, 1)
print(data)

Linux

Unter Debian Linux können alle notwendigen Bibliotheken und die Python Laufzeitumgebung mit sudo apt install python3-usb installiert werden. Sind für den PC keine Adminrechte verfügbar oder ein vergleichbares Paket in der verwendeten Distribution nicht enthalten, kann versucht werden bei einer vorhandenen Python Installation mit pip3 install pyusb das Paket lokal zu installieren. Die weiter benötigte libusb Bibliothek dürfte auf den meisten Systemen schon vorhanden sein, da populäre Programme wie VLC, CUPS oder WINE sie bereits als Abhängigkeit installieren.

Ist auf Seiten der Software alles notwendige vorhanden, schlägt der Zugriff zunächst fehl, da die Zugriffsrechte nicht ausreichen. Neben dem eher unschönen Workaround, das Programm einfach als Root (sudo) auszuführen, ist die bessere Variante eine passende udev Regel anzulegen. Das geht leider aber auch nur mit Root-Rechten. Hierzu muss unter /etc/udev/rules.d/ eine neue Datei mit der Endung .rules angelegt werden. Die Regeln werden dem Namen nach ausgeführt, wer also seine Regel mit 10- beginnen lässt, ist somit mit als erstes dran. Beispielsweise "10-myUsbTestDevice.rules". Hier hat die Datei folgenden Inhalt:

#Allows all users in the group plugdev to read and write USB devices
#  with the VID=0x1209 and PID=0x1
ATTRS{idVendor}=="1209", ATTRS{idProduct}=="0001", GROUP="plugdev", MODE="0660"

In diesem Fall können danach alle USB Geräte mit der passenden Hersteller und Produkt ID von Nutzern in der Gruppe plugdev gelesen und geschrieben werden (660 => Erste 6 Der Eigentümer darf lesen und schreiben, zweite 6 Die Nutzer der Gruppe dürfen lesen und schreiben und die 0 verbietet allen anderen den Zugriff). Falls die verwendete Linux Distribution keine Gruppe plugdev besitzt, muss natürlich eine andere eingetragen werden. Ist das USB Gerät bereits eingesteckt, wird die Regel nach einem neuen Einstecken übernommen, alternativ kann mit sudo udevadm trigger --subsystem-match=usb auch ein Neueinlesen ohne ab- und anstecken des USB Gerätes erreicht werden. Soll zu dem Programm ein Debian Paket bereit gestellt werden, kann es dem Anwender einfach gemacht werden und udevadm entsprechend automatisch während der Installation ausgeführt werden. Ergänzungen zu den Paketnamen und Installationsmethoden mit anderen Linux Distributionen sind willkommen.

Windows 10

Unter Windows gibt es leider ein paar mehr Dinge zu tun, bis es möglich ist mit einem USB Gerät zu kommunizieren. Zunächst kann nach der Installation von Python 3 mit pip install pyusb (ja, hier ohne 3 nach dem pip) ebenfalls die passende Python Bibliothek installiert werden. Je nach dem was schon vorher auf dem System installiert wurde, gibt es danach bei dem Versuch, das obige Programm auszuführen entweder die Fehlermeldung "No backend available" oder das in unserem Quellcode definierte "Error, device not found". Die Problembeschreibung lässt sich recht oft im Netz finden und nicht alle Lösungsvorschläge helfen jedem Nutzer (dies war die Motivation für diesen Wikieintrag). Daher lohnt es sich die Bibliotheksabhängigkeiten im Detail zu betrachten. Etwas Recherche führt zu folgender Abhängigkeitskette:

Pyusb-dependencies.png

Kein Backend vorhanden

PyUSB liefert die Unterstützung für drei Backends mit. Das erste gefundene Backend in der Reihenfolge libusb 1.0, libusb 0.1 und OpenUSB wird genutzt. Empfohlen wird hier libusb-1.0.dll. Dieses gibt es es entweder hier unter Downloads, dann Latest Windows Binaries. Getestet wurde Version 1.0.24 in dem Archiv aus dem Unterordner VS2019/MS64/dll. Alternativ kann auch mit pip install libusb1 eine passende dll heruntergeladen werden. Wo nach den DLLs gesucht wird, ist von Microsoft dokumentiert. Wahrscheinlich ist es nicht gewünscht die dlls manuell in eines der Windows Verzeichnisse zu kopieren. Auch das Verzeichnis des Pyhton Interpreters ist sicher nicht der geeignete Ort. Als letztes bietet sich noch an, über die PATH Variable den Suchpfad zu beeinflussen. Dies kann aus dem Python Programms selbst erfolgen.

import os
import pathlib
libsearch = str(pathlib.Path(__file__).parent.absolute())
os.environ['PATH'] = libsearch + os.pathsep + os.environ['PATH']

Mit diesem Code am Anfang kann die DLL einfach in dem selben Verzeichnis wie das Python Programm liegen.

Kein Gerät gefunden

Demnach kann die Meldung "Error, device not found" mehrere Ursachen haben:

  • Das Gerät ist tatsächlich nicht angeschlossen
  • Das Gerät funktioniert nicht
  • Für das Gerät ist ein Treiber installiert, aber das verwendete Backend von PyUSB unterstützt den Treiber nicht.
  • Für das Gerät ist kein Treiber installiert

Funktioniert das Gerät?

Die ersten beiden Punkte können überprüft werden, indem in der Geräte-Manager geöffnet wird. Hier sollte das Gerät angezeigt werden - egal ob ein Treiber installiert ist oder nicht.

Das Backend passt nicht zum Treiber

Ist aus irgend einem Grund noch eine libusb0.dll im System, kann es passieren dass diese genutzt wird und nicht mit dem verwendetem Treiber des USB Gerätes sprechen kann. Mit dem folgendem Code am Anfang seines Python Programms lässt sich das Laden von libusb 1.0 erzwingen. Der Code zum Anpassen der PATH Variable kommt gegebenenfalls davor.

import usb.backend.libusb1 as libusb1

backendUsblib1 = libusb1.get_backend()
if (backendUsblib1 is None):
	print('No backend found')

dev = usb.core.find(idVendor=0x1209, idProduct=0x7701, backend=backendUsblib1)

Es ist kein Treiber installiert

Um also mit einem USB Gerät zu kommunizieren, muss Windows zunächst mitgeteilt werden, welcher Treiber verwendet werden soll. Dieser Treiber sollte dann natürlich auch vorhanden sein.

Zadig

Die vermutlich einfachste Variante einem bestehendem USB Gerät einem Treiber zuzuweisen ist das Tool Zadig. Es lässt sich ein USB Gerät auswählen, dazu einen der vier Treiber (WinUSB, libusb-win32, libusbK oder USB Serial (CDC) und diesen mit einem Klick installieren. Auch nachträglich kann Geräten, die schon einen Treiber haben, ein anderer zuweisen werden. Je nach verwendetem Backend (siehe Abhängigkeitskette oben) sollte hier also der richtige ausgewählt werden. Allerdings hat das Tool einen echten Nachteil: Es gibt keinen Uninstaller. Der so installierte Treiber und die generierte .inf Datei landen irgendwo im Windowssystem. Das Trägt nicht gerade dazu bei das System "sauber" zu halten.

WinUSB und WCID

Wie oben aus dem Abhängigkeitskette ersichtlich wird, gibt es auch nativ von Microsoft den WinUSB Treiber. Ist die Möglichkeit gegeben, die Firmware des USB Gerätes zu verändern, lässt sich diese WCID Kompatibel machen. Eine detaillierte Übersicht wie dies geht gibt es hier. Die Kurzfassung: Windows schickt drei Controlrequests, um zu erfahren welchen Treiber verwendet werden soll.

  1. Windows fragt den String mit dem Index 0xEE. Hier muss MSFT100 und ein selbst gewähltes weiteres Zeichen als ID zurückgegeben werden. Zum Beispiel MSFT100W. Da der Descriptor 16Bit pro Zeichen benötigt, liefert das 'W' mit seinem zweiten Byte auch gleich das Padding.
  2. Dann wird ein Control Request geschickt, bei dem wIndex auf unserem gewählten Wert steht (hier also 0x57 = 'W') und 16 Byte angefordert werden. Als Antwort werden einfach die ersten 16 Byte der WCID Tabelle geschickt. In diesen ist (unter anderem) die Größe der WCID Tabelle definiert.
  3. Ein dritter Control Request wird mit dem selben wIndex, jetzt aber mit der korrekten Größe (hier 40Byte), geschickt und muss mit dem kompletten WCID Tabelle beantwortet werden.

Eigentlich sollte es hiermit möglich sein, auch beliebige andere Treiber, außer WinUSB zu nutzen. Die klappte in der Praxis jedoch nicht.

Windows erinnert sich

Die Webseite zur Erklärung der WCID listet im Detail auf, welche Registry Keys bei einem Anschluss eines Geräts angelegt werden. Wurde also etwas falsch konfiguriert, ist es gegebenenfalls notwendig diese manuell zu löschen. Eine automatische Löschung scheint es nicht zu geben, so dass alle PIDS/VIDS aller jemals angeschlossenen USB Geräte nachgeschaut werden können.

Schluss

Hat man also sein eigenes USB Gerät WCID kompatibel gemacht, lädt unter Windows den Treiber direkt aus dem Verzeichnis des Python Skripts und liefert eine passende dll mit, lässt sich das USB Gerät ohne manuelle Treiberinstallation einfach unter Windows nutzen. Für Linux empfiehlt es sich ein passendes .deb Paket zu bauen, welches die Abhängigkeit zu pyusb3-usb hat und eine passende udev Regel mitliefert. Damit sollte die Benutzung unter beiden Betriebssystemen so einfach wie möglich sein :)