Kampis Elektroecke

Implementierung der Standarddeskriptoren

Da der Kontrollendpunkt erfolgreich initialisiert wurde, soll es in diesem Teil um den Aufbau und die Definition der Standard Deskriptoren gehen. USB-Geräte werden ihren Aufgaben entsprechend in Klassen unterteilt (wie z. B. HID, Audio oder Printer) und für jede Kategorie gibt es eigene Treiber und zusätzliche Deskriptoren, sodass sich jedes USB-Gerät durch zwei Sätze Deskriptoren identifizieren kann:

  • Die Standard Deskriptoren
  • Die klassenspezifischen Deskriptoren

Dieser Teil des Tutorials befasst sich nur mit den Standard Deskriptoren, sprich um den Geräte-, den Konfigurations-, den Interface und den Endpunktdeskriptor, sodass der Host ein grobes Bild von dem angeschlossenen USB-Gerät bekommt. Die klassenspezifischen Deskriptoren folgen in einem späteren Teil und erlauben dem Host das grobe Bild des angeschlossenen USB-Gerätes abzurunden und schlussendlich den passenden Treiber für das Gerät zu laden. Doch dafür müssen wir erst einmal verstehen, wie ein Deskriptor aufgebaut ist und wie wir mit dem Host kommunizieren können.

Jedes USB-fähige Gerät muss über mindestens einen Geräte- und über mindestens einen kompletten Konfigurationsdeskriptor verfügen. Der Konfigurationsdeskriptor unterteilt sich zudem noch in den Konfigurationsdeskriptor selbst, mindestens einen Interfacedeskriptor und mindestens einen Endpunktdeskriptor. Die Software muss also die folgenden Deskriptoren enthalten:

  • Gerätedeskriptor
  • Konfigurationsdeskriptor
  • Interfacedeskriptor
  • Endpunktdeskriptor

Die Deskriptoren haben alle einen festen Aufbau, der in Kapitel 9.6 der USB Spezifikation beschrieben wird. Schauen wir uns mal nacheinander an, wie die einzelnen Deskriptoren aufgebaut sind…

Der Gerätedeskriptor:

Der Gerätedeskriptor ist der immer der erste Deskriptor, der vom Host angefragt wird, sobald das USB-Gerät mit dem Bus verbunden wird. Er hat die Aufgabe dem Host allgemeine Informationen über das angeschlossene Gerät zu liefern. Jedes USB-fähige Gerät besitzt einen Kontrollendpunkt, dessen Größe im Gerätedeskriptor beschrieben ist. Der Host muss diese Größe frühzeitig genug erfahren, damit die Kommunikation mit dem Gerät fehlerfrei ablaufen kann. Jedes USB-fähige Gerät darf nur einen Gerätedeskriptor besitzen, der folgendermaßen aufgebaut ist:

Offset Feld Größe Beschreibung
0 bLength 1 Größe des Deskriptors in Bytes
1 bDescriptorType 1 DEVICE Deskriptor (Feld = 1)
2 bcdUSB 2 Verwendete USB Version
4 bDeviceClass 1 Durch das USB-IF vergebener Klassencode
5 bDeviceSubClass 1 Durch das USB-IF vergebener Subklassencode
6 bDeviceProtocol 1 Durch das USB-IF vergebener Protokolcode
7 bMaxPacketSize0 1

Maximale Paketgröße für Endpunkt 0.

Muss entweder 8, 16, 32 oder 64 sein

8 idVendor 2 Durch das USB-IF vergebene Vendor-ID
10 idProduct 2 Durch den Hersteller vergebene Produkt-ID
12 bcdDevice 2 Releasenummer des Gerätes
14 iManufacturer 1 Index des Stringdeskriptors, der den Hersteller beschreibt
15 iProduct 1 Index des Stringdeskriptors, der das Produkt beschreibt
16 iSerialNumber 1 Index des Stringdeskriptors, der die Seriennummer beschreibt
17 bNumConfigurations 1 Anzahl der Gerätekonfigurationen

Der Gerätedeskriptor soll durch eine Struktur beschrieben werden:

Durch den Zusatz __attribute__((packed)) wird der Compiler angewiesen nur den tatsächlich benötigten Speicher zu reservieren und keine Paddingbytes einzufügen. Auf diese Weise können die von Host gesendeten Daten in einem Array gespeichert und in eine entsprechende Strukturvariable gecastet werden.

Jetzt kann eine entsprechende Variable für den Gerätedeskriptor erstellt werden:

Mit Hilfe des PROGMEM-Attributes wird der Deskriptor beim Programmstart nicht durch den Startup-Code vom Flash in den SRAM kopiert. Als Konsequenz daraus müssen die Daten direkt aus dem Programmspeicher gelesen werden, wodurch andere Assemblerinstruktionen verwendet werden müssen.


Hinweis:

Das Attribut PROGMEM ist für die Funktion des USB-Treibers nicht wichtig. Vielmehr stellt es eine Optimierung dar, die dafür sorgt, dass große Datenstrukturen nicht in den SRAM kopiert werden. Optimalerweise werden große Datenstrukturen (wie z. B. Bitmaps für Displays) oder konstante Strings über PROGMEM deklariert um SRAM zu sparen und die Startzeit vom Programm zu verkürzen.


Der initialisierte Gerätedeskriptor muss nun noch mit Werten gefüttert werden:

Die einzelnen Konstanten, Definitionen und Makros der Standarddeskriptoren habe ich in eine entsprechende Include-Datei eingetragen, sodass die eingetragenen Werte leichter zu verstehen sind. Da es sich bei diesem Deskriptor noch nicht um eine komplette Maus, sondern um einen Beispieldeskriotor handelt, wird als Device Class die Klasse Vendor, also Herstellerspezifisch, eingetragen. Die Vendor– und die Product-ID kann, wenn es sich nicht um ein kommerzielles Produkt handelt, frei gewählt werden. Man sollte allerdings aufpassen das nicht andere Geräte bereits die selben IDs verwenden, da es sonst ggf. zu Problemen mit den Treibern kommen kann.

Die Erstellung der anderen Deskriptoren erfolgt analog zum Gerätedeskriptor…

Der vollständige Konfigurationsdeskriptor:

Für die übrigen Deskriptoren werden ebenfalls entsprechende Strukturen angelegt:

Bei der Anfrage nach dem Konfigurationsdeskriptor werden, neben dem Konfigurationsdeskriptor, auch die Interface- und die Endpunktdeskriptoren übertragen. Daher werden die einzelnen Deskriptoren als Member für eine neue Konfigurationsstruktur genutzt, welche eine einzelne Konfiguration des Gerätes darstellt:

Anschließend werden die Deskriptoren mit Inhalt gefüllt:

Auch bei diesen Deskriptoren habe ich die einzelnen Felder durch Makros, bzw. entsprechende Konstanten füllen lassen.


Hinweis:

Bei den Deskriptoren handelt es sich erst einmal um Testdeskriptoren um die Kommunikation zwischen Host und Mikrocontroller zu üben. Es sind noch nicht die finalen Mausdeskriptoren, da einige Felder anders ausgefüllt werden müssen und der HID-Deskriptor fehlt. Diese Informationen werden später eingefügt und die Deskriptoren angepasst.


Optionale Stringdeskriptoren:

Als letztes wurden noch ein paar optionale Stringdeskriptoren deklariert:

Da Stringdeskriptoren eine Null-terminierte Zeichenkette im UNICODE-Format abspeichern, wird als Datentyp für den String ein Wider Character (w_char_t) genutzt. Ein w_char ist auf einem AVR 16 Bit groß und kann damit ideal ein einzelnes UNICODE-Zeichen speichern.

Anders als die vorangegangenen Deskriptoren werden die Stringdeskriptoren lediglich mit einer Zeichenkette gefüllt. Die Umwandlung einer Zeichenkette in einen entsprechenden Deskriptor wird über Makros ausgeführt:

Ein besonderer Stringdeskriptor ist der Deskriptor mit der ID 0:

Dieser String gibt mindestens eine Language-ID zurück. Über die vorhandenen Language-IDs kann die Anwendung auf dem Host die unterstützen Sprachen ermitteln und so die entsprechenden Deskriptoren auslesen. Das Makro CONV_LANG wandelt eine Primärsprache und eine Subsprache in die entsprechende Sprach-ID um und übergibt diese Sprache als Array an das Makro LANG_TO_STRING_DESCRIPTOR, welches dann den Stringdeskriptor mit der ID 0 erzeugt.

Damit sind die Deskriptoren vollständig. Im nächsten Teil schauen uns an wie der Host mit dem USB-Device kommuniziert. Dazu müssen wir diese sogenannten Standard Requests auswerten und beantworten. Wenn alles klappt, meldet sich der Mikrocontroller am Ende des nächsten Teils bereits erfolgreich beim Host, sodass wir mit ihm kommunizieren können.

Das komplette Projekt könnt ihr bei GitLab downloaden.

Zurück

Last Updated on

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.