Einleitung
USB-Hardware der
PIX18Fxx5x
Das Anpassen der
Firmware
Anpassen der Firmware an die nötige USB-KonfigurationDie Nutzung der Device-Treiber
Einträge in der Datei usbdsc.c
Anpassung der Anwendungssoftware an die Firmware
Delphi-Unit zum Nutzen des Microchip Custom DriverEin einfaches USB-Beispiel-Übungsprojekt
Das Schreiben der Windows-Anwendung und das Schreiben der PIC-Anwendung ist nichts Neues. So was musste man auch schon erledigen, als man noch RS232 oder den Parallelport benutzte.
Um USB nutzen zu können, müssen wir aber zusätzlich zwei Künste beherrschen:
Die PIC18Fxx5x enthalten ein USB2.0-Interface mit folgenden Eigenschaften:
Device
Mit diesem Interface lässt sich
ein USB-Device aufbauen, aber kein Controller.
Modes
Es werden die Modes Low-Speed (1.5
MBit/s) und Full-Speed (12 MBit/s) unterstützt, nicht
jedoch
der High-Speed-Mode (500 MBit/s).
Endpunkte
Es gibt 16 Hardware-Endpunkte. Jeder
Endpunkt
kann als ein out-Endpunkt und als ein in-Endpunkt programmiert werden.
Beides ist sogar gleichzeitig möglich, womit dann bis zu 16
out-Endpunkte
und 16 in-Endpunkte gleichzeitig zur Verfügung stehen. Da
der
USB-Standard keine bidirektionalen Endpunkte kennt (außer
Endpunkt0)
sind das dann 32 Endpunkte. Für die Endpunkte steht insgesamt ein
1-kByte-großer Speicherbereich (Dualport-RAM) zur Verfügung.
USB-Spannung
Der PIC hat einen internen
3,3V-Spannungsstabilisator,
der den nötigen USB-Spannungspegel erzeugt. Für seinen
Betrieb
ist an das Pin VUSB ein Siebkondensator (ca. 200 nF) nach Vss
anzuschließen.
Pins
Neben dem VUSB-Pin gehören
die Pins D+ und D- (USB-Datenleitungen) zum
USB-Interface.
D+ und D- sind die Aus/Eingänge des internen USB-Transceivers
(Leitungstreibers)
die direkt an die USB-Buchse angeschlossen werden können. Die
nötigen
USB-pull-up-Widerstände sind im PIC enthalten.
Der Anschluss eines externen
Transceivers
ist aber auch möglich, wenn z.B. eine galvanische Trennung zum
USB-Bus
gefordert sein sollte. Dann werden 6 Pins (VM, VP, RCV, VMO, VPO, _OE)
für den Anschluss benötigt.
Takt
Ein spezieller Taktgenerator
ist in der Lage, den nötigen USB-Takt aus einer Vielzahl von
möglichen
Eingangstakten zu erzeugen (48, 40, 24, 20, 16, 12, 8 oder 4 MHz).
Externe Bauelemente
Im einfachsten Fall eines
Bus-powered-Device
wird neben der USB-Buchse nur ein einzelner externer Kondensator (an VUSB)
benötigt.
Insgesamt gesehen ist die
Hardware-Seite
dieses Interface Ideal für Bastler, die auf USB umsteigen wollen
Die von Microchip bereitgestellte freie
Software findet man auf der Microchip-Homepage.
Diese Grafik zeigt den eine Übersicht
über die Sourcecode-Dateien für die Microchip Custom Driver
Firmware.
Das sind insgesamt ca. 50 Files in etwa 12 Verzeichnissen. Alles
Zusammen
ist ein MPLAB-Projekt, das sich mit dem C18-Compiler in ein HEX-File
compilieren
lässt. Das Hauptverzeichnis ist in diesem Beispiel das
Verzeichnis
"Demo". Die Unterverzeichnisse haben folgende Funktionen:
Je nachdem wieviel Konfigurationen
mit wieviel Interfaces und Endpunkten das USB-Device haben soll,
muss
man drei Konfigurationsfiles im Unterordner "autofiles"
editieren.
Natürlich muss auch die
PIC-Anwendung
ihren Weg in das HEX-File finden. Dafür packt man zunächst
alle
Sourcecode-Dateien der Anwendung in das "user"-Verzeichnis. Dann
muss man diese User-Source-Dateien in das Projekt aufnehmen, und
schon
werden sie zusammen mit der Firmware in ein gemeinsames HEX-File
Compiliert.
Die Custom Driver Firmware definiert in der Grundeinstellung:
Die gute Info vorweg:
Wer mit nur einer Konfiguration mit nur
je einem in- und einem out-Endpunkt (je 64 Byte groß,
Interrupt-Mode,
30 Abfragen pro Sekunde) auskommt (und für die meisten einfachen
Anwendungen
ist das genug), der kann die Microchip Custom Driver Firmware
weitestgehend
unverändert übernehmen. In diesem Fall kann man den
folgenden
Abschnitt grob überfliegen, und man
liest
dann hier genauer weiter.
Anpassen der Firmware an die nötige USB-Konfiguration
Die gesamte Struktur des jeweiligen USB-Device wird mit den Deskriptoren (in autofiles) beschrieben. In der Microchip-Firmeware haben diese Deskriptoren zwei Funktionen:
Im Ordner autofiles liegen drei wichtige Konfigurationsdateien.
Im Hauptordner des Projektes liegt die
Datei io_cfg.h. Sie enthält Makros die zur Initialisierung
und Bedienung der IO-Pins des PICs verwendet werden. Diese Datei ist an
das PICDEM FS USB-Demoboard angepasst. Um eine andere Platine verwenden
zu können (an der z.B. LEDs oder Taster an andere Pins
angeschlossen
sind, oder die anstelle des PIC18F4550 einen PIC18F2550 mit weniger
Ports
verwendet) muss diese Datei editiert werden
usbcfg.h
usbdsc.c Dieses File enthält die USB-Deskriptor-Information. Das sind:
Der Device-Deskriptor (18 Bytes) beschreibt das Device generell. Das heißt er enthält Hersteller- und Gerätekennung, sowie die Anzahl der möglichen Konfigurationen des Device. Für jede mögliche Konfiguration des Device (mindestesna also für eine) gibt es einen Konfigurations-Deskriptor (9 Bytes). Er beschreibt, wie viele Interfaces zur jeweiligen Konfiguration gehören, und wieviel Strom das Device in dieser Konfiguration vom USB-Bus braucht. Für jedes Interface jeder Konfiguration (mindestens also für eines) gibt es einen Interface-Deskriptor (9 Bytes). Hier steht der Interface-Typ (Maus, Keyboard, Massenspeicher, Audio, anderes ...) sowie die Anzahl der Endpunkte dieses Interfaces. Für jeden Endpunkt gibt es einen Endpunkt-Deskriptor (7 Bytes ), der die Größe des Endpunkt-Speichers und seine Betriebsart (Control, Interrupt, isochronous, bulk) festlegt, sowie angibt, wie oft der USB-Controller diesen Endpunkt abfragen (pollen) soll. |
Einträge in der Datei usbdsc.c
Die folgenden Beispiele stammen aus der
Microchip Custom Driver Firmware.
Device Deskripor
(18 Bytes lang, der
genaue Aufbau steht hier)
Es gibt genau einen Device Deskriptor
für
ein USB-Gerät.
Er enthält u.A. die Information
über
den Gerätehersteller (Vendor ID) un den GereteTyp (Product ID).
Außerdem enthält er die Anzahl
der Konfigurationen dieses Gerätes, in diesem Falle : 1.
In der Microchip-Firmware sieht das
dann
so aus:
/* Device Descriptor */ rom USB_DEV_DSC device_dsc= { sizeof(USB_DEV_DSC), // Size of this descriptor in bytes DSC_DEV, // DEVICE descriptor type 0x0200, // USB Spec Release Number in BCD format 0x00, // Class Code 0x00, // Subclass code 0x00, // Protocol code EP0_BUFF_SIZE, // Max packet size for EP0, see usbcfg.h 0x04D8, // Vendor ID 0x000C, // Product ID: PICDEM FS USB (DEMO Mode) 0x0000, // Device release number in BCD format 0x01, // Manufacturer string index 0x02, // Product string index 0x00, // Device serial number string index 0x01 // Number of possible configurations }; |
Der Device-Deskriptor enthält Verweise auf drei Strings, die zur näheren Beschreibung des Devices dienen:
rom struct{byte bLength;byte bDscType;word
string[1];}sd000={ sizeof(sd000),DSC_STR,0x0409}; rom struct{byte bLength;byte bDscType;word string[25];}sd001={
rom struct{byte bLength;byte bDscType;word
string[33];}sd002={ rom const unsigned char *rom USB_CD_Ptr[]={&cfg01,&cfg01};
|
Eine weitere Pointer-Tabelle (USB_CD_Ptr)
enthält
Pointer
auf
alle Configuration-Deskriptoren des Device.
Der
Eintrag mit dem Index 0 ist aber nur ein Dummy-Eintrag. Obwohl im
Beispiel
2 Einträge zu sehen sind, ist also der erste nur ein Dummy, und
nur
der zweite (cfg01) ist ein Pointer auf die erste Configuration.
Konfigurations-Deskriptor
(9 Bytes lang, der
genaue
Aufbau
steht
hier)
Im Device Deskriptor stand die Anzahl der möglichen Konfigurationen des Devices. Für jede dieser Konfigurationen muss ein Konfigurations-Deskriptor existieren. Diese sind durchnummeriert. Der erste (und in diesem Beispiel der einzige) ist CFG01.
Der Konfigurations-Deskriptor enhält u.A. die Anzahl der in dieser Konfiguration enthaltenen Interfaces, sowie den maximalen Stromverbrauches des Devices in dieser Konfiguration.
Interface-Deskriptoren und
Endpoint-Deskriptoren
sind in der Microchip-Firmware eigentlich Bestandteil ihres
Konfigurations-Deskriptors,
ich beschreibe sie aber doch lieber separat.
Im Beispiel gibt es die folgende
Konfiguration
/* Configuration 1 Descriptor */ CFG01= { /* Configuration Descriptor */ sizeof(USB_CFG_DSC), // Size of this descriptor in bytes DSC_CFG, // CONFIGURATION descriptor type sizeof(cfg01), // Total length of data for this cfg 1, // Number of interfaces in this cfg 1, // Index value of this configuration 0, // Configuration string index _DEFAULT, // Attributes, see usbdefs_std_dsc.h 50, // Max power consumption (2 x mA) /* Interface
Descriptor
*/ /* Endpoint
Descriptors
*/ |
Interface-Deskriptor
(9 Bytes lang, der
genaue Aufbau steht hier)
Im Konfigurations Deskriptor stand die
Anzahl der Interfaces dieser Konfiguration. Für jedes dieser
Interfaces
muss ein Interface-Deskriptor existieren. Diese sind
durchnummeriert.
Der erste (und in diesem Beispiel der einzige) hat die Nummer 0.
Der Interface-Deskriptor enhält u.A.
die Anzahl der in dieser Konfiguration enthaltenen Endpunkte.
.
Im Beispiel haben wir nur ein Interface:
/* Interface Descriptor */ sizeof(USB_INTF_DSC), // Size of this descriptor in bytes DSC_INTF, // INTERFACE descriptor type 0, // Interface Number 0, // Alternate Setting Number 2, // Number of endpoints in this intf 0x00, // Class code 0x00, // Subclass code 0x00, // Protocol code 0, // Interface string index |
Endpunkt-Deskriptor
(7 Bytes lang, der
genaue Aufbau steht hier)
Im Interface Deskriptor stand die
Anzahl
der Endpunkte dieser Konfiguration, im Beispiel sind es 2. Für
jeden
dieser Endpunkte muss ein Endpunkt-Deskriptor existieren. Dieser
enthält
die Endpunkt-Nummer, die Datenrichtung und die Betriebsart
des Endpunktes.
Im Beispiel gibt es zwei Endpunkte:
/* Endpoint Descriptors */ sizeof(USB_EP_DSC),DSC_EP,_EP01_OUT,_INT,USBGEN_EP_SIZE,32, sizeof(USB_EP_DSC),DSC_EP,_EP01_IN,_INT,USBGEN_EP_SIZE,32 |
Der Endpunkt0 muss nicht definiert
werden. Er ist an anderer Stelle in der Firmware fest eingestellt, da
er
in jedem Fall benötigt wird.
Anpassung der Anwendungssoftware an die Firmware
Der eigentliche Boss in dieser Software
ist eindeutig die Firmware, und nicht die Anwendungssoftware. Wie geht
denn die Firmware mit der Anwendungssoftware um?
Das Hauptprogramm des Projekts ist die
Datei main.c im Verzeichnis Demo. In ihr läuft
folgende
Haupt-Routine:
void main(void) { InitializeSystem(); while(1) { USBTasks(); // USB Tasks ProcessIO(); // See user\user.c & .h }//end while }//end main |
Wie man sieht handelt es sich um eine Endlosschleife, in der immer wieder die beiden Funktionen USBTasks() und ProcessIO() aufgerufen werden.
USBTasks() prüft, ob es
etwas
für die USB-Firmware zu tun gibt, und erledigt bei Bedarf diese
Arbeit.
ProcessIO() ist dagegen die
PIC-Anwendung.
Damit das Zusammenspiel funktioniert,
darf
die PIC-Anwendung nur kurz laufen, und muss sich dann beenden, um
USBTasks
eine Chance zu geben, seinen Job zu erledigen. Anschließend kann
ProcessIO
wieder weitermachen, aber nicht zu lange. Damit haben wir die erste
Anforderung
an die Anwendungssoftware gefunden. Sie sollte stottern können.
Da die meisten PIC-Anwendungsprogramme
im Hauptprogramm immer und immer wieder eine Schleife durchlaufen,
muss
man diese Schleife nun auftrennen. Ist das Ende Erreicht, springt man
nicht
zum Schleifenanfang, sondern beendet die Arbeit. Damit geht die
Kontrolle
an main zurück, das USBTasks aufruft. Ist USBTasks
fertig, wird durch main wieder ProcessIO aufgerufen,
und
mann läuft wieder genau ein Mal durch die Schleife .....
Die Hauptroutine der Anwendungssoftware
sollte in Verzeichnis user in der Datei user.c (und
user.h)
stehen und ProcessIO() heißen. Dann wird sie in der
Standardeinstellung
vom Firmwareprojekt gefunden und korrekt eingebunden.
Nun muss die Firmware natürlich noch auf die Speicher in den Endpunkten zugreifen können. Dafür stehen zwei Routinen bereit, die in usbgen.c/usbgen.h definiert sind:
USBGenRead
kopiert Daten aus dem Speicher des
out-Endpoints
in einen Pufferspeicher der Anwendungssoftware. Das klappt
natürlich
nur, wenn vom Controller auch Daten geliefert wurden. ansonsten kehrt
die
Funktion unverrichteter Dinge zurück.
Man muss sich für die Nutzung dieser Routinen einen Pufferspeicher definieren der so groß ist, wie der Speicher des Endpunktes. Ein 64-Byte langes Byte-Array wäre z.B. geeignet.
Die Nutzung der Device-Treiber
Nach dem Einschalten des PC mit angeschlossenen USB-Geräten hat der PC folgende Aufgaben zu erledigen:
Um es zu benutzen, muss die
Anwendersoftware
den Device-Treiber verwenden. Um den Microchip Custom Driver zu
verwenden,
benutzt man Funktionen aus der mpusbapi.dll . (C:\MCHPFSUSB\Pc\Mpusbapi\Dll)
Die DLL wurde in C geschrieben. Sie
verwendet
einige Daten-Typen, deren Aufbau in der C-Header-Datei mpusbapi.h
(C:\MCHPFSUSB\Pc\Mpusbapi\Dll\Borland_C) beschrieben sind. Wer seine
Anwendungssoftware
auch in C schreiben will, fügt diese Header-Datei einfach seinem
Projekt
zu, und hat damit alle Daten-Typen und Konstanten definiert. Ich
arbeite
mit Delphi, und habe eine Unit (usbdll.pas) zusammengestellt, die die
Daten-Typen,
die Konstanten und die Funktionsaufrufe der DLL enthält. Diese
Unit
basiert auf Informationen von
http://www.sixca.com/delphi/article/microchip_usb.html.
Autor: sprut
erstellt: 02.03.2006
letzte Änderung 07.03.2006