Nie takie USB straszne jak je malują, cz.2 - deskryptory

Wszystko co dotyczy płytek z rodziny Discovery firmy STM
ODPOWIEDZ
Awatar użytkownika
elvis
Użytkownik
Posty: 35
Rejestracja: 30 lis 2018, 17:50

Nie takie USB straszne jak je malują, cz.2 - deskryptory

Post autor: elvis » 03 lip 2019, 20:14

W poprzednim wpisie (viewtopic.php?f=9&t=520) pokazałem jak wygląda przykładowa komunikacja STM32 z hostem. Wielokrotnie pojawiały się nieco tajemnicze deskryptory. Czas przyjrzeć im się nieco bliżej i zobaczyć, że nie ma w nich nic trudnego.

Jako pierwszy pobierany był deskryptor urządzenia (GET_DEVICE_DESCRIPTOR), czyli zestaw podstawowych informacji o podłączonym do portu USB urządzeniu. W projekcie wygenerowanym przez CubeMX zawartość tego deskryptora znajdziemy w pliku Middlewares\ST\STM32_USB_Device_Library\Core\Src\usbd_core.c, a dokładnej w tablicy USBD_FS_DeviceDesc:

Kod: Zaznacz cały

__ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END =
{
  0x12,                       /*bLength */
  USB_DESC_TYPE_DEVICE,       /*bDescriptorType*/
  0x00,                       /*bcdUSB */
  0x02,
  0x00,                       /*bDeviceClass*/
  0x00,                       /*bDeviceSubClass*/
  0x00,                       /*bDeviceProtocol*/
  USB_MAX_EP0_SIZE,           /*bMaxPacketSize*/
  LOBYTE(USBD_VID),           /*idVendor*/
  HIBYTE(USBD_VID),           /*idVendor*/
  LOBYTE(USBD_PID_FS),        /*idProduct*/
  HIBYTE(USBD_PID_FS),        /*idProduct*/
  0x00,                       /*bcdDevice rel. 2.00*/
  0x02,
  USBD_IDX_MFC_STR,           /*Index of manufacturer  string*/
  USBD_IDX_PRODUCT_STR,       /*Index of product string*/
  USBD_IDX_SERIAL_STR,        /*Index of serial number string*/
  USBD_MAX_NUM_CONFIGURATION  /*bNumConfigurations*/
};
Pierwszy bajt to całkowita długość tablicy, drugi to identyfikator typu deskryptora (o wartości 0x01). Większość deskryptorów ma podobny format - na początku znajdziemy wielkość struktury oraz jej typ. Dzięki temu host nie wiedząc z jakim urządzeniem ma do czynienia może zapytać o pierwsze kilka bajtów, a następnie jeszcze raz już o cały deskryptor.
Kolejne pola to:
  • bcdUSB - wersja protokołu, czyli USB 2.0
  • bDeviceClass - klasa urządzenia, nieużywane pole zamiast tego klasa jest przypisana interfejsowi
  • bDeviceSubClass - podklasa urządzenia
  • bDeviceProtocol - protokół używany przez urządzenie
  • bMaxPacketSize - bardzo ważne pole, definiuje wielkość bufora endpointa zerowego (64B)
  • idVendor:idProduct - identyfikator producenta i produktu. Wymaga przydzielenia odpowiedniego numeru, co kosztuje
  • bcdDevice - numer wersji urządzenia
  • iManufacturer - numer deskryptora z nazwą producenta lub zero jeśli nieużywane
  • iProduct - numer deskryptora z nazwą urządzenia lub zero jeśli nieużywane
  • iManufacturer - numer deskryptora z nazwą producenta lub zero jeśli nieużywane
  • iSerialNumber - numer deskryptora z numerem seryjnym lub zero jeśli nieużywane
  • bNumConfigurations - liczba dostępnych konfiguracji (1)
Dla porównania poniżej możemy zobaczyć jak te dane są przesyłane przez USB:

Obrazek

Warto zapamiętać, że ten deskryptor definiuje wielkość endpointa zerowego, czyli 64 bajty. Później bardzo się ta informacja przyda.

Deskryptory napisowe
Nie wiem czy to najlepsze tłumaczenie dla String Descriptor, ale nie wnikając w niuanse językowe chodzi o najzwyklejsze napisy. Poprzednio opisywany deskryptor urządzenia zawierał tylko indeksy odpowiednich napisów, host chcąc poznać np. nazwę producenta musi wysłać kolejne zapytanie, tym razem typu GET_STRING_DESCRIPTOR. Jako parametr zapytania podaje indeks, a w odpowiedzi dostaje napis zakodowany w Unicode.
Przykładowy projekt używa zwykłych napisów:

Kod: Zaznacz cały

#define USBD_MANUFACTURER_STRING     "STMicroelectronics"
#define USBD_PRODUCT_STRING_FS     "STM32 Human interface"
które dopiero na żądanie zamienia na unicode. Jest to mocno przekombinowane, inne implementacje stosów USB po prostu przechowują napisy w unicode i zwracaja na żądanie - ST lubi komplikować proste rzeczy.
Poniżej przykład odczytu takiego deskryptora:

Obrazek

Deskryptor konfiguracji
W naszym projekcie jego definicję znajdziemy w pliku Middlewares\ST\STM32_USB_Device_Library\Class\Src\usbd_hid.c w tablicy USBD_HID_CfgFSDesc

Kod: Zaznacz cały

__ALIGN_BEGIN static uint8_t USBD_HID_CfgFSDesc[USB_HID_CONFIG_DESC_SIZ]  __ALIGN_END =
{
  0x09, /* bLength: Configuration Descriptor size */
  USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
  USB_HID_CONFIG_DESC_SIZ,
  /* wTotalLength: Bytes returned */
  0x00,
  0x01,         /*bNumInterfaces: 1 interface*/
  0x01,         /*bConfigurationValue: Configuration value*/
  0x00,         /*iConfiguration: Index of string descriptor describing
  the configuration*/
  0xE0,         /*bmAttributes: bus powered and Support Remote Wake-up */
  0x32,         /*MaxPower 100 mA: this current is used for detecting Vbus*/

  /************** Descriptor of Joystick Mouse interface ****************/
  /* 09 */
  0x09,         /*bLength: Interface Descriptor size*/
  USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
  0x00,         /*bInterfaceNumber: Number of Interface*/
  0x00,         /*bAlternateSetting: Alternate setting*/
  0x01,         /*bNumEndpoints*/
  0x03,         /*bInterfaceClass: HID*/
  0x01,         /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
  0x02,         /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
  0,            /*iInterface: Index of string descriptor*/
  /******************** Descriptor of Joystick Mouse HID ********************/
  /* 18 */
  0x09,         /*bLength: HID Descriptor size*/
  HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/
  0x11,         /*bcdHID: HID Class Spec release number*/
  0x01,
  0x00,         /*bCountryCode: Hardware target country*/
  0x01,         /*bNumDescriptors: Number of HID class descriptors to follow*/
  0x22,         /*bDescriptorType*/
  HID_MOUSE_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/
  0x00,
  /******************** Descriptor of Mouse endpoint ********************/
  /* 27 */
  0x07,          /*bLength: Endpoint Descriptor size*/
  USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/

  HID_EPIN_ADDR,     /*bEndpointAddress: Endpoint Address (IN)*/
  0x03,          /*bmAttributes: Interrupt endpoint*/
  HID_EPIN_SIZE, /*wMaxPacketSize: 4 Byte max */
  0x00,
  HID_FS_BINTERVAL,          /*bInterval: Polling Interval */
  /* 34 */
};
W rzeczywistości tylko pierwsze 9 bajtów opisuje konfigurację, ale wszystkie deskryptory są przesyłane w odpowiedzi na jedno zapytanie GET_CONFIGURATION_DESCRIPTOR. Proste urządzenie typu HID ma jedną konfigurację, a w niej jeden interfejs. Jednak bardziej skomplikowane urządzenia mogą oferować jednocześnie wiele interfejsów - dzięki temu ST/Link jest jednocześnie programatorem oraz konwerterem USB-UART.
Deskryptor urządzenia nie zawierał klasy, zamiast tego jest ona zdefiniowana na poziomie interfejsu. Mamy więc pole bInterfaceClass, którego wartość to 0x03, czyli HID. Wartość nInterfaceProtocol określa rodzaj naszego urządzenia, czyli 0x02 = mysz.
Bardzo ważne jest pole bNumEndpoints, ponieważ określa ono liczbę endpointów - w naszym prostym urządzeniu to 1.
Sam endpoint zdefinowany jest na końcu, numer (adres) określa stała HID_EPIN_ADDR, która definiuje jego numer jako 1. Typ endpointu to INTERRUPT, a wielkość bufora: 4B - te informacje przydadzą się później.

Obrazek

O deskryptorach można byłoby napisać niejedną książkę. Nie mam niestety takiej ambicji ani wiedzy, chciałem tylko pokazać że nie jest to całkiem czarna magia. Po prostu trzeba doczytać, które pole co znaczy i dobrać odpowiednie wartości. W sieci są gotowe programy, które ułatwiają tworzenie deskryptorów, sama dokumentacja jest dostępna na stronie https://www.usb.org/ oraz właściwie w każdej dobrej książce lub publikacji dotyczącej USB.

Ja w tym momencie pójdę nieco na skróty - wykorzystam deskryptory z przykładowego programu :)

ODPOWIEDZ