USB mit PIC18Fxx5x

am Beispiel des PIC18F2550

zurück zu 18F-Interfaces , zurück zu 16-Bit-Kern-PICs , PIC-Prozessoren , Elektronik , Homepage

Einleitung
USB-Hardware der PIX18Fxx5x
Das Anpassen der Firmware

Anpassen der Firmware an die nötige USB-Konfiguration
Einträge in der Datei usbdsc.c
Anpassung der Anwendungssoftware an die Firmware
Die Nutzung der Device-Treiber
Delphi-Unit zum Nutzen des Microchip Custom Driver
Ein einfaches USB-Beispiel-Übungsprojekt



Einleitung
Die USB-Grundlagen habe ich bereits an anderer Stelle beschrieben. Kommen wir nun zur Praxis.

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:

  1. Das Anapssen der Microchip-Firmware an unsere Bedürfnisse
  2. Auf dem PC müssen wir mit unserer Anwendungssoftware auf den USB-Device-Treiber zugreifen können.
nach oben

USB-Hardware der PIX18Fxx5x

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
 

nach oben

Das Anpassen der Firmware

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.
 

nach oben

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:

Durch das Editieren der Deskriptoren kann man das Device also seinen individuellen Erfordernissen anpassen.
 

Im Ordner autofiles liegen drei wichtige Konfigurationsdateien.

Durch das Editieren dieser Dateien, wird die Firmware an die Bedürfnisse des Nutzers angepasst. Angeblich arbeitet Microchip an einer Windows-Software (Wizard) die es möglich machen soll, diese Arbeit auf einer grafischen Oberfläche interaktiv zu erledigen. Deshalb heisst dieser Ordner auch autofiles (für autogenerated).
Bis diese Software bereitsteht, muss man sich aber leider noch mit den Interna der Files beschäftigen, um sie manuell anpassen.
 

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
  • Konfigurations-Deskriptoren
  • Interface-Deskriptoren
  • Endpunkt-Deskriptoren
Diese Deskriptoren beschreiben den genauen Aufbau des USB-Interface. Ein Deskriptor ist ein Datensatz von wenigen Byte mit einem festgelegten Aufbau. Das erste Byte enthält die Länge des Deskriptors und das zweite Byte beschreibt den Typ des Deskriptors (Device-Deskriptor=1,  Configuration-Deskriptor=2, Interface-Deskriptor=4, Endpoint-Deskriptor=5). Die nachfolgenden Bytes sind haben je nach Deskriptor-Typ unterschiedliche, aber genau festgelegte Bedeutungen.

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.

usbdsc.h
Diese File enthält die Definition der Deskriptor-Struktur für alle  in usbdsc.c enthaltenen USB-Deskriptoren.
 

nach oben

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:

Die Strings selbst sind als sd000, sd001 und sd003 definiert, und Pointer auf diese Strings liegen in der Pointer-Tabelle USB_SD_Ptr unter den Indexen 0,1 und 2. Der Manufactor string index mit dem Wert 0x01 im Device Descriptor verweist auf den Eintrag mit dem Index 1 in USB_SD_Ptr. Dahinter verbirgt sich ein Pointer auf sd001. sd001 ist der String "Microchip Technology Inc.".
 
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={
sizeof(sd001),DSC_STR,
'M','i','c','r','o','c','h','i','p',' ',
'T','e','c','h','n','o','l','o','g','y',' ','I','n','c','.'};

rom struct{byte bLength;byte bDscType;word string[33];}sd002={
sizeof(sd002),DSC_STR,
'P','I','C','D','E','M',' ','F','S',' ','U','S','B',' ',
'D','e','m','o',' ','B','o','a','r','d',' ','(','C',')',
' ','2','0','0','4'};

rom const unsigned char *rom USB_CD_Ptr[]={&cfg01,&cfg01};
rom const unsigned char *rom USB_SD_Ptr[]={&sd000,&sd001,&sd002};

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 */
    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

    /* 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
};


 

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.
   

nach oben

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:

USBGenWrite
kopiert Daten aus einem Pufferspeicher der Anwendungssoftware in den Speicher des in-Endpoints. Von dort werden die Daten dann automatisch via USB zum Controller weitergeleitet.

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.

nach oben

Die Nutzung der Device-Treiber

Nach dem Einschalten des PC mit angeschlossenen USB-Geräten hat der PC folgende Aufgaben zu erledigen:

und dann für jedes Device: Erst danach ist das USB-Device einsatzbereit.

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.


Delphi-Unit zum Nutzen des Microchip Custom Driver

  nach oben


Ein einfaches USB-Beispiel-Übungsprojekt


zurück zu 18F-Interfaces , zurück zu 16-Bit-Kern-PICs , PIC-Prozessoren , Elektronik , Homepage


Quellen:
- Microchip,
- USB2.0-Spezifikation
- http://www.sixca.com/delphi/article/microchip_usb.html

Autor: sprut
erstellt: 02.03.2006
letzte Änderung 07.03.2006