![]() |
Linux, PC und HardwareProf. Jürgen Plate |
Die Vorteile von USB:
An der Rückseite eines modernen PCs findet man zwei oder mehr Buchsen vom Typ A. Kleinere, langsame Geräte wie beispielsweise Mäuse verwenden ein dünnes, fest angebrachtes Kabel mit einem Stecker vom Typ A. In anderen Fällen besitzt das Gerät selbst eine USB-Buchse vom Typ B. Die Verbindung erfolgt dann mit einem Kabel vom Typ A-B.
Die Kabel gibt es nur fertig konfektioniert und vergossen zu kaufen. Einzelne USB-Stecker sind nicht erhältlich - man muss gegebenenfalls ein Kabel "schlachten". Länge, Kabelquerschnitt, Abschirmung usw. sind genau vorgeschrieben. Auch der Unterschied zwischen Highspeed, Fullspeed und Lowspeed spielt hier eine Rolle. Das System der vorgeschriebenen Kabel verhindert zuverlässig, dass ein Lowspeed-Kabel für eine Fullspeed-Verbindung eingesetzt wird. Alle Verbindungskabel sind Fullspeed-Kabel, während Lowspeed-Kabel nur fest angebaut vorkommen. Für den Fall einer notwendigen Kabelverlängerung gibt es Verlängerungskabel vom Typ A-A.
Standardstecker | |||
---|---|---|---|
![]() | |||
Pin | Name | Farbe | Beschreibung |
1 | VCC | Rot | +5 V |
2 | D- | Weiß | Data - |
3 | D+ | Grün | Data + |
4 | GND | Schwarz | Masse |
Mini- und Microstecker | |||
![]() | |||
Pin | Name | Farbe | Beschreibung |
1 | VCC | Rot | +5 V |
2 | D- | Weiß | Data - |
3 | D+ | Grün | Data + |
4 | ID | keine | Unterscheidung von Micro-A- und Micro-B-Stecker: Typ A: Masse, Typ B: nicht verbunden |
5 | GND | Schwarz | Masse |
USB besitzt aber auch Nachteile:
Eigenschaften von USB 1.1:
Eigenschaften von USB 2.0:
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:
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-. |
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.
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.
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:
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:
Beispiel 1: Geräteadresse zuweisen bei der Konfiguration (Standard Device Request):Die Datenübertragung beim Control-Transfer ist über eine integrierte Fehlerkorrektur und über eine Handshake-Prozedur besonders gesichert.
SETUP-Token (Befehlspaket) vom Host (enthält Geräteadresse 0 und Endpunkt-Adresse EP0)
Datenpaket vom Host (enthält u.a. die neue Geräteadresse)
ACK-Handshake–Paket an Host (Übertragung fehlerfrei)
IN-Token vom Host (enthält Geräteadresse 0 und Endpunkt-Adresse EP0)
0-Byte-Datenpaket an Host ("konnte Befehl ausführen")
ACK-Handshake–Paket vom Host
Beispiel 2: Lesen eines Device Descriptors:
SETUP-Token (Befehlspaket) vom Host (enthält Geräte- und Endpunkt-Adresse)
Datenpaket vom Host (enthält Befehlsparameter „Descriptor abfragen“)
ACK-Handshake–Paket an Host
IN-Token vom Host (enthält Geräteadresse 0 und Endpunkt-Adresse EP0)
Datenpaket an Host mit dem Device Descriptor
ACK-Handshake–Paket vom Host
Die folgenden drei Transfers beginnen (bzw. enden) in den Endpunkten EP1 bis EP15 und sind unidirektional. Die dazugehörigen Pipes werden als Stream Pipes bezeichnet.
Transfer Type | Control | Interrupt | Isochronous | Bulk |
USB 1.1 | X | X | - | - |
USB 2.0 | X | X | X | X |
Datenbytes/Millisekunde pro Transfer (full speed) | 832, in dreizehn 64-Byte Transfers | 64 | 1023 | 1216, in neunzehn 64-Byte Transfers |
Datenbytes/Millisekunde pro Transfer (low speed) | 24, in drei 8-Byte Transfers | 0.8, 8 Bytes in 10 ms | verboten | verboten |
Reservierte Bandbreite | 10% | 90% Int. & Iso. gemeinsam |
90% Int. & Iso. gemeinsam | keine |
Fehlerkorrektur | ja | ja | ja | ja |
Garantierte Datenrate | nein | nein | ja | nein |
Garantierte Latezzeit (max. Zeit zwischen Transfers) | nein | ja | ja | nein |
Die einem Endpoint zugeordnete Transferart ermittelt der Gerätetreiber aus den jeweiligen Deskriptoren (Gerät "sagt", welchen Transfer es benötigt).
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:
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.
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.
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.
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.
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.
Bei vielen USB-Geräten existiert jedoch oft nur eine Konfiguration, obwohl durch dieses System eine enorme Flexibilität möglich wäre.
Bytes | Offset | Name | Inhalt |
---|---|---|---|
1 | 0 | bLength | Grösse des Descriptors in Bytes |
1 | 1 | bDescriptorType | Descriptor-Typ (0x01) |
2 | 2 | bcdUSB | USB-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 |
1 | 4 | bDeviceClass | Klassen-Code |
1 | 5 | bDeviceSubClass | Subklassen-Code |
1 | 6 | bDeviceProtocol | Protokoll-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. |
1 | 7 | bMaxPacketSize | Maximale Paketgrösse für ENDP 0. Mögliche Werte sind 8, 16, 32 und 64. |
2 | 8 | idVendor | Vendor-ID |
2 | 10 | idProduct | Produkt-ID |
2 | 12 | bcdDevice | Version des Gerätes (Format wie USB-Version). |
1 | 14 | iManufacturer | String-Index für Hersteller oder NULL. |
1 | 15 | iProduct | String-Index für Produkt oder NULL. |
1 | 16 | iSerialNumber | String-Index für Seriennummer oder NULL. |
1 | 17 | bNumComfigurations | Anzahl Konfigurationen |
Bytes | Offset | Name | Inhalt |
---|---|---|---|
1 | 0 | bLength | Grösse des Descriptors in Bytes |
1 | 1 | bDescriptorType | Descriptor-Typ (0x02) |
2 | 2 | wTotalLenght | Länge der total zurückgegebenen Daten (gesamter Baum) |
1 | 4 | bNumInterfaces | Anzahl Interfaces |
1 | 5 | bConfigurationVal | Konfigurations-Nummer |
1 | 6 | iConfiguration | String-Index für Konfiguration |
1 | 7 | bmAttributes | Bit 7 = 1: Bus-powered (nur USB 1.1), Bit 6 = 1: self-powered, Bit 5 = 1: Gerät darf den Host aufwecken |
1 | 8 | bMaxPower | benötigter Strom (mA). |
Bytes | Offset | Name | Inhalt |
---|---|---|---|
1 | 0 | bLength | Grösse des Descriptors in Bytes |
1 | 1 | bDescriptorType | Descriptor-Typ (0x04) |
1 | 2 | bInterfaceNumber | Interface-Nummer |
1 | 3 | bAlternateSetting | nächstes alternatives Interface |
1 | 4 | bNumEndpoints | Anzahl ENDP für dieses Interface (ohne ENDP 0) |
1 | 5 | bInterfaceClass | Klassen-Code |
1 | 6 | bInterfaceSubClass | Subklassen-Code |
1 | 7 | bInterfaceProtocol | Protokoll-Code. Die letzten drei Werte stehen für Treiber Informationen, die das System zu laden hat. |
1 | 8 | iInterface | String-Index für Index-Beschreibung. |
Bytes | Offset | Name | Inhalt |
---|---|---|---|
1 | 0 | bLength | Grösse des Descriptors in Bytes |
1 | 1 | bDescriptorType | Descriptor-Typ (0x05) |
1 | 2 | bEndpointAddress | Bit 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. |
1 | 3 | bmAttributes | Bits 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 |
2 | 4 | wMaxPacketSize | Maximale Paketgrösse |
1 | 6 | bInterval | Nur für Asynchron und
Interrupt: Anzahl Frames, die zwischen den Pollings gewartet werden muss. |
Bytes | Offset | Name | Inhalt |
---|---|---|---|
1 | 0 | bLength | Grösse des Descriptors in Bytes |
1 | 1 | bDescriptorType | Descriptor-Typ (0x03) |
2 | 2 | wLANGID[0] | Code-Null-Sprache |
2 | 4 | wLANGID[1] | Code-Eins-Sprache |
2 | 6 | wLANGID[2] | Code-x-Sprache |
Die Sprachen sind international für USB festgelegt. Die folgenden Deskriptoren definieren die eigentlichen Strings:
Bytes | Offset | Name | Inhalt |
---|---|---|---|
1 | 0 | bLength | Grösse des Descriptors in Bytes |
1 | 1 | bDescriptorType | Descriptor-Typ (0x03) |
n | 2 | bString | String der Länge n |
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.
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 0Nach 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=256msVor 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.7Mit 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 "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, 12MUm 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 0Das "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"
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:
Die Installation kann in der Regel mit dem Paketmanager der Distribution erfolgen, z. B. (als root-User):
apt-get install libusb-1.0.0 apt-get install libusb-1.0.0-dev ldconfigWenn das nicht klappt lassen sich die Bibliotheken aber auch aus dem Quellen problemlos compilieren (habe ich bei Raspberry Pi so gemacht). Man muss dann nur ggf. auf den Library- und Include-Pfad achten - Raspberry Pi beispielsweise:
export LIBUSB_LIBDIR=/usr/lib/arm-linux-gnueabihf export LIBUSB_INCDIR=/usr/include/libusb-1.0Bei der Standard-Configuration befinden sich die Headerfiles für C unter #include <libusb-1.0/libusb.h> und beim Compiler ist die Bibliothek mittels -lusb-1.0 einzubinden.
Die Installation kann in der Regel auch hier mit dem Paketmanager der Distribution erfolgen, z. B. (als root-User):
apt-get install libusb apt-get install libusb-dev ldconfigBei der Standard-Configuration befinden sich die Headerfiles für C unter #include <usb.h> und beim Compiler ist die Bibliothek mittels -lusb einzubinden.
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:
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: 001Das 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:0000Will 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 mADas 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); }
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: xx000000An 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:
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 ...
tail -f /var/log/messagesDer 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 driverMan 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.
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: