Kampis Elektroecke

Initialisierung des Kontrollendpunktes

Im letzten Kapitel ist der USB-Controller des AT90USB1287 erfolgreich als USB-Device konfiguriert worden. Damit sich der Mikrocontroller wie ein USB-Device verhalten kann werden zusätzlich noch Endpunkte, also Ein- und Ausgabepuffer für den Host, benötigt. Diese Endpunkte haben die Aufgabe, Daten, die vom Host gesendet werden, zu empfangen (OUT Endpunkt) oder die Daten, die zum Host gesendet werden sollen (IN Endpunkt), zu speichern.

Jedes USB-fähige Gerät benötigt mindestens einen Endpunkt, den sogenannten Kontrollendpunkt. Dieser Endpunkt ist der einzige Endpunkt, der gleichzeitig als IN– und OUT-Endpunkt konfiguriert werden kann und wird zur Steuerung und Initialisierung des USB-Gerätes verwendet. Er besitzt immer die Standardadresse 0.

Der AT90USB1287 verfügt über insgesamt 832 Bytes an Speicher (DPRAM), der für insgesamt sieben USB-Endpunkte genutzt werden kann. Dieser Speicher unterliegt der folgender Aufteilung:

  • Max. 64 Bytes für den Endpunkt 0 (Standard Kontrollendpunkt)
  • Max. 256 Bytes für einen Endpunkt (aufgeteilt in 1/2 Bänke)
  • Max. 64 Bytes für die restlichen fünf Endpunkte (aufgeteilt in 1/2 Bänke)

Bevor die Endpunkte verwendet werden können, müssen sie erst konfiguriert werden. Jede Konfiguration, bzw. Speicherreservierung eines Endpunktes muss in aufsteigender Reihenfolge erfolgen. Somit muss der Endpunkt 0 als erstes erstellt werden, gefolgt von Endpunkt 1, Endpunkt 2, usw.

Wird der Mikrocontroller mit dem USB verbunden wird, so löst der USB-Host (also der PC) einen USB Reset aus. Durch den USB Reset werden sämtliche Endpunkte (mit Ausnahme vom Endpunkt 0) deaktiviert und müssen neu konfiguriert werden. Daher soll der USB Reset als Einstiegspunkt für die Konfiguration des Kontrollendpunktes durch die Software dienen. Das Ende des USB Resets kann mit Hilfe des EORSTI-Bits im UDINT-Register detektiert werden. Dieser zusätzliche Interrupt muss während der Initialisierung aktiviert werden:

// Alt
USBCON |= (0x01 << VBUSTE);

// Neu
UDIEN |= (0x01 << EORSTE);

Anschließend wird die ISR angepasst:

ISR(USB_GEN_vect)
{
	...

	if(UDINT & (0x01 << EORSTI))
	{
		UDINT &= ~(0x01 << EORSTI);

		_USBDeviceState = USB_STATE_RESET;

		Endpoint_Configure(0, ENDPOINT_TYPE_CONTROL, ENDPOINT_CONTROL_SIZE, 0);
	}
}

Sobald der USB Reset beendet worden ist, wird das entsprechende Interruptflag gelöscht und der Zustand des USB-Treibers aktualisiert. Anschließend wird der Endpunkt 0, konfiguriert. Für den Funktionsaufruf werden die Adresse des Endpunktes, der Typ des Endpunktes, die Größe des Endpunktes und die Anzahl an Speicherbänke übergeben. Da in diesem Beispiel ein Low-Speed USB-Gerät implementiert werden soll, ist die Größe der Übertragung und damit die Größe des Endpunktes auf 8 Byte festgelegt.

Die Konfiguration eines Endpunktes läuft immer nach dem folgenden Schema ab:

Die CPU kann immer nur auf einen Endpunkt gleichzeitig zugreifen. Der aktive Endpunkt wird über das UENUM-Register bestimmt und in dieses Register dürfen nur Werte von 0 bis 7 reingeschrieben werden. Anschließend wird der Endpunkt über das EPEN-Bit im UECONX-Register aktiviert. Nun können die Konfigurationsbits für die Richtung und den Endpunkttyp im UECFG0X-Register, sowie die Endpunktgröße und die Anzahl Speicherbänke im UECFG1X-Register gesetzt werden. Zum Schluss wird das ALLOC-Bit im UECFG1X-Register gesetzt, wodurch der benötigte Speicher für den Endpunkt reserviert wird. Wenn alles geklappt hat, wird das CFGOK-Bit im UESTA0X-Register gesetzt und der Endpunkt ist einsatzbereit.


Achtung:

Das CFGOK-Bit wird nicht gesetzt, wenn nicht genug DPRAM verfügbar ist um den angefragten Speicher zu reservieren!


Bei der Umsetzung des Endpoint actication flows muss zusätzlich berücksichtigt werden, dass die Endpunkte nur in aufsteigender Reihenfolge konfiguriert werden können. Die Funktionsweise der Speicherreservierung wird in Kapitel 22.7 des Datenblattes beschrieben:

Bei der Reservierung des Speichers für einen Endpunkt sucht der USB-Controller automatisch nach nicht genutztem Speicher. Sobald er welchen findet, wird bei Bedarf der direkt darüber liegende Endpunkt nach oben geschoben. Der Speicherbereich aller anderen Endpunkte wird nicht verschoben, wodurch es passieren kann, dass zwei Endpunkte den selben Speicherbereich nutzen!

Durch die Vermeidung von Lücken im Speicher wird und eine fehlerhafte Konfiguration vermieden werden (siehe Bild), muss dafür gesorgt werden, dass der Speicher aller Endpunkte, die eine höhere Adresse besitzen als der konfigurierte Endpunkt, nach unten rutscht, sodass Speicherlücken im DPRAM vermieden werden. Da der USB-Controller diesen Vorgang nur für den Endpunkt mit der Adresse k + 1 automatisch durchführt, müssen alle weiteren Endpunkte manuell durch die Software verschoben werden. Dazu werden die Endpunkte der Reihe nach ausgewählt und deaktiviert. Anschließend muss geprüft werden ob das ALLOC-Bit gesetzt ist.

  • ALLOC gesetzt: Speicher ist reserviert und muss verschoben werden
  • ALLOC nicht gesetzt: Speicher ist frei und kann mit den Daten des darüber liegenden Endpunktes überschrieben werden

Um einen Endpunkt nach unten zu verschieben, werden die Konfigurationsdaten des jeweiligen Endpunktes gespeichert und das ALLOC-Bit gelöscht, wodurch der genutzte Speicher wieder freigegeben wird. Anschließend werden die Konfigurationsdaten des Endpunktes zurückgeschrieben und das ALLOC-Bit gesetzt, wodurch der Speicher wieder reserviert wird.

Der vorgestellte Ablauf muss nun als Code umgesetzt werden. Die fertige Funktion kann z. B. folgendermaßen aussehen:

uint8_t Endpoint_Configure(const uint8_t Address, const Endpoint_Type_t Type, const uint8_t Size, const uint8_t DoubleBank)
{
    uint8_t Address_Temp = Address & 0x0F;

    if((Address_Temp & 0x07) > ENDPOINT_MAX_ENDPOINTS)
    {
        return 0x00;
    }

    for(uint8_t i = Address_Temp; i < ENDPOINT_MAX_ENDPOINTS; i++)
    {
        uint8_t UECFG0X_Temp;
        uint8_t UECFG1X_Temp;
        uint8_t UEIENX_Temp;

        Endpoint_Select(i);

        if(i == Address_Temp)
        {
            UECFG0X_Temp = (Type << EPTYPE0);

            if(Address & ENDPOINT_DIR_MASK_IN)
            {
                UECFG0X_Temp |= (0x01 << EPDIR);
            }

            if(DoubleBank > 0x01)
            {
                UECFG1X_Temp |= (0x01 << EPBK0);
            }

            uint8_t Temp = 0x08;
            uint8_t EPSIZE = 0x00;
            while(Temp < Size)
            {
                EPSIZE++;
                Temp <<= 0x01;
            }
            
            UECFG1X_Temp |= (EPSIZE << EPSIZE0);

            UECFG1X_Temp |= (0x01 << ALLOC);

            UEIENX_Temp = 0x00;
        }
        else
        {
            UECFG0X_Temp = UECFG0X;
            UECFG1X_Temp = UECFG1X;
            UEIENX_Temp = UEIENX;
        }

        if(!(UECFG1X_Temp & (0x01 << ALLOC)))
        {
            continue;
        }

        Endpoint_Disable();

        UECFG1X &= ~(0x01 << ALLOC);

        Endpoint_Enable();
        UECFG0X = UECFG0X_Temp;
        UECFG1X = UECFG1X_Temp;
        UEIENX = UEIENX_Temp;

        if(!(UESTA0X & (0x01 << CFGOK)))
        {
            return 0x00;
        }
    }

    Endpoint_Select(Address_Temp);

    return 0x01;
}

Der Abschnitt

uint8_t Temp = 0x08;
uint8_t EPSIZE = 0x00;
while(Temp < Size)
{
	EPSIZE++;
	Temp <<= 0x01;
}	
UECFG1X_Temp |= (EPSIZE << EPSIZE0);

rechnet die übergebene Größe des Endpunktes (in Byte) in eine entsprechende Bitmaske um. Diese Bitmaske wird dann an die korrekte Stelle geschoben und in das entsprechende Register kopiert.

Zur Kontrolle kann das Ergebnis der Konfiguration mittels einer roten oder grünen LED ausgegeben werden.

if(Endpoint_Configure(0, ENDPOINT_TYPE_CONTROL, 8, 0))
{
	PORTD |= (0x01 << 0x05);
}
else
{
	PORTD |= (0x01 << 0x04);
}

Wenn die LED grün leuchtet ist der Kontrollendpunkt fertig konfiguriert und die erste Konfiguration des USB-Devices abgeschlossen.

Bisher wartet der Mikrocontroller darauf mit dem USB verbunden zu werden und konfiguriert dann den Kontrollendpunkt. Währenddessen sendet der Host eine Anfrage nach einem Gerätedeskriptor zu dem Mikrocontroller. Die Anfrage wird zum jetzigen Zeitpunkt aber noch unbeantwortet gelassen, da der Mikrocontroller noch nicht weiß wie er auf die Anfrage reagieren soll. Der Host erkennt somit kein gültiges Gerät und meldet einen Fehler. Dieses Problem werden wir später adressieren. Zuerst müssen wir einen Satz Deskriptoren für das Gerät definieren.

Zurück

Schreibe einen Kommentar

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