Kampis Elektroecke

Anmeldung beim Host – Teil 2

Im vorherigen Teil habe ich damit begonnen den Mikrocontroller in die Lage zu versetzen sich beim Host erfolgreich als USB-Gerät anzumelden. Dazu wurde eine entsprechende Poll-Funktion geschrieben und der erste von drei benötigten Standard-Requests ausgewertet. 

Sobald der Host den Geräte-Deskriptor empfangen und ausgewertet hat, legt er eine Adresse für das USB-Gerät fest und übermittelt diese mit einem SET_ADDRESS-Request. Bei diesem Request das Bitmuster für das Feld bmRequestType 000000002, bzw.

  • Transferrichtung: Host to Device
  • Art des Request: Standard
  • Empfänger: Device

Über das Feld wValue sendet der Host die zugewiesene Geräteadresse an das angeschlossene Gerät. Alle anderen Felder sind mit Null gefüllt bzw. nicht vorhanden. Die übermittelte Adresse muss von der CPU ausgelesen und in das UADD-Register kopiert werden. Dies erfolgt in folgenden Schritten:

  • Adresse aus dem SETUP-Paket auslesen
  • Die Adresse in das UADD-Register kopieren, aber das ADDEN-Bit gelöscht lassen
  • Ein IN-Paket mit einer Datenlänge von 0 Byte senden
  • ADDEN-Bit setzen
case REQUEST_SET_ADDRESS:
{
	if(_ControlRequest.bmRequestType == (REQUEST_DIRECTION_HOST_TO_DEVICE | REQUEST_TYPE_STANDARD | REQUEST_RECIPIENT_DEVICE))
	{
		uint8_t Address = (_ControlRequest.wValue & 0x7F);

		UDADDR = (Address & 0x7F);
		Endpoint_HandleSTATUS(_ControlRequest.bmRequestType);
		UDADDR |= (0x01 << ADDEN);

		_DeviceState = USB_STATE_ADDRESSED;
	}
}

Die Funktion Endpoint_HandleSTATUS übernimmt die STATUS-Stage der Kommunikation, indem sie entweder ein IN-Paket mit einer Datenlänge von 0 Byte sendet (wenn es ein OUT Request war) oder indem sie auf ein OUT-Paket wartet und dieses einließt (wenn es sich um einen IN Request handelt):

void Endpoint_HandleSTATUS(const USB_RequestDirection_t Direction)
{
	if(Direction & REQUEST_DIRECTION_DEVICE_TO_HOST)
	{
		while(!(Endpoint_OUTReceived()))
		{
			if(_DeviceState == USB_STATE_UNATTACHED)
			{
				return;
			}
		}

		Endpoint_AckOUT();
	}
	else
	{
		Endpoint_FlushIN();

		while(!(Endpoint_INReady()))
		{
			if(_DeviceState == USB_STATE_UNATTACHED)
			{
				return;
			}
		}
	}
}

Damit der USB-Controller, im Falle einer Trennung vom Bus, nicht permanent in der Schleife festhängt, wird zusätzlich noch der Zustand des Automaten überprüft und ggf. die Schleife verlassen. Abschließend wird der Zustand des Automaten auf USB_STATE_ADDRESSED gesetzt, womit auch dieser Request erfolgreich abgearbeitet ist.

Der letzte notwendige Request ist der SET_CONFIGURATION-Request, bei dem der Host eine Gerätekonfiguration auswählt und die ausgewählte Konfiguration im USB-Gerät aktiviert. Hier lautet das Bitmuster für das Feld bmRequestType 000000002, bzw.

  • Transferrichtung: Host to Device (also ein OUT-Transfer)
  • Art des Request: Standard
  • Empfänger: Device

Wie auch beim SET_ADDRESS-Request besitzt dieser Request lediglich das Feld wValue als Informationsträger, welches mit dem Index, der vom Host ausgewählten Konfiguration, gefüllt ist.


Achtung:

Der Host adressiert die Konfiguration anhand des Feldes bConfigurationValue des Konfigurations-Deskriptors. Dieses Feld darf nicht Null sein, da das Gerät dann, der Spezifikation entsprechend, zurück in den Zustand nach der Adressierung wechseln muss.


Sobald der Konfigurationsindex empfangen wurde, soll die CPU zusätzlich überprüfen ob eine entsprechende Konfiguration vorhanden ist. Dazu wird mit Hilfe der Funktion USB_GetDescriptorder entsprechende Konfigurations-Deskriptor ausgelesen und geprüft ob dieser vorhanden ist.

case REQUEST_SET_CONFIGURATION:
{
	if(_ControlRequest.bmRequestType == (REQUEST_DIRECTION_HOST_TO_DEVICE | REQUEST_TYPE_STANDARD | REQUEST_RECIPIENT_DEVICE))
	{
		_Configuration = (uint8_t)_ControlRequest.wValue & 0xFF;
		if(_Configuration > 0x00)
		{
			USB_DeviceDescriptor_t* Descriptor;
			Endpoint_HandleSTATUS(_ControlRequest.bmRequestType);
			if((USB_GetDescriptor((DESCRIPTOR_TYPE_CONFIGURATION << 0x08) | (_Configuration - 0x01), _ControlRequest.wIndex, (void*)&Descriptor) == 0x00) && (_USBEvents.Error != NULL))
			{
				_USBEvents.Error();
			}
			else
			{
				_DeviceState = USB_STATE_CONFIGURED;
				if(_USBEvents.ConfigurationChanged != NULL)
				{
					_USBEvents.ConfigurationChanged(_Configuration);
				}
			}
		}
		else
		{
			_DeviceState = USB_STATE_CONFIGURED;
		}
	}
}

Wenn die Konfiguration erfolgreich abgeschlossen wurde, ist das Gerät einsatzbereit. Um das zu signalisieren wird das ConfigurationChanged-Event ausgeführt. Dieses Event kann von der Applikation genutzt werden, um z. B. zusätzliche Endpunkte zu konfigurieren.

void USB_Event_ConfigurationChanged(const uint8_t Configuration)
{
    if(Endpoint_Configure(IN_EP, ENDPOINT_TYPE_INTERRUPT, EP_SIZE, 1) && Endpoint_Configure(OUT_EP, ENDPOINT_TYPE_INTERRUPT, EP_SIZE, 1))
    {
        GPIO_Set(GET_PERIPHERAL(LED0_GREEN), GET_INDEX(LED0_GREEN));
        GPIO_Set(GET_PERIPHERAL(LED0_RED), GET_INDEX(LED0_RED));
    }
    else
    {
        USB_Event_OnError();
    }
}

Damit wären die drei wichtigsten Requests implementiert. Die vollständige Funktion USB_Device_ControlRequest sieht nun folgendermaßen aus:

void USB_Device_ControlRequest(void)
{
	uint8_t* RequestHeader = (uint8_t*)&_ControlRequest;

	for(uint8_t i = 0x00; i < sizeof(USB_SetupPacket_t); i++)
	{
		*(RequestHeader++) = UEDATX;
	}

	UEINTX &= ~(0x01 << RXSTPI);

	switch(_ControlRequest.bRequest)
	{
		case REQUEST_SET_ADDRESS:
		{
			if(_ControlRequest.bmRequestType == (REQUEST_DIRECTION_HOST_TO_DEVICE | REQUEST_TYPE_STANDARD | REQUEST_RECIPIENT_DEVICE))
			{
				uint8_t Address = (_ControlRequest.wValue & 0x7F);
				UDADDR = (Address & 0x7F);
				Endpoint_HandleSTATUS(_ControlRequest.bmRequestType);
				UDADDR |= (0x01 << ADDEN);

				_DeviceState = USB_STATE_ADDRESSED;
			}

			break;
		}
		case REQUEST_GET_DESCRIPTOR:
		{
			if(_ControlRequest.bmRequestType == (REQUEST_DIRECTION_DEVICE_TO_HOST | REQUEST_TYPE_STANDARD | REQUEST_RECIPIENT_DEVICE))
			{
				const void* Descriptor;
				uint16_t DescriptorSize;
				Descriptor = USB_GetDescriptor(_ControlRequest.wValue, _ControlRequest.wIndex, &DescriptorSize);
				if((DescriptorSize == 0x00) && (_USBEvents.Error != NULL))
				{
					_USBEvents.Error();
				}
				USB_DeviceStream_ControlIN(Descriptor, DescriptorSize, _ControlRequest.wLength);
			}

			break;
		}
		case REQUEST_SET_CONFIGURATION:
		{
			if(_ControlRequest.bmRequestType == (REQUEST_DIRECTION_HOST_TO_DEVICE | REQUEST_TYPE_STANDARD | REQUEST_RECIPIENT_DEVICE))
			{
				_Configuration = (uint8_t)_ControlRequest.wValue & 0xFF;
				if(_Configuration > 0x00)
				{
					USB_DeviceDescriptor_t* Descriptor;
					Endpoint_HandleSTATUS(_ControlRequest.bmRequestType);
					if((USB_GetDescriptor((DESCRIPTOR_TYPE_CONFIGURATION << 0x08) | (_Configuration - 0x01), _ControlRequest.wIndex, (void*)&Descriptor) == 0x00) && (_USBEvents.Error != NULL))
					{
						_USBEvents.Error();
					}
					else
					{
						_DeviceState = USB_STATE_CONFIGURED;
						if(_USBEvents.ConfigurationChanged != NULL)
						{
							_USBEvents.ConfigurationChanged(_Configuration);
						}
					}
				}
				else
				{
					_DeviceState = USB_STATE_CONFIGURED;
				}
			}

			break;	
		}
	}
}

Nun wird das Programm auf den Mikrocontroller kopiert und das Board über den USB mit dem Computer verbunden. Sobald das Programm gestartet wurde, meldet sich das Gerät am Computer an. Über einen Blick in den Geräte-Manager kann man nachschauen, ob die Anmeldung erfolgreich verlaufen ist.

Auf einem Windows-PC kann das Programm USBView dazu genutzt werden sämtliche Informationen, wie die Gerätekonfiguration, die USB-Adresse, etc. anzuzeigen. Dies ist besonders praktisch um Fehler bei der Enumeration zu suchen und zu erkennen.

[Port4] 

Is Port User Connectable:         yes
Is Port Debug Capable:            no
Companion Port Number:            4
Companion Hub Symbolic Link Name: USB#VID_05E3&PID_0617#6&1f56a0e&0&1#{f18a0e88-c30c-11d0-8815-00a0c906bed8}
Protocols Supported:
 USB 1.1:                         yes
 USB 2.0:                         yes
 USB 3.0:                         no

       ---===>Device Information<===---
String Descriptor for index 2 not available while device is in low power state.

ConnectionStatus:                  
Current Config Value:              0x00  -> Device Bus Speed: Low
Device Address:                    0x1A
Open Pipes:                           0
*!*ERROR:  No open pipes!

          ===>Device Descriptor<===
bLength:                           0x12
bDescriptorType:                   0x01
bcdUSB:                          0x0011
bDeviceClass:                      0xFF  -> This is a Vendor Specific Device
bDeviceSubClass:                   0x00
bDeviceProtocol:                   0x00
bMaxPacketSize0:                   0x08 = (8) Bytes
idVendor:                        0x0123 = Vendor ID not listed with USB.org
idProduct:                       0x4567
bcdDevice:                       0x0001
iManufacturer:                     0x01
String Descriptor for index 1 not available while device is in low power state.
iProduct:                          0x02
String Descriptor for index 2 not available while device is in low power state.
iSerialNumber:                     0x03
String Descriptor for index 3 not available while device is in low power state.
bNumConfigurations:                0x01

       ---===>Full Configuration Descriptor<===---

          ===>Configuration Descriptor<===
bLength:                           0x09
bDescriptorType:                   0x02
wTotalLength:                    0x0020  -> Validated
bNumInterfaces:                    0x01
bConfigurationValue:               0x01
iConfiguration:                    0x00
bmAttributes:                      0xC0  -> Self Powered
MaxPower:                          0x32 = 100 mA

          ===>Interface Descriptor<===
bLength:                           0x09
bDescriptorType:                   0x04
bInterfaceNumber:                  0x00
bAlternateSetting:                 0x00
bNumEndpoints:                     0x02
bInterfaceClass:                   0xFF  -> Interface Class Unknown to USBView
bInterfaceSubClass:                0x00
bInterfaceProtocol:                0x00
iInterface:                        0x00

          ===>Endpoint Descriptor<===
bLength:                           0x07
bDescriptorType:                   0x05
bEndpointAddress:                  0x81  -> Direction: IN - EndpointID: 1
bmAttributes:                      0x03  -> Interrupt Transfer Type
wMaxPacketSize:                  0x0008
bInterval:                         0x0A

          ===>Endpoint Descriptor<===
bLength:                           0x07
bDescriptorType:                   0x05
bEndpointAddress:                  0x02  -> Direction: OUT - EndpointID: 2
bmAttributes:                      0x03  -> Interrupt Transfer Type
wMaxPacketSize:                  0x0008
bInterval:                         0x0A

Der Host hat somit schon mal erfolgreich ein neues Gerät erkannt und sämtliche Deskriptoren abgefragt, sowie eine Busadresse vergeben. Aktuell wird noch ein Fehler ausgegeben, da der Host keinen passenden Treiber gefunden hat. Dem entsprechend wurde auch noch keine Pipe zum Device hergestellt, wodurch noch keine Kommunikation möglich ist. Dieses Problem wird dann im nächsten Teil des Tutorials behoben.

Zurück

Schreibe einen Kommentar

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