07.09.2006 • Messtechnik

Steuerung von USB-basierten Messgeräten unter Linux

Einführung
Das Betriebssystem Linux hat in den vergangenen Jahren viele neue Anhänger gefunden. Viele Firmen und Institutionen haben die Attraktivität des Betriebssystems auch für normale Anwendungen und Anwender entdeckt. Auch in der (bisweilen trägen) Messtechnikindustrie macht sich dieser Trend deutlich bemerkbar.

In der Vergangenheit krankte der Einsatz von Linux in der Messtechnik immer wieder an der begrenzten Verfügbarkeit von Treibern für messtechnikspezifische I/O Schnittstellen wie GPIB. Die Hersteller tun sich schwer, die Vielzahl von Varianten, Distributionen und Versionen von Linux zu unterstützen.
Vor diesem Hintergrund ist der Trend, auch in der Messtechnik auf Standard Computerschnittstellen wie LAN und USB zu setzen, ein wahrer Segen für Linux Anwender - sie können nun die Unterstützung des Betriebssystems nutzen, um ihre Geräte anzusteuern.
Während die Steuerung von LAN-basierten Geräten (z.B. über TCP) überraschend einfach und übersichtlich ist (man würde das aufgrund der Komplexität von Ethernet vielleicht nicht erwarten), so gestaltet sich die Kontrolle von USB-Geräten doch deutlich komplexer. Das liegt in erster Linie daran, daß man sich mit so vielen Themen befassen muss: Mit dem USB Standard selbst, seiner Auslegung für Messgeräte (USBTMC), mit der Programmierung eines Device-Treibers unter Linux... Dieser Artikel vermittelt einen Überblick über diese Themen und erleichert so den Einstieg für jene furchtlosen Anwender, die sich dieser interessanten Aufgabe annehmen wollen.

Eine wichtige Frage vorab...

Am Anfang muss zunächst eine grundsätzliche Frage geklärt werden. Soll für die Steuerung der USB-Geräte ein Device-Treiber entwickelt werden oder soll die Steuerung direkt auf der Applikationsebene (im „User Space“) erfolgen?

In der Regel wird für die Steuerung von Hardware ein Device-Treiber verwendet, der Teil des Kernels ist und damit praktischerweise über gewisse „Sonderrechte“ verfügt. Der Benutzer kann dann über sog. Device Files (auch „Special Files“) im /dev Verzeichnis auf die vom Treiber bedienten Geräte zugreifen. Normalerweise wird dann für jedes Gerät ein eigenes Device File eingerichtet, das jedoch in diesem Fall auf den gleichen Treiber im Kernel verweist (mehr dazu unten).

Der Vorteil dieser Methode ist, daß der Benutzer nun mit den ihm vertrauten Mechanismen des File I/O auf den Treiber und die Geräte zugreifen kann. Man kann z.B. mit einem einfachen write() ein Gerätekommando an das Gerät senden. Ebenso kann man die Ausgabe eines anderen Programmes auf das Device File „umleiten“. Für den Benutzer bedeutet dies ein hohes Maß an Flexibilität.

Die Alternative wäre die Steuerung der USB-Geräte direkt aus der Applikation heraus. Diese Methode erfordert die Verfügbarkeit eines API (Programmierschnittstelle), das verwendet werden kann, um im User Space (ausserhalb des Kernel) USB-Operationen durchzuführen. Mit libusb steht ein solches API zur Verfügung.

Diese Methode wird häufig dann eingesetzt, wenn der Benutzer mit einer einzigen, festen Applikation auf die USB-Hardware zugreift, z.B. wenn es sich um eine USB-Kamera oder einen Scanner handelt.

Für Messtechnikanwendungen wird man eher den Weg über einen Device-Treiber gehen (auch, wenn es einen Mehraufwand in der Entwicklung bedeutet), da der Benutzer dann in den Genuss der bereits erwähnten Flexibilität durch die Verwendung von Device Files kommt. Der Ansatz mittels libusb wird daher im Folgenden nicht näher beschrieben, allerdings finden viele der beschriebenen Konzepte auch hier Anwendung. Interessierte finden weitere Informationen zu libusb unter [1].

Linux Device-Treiber

Werfen wir also zunächst einen Blick auf einen Linux Kernel-Treiber (Module). USB-Messgeräte werden in der Regel über Textkommandos programmiert, wie wir sie von GPIB kennen. Die entsprechende Treiberarchitektur wäre ein „Character“ Device-Treiber.

Bild 1 zeigt einen Überblick über die wichtigsten Funktionen, die ein solcher Treiber zur Verfügung stellen muss.

Die Funktionen open, release, read und write entsprechen den Dateioperationen, mit deren Hilfe der Benutzer später auf die Geräte zugreifen kann. So sollte open die Verbindung zum Gerät initialisieren, release hingegen sollte die Verbindung schliessen und evtl. reservierte Systemresourcen (Kernel-Speicher etc.) wieder freigeben. Write sendet die angegebene Zeichenkette zum Gerät, read liest die Geräteantwort und kopiert sie in den angegebenen Pufferbereich.

Diese Funktionen gehören zur absoluten Minimalausstattung des Treibers. In der Praxis werden häufig weitere Funktionen implementiert, allen voran ioctl, worüber spezielle Operationen wie das Zurücksetzen des Geräts durchgeführt werden könnten.

Die Funktionen init und exit dienen der Installation des Treibers selbst im Kernel. init wird nach dem Laden des Treibers aufgerufen und kann verwendet werden, um den Treiber einmalig zu konfigurieren bzw. Systemresourcen zu reservieren. Diese müssen dementsprechend beim Entladen des Treibers in exit wieder freigegeben werden.

Bei der Erstellung eines Kernel-Moduls müssen einige Besonderheiten beachtet werden. Zunächst einmal erfordert der Compile-Vorgang die entsprechenden Kernelquellen (zu finden unter /usr/src/linux-2.6.13 o.ä.). Diese werden bei den meisten Distributionen nicht standardmässig installiert, sind aber in der Regel auf den Installationsmedien vorhanden. Hier muss also ggf. nachinstalliert werden.

Die Include-Dateien und Systemfunktionen, die wir normalerweise (im User Space) verwenden, lassen sich in den meisten Fällen nicht für Kernel-Module verwenden. Im Gegenzug hält das System allerdings eine Vielzahl von Includes bereit, die speziell für Kernel-Module gedacht sind. Wir finden diese unter /usr/src/include. Einige Beispiele dazu: Speicher wird nicht über malloc reserviert, sondern über kmalloc (die Kernel-Version von malloc). Für Ausgaben verwendet ein Kernel-Modul kprintf – die Ausgabe geht dann nicht an die Console, sondern wird an die Systemlogdatei angehängt (/var/log/messages oder ähnlich).

War der Compile-Vorgang erfolgreich, kann das Modul statisch in die Kernel-Konfiguration aufgenommen werden oder dynamisch mit Hilfe des insmod Kommandos geladen werden. Insmod (laden) und rmmod (entladen) erleichtern die Entwicklung von Treibern erheblich, da nicht bei jeder Treiberänderung gebootet werden muss.

Wie erfolgt nun der eigentliche Zugriff auf den Treiber und die Geräte, die der Treiber bedient? Dafür legt der Benutzer im /dev Verzeichnis mit Hilfe des mknod Kommandos Device Files an, die den Treiber im Kernel über eine sog. Major Number und ein bestimmtes Gerät „hinter“ dem Treiber über eine Minor Number referenzieren (siehe Bild 2).

Ein Device File stellt damit die eigentliche Benutzerschnittstelle zum Treiber dar. Major und Minor Number müssen natürlich zum Kernel-Modul passen. Wie werden die Nummern vergeben? Ein Treiber kann sich in der init Funktion für eine bestimmte (fest gewählte) Minor Number registrieren oder dynamisch vom System eine freie Nummer verlangen. Im nächsten Schritt kann dann ein Bereich für die Minor Numbers allokiert werden, der der maximalen Gerätezahl entsprechen muss, die der Treiber bedienen soll.

Nähere Informationen zur Entwicklung von Device-Treibern aller Art finden Sie in [2].

Der USB-Standard


Der USB-Standard ist relativ komplex, weil er nicht nur die elektrischen und mechanischen Aspekte der Verbindung beschreibt, sondern auch höhere Protokollschichten für eine Vielzahl von Anwendungen definiert.

Demnach kann ein USB-Gerät mehrere, alternative Konfigurationen haben. Innerhalb jeder Konfiguration sind Funktionalitäten logisch zusammengefasst in sog. Interfaces. Jedes Interface verwendet wiederum typischerweise eine Reihe von Endpoints (logischen Endpunkten) für die eigentliche Kommunikation. Je nach Anwendung werden verschiedene Arten von Endpoints verwendet.

Control Endpoints werden für Steuerungsaufgaben verwendet, z.B. um ein Gerät zurückzusetzen. Hier werden nur geringe Datenmengen übertragen.

Bulk Endpoints können für die Übertragung grösserer Datenmengen verwendet werden und sind daher der Regelfall für die eigentliche Übertragung von Daten an ein Gerät.

Interrupt Endpoints
werden für die Übertragung kleiner Datenmengen verwendet, die jedoch ohne unnötige Verzögerung übertragen werden sollen. Dafür wird auf dem Bus die nötige Bandbreite reserviert.

Isochronous Endpoints werden für “Streaming-Applikation” verwendet, wo grosse Datenmengen dauerhaft und regelmässig übertragen werden sollen (z.B. Audio oder Video-Streams).

Für die Steuerung von USB-Geräten muss man also die eigentlichen Kommandos, aber auch den jeweils zu verwendenden Endpoint kennen. Diese Details beschreibt der USB-Standard für eine ganze Reihe von Geräteklassen – eine dieser Klassen ist USBTMC (siehe unten).

Nähere Informationen zu USB finden Sie unter [3].

API für USB-Kommunikation unter Linux

Bild 3 zeigt den Aufbau des Kernels im Hinblick auf USB. Abhängig vom verwendeten USB Host Controller muss der Kernel über einen Treiber verfügen, der den entsprechenden Chipsatz bzw. das Produkt ansteuern kann. Auf diese Treiber wird jedoch nicht direkt zugegriffen, sondern über den USB Core. Dieser stellt den anderen Kernel-Moduln ein API zur Verfügung, mit Hilfe dessen USB-Operationen durchgeführt werden können.

USB-Operationen werden in der Regel in Form eines URB (USB Request Block) in Auftrag gegeben. Ein URB beschreibt den jeweiligen Endpunkt und die zu übertragenden Daten und wird vom System “im Hintergrund” ausgeführt. Das Ergebnis der Operation wird dem Auftraggeber über einen entsprechenden Callback mitgeteilt. Es gibt aber auch einfache “Komfortfunktionen”, die erst nach Beendigung der USB-Operation “zurückkehren”.

Ein USB-Treiber muss sich bei der Installation zunächst beim USB Core registrieren. Er teilt dem Kern dabei unter anderem mit, welche USB Devices bedient werden sollen. Dabei können verschiedene Kriterien angegeben werden, z.B. ein bestimmter Hersteller-Code oder eine bestimmte Geräteklasse (in unserem Fall wäre das die Klasse USBTMC).

USBTMC

Der USB-Standard enthält auch eine Reihe von Unterspezifikationen für bestimmte Klassen von Endgeräten, z.B. für Mäuse, Tastaturen... oder eben für Messgeräte. Die Spezifikation für letztere, namens USBTMC, wurde von führenden Messgeräteherstellern gemeinsam mit dem USB Forum entwickelt. Hier ist also im Detail beschrieben, wie mit einem Messgerät kommuniziert wird.

Bild 4 zeigt das Nachrichtenformat beim Senden eines Kommandos an das Messgerät (DEV_DEP_MSG_OUT Kommando). Diese Nachricht wird an den Bulk Endpoint des Geräts geschickt.

Große Sendungen können bei Bedarf aus mehreren Einzelsendungen zusammengesetzt werden. Bei der letzten Sendung wird dann das EOM (End of Message) Bit gesetzt. Alle Sendungen an den Bulk Endpoint müssen ggf. mit zusätzlichen Nullzeichen auf ein Vielfaches von vier Bytes vergrössert werden; dies gibt dem Gerät die Möglichkeit, mit einem direkten DMA-Zugriff zu arbeiten.

Bild 5 zeigt das Nachrichtenformat beim Lesen einer Geräteantwort. Dabei wird das Gerät zunächst mit einer DEV_DEP_MSG_IN Meldung an den Bulk Endpoint aufgefordert, Daten zu liefern. Die Antwort wird vom Bulk Endpoint gelesen.

Das Gerät sendet vor den eigentlichen Antwortdaten einen Header, der es erlaubt, die Anwort der jeweiligen Anfrage zuzuordnen.

 Das Gerät sendet vor den eigentlichen Antwortdaten einen Header, der es erlaubt, die Anwort der jeweiligen Anfrage zuzuordnen.

Näheres zum USBTMC Standard finden Sie unter [4].

Fazit


Die Steuerung von USB-Geräten ist aus Sicht des Programmiers sicherlich komplexer, als die von LAN-basierten Geräten. Der Aufwand lohnt sich aber insofern, als daß alle USB-basierten Messgeräte der USBTMC-Spezifikation folgen sollten (es gibt eigentlich keine Alternative innerhalb des USB-Standards) und der Standard sehr stabil ist. Wer sich also einmal die Arbeit macht, einen Treiber für seine Distribution zu erstellen, ist für die Zukunft gerüstet. Vorwiegend für den Laboreinsatz und im unteren Preissegment wird USB sicherlich eine wichtige Rolle spielen.

Diese Produktinformation
ist aus unserem Archiv!

Aktuelle Produkte finden Sie über die Suche ...
gradient arrows

Agilent Technologies Deutschland GmbH

Herrenberger Str. 130
71034 Böblingen

Tel: +49 01805 / 24-6333*
Fax: +49 01805 / 24-6336* (*0,12 EUR/min)