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_GetDescriptor
der 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