Linux, PC und Hardware


Prof. Jürgen Plate

Universal Serial Bus

USB steht für "Universal Serial Bus" (Universeller Serieller Bus) und ist ein Industrie-Standard, um periphere Geräte an den Computer anzuschließen. Die Entwicklung hat bereits Anfang der 90er Jahre begonnen, dümpelte jedoch einige Jahre vor sich hin. Es dauert noch bis 1996, bis sich USB auf dem Markt meldete, und erst 1998 mit der Version 1.1 begann sich USB langsam durchzusetzen. Mehr und mehr Computer haben USB-Schnittstellen und mit Windows 98 kam das erste Windows, das USB voll unterstützt. Beteiligt an der Entwicklung sind Firmen wie Intel, Apple, Compaq oder Microsoft. Inzwischen ist USB mit den Versionen 1.1 und 2.0 ein Standard mit hoher Marktdurchdringung.

USB-Grundlagen

Warum verwendet man USB? Gründe gibt es viele: Neben der inzwischen großen Marktverbreitung spricht vor allem die einfache Installation und Handhabung auf Benutzerseite dafür. Zwar sind die Steckverbinder nicht der Weisheit letzter Schluss und auch die Datenübertragungsrate wesentlich geringer als beim Netzwerk, aber die Vorteile überwiegen in vielen Anwendungsfällen: Hot plugging, Plug-and-Play sowie der Verzicht auf weitere I/O-Ports, DMA- und Interrupt-Kanäle. Die Installation ist zumindest auf USB-tauglichen Windows-Plattformen idiotensicher. USB-Geräte steckt man einfach während des Betriebs ein. Sofortiger Einsatz ist möglich, ein nerviger Reboot entfällt, und es lassen sich bis zu 127 Geräte anschließen. Zudem vermeiden USB-Geräte Ressourcenkonflikte. Der Controller im PC benutzt zwar generell einen Interrupt und 32 I/O-Ports, doch die angeschlossenen USB-Geräte belegen keine weiteren System-Ressourcen. Die Stromversorgung von bis zu 500 mA je USB-Port wird auch gleich mitgeliefert und erlaubt somit eine Vielfalt an Funktionen und Anwendungsmöglichkeiten zur Erweiterung von Schnittstellen. Durch die Kaskadierung von USB-Hubs lässt sich, wie bei Netzwerken, eine baumartige Struktur erzielen. Die maximale Länge eines Kabelsegmentes beträgt fünf Meter. Die Daten werden als Differenz-Signal mit einer Geschwindigkeit von 12 Mbit/s oder 1,5 Mbit/s übertragen. Bei USB 2.0 ist ausserdem eine Highspeed-Übertragung mit 480Mbit/s spezifiziert. Da USB-2.0 zu USB-1.1-Geräten abwärtskompatibel ist, gibt es an neueren Rechnern keine USB-1.1-Schnittstellen mehr.

Die Vorteile von USB:

Standardstecker
PinNameFarbeBeschreibung
1VCCRot+5 V
2D-WeißData -
3D+GrünData +
4GNDSchwarzMasse
Mini- und Microstecker
PinNameFarbeBeschreibung
1VCCRot+5 V
2D-WeißData -
3D+GrünData +
4IDkeineUnterscheidung von
Micro-A- und Micro-B-Stecker:
Typ A: Masse, Typ B: nicht verbunden
5GNDSchwarzMasse

USB besitzt aber auch Nachteile:

Eigenschaften von USB 1.1:

Eigenschaften von USB 2.0:

USB-Signale

Die Signale auf den beiden Leitungen, D+ und D-, sind Differenzsignale mit Spannungspegeln von 0 und 3,3 Volt. Die Versorgungsspannung am USB kann bis zu 5,25 V betragen und bei starker Belastung bis auf 4,2 V abfallen. Man unterscheidet zwischen Geräten mit eigenem Netzteil und solchen, die über den USB versorgt werden. In vielen Fällen können beide Betriebsarten gewählt werden. Viele Geräte besitzen eine Netzteilbuchse, die zur Schonung des versorgenden PC mit einem externen Netzteil verbunden werden kann. Nach den USB-Spezifikationen ist die Stromaufnahme aus dem Bus automatisch begrenzt.

Man unterscheidet - wie schon erwähnt - zwischen Lowspeed-, Fullspeed und Highspeed-Signalen. Bei einer Lowspeed-Verbindung beträgt die Datenübertragungsrate 1,5 MBit/s, bei einer Fullspeed-Verbindung 12 MBit/s. Die Geschwindigkeit wird vom Master vorgegeben, die angeschlossenen Geräte synchronisieren sich darauf. Die Datencodierung und -decodierung erfolgt durch die USB-Hardware. Die Datenübertragung ist differentiell (D+, D-).
J-Pegel: Differenz zwischen D+ und D- ist positiv (differentielle "1")
K-Pegel: Differenz zwischen D+ und D- ist negativ (differentielle "0")

Es gibt weder Handshake-Signale noch eine eingestellte Baudrate oder eine separate Taktleitung. Der Takt für die Abtastung des übertragenen Datenstroms und das Synchronisiersignal für den Takt werden aus einem Synchronisier-Bitmuster und aus dem Datenstrom selbst generiert. Jede "0" im Bitstrom liefert einen Pegelwechsel und damit ein Signal für die Taktgewinnung.

Nach einer Folge von sechs Einsen ("1") wird automatisch in den Datenstrom eine "0" eingefügt (Bit Stuffing). Damit kommt spätestens nach der sechsten Taktperiode ein Signal für die Takt-Synchronisierung. Jedes Gerät hat spätestens nach sieben Taktperioden Gelegenheit, seinen internen Takt mit dem Bustakt zu synchronisieren. Jedes Datenpaket wird zum Zweck der Synchronisation von einem Sync-Byte angeführt.

Der Host muss das Anstecken eines Geräts bemerken und gleichzeitig die Geschwindigkeitsklasse identifizieren können (Hot Plug-and-Play). Bei Low- und Full-Speed- Geräten ist dies einfach; das Gerät zieht entweder den D- -Pegel oder den D+ -Pegel mit einem Widerstand auf high:

High- oder Full-Speed-Gerät

Low-Speed-Gerät

Für High-Speed-Geräte steht keine weitere Datenleitung zur Verfügung, ausserdem muss sich ein High-Speed-Gerät an einem USB-1.1-Controller wie ein Full-Speed-Gerät verhalten (High-Speed-Geräte sollen auch an einen Full-Speed-Controller anschließbar sein). Deswegen wurde für High-Speed-Geräte die weiter unten beschriebene Anmeldeprozedur spezifiziert.

kein Gerät: D+, D- = low (>2,5us)
Low Speed: D- = high (>2,5us)
Full Speed: D+ = high (>2,5us)
High Speed: verhält sich zunächst wie ein Full-Speed-Gerät (D+ = high), und ist damit von USB-1.1-Controllern/Hubs als Full-Speed-Gerät identifizierbar. Danach identifiziert sich das High-Speed-Gerät durch Abschalten des Pull-Up-Widerstands und aktiviert die 45-Ohm-Abschlusswiderstände an D+ und D-.

Speed mit USB 3.0

Der USB 2.0-Standard bekam 2008 einen Nachfolger, den neuen SuperSpeed-USB (USB 3.0). Dieser vervielfacht Datentransfer und Leistung. Wie schon seine Vorgänger nahm USB 3.0 auber auch erst langsam Fahrt auf. Der neue Standard erlaubt theoretisch eine Datenrate von bis 5 GBit/s und eine 1,6 Mal schnellere Datenübertragung als eSATA. Die Vorteile des neuen USB SuperSpeed liegen nicht nur in der hohen Geschwindigkeit. Auch die Stromversorgung von Peripheriegeräten wird mit USB 3.0 deutlich verbessert. Über die neue Busverbindung können angeschlossene Geräte bis zu 80% mehr Energie beziehen als über eine USB 2.0-Schnittstelle. Damit stehen nun 150 mA Stromstärke pro Gerät zur Verfügung. Zudem können auf Aufforderung bis zu 900 mA bereitgestellt werden.

SuperSpeed-USB verwendet eine dem "SerialATA" ähnliche Technik mit Full-Duplex-Übertragung über zusätzliche Adernpaare im Kabel. USB 3.0-Kabel verfügen daher über zwei Adern für +5 V und Masse sowie zwei Adern für den herkömmlichen Datentransfer. Darüber hinaus gibt es zwei abgeschirmte, verdrillte Adernpaare für Senden und Empfangen der SuperSpeed-Daten. Dshalb haben die (meist blauen) USB 3.0-Kabel einen größeren Durchmesser als Ihre Vorgänger - und sie dürfen nur maximal drei Meter lang sein. USB-3.0-Steckverbinder haben auch fünf zusätzliche Kontakte, dabei bleiben Typ-A-Stecker aber zu bisherigen USB-Buchsen kompatibel; die Entwickler haben die neuen Kontakte in bisher ungenutzte Bereiche gequetscht. Bei Micro-USB- und Typ-B-Verbindern klappt das nicht, sie erhalten jeweils einen "Anbau". USB-Geräte können in einen Stand-by-Modus schalten, wenn gerade nichts zu tun ist. Fordert der Host aber Daten an, so kann er das Gerät auch aufwecken. Das regelmäßige Polling ist damit abgeschafft, stattdessen können sowohl Geräte wie Host eine Datenübertragung anstoßen. Fragt der Host nach Daten und das Gerät meldet sich nicht, gilt es wirklich als nicht mehr vorhanden.

Gleichzeitig arbeitet USB 3.0 energieeffizienter als frühere Versionen. Beispielsweise sorgen Ruhe- und Wartemodi sowie neue Energiemanagementfunktionen auf der Geräte- und Funktionsebene für einen geringeren Stromverbrauch. Alte USB-Geräte können ohne Probleme weiterverwendet werden, denn USB 3.0 ist voll abwärtskompatibel mit USB 2.0 und USB 1.1. Auch ist es möglich, neue 3.0-Geräte an alten 2.0- bzw. 1.1-Hosts zu betreiben. Eine Ausnahme stellen Mini- und Micro-B-Verbinder sowie Mini-A-Verbinder dar. Durch Änderungen beim USB-3.0-Stecker lassen sich zwar alte Stecker in neuen Buchsen, nicht jedoch neue Stecker in alten Buchsen verwenden.

Bisher muss sich ein USB-2.0-Gerät regelmäßig beim Host melden, sonst galt es als abgesteckt. Dieses "Polling" kostet Strom und senkt durch ständige Übertragung von Statusmeldungen statt Nutzdaten auch Bandbreite. Bei USB 3.0 kann ein Gerät dem Host aber auch melden, dass es derzeit für Datenübertragungen nicht bereit ist, der Host fragt dann nicht weiter. Erst wenn das Gerät selbst wieder Daten übertragen will, teilt es das dem Host mit. Wie auch die Kabel werden die Hubs für USB 3.0 deutlich komplexer. Ein USB-3.0-Hub enthält nämlich neben der Logik für den neuen Standard einen kompletten Hub nach Version 2.0. Dadurch lassen sich 2.0- und 3.0-Geräte mischen, die Übertragungen laufen auf verschiedenen Pfaden ab.

Steckverbinder-Chaos

Eigentlich waren im USB-Standard nur die Steckverbinter Typ A und B vorgesehen. Schon der oben erwähnte Mini-USB-Verbinder ist streng genommen nicht Teil des USB-1.1-Standards. Für Geräte mit geringerem Platz für den Stecker (digitale Kameras, Mobiltelefone usw.) existieren auch verschiedene kompaktere USB-Steckverbinder. Im USB-2.0-Standard sind ausschließlich die fünfpolige Mini- und Micro-Varianten (plus Schirm) hinzugekommen. Micro-USB-Steckverbinder ersetzen in naher Zukunft den Mini-Verbinder komplett, lediglich der relativ weitverbreitete Mini-B-Verbinder wird derzeit noch geduldet. Die Micro-USB-Verbinder sind dank der im Standard geforderten Edelstahlkrampe deutlich stabiler ausgeführt. Gemäß USB-2.0-Standard gibt es drei Varianten, die genau wie bei Mini-USB allesamt fünfpolig ausgeführt sind: Micro-A (rechteckige Bauform für die Host-Seite) und Micro-B (Trapez-Bauform für die Geräteseite). Mit USB 3.0 kommen neue Varianten der Micro-A- und -B-Steckverbinder auf den Markt (siehe unten).

Daneben gibt es noch eine ganze Reihe proprietärer Steckverbinder, die zwar in der Regel elektrisch mit USB 2.0 kompatibel sind, jedoch nur über teilweise schwer erhältliche Adapterkabel mit USB-Komponenten gemäß dem USB-Standard verbunden werden können. Fatalerweise werden jedoch auch diese Frickel-Steckverbinder häufig als "Mini-USB" bezeichnet, was immer wieder zu Missverständnissen führt und vermieden werden sollte. Nicht zuletzt deshalb soll der Micro-USB-Standard hier den Wildwuchs beenden. Verbreitet sind unterschiedlichste Ausführungen mit vier Pins, mehrere inkompatible Varianten mit acht Pins sowie Stecker mit 11, 12 Pins und 14 Pins, die auch noch andere, nicht-USB-spezifische Signale führen.

Das USB Implementors Forum (USB-IF) hat 2014 die Spezifikationen für einen neuen Typ-C-Anschluss von USB standardisiert, der in Zukunft in fast allen Geräten zum Einsatz kommen könnte. Der Standard beschreibt den Stecker, die dazugehörige Buchse und das Kabel. Der neue Typ-C-Anschluss zeichnet sich in erster Linie durch seine kompakten Maße sowie die Tatsache aus, dass es nun egal ist, in welcher Lage er eingesteckt wird. Die Öffnung für den Typ-C-Anschluss ist laut Datenblatt nur 8,4 mm x 2,6 mm gross. Auch die dazugehörige Buchse ist sehr kompakt gestaltet. So können die beidseitig verwendeten Kontakte auch von beiden Seiten der Buchse abgegriffen werden. Noch flacheren Smartphones und Tablets steht also nichts mehr im Wege. Bei der Entwicklung hat auch der mechanische Halt in der Buchse eine wichtige Rolle gespielt. Auch nach 10.000 Ansteckvorgängen soll der neue Typ-C-Anschluss noch funktionieren und einen ausreichend Halt bieten. Das Bild zeigt erste Entwürfe des Steckers im Vergleich zu einem Micro-USB-3.0-Stecker.

Theoretisch soll der Typ-C-Anschluss in der Lage sein, bis zu 10 GByte Datenrate und bis zu 100 Watt Leistung über die Leitungen zu übertragen. Dies muss natürlich auch von der USB-Buchse und dem Kabel unterstützt werden. Bisher (Mitte 2014) existieren noch keine Prototypen des Typ-C-Anschlusses. Das USB Implementors Forum beschränkte sich in seiner Veröffentlichung bisher (2014) auf technische Zeichnungen sowie Renderbilder. Inzwischen gobt es auch Fotos:

Typ-C-Stecker rasten zwar beim Einstecken hör- und spürbar ein, für industrielle Anwendungen ist das jedoch zu wenig: Eine Steckverbindung muss fallweise Vibrationen überstehen, die eine Steckverbindung lockern können. Damit der universelle Typ-C-Stecker auch in industriellen Anwendungen zum Einsatz kommen kann, hat das Standardisierungsgremium USB-IF 2016 eine Spezifikation für verschraubte Typ-C-Steckverbindungen verabschiedet.

Die erste Revision dieser USB Type-C Locking Connector Specification sieht zwei Arten von Verschraubungen vor, mit zwei oder einer Schraube. Bei ersterer sitzen die beiden Verschraubungen in der Längsachse des Typ-C-Steckers, wodurch es wie beim Typ-C-Verbinder egal ist, in welcher Orientierung man den Stecker in die Buchse steckt. Bei der Variante mit einer Verschraubung für Anwendungen mit begrenztem Platz klappt das nicht. Weil sich an der Typ-C-Buchse selbst nichts ändert, lassen sich herkömmliche Typ-C-Kabel weiterhin ohne Schrauben in Buchsen mit danebenliegenen Befestigungslöchern einstecken. Damit auch verschraubbare Kabel an Geräten verwendbar sind, die keine Bohrungen aufweisen, müssen die Schrauben gemäß Spezifikation vollständig versenkbar sein.


Bild: Newnex

Logos

Mittlerweile gibt es für die zahlreichen USB-Varianten Vorschläge für Logos - je nachdem, ob der USB-Port genügend Strom liefert oder mit Display-Port ausgerüstet ist. Das typische USB-Logo wird bei "Power Delivery" schwarz unterlegt. Für den Display Port kommt rechts neben der Buchse noch das "D"-Logi dazu.

Steckanschluss auf dem Mainboard

Zwar sind in der Regel USB-Zusatzkarten für den PC, z. B. USB-Interfaces für SD- und andere Speicherkarten mit den richtigen Steckern konfiguriert. Zur Information soll aber hier noch die Belegung des doppelreihigen Postensteckers im PC gezeigt werden.

USB-Topologie

USB ist trotz des Namens kein echts Bussystem. Bei einem Bus hängen alle Geräte an einer Leitung, wogegen bei USB nur alle Geräte die vom Rechner gesendeten Daten (fast) gleichzeitig empfangen. Bei USB handelt es sich im Grunde um einen sternförmigen Aufbau mit mehreren Zwischenebenen. Jedes Gerät ist über eine eigene Leitung mit dem Computer oder mit einem dazwischen geschalteten Hub verbunden. USB ist also ein sternförmiges Netz von Geräten (Device), die alle von einem Controller (im PC) verwaltet werden.

Angeschlossene Geräte (Tastatur, Maus, Drucker usw.) werden Funktionen (Functions) genannt. Nach dem Einschalten besitzt ein Gerät die Default-Adresse 0. Bei der Initialisierung - hier Enumeration genannt - wird jedem Gerät eine eigene Adresse zugewiesen. Jedes Gerät besitzt entweder eine eigene Spannungsversorgung (self powered) oder es wird über das USB-Kabel versorgt (bus powered). Vor der Initialisierung darf die Stromaufnahme aus dem USB-Kabel maximal 100 mA und nach der Initialisierung maximal 500 mA betragen.

Beim Reset des USB (oder beim Anstecken eines Gerätes an den Bus), meldet sich jedes Device beim Controller mit der fiktiven Adresse 0 an. Der Controller gibt dem Device eine richtige Adresse (1..127) und liest die Konfiguration aus dem Device aus. In dieser Konfiguration gibt das Device an, wieviele Pufferspeicher (genannt Endpunkte) es besitzt, wie groß diese Puffer sind (max. 64 Byte) und ob das für den Controller Lesepuffer (In) oder Schreibpuffer (Out) sind. Außerdem wird dem Controller gesagt, wie häufig er prüfen soll, ob im Lesepuffer Daten für ihn bereitliegen. Die Konfiguration enthält noch weitere Informationen (beispielsweise über den Strombedarf des Device).

Ein Hub ermöglicht den Anschluß mehrerer Geräte. Ein Hub nimmt Daten von den Geräten entgegen und liefert sie an den Rechner oder an den nächst höheren Hub. Umgekehrt nimmt ein Hub Daten vom Computer entgegen und leitet sie an das richtige Gerät oder den nächst tieferen Hub weiter. Jeder Hub besitzt einen Upstream-Port in Richtung Host und mehrere Downstream-Ports in Richtung der Geräte oder weiterer Hubs. Vom Host gesendete Daten leitet der Hub an alle seine aktiven Downstream-Ports weiter. Daten vom Gerät sendet der Hub nur in Richtung Host. Ein Hub isoliert angeschlossene Full/ Low-Speed-Geräte vom High-Speed-Mode und leitet an diese Geräte gerichtete Daten in der passenden Geschwindigkeit weiter. Außerdem übernimmt der Hub das Power-Managment für die angeschlossenen Geräte. Hubs besitzen eine eigene Spannungsversorgung oder werden über das USB-Kabel versorgt. Zwischen den Computer und ein Gerät lassen sich bis zu sieben Hubs schalten - es gibt also bis zu sieben "Ebenen". In jeder Ebene darf das USB-Kabel 5 m lang sein. Ein Gerät kann also maximal 35 m vom Computer entfernt stehen. Die Daten bewegen sich nur zwischen dem Computer und dem jeweils angesprochenen Gerät. Die USB-Geräte können sich untereinander nicht verständigen.

Die Kontrolle über die Daten hat alleine der Computer. Er fragt regelmäßig alle Geräten ab (Polling). Die Geräte dürfen von sich aus keinerlei Daten senden. Dieses Polling kostet zwar Rechenzeit, erlaubt aber einen einfacheren Aufbau der USB-Geräte. Ein USB-Host besteht somit aus einem einzigen Hostcontroller, einem Root Hub (USB-Anschlüsse am PC), der Systemsoftware und den Gerätetreibern. Den Datenfluß vom Host weg zu den Geräten nennt man "downstream", den Datenfluß in umgekehrter Richtung "upstream". Tauscht der Computer mit einem Gerät Daten aus, baut er immer eine direkte Verbindung dorthin auf, die "Pipe" genannt wird.

Für den Host gibt es derzeit 3 Realisierungs-Varianten:

In einem USB2.0-Host-Controller sind ein oder mehrere UHCI- oder OHCI-Controller für USB1.1 Geschwindigkeitsklassen (Low- und Full-Speed) eingebettet (Companion Host Controller). Eine Routing-Logik bewirkt, dass USB-1.1-Geräte mit UHCI/OHCI-Controllern und USB-2.0-Geräte mit dem EHCI-Controller verbunden werden können. Erkennt ein USB-2.0-Treiber ein angeschlossenes Gerät als Full- oder Low-Speed-Gerät, konfiguriert er die Routing-Logik so, dass dieses Gerät mit den Ports eines Companion USB Host Controllers verbunden wird.

Jeder Datentransfer ist über Transaktions-Deskriptoren beschrieben, man spricht auch von virtuellen Datenkanälen, sogenannten "Pipes", die der Host einrichtet. Jede Pipe beginnt im Pufferbereich des Arbeitsspeichers und endet in einem Endpunkt (Endpoint EP) eines Geräts. Ein Endpunkt im Gerät ist ein Sende/Empfangs-FIFO. Die Eigenschaften eines Endpunkts sind im Endpoint-Descriptor festgelegt. Dieser ist in jedem Gerät fest abgespeichert.

Damit der Host die Configuration auslesen kann, besitzt jedes Interface einen EP0, der für die Konfiguration zuständig ist. Dieser ist der einzige Endpunkt, der sowohl ausgelesen als auch beschrieben werden kann, alle anderen Endpunkte beherrschen nur eine Datenrichtung. Der EP0 kann auch für den Datentransfer mitbenutzt werden, er bietet jedoch nur eine geringe Datenrate von 800 Byte/s. Alle anderen Endpunkte sind uni-direktional (senden oder empfangen). Die im EP0 übertragenen Daten sind als Standard-Device-Requests definiert. Die zum EP0 aufgebaute Pipe ist eine "Message" Pipe. Low-Speed-Geräte unterstützen nur Control- und Interrupt-Endpunkte.

Jedes USB-Gerät kann mehrere Endpunkte und mehrere Pipes unterstützen. Die Adressierung auf dem Bus erfolgt über eine 7-Bit-USB-Adresse und im Gerät über eine 4-Bit-Endpunkt-Nummer. Am Bus sind dadurch maximal 127 Geräte und Hubs anschließbar. Die Endpunkt-Nummer ermöglicht die Realisierung verschiedener adressierbarer Funktionen innerhalb eines USB-Geräts. Ein Endpunkt ist eindeutig definiert durch seine Nummer und seine Richtung. Pro Gerät sind daher neben dem EP0 noch 15 IN- und 15 OUT-Endpunkte möglich. Bezugspunkt für die Richtung des Datentransfers ist der Host:

Ein großer Vorteil von USB ist die automatische Erkennung (Plug and Play) neu angeschlossener Geräte. Das Betriebssystem muss dazu in der Lage sein, Informationen von einem Gerät abzufragen, um dann den passenden Treiber zu laden und das Gerät entsprechend anzusprechen. Ein neues Gerät wird dabei angemeldet (Enumeration), es erhält eine Bus-Adresse und wird durch einen speziellen Treiber unterstützt. Die Enumeration wird völlig selbstständig vom Betriebssystem ausgeführt. Den Anschluss eines neuen Gerätes erkennt der Hub Pull-Up-Widerstand der Datenleitung. Nun laufen folgende Schritte ab:
  1. Der Hub informiert den Host über das neu angeschlossene Gerät.
  2. Der Host erfragt vom Hub den Port des neuen Geräts.
  3. Der Host schaltet per Befehl diesen Port ein und führt einen Bus-Reset aus.
  4. Der Hub erzeugt ein Reset-Signal (10 ms) und gibt den Versorgungsstrom für das Gerät frei.
  5. Das Gerät ist nun bereit und antwortet auf der Default-Adresse 0.
  6. Der Host weist dem Gerät eine eigene Bus-Adresse zu.
  7. Der Host liest alle Konfigurations-Informationen aus dem Gerät.
  8. Der Host weist dem Gerät eine der möglichen Konfigurationen zu.
Die Geschichte ist recht komplex und jedes Gerät braucht seinen Treiber. Im Gegensatz zu Nicht-USB-Komponenten wird der Treiber eben nicht beim Booten oder gar manuell geladen, sondern vom USB-Subsystem automatisch aktiviert, wenn das Gerät am Bus auftaucht. Irgendwann stehen dann wieder die Standard-Dateifunktionen zum Ansprechen des Geräts zur Verfügung.

Datenübertragungsmodi

USB 2.0 kennt vier Datenübertragungsarten: Die folgende Tabelle fasst die verschiedenen Modi nochmals zusammen:

Transfer TypeControl InterruptIsochronous Bulk
USB 1.1XX --
USB 2.0XX XX
Datenbytes/Millisekunde pro Transfer
(full speed)
832, in dreizehn 64-Byte Transfers 6410231216, in neunzehn 64-Byte Transfers
Datenbytes/Millisekunde pro Transfer
(low speed)
24, in drei 8-Byte Transfers 0.8, 8 Bytes in 10 msverbotenverboten
Reservierte Bandbreite10%90%
Int. & Iso. gemeinsam
90%
Int. & Iso. gemeinsam
keine
Fehlerkorrekturjaja jaja
Garantierte Datenrateneinnein janein
Garantierte Latezzeit
(max. Zeit zwischen Transfers)
neinja janein

Die einem Endpoint zugeordnete Transferart ermittelt der Gerätetreiber aus den jeweiligen Deskriptoren (Gerät "sagt", welchen Transfer es benötigt).

Initialisierung (Bus Enumeration)

Das Initialisieren oder Konfigurieren eines Geräts am USB wird mit Enumeration bezeichnet. Ein Gerät durchläuft dabei vier der sechs Gerätezustände:

Powered → Default → Addressed → Configured.

Die beiden anderen Zustände sind: Attached (Hub versorgt Gerät nicht mit Strom) und Suspended (Gerät erkennt mindestens 3ms auf dem Bus keine Aktivität und reduziert die Stromaufnahme). Das Anstecken oder Abziehen eines Geräts am Bus wird, wie erwähnt, durch den Hub erkannt und dem Host gemeldet. Der Host fragt den Hub ab (Full-Speed- oder Low-Speed-Gerät, Port-Adresse) und führt dann die Enumeration in folgenden Schritten durch:

  1. Anstecken des Geräts am Hub bzw. Einschalten des Systems
  2. Port Enable durch den Hub, Hub liefert max. 100mA ans Gerät („Powered“)
  3. Hub erkennt Geschwindigkeitsklasse des Geräts über die Signalleitungen D+, D-
  4. Hub informiert Host über Interrupt-Kanal
  5. Hub setzt Gerät zurück: D+ und D- auf logisch Null, danach ist Gerät im Zustand „Default“. Gerät ist jetzt für Control-Transfers zum EP0 bereit.
  6. Host ermittelt über GetDescriptor die maximale Paketgrösse des Kanals (Adr=0, EP0)
  7. Host weist Geräteadresse zu über SetAddress („Addressed“)
  8. Host liest Deskriptoren über GetDescriptor
  9. Host ermittelt aus den Deskriptoren die Ressourcen-Auslastung durch das Gerät (FIFO-Größe, Polling-Intervalle, Stromaufnahme)
  10. Host lädt einen Gerätetreiber
  11. Host weist über den Gerätetreiber eine Konfiguration zu mit SetConfiguration („Configured“)
Nach dem Ende der Enumeration werden Interrupt-Endpunkte zyklisch abgefragt und Isochrone Endpunkte jede Millisekunde angesprochen.

Nach einem "Power-on-Reset" arbeiten alle USB-Geräte auf Adresse 0 (Default). Es sind alle Downstream-Ports deaktiviert, die folgenden Standard-Device-Requests sieht nur der Hub1. Zuerst wird der Hub1 enumeriert, dann fragt der Host den Interrupt-Endpunkt des Hub1 ab und erkennt, daß weitere Geräte an den Downstream-Ports von Hub1 angeschlossen sind. Sukzessive schaltet der Host die Ports des Hub1 frei und enumeriert die Geräte.

Bus-Kommunikation

Zur Kommunikation über den Bus dienen Datenpakete. Davon gibt es zwei Arten, zum Übermitteln von Control- und Steuerungsinformationen oder zum Datentransfer. Beide sind aber im Grunde gleich aufgebaut: Zur zeitlichen Synchronisation der Geräte sendet der Host periodisch ein Paket SOF (Start-of-Frame-Token) bzw. uSOF (bei high Speed) auf den Bus. Damit wird der Bus in Zeitintervalle (Frames) von 1 ms (SOF bei Full Speed) bzw. von 0,125 ms (uSOF bei High Speed) eingeteilt. SOF (uSOF)-Tokens entfallen bei Low-Speed-Anschlüssen. Nicht jedes Gerät überträgt Daten in jedem Frame.

Der Host-Controller-Treiber verteilt die Datentransfers für die verschiedenen Geräte auf die einzelnen Zeitintervalle (Scheduling der Transaktionen).

Da ein USB-Gerät von sich aus weder einen Transfer starten noch per Interrupt einen Transfer anfordern kann, kann man für ein Gerät nur eine Pseudo-Interrupt-Fähigkeit herstellen: Ordnet man einem Gerät einen Interrupt-Endpunkt zu, dann wird es periodisch im Millisekundenabstand vom Host abgefragt und kann so Transferanforderungen dem Host bekanntmachen.


Oszilloskop-Bilder, links idle, rechts mit Daten

Der Hostadapter selbst ist jedoch interrupt-fähig, d. h., er kann von der CPU Bedienung anfordern. Das Anwender-Programm startet einen Datentransfer, indem es beim USB-Geräte-Treiber einen Transfer anfordert. Es wird eine Verbindung ("Pipe") etabliert und die Daten werden übertragen. Damit keine Daten verloren gehen, muß jedes USB-Gerät einen Datenpuffer haben, dessen Größe sich nach dem typischen Datenaufkommen und nach seiner Datentransferrate richtet. Ebenso sind Pufferspeicher auf der Host-Seite notwendig, um Daten eines Geräts zwischenspeichern zu können für den Fall, dass die Daten nicht schnell genug zum Arbeitsspeicher der CPU übertragen werden.

Der Datentransfer vom Host zu den Geräten erfolgt im sogenannten Broadcast-Modus, d. h. die Daten werden über die Hubs an alle Geräte verteilt. Nur das Gerät, das die eigene Adresse im Token-Paket erkennt, antwortet. Eine direkte Kommunikation zwischen zwei Geräten ohne Beteiligung des Host ist nicht möglich, sie muss grundsätzlich über den Host laufen.

Software-Schichten

Die Steuerung/Abfrage von USB-Geräten erfolgt über "USB Device Requests", auf die jedes Gerät reagieren muß. Eine Untermenge davon sind die "Standard Device Requests". Device Requests übergibt der Geräte-Treiber in Form von I/O-Request-Packets IRPs an den Bus- Treiber. Alle Requests werden mit SETUP-Token über den bidirektionalen Control-Endpunkt EP0 abgewickelt.

Beim "GetStatus"-Request wird beispielswesie der Device-Status vom Host abgeholt (Datentransferrichtung=IN). Zusätzlich zu den Standard-Device-Requests unterstützt jedes Gerät noch gerätespezifische Requests. In vielen Fällen sind dies Befehle aus den SCSI-Befehlssätzen der SCSI-3-Norm.

Paket-Formate

Die Informationsübertragung erfolgt in Paketen, deren allgemeines Format der folgenden Grafik entspricht:

Der Host sendet periodisch ein Start-of-Frame-Token (SOF) als Zeitmarke USB und erzeugt damit Zeitfenster: 1 ms im Full Speed Mode (SOF), 0,125 ms im High Speed Mode (uSOF). Die Frame Number (11 Bit) wird bei jedem SOF hochgezählt (modulo 2048). Im High-Speed-Mode erhöht der Host die-Frame Number nur nach jedem achten uSOF.

Das SOF wird im Full-Speed-Mode gesendet. Low-Speed-Ports können daher kein SOF empfangen, deshalb wird das SOF vom Hub in eine spezielle Signalisierung EOP umgewandelt (Low-Speed-Keep-Alive: Auf die beiden zum Low-Speed-Gerät führenden Signaladern D+/D- wird für 2 Low-Speed-Taktzyklen Low-Pegel angelegt).

Ein Gerät, das kein SOF oder Low-Speed-Keep-Alive EOP empfängt, geht in den Schlaf-Modus (Suspend Mode) mit verringerter Stromaufnahme (ca. 0,5 mA). Das SOF-Token geht an alle Geräte.

Befehls-Pakete (mit SETUP-Token) enthalten eine Geräteadresse und eine Endpunktadresse. Nur Geräte, die ihre Adresse in einem Paket erkennen, dürfen reagieren. Dasselbe gilt für IN- und OUT-Token.

Die Nutzdaten werden in DATA0- und DATA1-Paketen übertragen. Sind es größere Datenmengen, werden – aus Gründen einer eventuellen Fehlerbehandlung - immer abwechselnd DATA0- und DATA1-Pakete verwendet. Maximal sind pro Paket 1024 Byte Nutzdaten (Full Speed) bzw. xxxx Byte (High Speed) übertragbar. Die Nutzdaten sind über eine 16-Bit-CRC-Information gesichert. Der Empfang von Nutzdaten wird über ein ACK-Paket (erfolgreich) oder eine NAK-Packet (nicht erfolgreich) quittiert. Über das STALL-Paket fordert ein Gerät die Hilfe des Host an wenn es z. B. weder Daten empfangen noch senden kann.

Konfigurationen

Jedes Gerät muss vom Host erkannt werden. Es muss also die eigene Funktionalität kennen und die entsprechenden Konfigurationen dem Host übermitteln. Beispielsweise können einige Geräte mit eigener Stromversorgung arbeiten (self-powered) oder vom USB versorgt werden (bus-powered). Das Gerät teilt dem Host seine Konfiguration über den Device-Deskriptor mit. Dieser Deskriptor enthält alle wichtigen Daten über das Gerät wie z. B. Gerätetyp, Hersteller-ID, Produkt-ID, Seriennummer und unterstützte USB-Version. Dieser Devicedeskriptor ist aber nicht der einzige Deskriptor; der USB-Standard definiert einige weitere Typen von Deskriptoren, welche in einem hierarchischen Zusammenhang stehen:

Bei vielen USB-Geräten existiert jedoch oft nur eine Konfiguration, obwohl durch dieses System eine enorme Flexibilität möglich wäre.

Device-Descriptor

Die Informationen sind als Baum in einem Gerät gespeichert. Zuoberst (Wurzelknoten) steht jeweils der Device-Descriptor. Dieser beinhaltet folgende Daten:

BytesOffsetNameInhalt
10bLengthGrösse des Descriptors in Bytes
11bDescriptorTypeDescriptor-Typ (0x01)
22bcdUSBUSB-Version als packed-BCD mit dem Format JJMN,
wobei JJ die Hauptnummer und M und N jeweils Sub-Nummern der vorangehenden sind.
USB 1.0 → 0x0100, USB 1.1 → 0x0110 und USB 2.0 → 0x0200
14bDeviceClassKlassen-Code
15bDeviceSubClassSubklassen-Code
16bDeviceProtocolProtokoll-Code. Die letzten drei Grössen dienen dem System, um den richtigen Klassen-Treiber zu aktivieren.
Bei Klassen-Code 0 definiert jedes Interface seinen eigenen Klassen-Code.
Bei Klassen-Code 0xff ist der Code Vendor-spezifisch.
17bMaxPacketSize Maximale Paketgrösse für ENDP 0. Mögliche Werte sind 8, 16, 32 und 64.
28idVendorVendor-ID
210idProductProdukt-ID
212bcdDeviceVersion des Gerätes (Format wie USB-Version).
114iManufacturerString-Index für Hersteller oder NULL.
115iProductString-Index für Produkt oder NULL.
116iSerialNumberString-Index für Seriennummer oder NULL.
117bNumComfigurationsAnzahl Konfigurationen

Configuration-Descriptor

Ein Configuration-Descriptor enthält Informationen über den aktuellen Zustand des Geräts, beispielsweise, ob eine eigene Stromversorgung verwendet wird oder die Anzahl Interfaces, die das Gerät unter dieser Konfiguration anbietet. Sobald ein solcher Deskriptor geladen wird, wird automatisch der gesamte darunterstehende Baum übergeben.

BytesOffsetNameInhalt
10bLengthGrösse des Descriptors in Bytes
11bDescriptorTypeDescriptor-Typ (0x02)
22wTotalLenghtLänge der total zurückgegebenen Daten (gesamter Baum)
14bNumInterfacesAnzahl Interfaces
15bConfigurationValKonfigurations-Nummer
16iConfigurationString-Index für Konfiguration
17bmAttributesBit 7 = 1: Bus-powered (nur USB 1.1),
Bit 6 = 1: self-powered,
Bit 5 = 1: Gerät darf den Host aufwecken
18bMaxPowerbenötigter Strom (mA).

Interface-Descriptor

Ein Interface-Descriptor ist eine Art Header für die verschiedenen implementierten Funktionalitäten. Beispielsweise könnte ein Multifunktionsgerät Interfaces anbieten für das Fax, den Printer und den Scanner. Diese Interfaces könnnen alle gleichzeitig aktiv sein. Der Host kann jedoch zwischen den Interfaces umschalten. Tatsächlich können sogar mehrere Interfaces für die gleiche Funktionalität erstellt werden. Dazu gibt es im Interface-Descriptor nebst der Interfacenummer und der anzahl Entpoints noch eine Nummer für das nächste alternative Interface.

BytesOffsetNameInhalt
10bLengthGrösse des Descriptors in Bytes
11bDescriptorTypeDescriptor-Typ (0x04)
12bInterfaceNumberInterface-Nummer
13bAlternateSettingnächstes alternatives Interface
14bNumEndpointsAnzahl ENDP für dieses Interface (ohne ENDP 0)
15bInterfaceClassKlassen-Code
16bInterfaceSubClassSubklassen-Code
17bInterfaceProtocolProtokoll-Code. Die letzten drei Werte stehen für Treiber Informationen, die das System zu laden hat.
18iInterfaceString-Index für Index-Beschreibung.

Endpoint-Descriptor

Ein Endpoint-Descriptor speichert den Transfer-Typ, die Richtung, das Polling-Intervall und die maximale Paketgrösse. Der Endpoint 0 wird nicht durch einen Descriptor beschrieben, er ist standardisiert (Kontroll-Funktion).

BytesOffsetNameInhalt
10bLengthGrösse des Descriptors in Bytes
11bDescriptorTypeDescriptor-Typ (0x05)
12bEndpointAddressBit 7 ist 0 für einen Empfänger und 1 für einen Sender.
Die Bits 6 bis 4 sind reserviert (0).
Die Bits 3 bis 0 bezeichnen die Nummer des ENDP.
13bmAttributesBits 1 und 0 legen den Transfertyp fest:
00=Kontroll
01=Asynchron
10=Synchron (Bulk)
11=Interrupt
Bits 7 bis 2 sind grunsätzlich reserviert, ausser wenn Asynchron:
Bits 3 und 2: 00=Keine Synchronisation, 01=Asynchron, 10=Adaptiv, 11=Synchron
Bits 5 und 4: 00=Data Edpoint, 01=Feedback Endpoint, 10=Explicit Feedback Data Endpoint, 11=reserved
24wMaxPacketSizeMaximale Paketgrösse
16bIntervalNur für Asynchron und Interrupt:
Anzahl Frames, die zwischen den Pollings gewartet werden muss.

String-Descriptor

Darin werden die genauen Bezeichnungen der Geräte, Interfaces usw. festgehalten. Die Strings liegen in Unicode vor. Jeder Index, der in den oben aufgeführten Deskriptoren nicht benutzt wird, muss 0 enthalten. Der Index 0 beinhaltet folgende Definitionen:

BytesOffsetNameInhalt
10bLengthGrösse des Descriptors in Bytes
11bDescriptorTypeDescriptor-Typ (0x03)
22wLANGID[0]Code-Null-Sprache
24wLANGID[1]Code-Eins-Sprache
26wLANGID[2]Code-x-Sprache

Die Sprachen sind international für USB festgelegt. Die folgenden Deskriptoren definieren die eigentlichen Strings:

BytesOffsetNameInhalt
10bLengthGrösse des Descriptors in Bytes
11bDescriptorTypeDescriptor-Typ (0x03)
n2bStringString der Länge n

Seriell-USB-Konverter

Diese Bausteine werden für die Kommunikation zwischen PC und Mikrocontroller verwendet. Die Bausteine werden am USB-Bus als "Device" angeschlossen und stellen daher ein Endgerät dar. Man kann damit keine anderen USB-Endgeräte ansteuern, dazu braucht man einen richtigen USB-Host.

Die Firma FTDI bietet relativ preisgünstige Bausteine für den USB an, die die Kommunikation beispilsweise zwischen PC und Mikrocontroller ermöglichen, indem sie den physikalisch vorhandenen USB ausblenden und stattdessen eine wesentlich einfachere serielle Kommunikation ermöglichen. Dazu wird am PC ein virtueller COM-Port installiert und der Mikrocontroller an den Leitungen RXD und TXD des Wandlerchips angeschlossen. Am PC kann man nun, z. B. mit einem Terminalprogramm, den virtuelle COM-Port öffnen und Daten mit dem Mikrocontroller austauschen. Die bekanntesten USB-Seriell Wandlerchips sind der schon etwas ältere FT232BL und der FT232RL.

Die Abbildung zeigt eine Grundschaltung mit dem FT232, wobei in der Minimalversion das EEPROM und der Resonator auch entfallen können.

Der FT232 ist ein vollvertiger Seriell-USB-Konverter, der aber auch allein mit RXD und TXD gut funktioniert. So genügt es, die beiden seriellen Datenleitungen eines Mikrocontrollers anzuschließen, um über USB zu kommunizieren. Der Treiber zum FT232BM stellt eine virtuelle serielle Schnittstelle bereit, die genauso einfach so wie eine reale Schnittstelle angesprochen wird. Die Treiber werden unter Windows (ab XP) und unter Linux automatisch installiert. Neben der Seriell-Emulation kann der Baustein auch im sogenannten Bit-Bang-Modus getrieben werden. Hier verhalten sich die Anschlüsse wie ein bidirektionaler 8-Bit-Parallelport.

Wie der FT232 genau anzuschliessen ist, hängt von der geplanten Verwendung ab. Beispielsweise ist zum Abspeichern von geänderten USB-Parametern wie der nominellen Stromaufnahme oder einer individuellen Gerätekennung ein EEPROM nötig und bei 3,3-V-Mikrocontrollern ist eine zweite Versorgungsspannung notwendig. Deshalb wird für Details der Schaltung auf die Herstellerseite verwiesen, auf der es neben dem Datenbuch auch einen Designer Guide gibt. Zudem findet man online viele Schaltpläne mit dem FT232, beispielsweise bei Olimex (http://www.olimex.com/). Das Programm, mit dem man die Daten im EEPROM auslesen und ändern kann, beispielsweise die nominelle Stromaufnahme, gibt es kostenlos bei FTDI (http://www.ftdichip.com/FTProducts.htm).

Für den noch einfacheren Einsatz bietet FTDI auch zwei Variante in einen 9-poligen Sub-D-Stecker und einer -Buchse an, Damit kann ohne Änderung am Platinenlayout eines Boards auf USB umgerüstet werden. Ausgangsseitig bieten sie einen USB-Anschluss mit einer USB-Buchse vom Typ "Mini-B". Die integrierte Logik konvertiert den Datenverkehr von USB auf das serielle Format und umgekehrt. Auf diese Weise kann man sehr einfach Geräte mit USB-Anschlüssen ausstatten, ohne schaltungstechnisch oder bezüglich Firmware etwas ändern zu müssen. Diese Eigenschaft macht solche Module gerade auch für den Selbstbau interessant. Es gibt die Module in zwei Ausführungen, um neunpolige serielle Buchsen und Stecker für Platinenmontage zu ersetzen.

Die Konverter-Module enthalten die notwendige Elektronik, um eine vollständige USB/RS232-Konversion zu erledigen. Die Elektronik der Module basiert auf dem FTDI-Chip FT232R, einem Konverter von USB 2.0 nach seriellem UART, der das komplette USB-Protokoll selbstständig abhandelt. Die Elektronik im Modul wird von der externen USB-Verbindung versorgt. Die Module erlauben eine maximale serielle Transferrate von bis zu 1 Mbit/s, die also in der Regel lediglich durch die RS232-Elektronik des Geräts beschränkt ist, das mit solch einem Modul ausgestattet wird.

Weitere Info unter DB9-USB-RS232-Modul-Datenblatt

Eine weiter Variante ist ein im USB-Stecker eingegossener FT232, dessen Anschlüsse als Kabel herausgeführt sind.

Informationen über den USB einholen

Diese Information kann man auch per Programm erhalten, wenn man sich des Pseudodateisystems /proc bedient. Es handelt sich dabei um Informationen aus dem Betriebssystemkern, die auf eine Datei- und Verzeichnisstruktur abgebildet werden, damit man mit einfachen Dateifunktionen der Programmiersprache darauf zugreifen kann. Für jeden laufenden Prozess gibt es beispielsweise ein Unterverzeichnis. Ebenso finden Sie hier Infos über Speicher, CPU und E/A-Adressen. Die USB-Infos befinden sich unterhalb von /proc/bus/usb.

Sollte dies Verzeichnis leer sein, ist die Geräteschnittstelle der USB-Devices nicht ins /proc-Verzeichnis abgebildet worden. Dem können Sie leicht abhelfen, indem Sie die Datei /etc/fstab um die folgende Zeile ergänzen und danach mount -a als root aufrufen.

usbfs   /proc/bus/usb   usbfs   devmode=0664,auto   0 0
Nach einen Neustart wird das Verzeichnis dann immer automatisch eingebunden. Das Dateisystem usbfs ist wie proc ein rein virtuelles Dateisystem. Interessant ist die nun in /proc/bus/usb zu findende Datei devices, die beispielsweise folgenden Inhalt hat:
T:  Bus=04 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#=  1 Spd=12  MxCh= 2
B:  Alloc=  0/900 us ( 0%), #Int=  0, #Iso=  0
D:  Ver= 1.10 Cls=09(hub  ) Sub=00 Prot=00 MxPS=64 #Cfgs=  1
P:  Vendor=1d6b ProdID=0001 Rev= 2.06
S:  Manufacturer=Linux 2.6.28-18-server uhci_hcd
S:  Product=UHCI Host Controller
S:  SerialNumber=0000:00:1d.2
C:* #Ifs= 1 Cfg#= 1 Atr=e0 MxPwr=  0mA
I:* If#= 0 Alt= 0 #EPs= 1 Cls=09(hub  ) Sub=00 Prot=00 Driver=hub
E:  Ad=81(I) Atr=03(Int.) MxPS=   2 Ivl=255ms
T:  Bus=03 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#=  1 Spd=12  MxCh= 2
B:  Alloc=  0/900 us ( 0%), #Int=  0, #Iso=  0
D:  Ver= 1.10 Cls=09(hub  ) Sub=00 Prot=00 MxPS=64 #Cfgs=  1
P:  Vendor=1d6b ProdID=0001 Rev= 2.06
S:  Manufacturer=Linux 2.6.28-18-server uhci_hcd
S:  Product=UHCI Host Controller
S:  SerialNumber=0000:00:1d.1
C:* #Ifs= 1 Cfg#= 1 Atr=e0 MxPwr=  0mA
I:* If#= 0 Alt= 0 #EPs= 1 Cls=09(hub  ) Sub=00 Prot=00 Driver=hub
E:  Ad=81(I) Atr=03(Int.) MxPS=   2 Ivl=255ms
T:  Bus=02 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#=  1 Spd=12  MxCh= 2
B:  Alloc=  0/900 us ( 0%), #Int=  0, #Iso=  0
D:  Ver= 1.10 Cls=09(hub  ) Sub=00 Prot=00 MxPS=64 #Cfgs=  1
P:  Vendor=1d6b ProdID=0001 Rev= 2.06
S:  Manufacturer=Linux 2.6.28-18-server uhci_hcd
S:  Product=UHCI Host Controller
S:  SerialNumber=0000:00:1d.0
C:* #Ifs= 1 Cfg#= 1 Atr=e0 MxPwr=  0mA
I:* If#= 0 Alt= 0 #EPs= 1 Cls=09(hub  ) Sub=00 Prot=00 Driver=hub
E:  Ad=81(I) Atr=03(Int.) MxPS=   2 Ivl=255ms
T:  Bus=01 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#=  1 Spd=480 MxCh= 6
B:  Alloc=  0/800 us ( 0%), #Int=  0, #Iso=  0
D:  Ver= 2.00 Cls=09(hub  ) Sub=00 Prot=00 MxPS=64 #Cfgs=  1
P:  Vendor=1d6b ProdID=0002 Rev= 2.06
S:  Manufacturer=Linux 2.6.28-18-server ehci_hcd
S:  Product=EHCI Host Controller
S:  SerialNumber=0000:00:1d.7
C:* #Ifs= 1 Cfg#= 1 Atr=e0 MxPwr=  0mA
I:* If#= 0 Alt= 0 #EPs= 1 Cls=09(hub  ) Sub=00 Prot=00 Driver=hub
E:  Ad=81(I) Atr=03(Int.) MxPS=   4 Ivl=256ms
Vor den jeweiligen Zeilen in der Datei stehen Buchstaben, die die Information in dieser Zeile genauer definieren.

T     Einleitung der Gerätedefinition und Position des Geräts in der USB-Topologie.
B     Die Bandbreite des Geräts. Dieser Wert wird nur für Wurzel-Hubs angegeben.
D     Beschreibung des Geräts aus der Geräteinformation.
P     Beschreibung des Herstellers aus der Geräteinformation.
S     Beschreibung des Geräts im Klartext.
C     Informationen über die Konfiguration (* bedeutet "aktive" K.).
I     Informationen über die Schnittstelle.
E     Informationen über die Endpunkteigenschaften.

Sehen Sie sich doch einmal alle Zeilen an, die mit "T" und "S" beginnen:

T:  Bus=04 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#=  1 Spd=12  MxCh= 2
S:  Manufacturer=Linux 2.6.28-18-server uhci_hcd
S:  Product=UHCI Host Controller
S:  SerialNumber=0000:00:1d.2

T:  Bus=03 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#=  1 Spd=12  MxCh= 2
S:  Manufacturer=Linux 2.6.28-18-server uhci_hcd
S:  Product=UHCI Host Controller
S:  SerialNumber=0000:00:1d.1

T:  Bus=02 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#=  1 Spd=12  MxCh= 2
S:  Manufacturer=Linux 2.6.28-18-server uhci_hcd
S:  Product=UHCI Host Controller
S:  SerialNumber=0000:00:1d.0

T:  Bus=01 Lev=00 Prnt=00 Port=00 Cnt=00 Dev#=  1 Spd=480 MxCh= 6
S:  Manufacturer=Linux 2.6.28-18-server ehci_hcd
S:  Product=EHCI Host Controller
S:  SerialNumber=0000:00:1d.7
Mit dem Anfangsbuchstaben "T" begint eine neue Gerätedefinition. In der Zeile stehen Informationen, die einen genaueren Einblick in die Topologie der USB-Geräte ermöglichen: Die mit "S" beginnenden Zeilen liefern herstellerbezogene Textinformationen.

Die mit "D" beginnenden Zeilen informieren über den Device-Descriptor. Ver legt fest, welche Spezifikationen das Gerät unterstützt, und Cls bezeichnet die Geräteklasse. Ist diese größer als 00, ist die Klasse vom Interface abhängig. Sub spezifiziert die Unterklasse des Geräts. Prot legt das unterstützte Protokoll fest und MxPS die maximale Paketgröße der Daten vom Endpunkt 0. Schließlich erfährt man durch \#Cfgs, wie viele Konfigurationsvarianten das Gerät erlaubt.

Die mit "P" beginnenden Zeilen informieren ebenfalls über den Device-Descriptor, jedoch sind hier die Vendor-Id und die Produkt-Id sowie die Revisionsnummer aufgelistet.

"C"-Zeilen liefern den Configuration-Descriptor für jede mögliche Konfiguration. Es gibt also \#Cfgs Zeilen, wobei die aktive Konfiguration durch ein Sternchen markiert ist. Der Wert \#If gibt die Anzahl der Interfaches und Cfg\# die Nummer der beschriebenen Konfiguration an. Atr listet hexadezimal die Attribute (0x80: bus-powered, 0x40: self-powered, 0x20: remote-wake-up möglich). Schliesslich gibt MxPwr die maximal vom Gerät benötigte Leistung an.

Für jedes Interface der "C"-Zeilen geben die "I"-Zeilen die Spezifikationen an. If\# bezeichnet die Interface-Nummer, Alt die Einstellungsalternative. \#Ep enthält die Anzahl der Endpoints, Cls die jeweilige Geräteklasse und Sub die Unterklasse. Prot gibt das Protokoll für die aufgelistete Klassen-/Unterklassenkombination an.

Für jeden in der "I"-Zeile angegebenen Endpoint folgt eine "E"-Zeile. Nur der Endpoint 0 wird nicht angezeigt. In dieser Zeile wird mit Atr der verwendete Transfertyp und mit Ad die Endpoint-Adresse bezeichnet, hinter der in Klammern angegeben wird, ob es sich um einen In- oder Out-Endpoint handelt. MxPS gibt wieder die maximale Paketgröße der Daten an, die der jeweilige Endpunkt senden oder empfangen kann. Ivl spezifiziert die Wartezeit zwischen zwei aufeinanderfolgenden Interrupt-Transfers in ms. Der Wert wird für isochronen Transfer auf 1 gesetzt und bei Bulk-Transfers ignoriert.

Mit diesem Wissen kann man schon relativ viel Info aus der devices-Datei herausholen. Das folgende Programm implementiert beispielhaft eine Variante von lsusb. Es zeigt, wie mit der Information umgegangen werden und wie man für eigene Anwendungen den Bus erforschen kann.

use strict;
use warnings;

my $DEVFILENAME = "/proc/bus/usb/devices";

my $showconfig = "yes";
my ($bus, $level, $parent, $port, $count, $devnum, $speed,
    $maxchild, $devclass, $intclass, $driver, $ifnum, $ids,
    $showclass, $lastif, $HCtype, $nconfig, $HC, $product, $temp);
my @fields;

open(DEVNUM, "<$DEVFILENAME") or
  die "$0: cannot open '$DEVFILENAME'\n";

while (my $line = <DEVNUM>)
  {
  # skip all lines except those we recognize
  next if ($line !~ "^[CDISPT]:");
  chomp $line;

  # First convert '=' signs to spaces.
  $line =~ tr/=/ /;
  # and convert all '(' and ')' to spaces.
  $line =~ tr/(/ /;
  $line =~ tr/)/ /;
  # split the line at spaces.
  @fields = split / +/, $line;

  # T opology:  Bus=01 Lev=01 Prnt=01 Port=03 Cnt=01 
                                        Dev#=  3 Spd=1.5 MxCh= 0
  if ($line =~ "^T:")
    {
    # split yields: $bus, $level, $parent, $port, $count, 
                                        $devnum, $speed, $maxchild.
    $bus    = $fields [2];
    $level  = $fields [4];
    $parent = $fields [6];      # parent devnum
    $port   = $fields [8] + 1;  # make $port 1-based
    $count  = $fields [10];
    $devnum = $fields [12];
    $speed  = $fields [14];
    $maxchild = $fields [16];
    $devclass = "?";
    $intclass = "?";
    $driver   = "?";
    $ifnum    = "?";
    $showclass = "?";           # derived from $devclass or $intclass
    $lastif = "?";              # show only first altsetting
    $HCtype = "?";
    $nconfig = "0";
    $showconfig = "no";
    next;
    }

  # C onfiguration: * #Ifs= 1 Cfg#= 1 Atr=a0 MxPwr=100mA
  if ($line =~ "^C:")
    {
    if ($line =~ "^C:\\*")
      {  $showconfig = $fields[4]; }
    else { $showconfig = "no"; }
    next;
    }

  # D evice:  Ver= 1.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 8 #Cfgs=  1
  if ($line =~ "^D:")
    {
    $devclass = $fields [5];
    $nconfig = $fields [13];
    next;
    }

  # P roduct ID:  Vendor=0aa7 ProdID=0304 Rev=0.52
  if ($line =~ "^P:")
    {
    $ids = $line;
    $ids =~ s/P: +//;
    $ids =~ s/ +/ /g;
    $ids =~ s/Vendor /Vendor=/g;
    $ids =~ s/ ProdID /, ProdID=/g;
    $ids =~ s/ Rev /, Rev=/g;
    next;
    }
  # in case this is a root hub, look at the device strings.
  if ( $line =~ "^S:" )
    { # for S: line
    if ($level == 00 && $line =~ "hcd")
      { $HCtype = $fields [4]; }
    elsif ($level == 00 && $line =~ "HCI" && $HCtype eq "?")
      { $HCtype = $fields [3]; }
    if ($line =~ "Product")
      {
      my $product = $line;
      $product =~ s/Product //;
      $product =~ s/S: +//;
      $product =~ s/ +/ /g;
      $product =~ s/ $//g;
      }
    next;
    }
  # only shows interface descriptors
  # for the active configuration and
  # for the first (prefer: active!) altsetting
  next if (($line !~ "^I:") || ("$showconfig" eq "no"));

  # I nterface:  If#=0 Alt=0 #EPs= 1 Cls=03(HID) Sub=01 Prot=02 Driver=hid
  $intclass = $fields [9];
  $ifnum    = $fields [2];
  $driver   = $fields [15];

  if (($devclass eq ">ifc") || ($devclass eq "unk."))
    {  # then use InterfaceClass, not DeviceClass
    $showclass = $intclass;
    }
  else
    {  # use DeviceClass
    $showclass = $devclass;
    }

  if ($level == 0)
    {
    if ($HCtype =~ "UHCI-alt")
      { $HC = "uhci"; }
    elsif ($HCtype =~ "UHCI")
      {  $HC = "usb-uhci"; }
    elsif ($HCtype =~ "OHCI")
      {  $HC = "usb-ohci"; }
    else
      {  $HC = $HCtype; }
    printf("/: Bus %s.Port %s: Dev %s, Class=root_hub, Drv=%s/%sp, %sM\n",
           $bus, $port, $devnum, $HC, $maxchild, $speed );
    }
  elsif ($lastif ne $ifnum)
    {
    $temp = $level;
    while ($temp >= 1)
      {
      print "    ";
      $temp--;
      }

    if ($nconfig ne "1")
      { $temp = " Cfg $showconfig/$nconfig"; }
    else
      { $temp = "";  }
    printf("|_ Port %s: Dev %s%s, If %s, Prod=%s, Class=%s, Drv=s%s, "
           $port, $devnum, $temp, $ifnum, $product, $showclass, $driver);
    printf("%sM, %s\n", 
           ($maxchild == 0) ? "" : ("/" . $maxchild . "p"), $speed, $ids);
    $lastif = $ifnum;
    }
  $product = "";
  }

close (DEVNUM);
Die Ausgabe des Programms könnte beispielsweise folgendermaßen aussehen:
/: Bus 00.Port 1: Dev 1, Class=root_hub, Drv=?/2p, 12M
    |_ Port 2: Dev 2, If 0, Prod=, Class=hub, Drv=shub, /4pM, 12
        |_ Port 1: Dev 3, If 1, Prod=, Class=vend., Drv=scpia, M, 12
/: Bus 01.Port 1: Dev 1, Class=root_hub, Drv=?/2p, 12M
    |_ Port 2: Dev 2, If 0, Prod=, Class=hub, Drv=shub, /4pM, 12
        |_ Port 1: Dev 3, If 1, Prod=, Class=vend., Drv=scpia, M, 12
        |_ Port 3: Dev 5, If 0, Prod=, Class=HID, Drv=shid, M, 1.5
/: Bus 02.Port 1: Dev 1, Class=root_hub, Drv=usb-uhci/2p, 12M
Um die USB-Programme auch als normaler User laufen zu lassen, kann man -- sofern nicht schon vorhanden -- eine Gruppe "usb" erzeugen (mittels addgroup oder group\-add) und die Zeile in /etc/fstab erweitern:
usbfs   /proc/bus/usb   usbfs   devmode=0664,devgid=xxx,auto   0 0
Das "xxx" ersetzen Sie durch die Gruppennummer der Gruppe "usb".

Bei Debian/Ubuntu ergänzen Sie noch in /etc/udev/permissions.rules die entsprechende SUBSYSTEM-Zeile:

SUBSYSTEM=="usb_device", MODE="0664", GROUP="usb"

USB über die libusb ansteuern

Zur Ansteuerung des USB mit eigenen Programmen gibt es die libusb, eine in C geschriebene Programmbibliothek zum Schreiben und Lesen auf USB-Geräte. Dabei kommt libusb unter Linux ohne eigene Kernelmodule aus. libusb ist zurzeit für Linux, BSD, und Mac OS X verfügbar. Es existiert außerdem eine Portierung auf Windows-Systeme, die libusb-win32, die aber noch sehr experimentell ist.

Libusb ist also eine High-Level-API, welche die Low-Level-Kernel-Interaktionen der USB-Module verbirgt. Es bietet eine Reihe von Funktion, die ausreichen, um einen Gerätetreiber für ein USB-Gerät im Userspace entwickeln. Die so vereinfachte Schnittstelle ermöglicht es Entwicklern, USB-Programme aus dem Userspace zu entwickeln. Verwirrend für den Einsteiger ist, dass es zwei Versionen nebeneinander gibt, die zudem eine unterschiedliche API haben:

Des Weitern existiert USBFS, ein Dateisystem speziell für USB-Geräte. Per Default wird dieses Dateisystem eingebunden, wenn das System gestartet wird. Es kann unter /proc/bus/usb/ gefunden werden. Dieses Dateisystem besteht aus Informationen über alle USB-Geräte, die mit dem Computer verbunden sind. Libusb macht von diesem Dateisystem Gebrauch, um mit den USB-Geräten zu interagieren. Wie das Perl-Beispiel weiter oben zeigt, kann man hier schon sehr viele Informationen finden.

Jedes USB-Gerät hat seine eigene Konfigurationswerte (Hersteller-ID, Produkt-ID, etc.). Man verwendet diese Einstellungen, um das gewünschte Gerät zu detektieren und dann anzusprechen. Zuerst muss also eine Funktion geschrieben werden, die nach einem bestimmten Gerät sucht; die grundlegenden Operationen dafür sind:

  1. Initialisieren der Bibliothek durch Aufruf der Funktion libusb_init() und Erstellen einer Sitzung mit dem Device.
  2. Mittels libusb_get_device_list() bekommt man eine Liste der angeschlossenen Geräte.
  3. In einer Schleife wird die Liste aller Devices durchlaufen und die Optionen werden abgefagt und überprüft.
  4. Ist das gewünschte Device gefunden, öffnen Sie eine Device-Handle entweder durch libusb_open() oder libusb_open_device_with_vid_pid() (wenn Sie Vendor- und Produkt-ID des Geräts kennen).
  5. Geben Sie die Liste, die Sie mit Hilfe von libusb_get_device_list() erhalten haben, mittels libusb_free_device_list() wieder frei.
  6. Fordern Sie das Interface mit libusb_claim_interface() an. Gegebenfalls muss das Device vom generischen Kerneltreiber gelöst werden.
  7. Nun kann das Programm mit dem USB-Device kommunizieren.
  8. Geben sie das Device mit Hilfe von libusb_release_interface() wieder frei.
  9. Schließen Sie das Device mittels libusb_close().
  10. Beenden Sie Sie die Sitzung mit libusb_exit().
OK, jetzt haben Sie die Grundidee, wie Sie mit einem USB-Gerät programmtechnisch umgehen. Mir ist auch klar, dass der Kreis derjenigen, die sich in die komplexe Programmierung der USB-Schnittstelle stürzen, nicht unbedingt bei den Perl-Programmierern, sondern eher bei Adepten der Programmiersprachen C oder C++ zu finden ist. Auch würde eine ausführliche Beschreibung der libusb genügend Stoff für ein eigenes Buch bilden. Man merkt die C-Affinität auch daran, dass das entsprechende Perl-Modul im Prinzip nur ein Wrapper um die libusb herum ist. Ich belasse es daher bei einer kurzen Beschreibung des Perl-Moduls mit einigen einfachen Beispielen bewenden:

new erzeugt ein neues Device::USB-Objekt.

debug_mode erlaubt das Lowlevel-Debugging der Nachrichten, die von der libusb kommen. Der Parameter 0 schaltet das Debugging ab, 1 und 2 legen den Pegel der Geschwätzigkeit fest.

find_busses gibt die Anzahl der neu hinzugekommenen oder entfernten Busse zurück.

find_devices gibt die Anzahl der neu hinzugekommenen oder entfernten Devices zurück. Sollte nach find_busses aufgerufen werden.

find_device sucht nach einem bestimmten USB-Device. Als Parameter werden Ven\-dor-Id und Product-Id übergeben. Gib es mehrere identische Devices, wird nur die Referenz auf das erste zurückgegeben. Fall kein passendes Device existiert, gibt die Methode undef retour.

find_device_if sucht nicht nach den Vendor- und Product-Ids, sondern es wird der Methode eine Code-Referenz übergeben. Für jedes Device wird der Code ausgeführt. Liefert der Code einen wahren Wert, wird eine Referenz auf das Device zurückgegeben.

list_devices listet alle Devices zu einer bestimmten Kombination von Vendor- und Product-Id, die als Parameter übergeben werden. Wird die Methode ohne Parameter aufgerufen, liefert die Methode alle Devices. Ein Aufruf ohne Product-Id liefert alle Devices zur angegebenen Vendor-Id.

list_devices_if listet alle Devices entsprechend einem als Referenz übergebenen Code auf (wie bei find_device_if). Das folgende Beispiel liefert alle USB-Hubs:

   my @devices = $usb->list_devices_if(
     sub { Device::USB::CLASS_HUB == $_->bDeviceClass() }
    );

list_busses liefert eine komplette Liste aller Busse und Devices und erspart somit die Aufrufe der entsprechenden find_xxx-Methoden. Die Methode liefert ein Array von Bussen.

get_busses wird nach find_busses und find_devices aufgerufen und liefert eine Referenz auf ein Array aller Busse.

Das erste Beispiel listet die verfügbaren Busse auf:

use strict;
use warnings;
use Device::USB;

my $usb = Device::USB->new();

foreach my $bus ($usb->list_busses())
  {
  print $bus->dirname(), ":\n";
  foreach my $dev ($bus->devices())
    {
    print "     ", $dev->filename(), "\n";
    }
  }
Die Ausgabe ist recht unspektakulär:
005:
    002
    001
004:
    001
003:
    001
002:
    001
001:
    001
Das folgende Miniprogramm listet alle Vendor-Ids und Product-Ids auf:
use strict;
use warnings;
use Device::USB;

my $usb = Device::USB->new();
my $dev;

foreach($usb->list_devices())
  {
  $dev = $usb->find_device($_->idVendor(), $_->idProduct());
  printf "Device: %04X:%04X\n", $dev->idVendor(), $dev->idProduct();
  }
Die Ausgabe ist wieder sehr spartanisch:
Device: 04F3:04A0
Device: 0409:0058
Device: 0000:0000
Device: 0000:0000
Device: 0000:0000
Will man nach einem bestimmten Device suchen, verwendet man find_device() wie im folgenden Programm, das mit Hilfe des Data-Dumper-Moduls die Device-Referenz auch gleich umfangreich auflistet. Wird das Programm ohne Parameter aufgerufen, werden alle USB-Devices und -Busse gedumpt. Die Ausgabe ist nicht sehr benutzerfreundlich, aber umfangreich. Mit Vendor- und Product-Id als Parameter (entweder dezimal oder hexadezimal mit führender 0) aufgerufen wird nur das entsprechende Device gedumpt.
use strict;
use warnings;
use Device::USB;
use Data::Dumper;

my $usb = Device::USB->new();
$Data::Dumper::Indent = 1;

if(@ARGV)
  {
  my $dev = $usb->find_device(map{/^0/xm ? hex($_):$_} @ARGV[0,1]);
#  my $dev = $usb->find_device(hex($ARGV[0]), hex($ARGV[1]));
  die "Device not found.\n" unless defined $dev;

  print "Device: ", $dev->filename(), "\n";
  printf "     ID hex %04x:%04x\n", $dev->idVendor(), $dev->idProduct();
  printf "     ID dec %5d:%5d\n", $dev->idVendor(), $dev->idProduct();
  print Dumper( $dev );
  }
else
  { print Dumper( [ $usb->list_busses() ] ); }
Man kann dem Bussystem aber noch viel mehr Informationen entlocken, wie das folgende Listing zeigt. Hier werden etliche libusb-Funktionen aufgerufen, deren Dokumentation nicht in den Dateien zu Device::USB, sondern bei Device::USB::Device zu finden sind. Damit man halbwegs erkennt, um welche Daten es geht, ist der Output etwas geschönt:
use strict;
use warnings;
use Device::USB;

my $usb = Device::USB -> new();
my $dev = $usb -> find_device (0x067B, 0x2303);

die "Device not found.\n" unless defined $dev;

print "Device: ", $dev->filename(), "\n";
printf "ID hex  %04x:%04x\n", $dev->idVendor(), $dev->idProduct();
printf "ID dec %5d:%5d\n\n", $dev->idVendor(), $dev->idProduct();

show('bcdUSB', $dev -> bcdUSB());
show('bDeviceClass', $dev -> bDeviceClass());
show('bDeviceSubClass', $dev -> bDeviceSubClass());
show('bDeviceProtocol', $dev -> bDeviceProtocol());
show('bMaxPacketSize0', $dev -> bMaxPacketSize0());
show('idVendor', sprintf('0x%04X',$dev -> idVendor()));
show('idProduct', sprintf('0x%04X',$dev -> idProduct()));
show('bcdDevice', $dev -> bcdDevice());
show('iManufacturer', $dev -> iManufacturer());
show('iProduct', $dev -> iProduct());
show('iSerialNumber', $dev -> iSerialNumber());
show('bNumConfigurations', $dev -> bNumConfigurations());

for my $i (0 .. $dev -> bNumConfigurations()-1)
  {
  show("\n[configuration $i]", '');
  my $cfg = $dev -> get_configuration ( $i);
  show('wTotalLength', $cfg -> wTotalLength());
  show('bNumInterfaces', $cfg -> bNumInterfaces());
  show('bConfigurationValue', $cfg -> bConfigurationValue());
  show('iConfiguration', $cfg -> iConfiguration());
  show('MaxPower', $cfg -> MaxPower() . ' mA');
  }

sub show
  {
  my ($key, $val) = @_;
  $key = substr ($key . ' 'x22, 0, 22);
  print ( $key, $val, "\n");
  }
Angewendet auf einen USB-zu-seriell-Konverter liefert das Programm folgende Informationen:
Device: 002
ID hex  067b:2303
ID dec  1659: 8963

bcdUSB                1.10
bDeviceClass          0
bDeviceSubClass       0
bDeviceProtocol       0
bMaxPacketSize0       64
idVendor              0x067B
idProduct             0x2303
bcdDevice             3.00
iManufacturer         1
iProduct              2
iSerialNumber         0
bNumConfigurations    1

[configuration 0]
wTotalLength          39
bNumInterfaces        1
bConfigurationValue   1
iConfiguration        0
MaxPower              100 mA
Das folgende C-Programm leistet etwas Ähnliches wie die obigen Perl-Tools. Es soll Ihnen etwas der Schrecken vor der Libusb nehmen. Auch dieses Programm listet Hardware-Details von USB-Geräten, die am Computer angeschlossen sind. Zum Übersetzen verwendie Sie das Kommando gcc -Wall -lusb-1.0 -o usb-test usb-test.c.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <libusb-1.0/libusb.h>

int main (int argc, char *argv)
   {
   libusb_device                    **devList = NULL;
   libusb_device                    *devPtr = NULL;
   libusb_device_handle             *devHandle = NULL;
   struct libusb_device_descriptor  devDesc;

   unsigned char strDesc[256];
   ssize_t       numUsbDevs = 0; 
   ssize_t       idx = 0;
   int           retVal = 0;

   // Sitzung eroeffnen, libusb initialisieren
   retVal = libusb_init (NULL);

   // Liste der angeschlossenen Geraete holen und durchlaufen
   numUsbDevs = libusb_get_device_list (NULL, &devList);
   idx = 0;
   while (idx < numUsbDevs)
      {
      printf ("\n[%d]\n", idx+1);
      // naechstes Device (Pointer) aus der Liste holen
      devPtr = devList[idx];

      // Device oeffnen
      retVal = libusb_open (devPtr, &devHandle);
      if (retVal != LIBUSB_SUCCESS) break;

      // device descriptor lesen
      retVal = libusb_get_device_descriptor (devPtr, &devDesc);
      if (retVal != LIBUSB_SUCCESS) break;

      Vendor- und Product-ID  sowie Seriennummer ermitteln
      printf ("   iManufacturer = %d\n", devDesc.iManufacturer);
      if (devDesc.iManufacturer > 0)
         {
         retVal = libusb_get_string_descriptor_ascii
                  (devHandle, devDesc.iManufacturer, strDesc, 256);
         if (retVal < 0) break;
         printf ("   string = %s\n",  strDesc);
         }
      printf ("   iProduct = %d\n", devDesc.iProduct);
      if (devDesc.iProduct > 0)
         {
         retVal = libusb_get_string_descriptor_ascii
                  (devHandle, devDesc.iProduct, strDesc, 256);
         if (retVal < 0) break;
         printf ("   string = %s\n", strDesc);
         }
      printf ("   iSerialNumber = %d\n", devDesc.iSerialNumber);
      if (devDesc.iSerialNumber > 0)
         {
         retVal = libusb_get_string_descriptor_ascii
                  (devHandle, devDesc.iSerialNumber, strDesc, 256);
         if (retVal < 0) break;
         printf ("   string = %s\n", strDesc);
         }
      
      // Device schliessen 
      libusb_close (devHandle);
      devHandle = NULL;
      // zum naechsten gehen
      idx++;
      }  // end of while loop

   // device schliessen, falls mit break aus der Schleife 
   // gesprungen wurden
   if (devHandle != NULL)
      { libusb_close (devHandle); }   

   // Sitzung beenden
   libusb_exit (NULL);
   return 0;
   }
Inzwischen sind bei Linux und Windows die Treiber für alle möglichen und unmöglichen Geräte bereits integriert, sodass eine direkte Programmierung der USB-Schnittstelle nur selten nötig sein dürfte. Ein tieferer Einstieg in die Programmierung mit der libusb würde auch, wie schon erwähnt, den Rahmen dieses Buches sprengen. Deshalb skizziert das folgende Listing nur die prinzipielle Vorgehensweise bei einem USB-Programm. Der schwierigste Teil ist die Initialisierung, da dort etliche Aktionen notwendig sind. Danach kann das Device mit den, im Modul Device::USB::Device dokumentierten Methoden (bulk_read(), control_msg() usw.) angesprochen werden. Beachten Sie auch, dass bei etlichen Lese- und Schreiboperationen der Datenpuffer \zb mit Nullbytes vorbesetzt sein muss:
# Device-spezifische Werte festlegen
my $VENDOR = 0x4711;
my $PRODUCT = 0x0042;
my $CONFIG = 1;
my $INTERFACE = 0;
my $MAX_INTERFACES = 2;

# Hauptprogramm
my $dev = init();   # siehe unten
 
   ...

# Beispiel fuer eine Control-Message
$dev->control_msg($REQTYP, $REQ, $VAL, 0, $data, $SIZE, $TIMEOUT);

   ...

# Beispiel fuer Lesen
my $buf = 0;
$dev->bulk_read(1, $buf, 1, 1000);

   ...

# Am Ende Interface freigeben
$dev->release_interface($INTERFACE);
exit 0;

sub init
  {
  # Bist Du root, hast Du's gut
  die "You have to be root ...\n" if($< != 0);
  
  # Device anlegen und suchen
  my $usb = Device::USB->new();
  my $dev = $usb->find_device($VENDOR, $PRODUCT);
  die "USB Device not found!\n" unless ($dev);

  # Treiber-Detach
  for my $interface (0 .. $MAX_INTERFACES) 
    {
    my $kdrv = $dev->get_driver_np($interface);
    if ($kdrv) 
      {
      if ($dev->detach_kernel_driver_np($interface) < 0) 
        { die "Cannot detach kernel driver '$kdrv'\n"; }
      }

  # Device oeffnen
  $dev->open() or die "Error opening USB device: $!\n";

  # Konfiguration waehlen (meist 0)
  if ($dev->set_configuration($CONFIG) < 0) 
    {  die "Error setting configuration $CONFIG: $!\n"; }
  
  # Interface reservieren
  if ($dev->claim_interface($INTERFACE) < 0) 
    { die "Error claiming interface $INTERFACE: $!\n"; }
  return ($dev);
  }

Auslesen des JoyWarrior 24F8

Schon länger lagen in meinem Labor einige JoyWarrior-Module 24F8 herum, zu denen zwar damals eine Windows-Software mitgeliefert wurde und die beim Anstecken an den Linux-PC auch erkannt wird. Aber so recht wollten die Module nicht so, wie ich wollte. So bildeten sie willkommene Testobjekte für erste Schritte bei der USB-Programmierung.

Der JoyWarrior 24F8 (Hersteller Code Mercenaries) ist ein hochauflösender Beschleunigungssensor mit USB-Schnittstelle. Das Modul eignet sich für Beschleunigungs- und Lagemessungen und für die Erfassung niederfrequenter Vibrationen. Die Nutzung erfolgt normalerweise als Eingabegerät (HID, Human Interface Device) an einem PC. Durch das geringe Gewicht und die kompakte Bauform können diese OEM-Module leicht in Geräte integriert werden. Der Sensor wird als Modulplatine mit USB-Interface-Chip und Beschleunigungssensor (Bosch BMA380 bzw. BMA180) geliefert, man muss nur noch ein USB-Kabel anlöten.

Ein direkt auf der Platine installierter MEMS-Sensor (MEMS = Micro Electro Mechanical System) dient zur Messung von Bescheunigungswerten in allen drei Achsrichtungen. Der Messbereich des JoyWarrior24F8 kann zwischen ±2 g und ±8 g variiert werden (beim JoyWarrior24F14 sogar zwischen ±1 g und ±16 g). Die Messungen erfolgen mit 10 bzw. 14 Bit Auflösung pro Achse und mit einer Rate von bis zu 125 Messwerten pro Sekunde.

Das USB-Interface ermöglicht den einfachen Anschluß an einen PC. Das Betriebssystem des Steuerrechners erkennt das Modul selbständig als Eingabegerät mit Hilfe der systemeigenen HID-Treiber. Der Sensor identifiziert sich gegenüber dem Host als Joystick bzw. Gamecontroller und erlaubt somit den einfachen Zugriff auf die Daten. Die Softwareunterstützung für Windows und MacOS X umfasst ein Konfigurationstool für die Einstellung der Sensorparameter, einen Neigungswinkelmesser, einen Datenrekorder, ein Kalibrierungstool und einige Beispielprogramme. In der JoyWarrior-Familie gibt es auch Joystick/Maus-Hybride, die wahlweise als Maus oder als Joystick funktionieren.

Die JoyWarrior Familie ist in der Lage, Anforderungen vom einfachen Gamecontroller bis zum hochwertigen industriellen Eingabegerät abzudecken. Die einfachsten JoyWarrior-Module unterstützen auch Taster, wie sie bei einem Gamepad benutzt werden. Eine Reihe von Standardvarianten ist verfügbar, um die häufigsten Anwendungen abzudecken.

Nun beginnt das Abenteuer. Ich wollte unter Linux den JoyWarrior direkt ansprechen und die Messwerte für die drei Achsen auslesen. Erstes Handicap war, dass dies auch gleichzeitig meine ersten Schritte mit der Libusb waren und ich auch bald gemerkt habe, dass ich noch viel zu wenig über das USB-Protokoll weiss. Aber wie heisst das alte Motto? "Beg, borrow, buy or steal software before you write it yourself!". Die Beispielprogramme gaben einige Infos her, aber nicht genug. Erhellendes kam aus dem Datenblatt des Sensors von Bosch. Die "global memory map" lieferte die Erkenntnis, dass die gesuchten Informationen in den Registern 2 bis 8 des Bausteins stehen. Da die Register nur 8 Bit breit sind, wird für jeden 10-Bit-Wert ein Registerpaar (lsb, msb) benötigt.

Das lsb-Register enthält die beiden niederwertigen Bits des Messwertes in den beiden oberen Bits (6 und 7), das msb-register enthält die acht höherwertigen Bits des Messwertes:

msb:    yyyyyyyy
lsb:    xx000000
An sich ganz schlau, denn wer mit weniger Präzision zufreiden ist, braucht nur das msb auszulesen und mit 4 zu multiplizieren. Zur Speicherung im Rechner bietet sich der Datentyp short (16 Bit) an.

Für den genauen Wert sind einige Schritte notwendig:

  1. msb auslesen (in eine Variable vom Typ short), zweimal nach links schieben (entspricht der Multiplikation mit 4)
    msb: yyyyyyyy → yyyyyyyy00
  2. lsb (in eine zweite short_Variable) auslesen und 6 Positionen nach rechts schieben
    lsb: xx000000 → 00000000xx
  3. Danach werden msb und lsb addiert (eine Oder-Verknüpfung tut es in diesem Fall auch. Man erhält ein 10-Bit-Resultat:
    yyyyyyyyxx
  4. Der Sensor liefert aber auch negative Werte; die Zahl, die bei den vorhergehenden Schritten ermittelt wurde, ist eine 10-Bit-Zahl in Komplementdarstellung. Ist das höchstwertige Bit gleich 1, bedeutet dies, dass es sich um einen negativen Wert handelt. Um den 10-Bit-Wert in ein 16-Bit-Short zu verwandeln, muss das "Vorzeichen" expandiert werden: positive: yyyyyyyyxx → 000000yyyyyyyyxx
    negative: yyyyyyyyxx → 111111yyyyyyyyxx Im negativen Fall muss also 0xFC000 (1111 1100 0000 0000) hinzu ge-odert werden.
  5. Da der Default-Messbereich (im folgenden Listing range genannt) ±8 g beträgt, ergibt sich der Endgültige X-, Y- oder Z-Wert zu:
    jw24f8wert * range / 2048.0
Diese Rechnerei übernehmen im folgenden Listing die Funktionen jw24f8_calc_byte() und jw24f8_get_xyz().

Dank der Vorarbeit von Jan Axelson war auch die Funktion jw24f8_read() nicht mehr allzu schwer zu realisieren. Es werden neben dem Handle zwei Parameter übergeben, das auszuführende Kommando (0x82: ein Byte lesen) und die Register-Adresse des Bosch-Chips. Hier siht man auch ganz deutlich das USB-Kommunikationsprinzip (zumindest bis Version 2.0). Zuerst sendet der Rechner eine Anforderung per Control-Transfer, die vom USB-Gerät dann mit Daten beantwortet wird. Die Antwort wird ebenfall per Control-Transfer gepollt.

Quasi außer Konkurrenz ist im Listing auch noch die Funktion jw24f8_write() enthalten, die genauso arbeitet. Hier wird per Control-Transfer ein Byte geschrieben und anschliessend per Polling der Status der Operation gelesen. Für die aktuelle Anwendung wird diese Funktion jedoch nicht gebraucht. Falls aber einmal die Sensor-Parameter per Programm geändert werden sollen, wird sie benötigt.

Die Funktion run() dient als Demo für das Ganze. Sie ruft in regelmäßigen Zeitabständen die Beschleunigungsdaten für X, Y und Z ab und gibt diese auf dem Bildschirm aus. Je nach Kommandozeilenparameter entweder auf die Standard-Fehlerausgabe oder auf die Standardausgabe. Hier kann man für eigene Projekte angreifen. Wegen der Gleitpunktoperationen muss beim Compilieren nicht nur die Libusb, sondern auch die Mathe-Lib eingebunden werden.


/* jw24f8.c
 * Created on: Jan 15, 2014 By  JPL
 *   Based on: generic_hid.c (Apr 22, 2011) By Jan Axelson
 *
 * Demonstrates communicating with the JoyWarrior24F8 device using HID interface.
 * Sends and receives 8-byte feature control reports.
 * Most of the routines are adapted from codemercs windows code for the 
 * tilt program available here:
 * http://www.codemercs.com/uploads/tx_sbdownloader/jw24f8_win_software_01.zip
 * See functions.ccp and functions_jw24f8.ccp source codes. 
 *
 * the 10 bit acceleration value will come within 2 bytes, msb and lsb,
 * msb: yyyyyyyy, lsb: xx000000. to get the right value, msb must be shifted
 * 2 positions to the left, lsb must shift 6 positions to the right, and
 * afterwards lsb and msb must be added:
 *   msb: yyyyyyyy shiftet --> yyyyyyyy00
 *   lsb: xx000000 shifted --> 00000000xx
 *   10 bit result: yyyyyyyyxx
 * to convert this to a value of type short, the sign must be expanded:
 *   positive: yyyyyyyyxx --> 000000yyyyyyyyxx
 *   negative: yyyyyyyyxx --> 111111yyyyyyyyxx
 *
 * Note: libusb error codes are negative numbers.
 * Note: very limited error checking is done to speedup runtime
 
******************************************** 
******************* Compiling **************
******************************************** 
* The application uses the libusb 1.0 API from libusb.org
* which must be installed first.
* 
* Compile the application with:
* 
* gcc -Wall -lusb-1.0 -lm -o jw24f8 jw24f8.c 
* 
*/


#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <fcntl.h>
#include <time.h>
#include "/usr/local/include/libusb-1.0/libusb.h"

/* Enter Vendor and Product ID for the device: */
#define VENDOR_ID  0x07c0 // Vendor code
#define PRODUCT_ID 0x1113 // product code

/* Values for bmRequestType in the Setup transaction's Data packet. */
static const int CONTROL_REQUEST_TYPE_IN = LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE;
static const int CONTROL_REQUEST_TYPE_OUT = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE;

/* From the HID spec:*/
static const int HID_GET_REPORT = 0x01;
static const int HID_SET_REPORT = 0x09;
static const int HID_REPORT_TYPE_INPUT = 0x01;
static const int HID_REPORT_TYPE_OUTPUT = 0x02;
static const int HID_REPORT_TYPE_FEATURE = 0x03;

/* With firmware support, transfers can be greater than the endpoint's max packet size. */
static const int MAX_CONTROL_IN_TRANSFER_SIZE = 8;
static const int MAX_CONTROL_OUT_TRANSFER_SIZE = 8;
static const int INTERFACE_NUMBER = 1;
static const int TIMEOUT_MS = 0;

/* Prototypes */
/* open device an detach from kernel driver */
struct libusb_device_handle * jw24f8_open(unsigned char serial[256], unsigned char product[256]);

/* close device, release to kernel driver */
void jw24f8_close(libusb_device_handle *devh);

/* send control transfer message to device */
unsigned char jw24f8_write(libusb_device_handle *devh,unsigned char address,unsigned char data);

/* get control transfer message from device */
unsigned char jw24f8_read(libusb_device_handle *devh,unsigned char cmd,unsigned char address);

/* get the x,y,z data by sending sucessive control transfer messages to device */
void jw24f8_get_xyz(libusb_device_handle *devh,float *X,float *Y,float *Z);

/* convert msb, lsb --> 10-bit value --> short value */
short jw24f8_calc_byte(unsigned char msb,unsigned char lsb);  

/* run data retrieval */ 
int run(int output); 

/* display help info */
void usage(char msg[]);



int main(int argc, char *argv[])
  { 
  int result;
  if (argc > 2) usage("Too many options");
  result = libusb_init(NULL);                // start the libusb functionality
  if (result < 0)
    {
    fprintf(stderr,"Can't start libusb.\n");
    return 1;
    }
  // check command line arguments
  if (argc == 1) run(0);                     // run program
  else if (!strcmp(argv[1],"-h")) usage(""); // display help info
  else if (!strcmp(argv[1],"-o")) run(1);    // redirect output
  else usage("Option not available");
  libusb_exit(NULL);                         // exit the usblib  
  return 0;
  }


void usage(char msg[])
  {
  /* display help info */
  if (msg[0]) fprintf(stderr,"%s\n",msg);  
  fprintf(stderr,
  "usage:\n",
  "  jw [options]\n",
  "  options:\n",
  "   -h : print this help\n",
  "   -o : stream readings to std output\n",
  "        can be redirected to file by\n",
  "        jw24f8 -o > output.txt\n",
  "   no option will print x, y, z values to stderr only.\n");
  exit(1);
  }

int run(int output)
  {
  /* do the work:
   * open device an display product and serial info
   * loop displaying X, Y, Z coordinates until enter key ist pressed
   * argument:
   *   output == 0: print to stderr
   *   output != 0: print to stdout
   */
  struct libusb_device_handle *devh = NULL;    // device handle
  unsigned char serial[256], product[256]; 
  int result = 0, tmp = 0, n = 0;
  char c;
  float X, Y, Z;

  devh = jw24f8_open(serial, product); 
  if (devh != NULL)
    {
    if (output) printf("# Name: %s / Serial: %s\n",product,serial);
    else fprintf(stderr,"# Name: %s / Serial: %s\n",product,serial);
    if (output) printf("#        X            Y            Z\n");
    else fprintf(stderr,"#        X            Y            Z\n");

     // set the terminal for endless loop until Enter is pressed
     // (http://forums.fedoraforum.org/showthread.php?t=147415)     
    tmp = fcntl(0, F_GETFL, 0);
    fcntl (0, F_SETFL, (tmp | O_NONBLOCK));
    // enter the not-so-endless-now loop (exit loop on enter key press)
    while (n <= 0)
      {
      jw24f8_get_xyz(devh,&X,&Y,&Z); // read the true g values.
      // redirect output to a file or comment the line below
      if (output) printf("%12.2f %12.2f %12.2f\n",X,Y,Z);
      else fprintf(stderr,"%12.2f %12.2f %12.2f\r",X,Y,Z); 
      usleep(50000);
      n = read(0, &c, 1); // see if an enter key was pressed
      }
    fcntl(0, F_SETFL, tmp); // set the terminal back to normal
    jw24f8_close(devh);
    }
  else // in case of no device detected
    {
    fprintf(stderr,"No device found.\n");
    return 1;
    }
  fprintf(stderr,"\nDone.\n\n");
  return 0;
  }

struct libusb_device_handle * jw24f8_open(unsigned char serial[256], unsigned char product[256]) 
  /* open device an detach from kernel driver 
   * return parameters:
   *   serial: serial number
   *   product: product ID
   * return value:
   *   pointer to device handle (or NULL, if no success)
   */
  {
  struct libusb_device_handle *devh = NULL;    // device handle
  struct libusb_device *dev = NULL;            // device
  struct libusb_device_descriptor desc;        // device descriptor (name, product, serial etc.)
  int result = 0;
  devh = libusb_open_device_with_vid_pid(NULL, VENDOR_ID, PRODUCT_ID); // get the first available device
  if (devh != NULL)
    {
    dev = libusb_get_device(devh); // get the device
    libusb_detach_kernel_driver(devh, INTERFACE_NUMBER); // Detach the hidusb driver from the HID to enable using libusb.
    result = libusb_claim_interface(devh, INTERFACE_NUMBER); // claim control over the interface
    dev = libusb_get_device(devh); // get the device
    result = libusb_get_device_descriptor(dev,&desc); // get the device descriptor      
    libusb_get_string_descriptor_ascii(devh,desc.iProduct,product,256);// get name
    libusb_get_string_descriptor_ascii(devh,desc.iSerialNumber,serial,256); // get serial
    }
  return devh;
  }

void jw24f8_close(libusb_device_handle *devh)
  /* close device, release to kernel driver
   * parameters:
   *   devh: device handle
   */
  {
  libusb_release_interface(devh, INTERFACE_NUMBER);
  libusb_attach_kernel_driver(devh, INTERFACE_NUMBER);
  libusb_close(devh);
  }


unsigned char jw24f8_write(libusb_device_handle *devh, unsigned char address, unsigned char data)
  /* send control transfer message to device
   *   message format: [0x82][address][data]
   * parameters:
   *   devh: device handle
   *   address, data: message contents
   * return:
   *   result of command
   */
  {
  unsigned char buff[MAX_CONTROL_OUT_TRANSFER_SIZE];
  int bytes_sent, bytes_received;

  memset(buff, 0, sizeof(data));
  buff[0] = 0x82;
  buff[1] = address;
  buff[2] = data; 
  // send command
  bytes_sent = libusb_control_transfer(
      devh,
      CONTROL_REQUEST_TYPE_OUT,
      HID_SET_REPORT,
      (HID_REPORT_TYPE_OUTPUT<<8)|0x00,
      INTERFACE_NUMBER,
      buff,
      MAX_CONTROL_OUT_TRANSFER_SIZE,
      TIMEOUT_MS);
  // receive result
  if (bytes_sent >= 0)
    {
    libusb_clear_halt(devh,130);
    bytes_received = libusb_control_transfer(
        devh,
        CONTROL_REQUEST_TYPE_IN,
        HID_GET_REPORT,
        (HID_REPORT_TYPE_INPUT<<8)|0x00,
        INTERFACE_NUMBER,
        buff,
        MAX_CONTROL_IN_TRANSFER_SIZE,
        TIMEOUT_MS);
    }
  else
    {
    fprintf(stderr, "Error sending  command\n");
    }
  return buff[2];    
  }

unsigned char jw24f8_read(libusb_device_handle *devh, unsigned char cmd, unsigned char address)
  {
  /* send control transfer message to device and receive data
   *   message format: [cmd][address]
   *   cmd defines the amount of bytes to read (0x82: 1 byte .. 0x87: 5 bytes)
   * parameters:
   *   devh: device handle
   *   cmd: command
   *   address: message address
   * return:
   *   result of command
   */
  unsigned char data[MAX_CONTROL_OUT_TRANSFER_SIZE];
  int bytes_sent, bytes_received;
  
  memset(data, 0, sizeof(data));
  data[0] = cmd;          // 0x82 to read one byte 0x83 for two bytes etc. until 0x87;
  data[1] = address | 0x80; 
  // send command 
  bytes_sent = libusb_control_transfer(
      devh,
      CONTROL_REQUEST_TYPE_OUT,
      HID_SET_REPORT,
      (HID_REPORT_TYPE_OUTPUT<<8)|0x00,
      INTERFACE_NUMBER,
      data,
      MAX_CONTROL_OUT_TRANSFER_SIZE,
      TIMEOUT_MS);
  // receive data
  if (bytes_sent >= 0)
    {
    libusb_clear_halt(devh,130);
    bytes_received = libusb_control_transfer(
        devh,
        CONTROL_REQUEST_TYPE_IN,
        HID_GET_REPORT,
        (HID_REPORT_TYPE_INPUT<<8)|0x00,
        INTERFACE_NUMBER,
        data,
        MAX_CONTROL_IN_TRANSFER_SIZE,
        TIMEOUT_MS);
    }
  else
    {
    fprintf(stderr, "Error sending  command\n");
    }
  return data[2];    
  }

void jw24f8_get_xyz(libusb_device_handle *devh, float *X, float *Y, float *Z)
  // 
  /* get the x,y,z data by sending sucessive
   * control transfer messages to device
   * parameters:
   *   devh: device handle
   * return parameters:
   *   X, Y and Z acceleration
   */
  {
  double range;
  unsigned char lsb, msb;

  range = 8.0;
  // get the x data (addresses see datasheet)
  lsb = jw24f8_read(devh,0x82,0x02);
  msb = jw24f8_read(devh,0x82,0x03);
  *X  = jw24f8_calc_byte(msb,lsb)*range/2048.0;
  // get the y data
  lsb = jw24f8_read(devh,0x82,0x04);
  msb = jw24f8_read(devh,0x82,0x05);
  *Y  = jw24f8_calc_byte(msb,lsb)*range/2048.0;
  // get the z data
  lsb = jw24f8_read(devh,0x82,0x06);
  msb = jw24f8_read(devh,0x82,0x07);
  *Z  = jw24f8_calc_byte(msb,lsb)*range/2048.0;
  }


short jw24f8_calc_byte(unsigned char msb, unsigned char lsb)
  {
  /* convert msb, lsb --> 10 bit value --> short value */
  short erg, LSB, MSB, EXEC;

  /* calc neg. values; EXEC will produce
     correct two's complement 16 bit value.
     for neg. numbers EXEC will be:
     0xFC000 --> 1111 1100 0000 0000 */
  if (msb & 0x80) EXEC = 0xFC00;
  else EXEC = 0; 
  /* expand 8 bit MSB to 10 bit 
     xxxxxxxx --> xxxxxxxx00 */
  MSB = (msb << 2) & 0x03FF;
  /* extract 2 bit LSB 
     xx000000 --> 000000xx */
  LSB = ((lsb & 0xC0) >> 6) & 0x0003;
  /* combine all three values */ 
  erg = MSB | LSB | EXEC;
  return erg;
  }
Man könnte den Sensor beispielsweise einsetzen um einen Automaten vor Vandalismus und Diebstahl zu schützen (JoyWarrior Appnote 8_01), kann man einerseits die Beschleunigung in X- und Y-Richtung auswerten (kippen), andererseits X und Z oder Y und Z, um festzustellen, ob die Kiste horizintal bewegt wird. An Mathematik wird nur der Satz des Pythagoras benötigt.

Um einen Automaten oder eine Kiste zu klauen muss das Ding in der Regel zuerst gekippt werden, um es auf einen Wagen oder eine andere Transportvorrichtung zu laden. Diese Neigung (tilt) kann man gut detektieren. Der JW24F8 wird horizontal im Gehäuse montiert und zur Berechnung des Neigungswinkels dient der Satz des Pythagoras:

tilt2 = X2 + Y2

Der Neigungswinkel ist dann aus sin(Tilt) zu ermittel. Als Code (daran denken - die C-Funktionen rechnen im Bogenmass) sieht das so aus:

#define PI 3.14159265
   ...
double Winkel(double x, double y)
  {
  /*  a*a + b*b = c*c (Satz des Phytagoras) */
  double angle;

  angle = sqrt((x*x) + (y*y));
  angle = asin(angle) * 180 / PI;
  return angle;
  }
Um die Neigung von einer horizontalen Beschleunigung zu unterscheiden, muss die Z-Achse mit verwendet werden. Auch hier wird wider die Neigung berechnet. Stimmen die Winkel der verschiedenen Rechnungen nicht überein, kann von einer horizontalen Beschleunigung ausgegangen werden.

Sinnvollerweise wird man nich die absoluten Werte verwenden, sondern in regelmäßigen Zeitabständen die aktuellen Werte mit den vorhergehenden vergleichen. Wenn beim Ergenis eine vorgegebene Schwelle überschritten wird, erfolgt der Alarm.

Tilt2 = (Xneu - Xalt)2
 + (Yneu - Yalt)2
 + (Zneu - Zalt)2

Um Fehlalarmen bei zufälligen Erschütterungen vorzubeugen, programmiert man noch einen "Tiefpass", der kurzzeitige Änderungen (z. B. kleiner 0,1 s) ausblendet.

Dann war da noch die Frage in einem Forum, wie man den Beschleunigungswert von 1 g in Z-Richtung wegbekommt ...

Welches USB-Device?

Je nach Linux-Distribution steht man aber oft vor der Frage, welches Device oder welche Schnittstelle anzusprechen ist, da viele Standard-USB-Geräte auf übliche Schnittstellen abgebildet werden. Dabei hilft Ihnen die Datei /var/log/messages. Bevor Sie das USB-Device einstöpseln, beobachten Sie mit dem folgenden Kommando diese Datei:
tail -f /var/log/messages
Der Parameter "-f" sorgt dafür, dass die Datei nicht sofort wieder geschlossen wird, sondern Sie alle neuen Einträge zu sehen bekommen. Beendet wird das Ganze erst mittels Strg-C. Beim Einstecken des oben schon verwendeten Seriell-Converters zeigt sich folgende Ausgabe (die vier sehr langen Zeilen der Meldungen wurde für das Listing umbrochen und entsprechend eingerückt):
Apr  6 17:38:41 donvito kernel: [25729.954559] usb 4-1: 
   new full speed USB device using uhci_hcd and address 4
Apr  6 17:38:41 donvito kernel: [25730.116496] usb 4-1: 
   configuration #1 chosen from 1 choice
Apr  6 17:38:41 donvito kernel: [25730.203074] usbcore: 
   registered new interface driver usbserial
Apr  6 17:38:41 donvito kernel: [25730.203448]
   /build/buildd/linux-2.6.24/drivers/usb/serial/usb-serial.c: 
   USB Serial support registered for generic
Apr  6 17:38:41 donvito kernel: [25730.203857] usbcore: 
   registered new interface driver usbserial_generic
Apr  6 17:38:41 donvito kernel: [25730.203866]
   /build/buildd/linux-2.6.24/drivers/usb/serial/usb-serial.c: 
   USB Serial Driver core
Apr  6 17:38:41 donvito kernel: [25730.207722]
   /build/buildd/linux-2.6.24/drivers/usb/serial/usb-serial.c: 
   USB Serial support registered for pl2303
Apr  6 17:38:41 donvito kernel: [25730.208182] pl2303 4-1:1.0: 
   pl2303 converter detected
Apr  6 17:38:41 donvito kernel: [25730.208670] usb 4-1: 
   pl2303 converter now attached to ttyUSB0
Apr  6 17:38:41 donvito kernel: [25730.208995] usbcore: 
   registered new interface driver pl2303
Apr  6 17:38:41 donvito kernel: [25730.209004]
   /build/buildd/linux-2.6.24/drivers/usb/serial/pl2303.c: 
   Prolific PL2303 USB to serial adaptor driver
Man sieht an der Ausgabe, dass es einen Treiber für das Device gibt, der automatisch eingebunden wird (die ersten beiden Meldungen). Da der Treiber für serielle Schnittstellen noch nicht aktiviert war, wird nun dieser eingebunden (die folgenden fünf Meldungen). Danach wird der spezielle Adapter mit der Device-Id 2303 erkannt und dessen Treiber ebenfalls eingebunden. Wichtig ist die dabei auftauchende Nachricht "pl2303 converter now attached to ttyUSB0", durch die Sie nun wissen, dass die Schnittstelle über die Gerätedatei /dev/ttyUSB0 als serielle Schnittstelle angesprochen werden kann.

Weitere Infos, Quellen usw.

Für die Windows-Benutzer bietet das Modul Win32::FTDI::FTD2XX eine Unterstützung für die seriellen Converter-Chips von FTDI, die in vielen Geräten zu finden sind -- beispeisweise auch in den "USB-Serielladaptern".

Unter www.intra2net.com/en/developer/libftdi/ erhalten Sie die auf der Libusb aufsetzende Bibliothek "libFTDI". Dies ist eine Open Source Library speziell für die FTDI-Chips FT232BM, FT245BM, FT2232C, FT2232D, FT245R und FT232H inklusive des "bitbang mode".

Auch für andere Hardwarekomponenten, darunter Temperatursensoren oder so exotische Dinge wie eine Abschussbasis für Spielzeugraketen finden sich CPAN-Module. Aber auch außerhalb von CPAN finden sich interessante Anwendungen, beispielsweise ein Interface für ein Energiemonitorsystem von ELV oder GPS-Tracker unter http://dokuwiki.nausch.org/.

Weitere Informationen:


Copyright © Hochschule München, FB 04, Prof. Jürgen Plate
Letzte Aktualisierung: