Make Magazin 1/2017
S. 114
Grundlagen
Aufmacherbild

USB für AVR-Mikrocontroller

Eigene oder alte Technik mit einer USB-Schnittstelle auszurüsten, zum Beispiel einen Game-Controller aus der 8-Bit-Ära, ist weniger kompliziert, als es klingt. Möglich macht es die Software-Bibliothek V-USB für Atmels AVR-Mikrocontroller, mit der man USB über zwei GPIO-Ports per Bit-Banging abwickelt.

Vor dem Einstieg in die Programmierung mit der V-USB-Bibliothek stellt dieser Artikel einige Grundprinzipien des USB vereinfacht vor. Die Spezifikation ist sehr umfangreich und kann deshalb nur oberflächlich erläutert werden. Der Theorieteil vermittelt grundlegende Kenntnisse, die den kreativen Umgang mit der V-USB-Bibliothek erleichtern, vor allem wenn es darum geht, eigene Projekte zu realisieren. Danach folgt eine praktische Einführung anhand von Beispielen.

USB (Universal Serial Bus) löste vor allem die bis in die Neunziger gebräuchlichen seriellen Schnittstellen RS232 und PS/2 ab, die an PCs zum Einsatz kamen, um Peripherie-Geräte, wie Mäuse, Tastaturen und Modems anzuschließen. Da USB universell einsetzbar ist und eine große Übertragungsrate zur Verfügung stellt, ersetzte es auch den Parallel-Port, der meistens dazu verwendet wurden Drucker oder Scanner anzuschließen. Auch Tablets stellen USB-Schnittstellen zum Anschluss von Peripherie zur Verfügung und Smartphones können als Peripherie an einen PC (Host) angeschlossen werden oder selbst die Rolle des Host übernehmen.

Seit dem Erscheinen der ersten USB-Spezifikation 1996 hat sich der Standard beträchtlich weiterentwickelt. Nicht nur die Datentransferrate ist gestiegen, sondern auch das Protokoll wurde erweitert. Die Möglichkeit, Kameras und Soundkarten per USB an einen Rechner anzuschließen, erfordert nicht nur eine hohe Datenrate, sondern auch eine dem Gerät zugesicherte Mindestbandbreite, was erst mit USB 2.0 als speziellem Übertragungsmodus eingeführt wurde.

Selbstgebauter AVR-Programmer mit USB-Schnittstelle in Aktion

Hardware

USB-Kabel für die Versionen 1.x und 2.0 führen grundsätzlich die gleichen Leitungen. Ab Version 3.0 sind zusätzliche Leitungen hinzugekommen, um den Vollduplex-Betrieb zu ermöglichen. Ein USB-Stecker der Version 2.0 oder kleiner passt jedoch immer in eine USB-3.0-Buchse. USB 3.1 hat neue Stecker und Buchsen, an die frühere Versionen nur noch mit Adapter passen. Für V-USB relevant sind nur die Stecker und Buchsen der USB-Versionen 1.x beziehungsweise 2.0, da V-USB nur den Low-Speed Datentransfer unterstützt.

Die Leitungen VBUS (5 Volt) und GND (Masse) können das USB-Gerät mit Strom versorgen, falls es keine eigene Stromversorgung besitzt. Der maximale Strom ist laut Standard zunächst auf 100 mA begrenzt, was durch eine Anforderung durch das USB-Gerät, aber bis zu 500 mA ausgedehnt werden kann. Der Standard schreibt weiter vor, dass der Host entscheiden muss, ob er so viel Strom zur Verfügung stellen kann. Die wenigsten Hersteller halten sich jedoch an diese Begrenzungen. Viele PCs liefern auch ohne Anfrage mehr Strom und verlassen sich darauf, dass die Hersteller der Peripherie den Bus nicht zu sehr belasten. Nicht nur bei selbstgebauten Geräten empfiehlt es sich, darauf zu achten, den Stromverbrauch über USB im Auge zu behalten. Falls das USB-Gerät für seine Funktion bis zu 500 mA benötigt, muss es laut Standard in einer Art Wartezustand bleiben, bis die 500 mA geliefert werden können. Ab USB 3.0 wurden die Werte angehoben, auf standardmäßig 150 mA und bis zu 900 mA auf Anfrage. Ein Gerät, das über USB mit Strom versorgt wird, nennt man bus powered.

Tabelle: Versionsgeschichte des USB-Standards

D+ und D- bilden ein differenzielles Leitungspaar, sie dienen zur Datenübertragung. Eine 1 wird übertragen, wenn das Spannungspotenzial von D+ höher ist als von D-. Ist es umgekehrt, wird eine 0 übertragen. Bei Low- und Full-Speed-Geräten sind die Spannungen auf 0 (niedriger Pegel) und zwischen 2,8 bis 3,6 Volt (hoher Pegel) festgelegt. Bei High-Speed-Geräten sind die Spannungen auf den Datenleitungen wesentlich niedriger (0 und zwischen 0,36 bis zu 0,44 Volt). High-Speed-Eingänge müssen auch bis zu 3,6 Volt aushalten, damit sie abwärtskompatibel bleiben.

Im Vergleich zu einer einzelnen Leitung ist die Übertragung der Daten über zwei differenzielle Leitungen weniger störungsanfällig, weil damit Einstreuungen ausgelöscht werden können. Wird ein Signal vom anderen subtrahiert, dann verschwindet die Einstreuung, das Nutzsignal wird jedoch verstärkt.

Tabelle: Leitungen

Die Stecker und Buchsen der Typen USB-Mini und -Micro haben zusätzlich einen ID-Pin, dessen Funktion ist es, zusätzlich anzuzeigen, ob es sich um einen Stecker oder eine Buchse des Typ A oder Typ B handelt. Bei Typ A ist ID mit Masse verbunden, bei Typ B ist ID nicht verbunden. Diese Unterscheidung ist vor allem bei Smartphones wichtig, denn dort ist nur eine Buchse angebracht, die beide Funktionen einnehmen kann: Typ A als Host und Typ B, wenn das Smartphone als Peripheriegerät an einen PC angeschlossen wird.

Ebenfalls in Hardware realisiert ist die Erkennung eines Low-Speed- oder Full-Speed-Gerätes. Bei Low-Speed muss D- auf 3,3 Volt gezogen werden. Bei Full-Speed wird D+ auf 3,3 Volt gezogen. Die USB-Spezifikation sieht dazu jeweils einen Pull-up-Widerstand mit 1,5 kΩ vor.

Protokoll

Damit der USB-Standard auch wirklich universell einsatzbar ist, muss das Protokoll einerseits eine Kommunikation ermöglichen, die bei allen Geräten gleich ist ,und ebenso Übertragungsmodi anbieten, die je nach Gerät unterschiedlich sein können, weil ein Drucker beispielsweise völlig anders behandelt werden muss als eine Webcam. Das genaue Format der Datenübertragung wird durch sogenannte Deskriptoren im Gerät festgelegt. Der bei allen Geräten gleiche Teil ist der Erstkontakt mit dem Host, genannt Enumeration.

Tabelle: Beispiele für Geräte-Klassen

Beim Anschließen eines Geräts passierst grob Folgendes: Zuerst stellt der Host anhand der Spannungen auf den Leitungen fest, ob eine Kommunikation in Low-Speed oder Full-Speed stattfinden kann. Dann fordert der Host den Geräte-Deskriptor an (dazu später mehr), der Informationen über die Art des Geräts und wie der Host damit kommunizieren kann, enthält. Nun erhält das Gerät eine eindeutige Nummer vom Host, über die es danach angesprochen wird. Die Erteilung dieser Nummer beziehungsweise Adresse gibt dem Vorgang des Erstkontakts auch den Namen Enumeration. Dann wird unter anderem der Strombedarf abgeklärt. Sind alle wichtigen Daten ausgetauscht, kann ein passender Treiber für das Gerät geladen werden. Danach kann es in Betrieb genommen werden.

Der Treiber wird anhand der Klasse beziehungsweise Subklasse ausgewählt, in die alle USB-Geräte eingeteilt werden und die in einem Deskriptor vermerkt sind. Es gibt Standardklassen und dazugehörige Standardtreiber, für die in der Regel keine spezielle Treiberinstallation nötig ist, da gängige Betriebssysteme mit den USB-Standardtreibern für alle Geräteklassen ausgeliefert werden. Daneben gibt es herstellerspezifische Klassen, die einen eigenen Treiber benötigen. Bei einem Gerät mit herstellerspezifischer Klasse wird der passende Treiber anhand der Hersteller-ID (Vendor ID, beziehungsweise VID) und der Produkt-ID (PID) ausgewählt.

Deskriptoren

Ein Gerät wird durch mehrere Deskriptoren beschrieben. Der Geräte-Deskriptor (Device Descriptor) enthält Informationen, die vor allem für die Enumeration von Bedeutung sind, wie die maximale Paketgröße, in der festgehalten wird, wie viele Daten das Gerät bei der Kommunikation maximal am Stück aufnehmen kann. Außerdem ist im Geräte-Deskriptor die Geräteklasse sowie die Hersteller-ID und Produkt-ID gespeichert.

Ein USB-Gerät hat außerdem eine oder mehrere Konfigurations-Deskriptoren. Dort wird beispielsweise der Strombedarf für die jeweilige Konfiguration spezifiziert. Kann der Host nicht genügend Strom liefern, muss es eine Konfiguration beziehungsweise einen Modus geben, in dem das Gerät trotzdem durch den Host erreichbar ist. Es besteht ja die Möglichkeit, dass irgendwann genug Strom geliefert werden kann und bis dahin muss das Gerät auf Anfragen vom Host reagieren können.

In einer Konfiguration können mehrere Interfaces festgelegt sein. Hier wird unter anderem die Geräte-Subklasse bestimmt. Zum Beispiel kann ein Gerät zur Klasse Audio gehören und zur Subklasse MIDIStreaming. Ein Interface kann mehrere sogenannte Endpunkte enthalten, über die die Übertragung der Nutzdaten erfolgt. Zu jedem Interface und jedem Endpunkt gibt es jeweils einen Deskriptor. Mehrere Interfaces sind zum Beispiel dann erforderlich, wenn es sich um ein Composite Device handelt, also ein Verbund-Gerät, wie etwa eine Webcam mit eingebautem Mikrofon.

Ein Gerät kann pro Konfiguration maximal 31 Endpunkte enthalten. Die Endpunkte werden nummeriert, wobei die 0 für den Kontrollendpunkt reserviert ist, mit dem zum Beispiel der Geräte-Deskriptor bei der Enumeration übertragen wird. Low-Speed-Geräte können maximal drei Endpunkte enthalten, womit die Möglichkeiten für Composite Devices eingeschränkt sind.

Übertragungsmodi

USB-Geräte haben je nach Klasse ganz unterschiedliche Anforderungen an die Datenübertragung. Eine Festplatte soll beispielsweise möglichst schnell große Mengen an Daten über USB senden und empfangen können. Dabei ist es nicht wichtig, dass zu jedem Zeitpunkt eine hohe Bandbreite zur Verfügung steht. Die Übertragung kann durchaus auch über kurze Zeiträume langsam geschehen. Es kommt viel mehr darauf an, dass die Daten fehlerfrei übermittelt werden. Dazu werden Prüfsummen eingesetzt und die korrekte Übertragung vom Empfänger bestätigt. Bei Soundkarten ist es ganz anders. Es wird eine feste Bandbreite benötigt, die zu jedem Zeitpunkt gewährleistet sein muss, denn sonst kann es zu Unterbrechungen im Stream kommen, die sich in unangenehm lauten Knacksern bemerkbar machen. Die hohe Bandbreite wird jedoch auf Kosten der Datensicherheit zur Verfügung gestellt. Eine Prüfsumme der Daten wird nicht erstellt und der korrekte Empfang wird auch nicht bestätigt. Um diese verschiedenen Anforderungen zu unterstützen, gibt es sogenannte Übertragungsmodi.

Enumeration

Ein USB-Peripherie-Gerät wird vom Host über eine eindeutige Nummer beziehungsweise Adresse angesprochen. Wenn das Gerät gerade angeschlossen wurde, nimmt es zunächst die Nummer 0 an. Der Host fordert nun einen Teil des Geräte-Deskriptors an, vor allem, um die maximale Paketgröße, die das Gerät akzeptieren kann, zu ermitteln. Dieser Teil ist entscheidend für die folgende Kommunikation, damit auch alle vom Host gesendeten Daten im Gerät ankommen. Als Nächstes wird eine eindeutige Adresse zwischen 1 und 127 zugeteilt. Dann wird der Rest des Geräte-Deskriptors übertragen und danach die Konfigurations-, Interface- und Endpunkt-Deskriptoren.

Da alle USB-Geräte beim Einschalten die Adresse 0 haben, werden nach dem Booten eines Host alle Geräte nach Anschluss sortiert hintereinander enumeriert.

VID/PID

Tabelle: USB-Übertragungsmodi

Um USB-Geräte verkaufen zu können, muss man normalerweise eine Hersteller-ID (VID) beim USB Implementors Forum Inc. kaufen. Die ID kostet mehrere tausend Euro und ist maximal zwei Jahre gültig, dann wird wieder eine Gebühr fällig. Hat man eine VID erstanden, kann man die Produkt-IDs (PIDs) frei vergeben. Es gibt auch Hersteller, die ihre IDs frei zur Verfügung stellen, zum Beispiel Obdev – die Entwickler von V-USB. Diese IDs sind jedoch nur für den Hobby-Gebrauch nutzbar. Es gibt auch Hersteller, deren VID man nutzen kann, wenn man bei ihnen einzelne PIDs kauft. Für kleinere Serien macht das zum Beispiel Obdev.

V-USB

Um ein AVR-basiertes Gerät mit einem USB-Anschluss auszustatten und diesen per V-USB zu betreiben, muss der AVR-Mikrocontroller mit einer bestimmten Taktfrequenz laufen. V-USB unterstützt 12, 12,8, 15, 16, 16,5, 18 und 20 MHz. Nur mit den Taktraten 12,8 und 16,5 MHz lässt V-USB eine Toleranz von 1 % zu. Damit eignen sich nur diese beiden, um den AVR-Mikrocontroller mit internem RC-Oszillator zu betreiben, für alle anderen Frequenzen benötigt man einen externen Quarz. Um mit dem RC-Oszillator Frequenzen von 12,8, beziehungsweise 16,5 MHz zu erzeugen, nutzt man die Möglichkeit, dass der Oszillator über ein Register kalibrierbar ist. Damit kann die Frequenz, die bei der Herstellung der Mikrocontroller nur grob eingestellt wird, nachbearbeitet werden. Dazu wird eine fertige Funktion der V-USB-Bibliothek genutzt.

Der ATtiny85 wird mit 5 Volt versorgt und die GPIOs für D+ und D- mit Z-Dioden auf maximal 3,6 Volt begrenzt.

Außerdem stellt sich die Frage nach der Versorgungsspannung. USB kann den AVR mit 5 Volt versorgen, die Datenleitungen D+ und D- benötigen jedoch Pegel zwischen 2,8 und 3,6 Volt. Man kann also die Versorgungsspannung reduzieren, aber dann wird kein zuverlässiger Betrieb des AVR mit hohen Taktraten mehr garantiert. Es ist also am sinnvollsten den Mikrocontroller mit 5 Volt zu betreiben und die Datenleitungen mit Zener-Dioden auf maximal 3,6 Volt zu begrenzen, so wie im Bild.

Zum Ausprobieren der V-USB-Bibliothek eignet sich am besten eines der zahlreichen Beispiele der V-USB-Webseite. V-USB wird unter der GPL-Lizenz veröffentlicht und Obdev bittet zusätzlich darum, dass man seine Projekte im Netz veröffentlicht, sodass diese auf der V-USB-Webseite verlinkt werden können, was die Fülle an Beispielen erklärt.

Leider werden die meisten Projekte nicht aktualisiert und so lassen sich viele davon nicht mit aktuellen Toolchains übersetzen. Um so ein Projekt trotzdem an den Start zu bekommen, ist es oft lediglich nötig, eine aktuelle Version von V-USB zu integrieren. Am Beispiel von Vusbtiny soll dies veranschaulicht werden.

Vusbtiny

Vusbtiny ist ein AVR-Programmer, also ein Gerät, um Programmcode in Form einer hex-Datei auf einen AVR-Mikrocontroller zu übertragen (flashen). Das ist besonders dann sehr hilfreich, wenn man einen AVR ohne Arduino-Bootloader betreiben möchte, oder versehentlich den Bootloader eines Arduinos gelöscht hat. Der Arduino-Bootloader wird im Flash-Speicher eines AVR gespeichert und ausgeführt, wenn Programmcode auf den Arduino übertragen wird. Fehlt der Bootloader (wie zum Beispiel bei einem selbstgebauten Arduino), kann dieser mit einem Programmer auf den AVR übertragen werden. Für die winzigen Mikrocontroller der ATtiny-Serie ist das Betreiben ohne Arduino-Bootloader aus Speicherplatzgründen oder um Pins zu sparen, besonders nützlich.

Avrdude ist das Programm, das in den meisten Fällen auf dem PC läuft, um einen AVR-Programmer zu steuern. Es läuft auch im Hintergrund der Arduino-IDE, immer dann, wenn Programmcode auf den Arduino übertragen wird. Vusbtiny ist kompatibel mit dem Programmer Usbtiny und wird von Avrdude als ebendieser erkannt.

Bevor man sich an die Software macht, benötigt man eine korrekt aufgebaute Hardware. Eine einfache Lösung ist der Aufbau der Schaltung auf einer Lochrasterplatine wie im Bild.

Nach dem Aufbau der Schaltung kann man sich um die Software kümmern. Zuerst lädt man sich das Quellcode-Archiv von Vusbtiny herunter (siehe Linksammlung). Das Archiv entpackt man nun, zum Beispiel mit 7zip oder mit dem Kommando tar xfz vusbtiny.tgz. In dem Ordner vusbtiny löscht man den Ordner usbdrv, der eine veraltete Version von V-USB enthält. Als Nächstes besorgt man sich eine aktuelle Version von V-USB aus unserer Linksammlung (zur Zeit ist das Version 20121206) und entpackt diese. Jetzt kann man den Unterordner usbdrv aus der aktuellen V-USB-Version in den Projektordner von Vusbtiny kopieren.

Schaltplan des Vusbtiny-Programmers
So kann man den Vusbtiny auf einer Lochrasterplatine aufbauen.

Vusbtiny sollte nun drei Dateien main.c, makefile, usbconfig.h und den Ordner usbdrv enthalten. Das makefile muss je nach verwendeter Hardware an ein paar Stellen angepasst werden. Dazu verwendet man einen Texteditor seiner Wahl, zum Beispiel den mit WinAVR installierten Editor Programmers Notepad. Die Zeile MCU=attiny45 muss in MCU=attiny85 geändert werden, falls man den Mikrocontroller ATtiny85 verwendet. Entsprechend verfährt man mit der Zeile PROGRAMMER_MCU=t45. Das Kommentarzeichen # in der Zeile #AVRDUDE_PROGRAMMERID=avrisp2 muss entfernt werden, falls man einen AVRISP MKII verwendet und das Flashen per Makefile erledigen möchte. Der Zeile AVRDUDE_PROGRAMMERID=usbtiny muss das Kommentarzeichen # vorangestellt werden, falls man es in der Zeile davor entfernt hat. Zum Flashen per Makefile kommt avrdude zum Einsatz. Wer einen anderen Programmer besitzt, muss dazu usbtiny beziehungsweise avrisp2 entsprechend ändern. Besitzt man keinen Programmer, kann man alternativ auch ein Arduino UNO benutzen. Die Zeile im makefile muss dann AVRDUDE_PROGRAMMERID=avrisp lauten. Wie das Flashen mit dem Arduino UNO genau funktioniert, beschreiben wir in einem Online-Artikel, der in der Linksammlung zu finden ist. Zum Übersetzen des Quellcodes gibt man nun das Kommando make ein und zum Flashen anschließend make install, beziehungsweise nutzt man dafür Arduino. Wer einen AVRISP MKII besitzt und den Wannenstecker für die Programmier-Pins benutzt hat, so wie es im Layout-Plan dargestellt ist, kann den AVRISP MKII direkt an den Wannenstecker anschließen. Der Vusbtiny nutzt diesen Stecker nicht nur, um die eigene Firmware zu erhalten, sondern auch, um danach andere Mikrocontroller zu flashen.

Der Vusbtiny-Programmer funktioniert nach dem Flashen noch nicht. Die Konfiguration des Reset-Pins und des Systemtakts auf 16,5 MHz muss noch vorgenommen werden. Da der Reset-Pin des ATtiny85 als GPIO-Pin benutzt wird, ist der Mikrocontroller danach nicht mehr umprogrammierbar, es sei denn, man hat Zugang zu einem High-Voltage-Programmer, zum Beispiel einem STK500. Der Systemtakt mit 16,5 MHz wird vom internen RC-Oszillator und einer PLL generiert, dadurch entfällt die Notwendigkeit, einen externen Quarz zu benutzen, und man spart ein Bauteil und zwei GPIO-Pins.

Diese Systemkonfiguration wird nicht im Programmcode vorgenommen, sondern über eine spezielle Speicherstelle im Flash. Es handelt sich dabei nur um wenige Bits, die beim AVR Fuse-Bits genannt werden, was so viel wie Sicherungs-Bits bedeutet. Details dazu befinden sich ebenfalls im Online-Artikel zum Thema Arduino UNO als In-System-Programmer, der in der Linksammlung zu finden ist. Um die Fuse-Bits zu setzen, benötigt man Avrdude und einen Programmer oder alternativ ein Arduino UNO. Welcher Programmer verwendet wird, stellt man bei Avrdude über die Kommandozeilen-Option -c ein. Ein AVRISP MKII wird mit avrips2 angegeben, der Vusbtiny mit usbtiny. Probeweise kann man auch nur den Systemtakt anpassen und den Reset-Pin in seiner ursprünglichen Funktion belassen. Das genügt, um festzustellen, ob der Vusbtiny-Programmer am USB-Port erkannt wird. Zum Flashen kann man ihn jedoch erst verwenden, wenn der Reset-Pin als GPIO konfiguriert wurde. Die Kommandozeile für das Anpassen der Fuse-Bits mit dem Reset-Pin im Reset-Modus und mit Arduino als Programmer lautet:

avrdude -c avrsip -p t85 -V -U

lfuse:w:0xe1:m -U hfuse:w:0xdd:m -U

efuse:w:0xff:m

Die Kommandozeile um den Systemtakt und den Reset-Pin als GPIO anzupassen lautet:

avrdude -c avrisp -p t85 -V -U

lfuse:w:0xe1:m -U hfuse:w:0x5d:m -U

efuse:w:0xff:m

Der Vusbtiny-Programmer wird unter Linux ohne Treiberinstallation erkannt. Um dies zu prüfen, schließt man den Vusbtiny an und benutzt das Kommando dmesg. Die letzten Zeilen der Ausgabe von dmesg müssen dann in etwa so aussehen:

usb 1-1: new low-speed USB device number 6

using xhci_hcd

usb 1-1: New USB device found,

idVendor=1781, idProduct=0c9f

usb 1-1: New USB device strings: Mfr=0,

Product=2, SerialNumber=0

usb 1-1: Product: USBtinySPI

In der V-USB-MIDI-Variante wird der Reset-Pin nicht als GPIO genutzt und muss deshalb über einen Pull-up-Widerstand auf 5 Volt gezogen werden.

Unter Windows muss man erst einen Treiber installieren (zum Download gelangt man über unsere Linksammlung). Die folgende Installationsbeschreibung bezieht sich auf Windows 10. Nach dem Download der Treiber-Zip-Datei muss diese entpackt werden. Jetzt kann man das Vusbtiny anschließen und muss warten, bis die automatische Treiberinstallation abbricht. Nun startet man den Geräte-Manager über Startmenü/Einstellungen/Geräte/Geräte-Manager. Dort gibt es einen Eintrag USBtinySPI. Diesen klickt man mit der rechten Maustaste an, woraufhin ein Menü erscheint, in dem man die Option Treibersoftware aktualisieren wählt. Danach klickt man Auf dem Computer nach Treibersoftware suchen und Durchsuchen. Hier wählt man den heruntergeladenen und entpackten Treiber-Ordner usbtiny_signed_8 aus und klickt auf Weiter und danach Schließen. Die letzten Schritte musste ich zweimal ausführen, da die Installation aus unerfindlichen Gründen beim ersten Mal mit einem Fehler abgebrochen ist.

Nach der Treiberinstallation wird das Vusbtiny als USBtiny im Geräte-Manager angezeigt. Ist das nicht der Fall, sollte man noch mal die korrekte Beschaltung der Pins kontrollieren. Das Vertauschen von D+ und D- ist ein häufiger Fehler.

Unter Linux und Windows kann man jetzt testen, ob Avrdude das Vusbtiny erkennt:

avrdude -c usbtiny -p t85

Der Test funktioniert auch ohne einen an den Vusbtiny angeschlossenen Mikrocontroller, und das meldet Avrdude dann in diesem Fall:

avrdude: initialization failed, rc=-1

Double check connections and try

again, or use -F to override

this check.

Wird der Vusbtiny nicht erkannt, dann lautet die Meldung:

avrdude: Error: Could not find USBtiny

device (0x1781/0xc9f)

So sieht der Aufbau der USB-MIDI-Faderbox auf einer Lochrasterplatine aus.

Beispiele umbauen

Um einen etwas tieferen Einblick in V-USB zu erlangen, haben wir zwei Beispielprojekte kombiniert und modifiziert. Als Grundlage wurden die Dateien makefile und usbconfig.h von Vusbtiny und main.c von V-USB-MIDI verwendet. Das Ergebnis ist eine winzige USB-MIDI-Faderbox mit einem Schieberegler und einem Taster, die nun als Eingabegerät für Musiksoftware, wie zum Beispiel Ableton Live verwendet werden kann. Die Hardware ist der des Vusbtiny sehr ähnlich. Der gesamte Code ist als Archiv über die Linksammlung erhältlich.

Viele Teile des Codes der beiden Projekte sind wiederverwendbar, für das Messen der Schiebereglerposition und des Tasterzustands braucht man jedoch eigenen Code. Der vorhandene Code muss außerdem an die Bedürfnisse des Projekts angepasst werden. In der Datei usbconfig.h werden die USB-Deskriptoren konfiguriert. Die Quellcodes sind mit Kommentaren gespickt, weshalb hier nur die wichtigsten Zeilen beschrieben werden.

Hersteller-ID und Geräte-ID werden in den Zeilen 218 und 227 in usbconfig.h angegeben. Hier sind die von Obdev empfohlenen IDs für MIDI-Geräte verwendet worden, so wie beim Projekt V-USB-MIDI:

218: #define USB_CFG_VENDOR_ID 0xc0, 0x16

227: #define USB_CFG_DEVICE_ID 0xe4, 0x05

Der Name des Herstellers und des Gerätes sowie die Geräte- und Interfaceklassen und -Subklassen werden in den Zeilen 242 bis 273 festgelegt. Die Namen werden als Stringdesrkiptoren gespeichert und die Anzahl der verwendeten Zeichen muss ebenfalls angegeben werden:

241: #define USB_CFG_VENDOR_NAME 'm',

'a', 'k', 'e', '-', 'm', 'a', 'g',

'a', 'z', 'i', 'n', '.', 'd', 'e

'

242: #define USB_CFG_VENDOR_NAME_LEN 15

251: #define USB_CFG_DEVICE_NAME 't',

'i', 'n', 'y', 'M', 'I', 'D', 'I'

252: #define USB_CFG_DEVICE_NAME_LEN 8

266: #define USB_CFG_DEVICE_CLASS 0

267: #define USB_CFG_DEVICE_SUBCLASS 0

271: #define USB_CFG_INTERFACE_CLASS 1

272: #define USB_CFG_INTERFACE_SUBCLASS 3

273: #define USB_CFG_INTERFACE_PROTOCOL 0

Die restlichen Einstellung übernimmt man am besten so, wie sie sind. Für eigene Projekte ist es am einfachsten in den Beispielprojekten der V-USB-Bibliothek abzugucken. Eine genaue Dokumentation jeder Einstellung findet man im Wiki von V-USB.

Die Deskriptoren werden im Flash abgelegt und die konfigurierten Werte aus usbconfig.h findet man deshalb am Anfang von main.c wieder. USB wird über Interrupts und Polling abgehandelt. Die für uns relevanten Funktionen in main.c sind hardewareInit() (Zeile 281) zur Konfiguration der Pins und des AD-Wandlers für den Schieberegler, die Funktion adc() (Zeile 327), die den Kanal des AD-Wandlers als Übergabewert erhält und den Messwert als 16-Bit-Integer zurückgibt, sowie die Funktion sendMidi() (Zeile 341), die aus dem V-USB-MIDI-Projekt übernommen wurde und für das Senden der MIDI-Daten verantwortlich ist. Die wichtigste Funktion ist natürlich main() (Zeile 360), in der die Funktionen zum Initialisieren der Hardware und der V-USB-Bibliothek aufgerufen werden.

In der Endlosschleife while(1) muss usbPoll() aufgerufen werden. usbPull() ruft alle relevanten Funktionen der V-USB-Bibliothek auf, die sicherstellen, dass die Kommunikation über USB reibungslos abläuft. Der restliche Code in der Endlosschleife darf nicht mehr als 50 ms Rechenzeit beanspruchen, da usbPoll() mindestens alle 50 ms aufgerufen werden muss.

In unserem Fall wird der Watchdog benutzt. Das ist ein Zähler, der beim Überschreiten seines Maximums einen Reset des Mikrocontrollers auslöst. Der Zähler läuft automatisch mit einstellbarer Taktrate und muss deshalb regelmäßig auf null gesetzt werden. Das wird ebenfalls in der Endlosschleife durch wdt_reset() erledigt. Der Watchdog ist dann nützlich, wenn das Programm einmal abstürzen sollte und in einer ungewollten Schleife hängen bleibt.

USB-MIDI-Faderbox mit LED zum Debugging

Auch das Messen der Position des Schiebereglers (Fader) und des Tasterzustands (Button) wird in main.c erledigt. Je nach Ergebnis der Messung wird der erhaltene Wert mit sendMidi() übertragen, jedoch nur, wenn sich der Messwert im Vergleich zur vorhergehenden Messung geändert hat. Dazu werden die Werte in den Variablen tmpb und tmpc zwischengespeichert, um sie im nächsten Durchlauf mit den neuen Werten vergleichen zu können.

button = (PINB & 2);

if(button != tmpb) {

if(button == 0) {

sendMidi(0x90, 62, 127);

} else {

sendMidi(0x80, 62, 0);

}

}

tmpb = button;

fader = adc(1);

controller = fader >> 3;

if(tmpc != controller) {

sendMidi(0xb0, 7, controller);

}

tmpc = controller;

Unser Code hat noch einiges an Verbesserungspotenzial. Da das eigentliche Senden der Daten über usbPoll() abgewickelt wird, gibt es einen Puffer, in dem die zu sendenden Daten zwischengespeichert werden. Wenn versucht wird, Daten zu senden, obwohl der Puffer noch voll ist, gehen die Daten verloren. Dies geschieht in unserem Fall, wenn man die Position des Schiebereglers schnell verändert und währenddessen wiederholt die Taste drückt. Um dies zu verhindern, könnte eine Sicherheitsabfrage in den Code eingebaut werden. V-USB unterstützt das, aber dazu muss man die Bibliothek sehr gut beherrschen und unser Beispiel würde einiges komplizierter werden.

Um sich weiter in V-USB einzuarbeiten, sind die vielen Beispiele auf der Homepage sehr hilfreich, aber auch die ausführliche und gut verständliche Dokumentation im Wiki von V-USB. pff