Kompozytowe urządzenia USB na mikrokontrolerach z serii LPC134x
Komunikacja między hostem a urządzeniem USB jest oparta na kanałach logicznych, zwanych potokami (pipes). Potok to połączenie między kontrolerem hosta a logiczną jednostką znajdującą się w urządzeniu, nazywaną punktem końcowym (endpoint). Jako że potoki odpowiadają punktom końcowym jeden do jednego, terminy te są czasami używane wymiennie. Punkty końcowe są grupowane w interfejsy, z których każdy jest skojarzony z jedną funkcją urządzenia (wyjątkiem jest tu endpoint o numerze zero, używany do konfiguracji urządzenia, który nie jest powiązany z żadnym interfejsem).
Od samego początku istnienia specyfikacji USB istniała niejednoznaczność w kwestii tego czy urządzenia wielofunkcyjne powinny móc wykorzystywać więcej niż jeden interfejs dla pojedynczej funkcji logicznej. Podstawowa specyfikacja nie przewidywała takiej opcji, jednak została ona w końcu wprowadzona w USB 2.0 za pomocą mechanizmu IAD (Interface Association Descriptor), który jest opisany w specyfikacji USB 2.0.
Urządzenie kompozytowe USB (ang. USB composite device) to pojedyncze urządzenie wykorzystujące więcej niż jeden niezależnie sterowany interfejs USB. Urządzenie kompozytowe ma tylko jeden adres, ponieważ host przypisuje adresy funkcjom, a nie interfejsom. Niniejsze opracowanie opisuje sposób implementacji takiego urządzenia za pomocą mikrokontrolera z rodziny LPC134x.
Deskryptory urządzeń USB
W czasie procesu wyliczania (enumeration), po wykryciu urządzenia, odblokowaniu portu i generacji resetu, host odczytuje z urządzenia zestaw deskryptorów zawierających informacje o nim. W standardzie USB każde urządzenie musi mieć zestaw najpopularniejszych deskryptorów: urządzenia, konfiguracji, interfejsu oraz punktów końcowych.
Deskryptor urządzenia (Device Descriptor) zawiera głównie informacje potrzebne systemowi operacyjnemu hosta do załadowania właściwego sterownika operacyjnego (m.in. identyfikatory producenta i produktu – Vendor ID oraz Product ID) oraz określenia liczby możliwych konfiguracji.
Dla każdej z konfiguracji urządzenie musi mieć deskryptor konfiguracji (Configuration Descriptor), zawierający m.in. informacje o sposobie wyboru danej konfiguracji oraz o liczbie dostępnych dla niej interfejsów.
Każda konfiguracja może mieć kilka interfejsów, z których wszystkie są aktywne w tym samym czasie, co pozwala na dostęp do różnych funkcji urządzenia poprzez różne sterowniki. Każdy interfejs opisywany jest w deskryptorze interfejsu (Interface Descriptor), zawierającym m.in. informację o liczbie punktów końcowych zawartych w tym interfejsie.
Punkty końcowe są opisywane w deskryptorach punktu końcowego (Endpoint Descriptor), zawierających m.in. informacje o rodzaju realizowanej przez nie transmisji (Control/Interrupt/Isochronous/Bulk) oraz rozmiarze bufora danych.
Ostatnim popularnym deskryptorem, występującym jednak opcjonalnie, jest deskryptor tekstowy (String Descriptor), zawierający dodatkowe informacje, np. opis urządzenia i jego konfiguracji czy nazwę producenta.
Rys. 1. Struktura deskryptorów
Deskryptor Interface Association Descriptor (IAD)
Podstawowy model konfiguracji założony przy pracy nad specyfikacją USB zakładał relację 1:1 między funkcjami w urządzeniu a interfejsami, jednak kilka specyfikacji klas rozszerzyło ten model i zdefiniowało funkcje urządzeń USB wykorzystujące kilka interfejsów (czyli także kilka deskryptorów interfejsu). Obsługa takiego rozwiązania wymaga nie tylko osobnego sterownika dla każdej funkcji, co pozostaje bez zmian w stosunku do pierwotnego rozwiązania, ale też powiązania kilku interfejsów z jedną instancją sterownika (rysunek 2). We wspomnianym na wstępie dokumencie Interface Association Descriptors ECN zdefiniowano nowy standardowy deskryptor oraz nowe zasady numeracji, pozwalające urządzeniu opisać, które interfejsy są skojarzone z daną jego funkcją, co rozwiązuje ten problem.
Rys. 2. Klasyczny model konfiguracji interfejsów i sterowników
Jedenasty typ deskryptora nosi nazwę deskryptora przypisania interfejsów (Interface Association Descriptor) i składa się z pól zestawionych w tabeli 1. Kluczowe znaczenie mają pola bFirstInterface i bInterfaceCount pozwalające określić, które interfejsy są skojarzone z daną funkcją.
Tab. 1. Struktura deskryptora Interface Association Descriptor
Offset | Nazwa pola | Typ wartości | Opis |
0 | bLength | Liczba | Rozmiar deskryptora w bajtach |
1 | bDescriptorType | Stała | INTERFACE ASSOCIATION Descriptor |
2 | bFirstInterface | Liczba | Numer pierwszego interfejsu skojarzonego z daną funkcją |
3 | bInterfaceCount | Liczba | Liczba kolejnych (położonych sąsiednio) interfejsów skojarzonych z daną funkcją |
4 | bFunctionClass | Klasa (Class) | Kod klasy (przypisany przez USB Implementers Forum) Wartość zero nie jest dozwolona. Jeśli pole ma wartość FFH, urządzenie jest klasy opracowanej przez producenta. Wszystkie pozostałe wartości są zarezerwowane dla USB-IF. |
5 | bFunctionSubClass | Podklasa (SubClass) | Kod podklasy (przypisany przez USB-IF). Jeśli pole bFunctionClass nie ma wartości FFH, wszystkie wartości są zarezerwowane dla USB-IF. |
6 | bFunctionProtocol | Protokół | Kod protokołu (przypisany przez USB-IF). Kody te są uwarunkowane przez wartości pól bFunctionClass i bFunctionSubClass. |
7 | iFunction | Indeks | Indeks deskryptora znakowego opisującego daną funkcję. |