środa, 18 stycznia 2017

[10] STM32F4 - CubeMx - USB HID Mysz oraz Klawiatura

Tym razem chciałbym opisać w jaki sposób wygenerować i dostosować projekt w programie CubeMx tak aby była możliwość obsługi urządzenia zarówno jako myszy jak i klawiatury jednocześnie. Ten post jest rozszerzeniem wcześniejszego dotyczącego HID'a na wcześniejszych wersjach bibliotek.

Za pomocą tej klasy urządzenie można przygotować do pracy jako kontroler do gier, klawiatura, myszka, wirtualny port com bądź urządzenie dostosowywane (custom), które po przygotowaniu programu na komputerze będzie przesyłać i odbierać odpowiednie komendy. Taki program może być napisany np. w takich językach jak C#, C++ czy Delphi.

Dzięki temu, że wykorzystywana jest klasa HID, to urządzenie jest wykrywane po podłączeniu do komputera. Pozwala to w bardzo łatwy sposób rozpocząć pracę z podanym urządzeniem.

Hardware:

Poniżej schemat części dotyczącej złącza USB:


W skład części elektrycznej układu wchodzi złącze microUSB, dwie diody świecące informujące o stanie działania aplikacji. Układ EMIF02-USB03, który jest filtrem dla zakłóceń elektromagnetycznych oraz zabezpiecza przed wyładowaniami elektrostatycznymi. Drugim układ to STMPS2141STR działający jako przełącznik który sterowany jest pinem Enable, czyli z nóżki PC0, gdy wystąpi na niej stan niski. Wobec tego ten pin należy skonfigurować jako wyjście, po czym należy je ustawić w stan niski.

Rezystory na liniach (termination impedance) są wpięte tak aby uzyskać odpowiednie wartości impedancji dla ścieżek. Ich wartość musi się zgadzać z informacjami przedstawionymi przez specyfikacje dla USB. Dodatkowo tak wpięte rezystory ograniczają szybkość narastania zboczy, co pozwala na zmniejszenie występowania zakłóceń elektromagnetycznych. Jednak przy wartości rezystancji wynoszącej 22Ohm nie będzie miało to znaczącego wpływu. Dodatkowo rezystory umieszczone w sygnale przez to, że zmieniają parametry sygnału pozwolą na redukcję przesłuchu sygnałów.

Cube Mx:

Projekt należy stworzyć standardowo z aktywacją RCC oraz Sys dla opcji debug: One Wire.

Następnym krokiem jest włączenie USB_OTG_FS:


Następnie następuje dołączenie bibliotek klasy HID.


Konfiguracja zegarów:


Pin PC0 musi być skonfigurowany jako wyjście, domyślnie w trybie wysokim (GPIO_Output_Level - HIGH).

Opis bibliotek:


Jednym z ważniejszych plików jest usb_desc.c. Zawiera on deskryptory, które umożliwiają konfigurację urządzenia USB.

Podstawowym jest deskryptor urządzenia, w jego skład wchodzą takie informacje jak:
wersja standardu urządzenia;
numery identyfikujące producenta (Vendor ID);
numery identyfikujące urządzenie (Product ID);

Do tworzenia własnych deskryptorów można się posłużyć programem HID Descriptor Tool. Jest on przydatny zwłaszcza dla niestandardowych aplikacji. Jeśli chodzi o podstawowe jak myszka czy klawiatura, to nie trzeba tego robić, wystarczy poszukać w internecie. Ponieważ dla standardowych urządzeń nie różnią się one za bardzo między sobą.

Główny deskryptor wygląda następująco:

  1. /* USB Standard Device Descriptor */
  2. __ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END =
  3. {
  4.     0x12,                       /*bLength */
  5.     USB_DESC_TYPE_DEVICE, /*bDescriptorType*/
  6.     0x00,                       /*bcdUSB */
  7.     0x02,
  8.     0x00,                       /*bDeviceClass*/
  9.     0x00,                       /*bDeviceSubClass*/
  10.     0x00,                       /*bDeviceProtocol*/
  11.     USB_OTG_MAX_EP0_SIZE,      /*bMaxPacketSize*/
  12.     LOBYTE(USBD_VID),           /*idVendor*/
  13.     HIBYTE(USBD_VID),           /*idVendor*/
  14.     LOBYTE(USBD_PID_FS),           /*idVendor*/
  15.     HIBYTE(USBD_PID_FS),           /*idVendor*/
  16.     0x00,                       /*bcdDevice rel. 2.00*/
  17.     0x02,
  18.     USBD_IDX_MFC_STR,           /*Index of manufacturer  string*/
  19.     USBD_IDX_PRODUCT_STR,       /*Index of product string*/
  20.     USBD_IDX_SERIAL_STR,        /*Index of serial number string*/
  21.     USBD_MAX_NUM_CONFIGURATION  /*bNumConfigurations*/
  22. };

Poniżej przedstawiam fragment programu z przedstawionym deskryptorem z pliku usbd_hid.c.

  1. __ALIGN_BEGIN static uint8_t USBD_HID_CfgDesc[USB_HID_CONFIG_DESC_SIZ]  __ALIGN_END =
  2. {
  3.           0x09, /* bLength: Configuration Descriptor size */
  4.           USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
  5.           USB_HID_CONFIG_DESC_SIZ,
  6.           /* wTotalLength: Bytes returned */
  7.           0x00,
  8.           0x01,         /*bNumInterfaces: 1 interface*/
  9.           0x01,         /*bConfigurationValue: Configuration value*/
  10.           0x00,         /*iConfiguration: Index of string descriptor describing
  11.           the configuration*/
  12.           0xE0,         /*bmAttributes: bus powered and Support Remote Wake-up */
  13.           0x32,         /*MaxPower 100 mA: this current is used for detecting Vbus*/
  14.           /************** Descriptor of Joystick Mouse interface ****************/
  15.           /* 09 */
  16.           0x09,         /*bLength: Interface Descriptor size*/
  17.           USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
  18.           0x00,         /*bInterfaceNumber: Number of Interface*/
  19.           0x00,         /*bAlternateSetting: Alternate setting*/
  20.           0x01,         /*bNumEndpoints*/
  21.           0x03,         /*bInterfaceClass: HID*/
  22.           0x00,   /*0x01*/      /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
  23.           0x00,   /*0x02*/      /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
  24.           0,            /*iInterface: Index of string descriptor*/
  25.           /******************** Descriptor of Joystick Mouse HID ********************/
  26.           /* 18 */
  27.           0x09,         /*bLength: HID Descriptor size*/
  28.           HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/
  29.           0x11,         /*bcdHID: HID Class Spec release number*/
  30.           0x01,
  31.           0x00,         /*bCountryCode: Hardware target country*/
  32.           0x01,         /*bNumDescriptors: Number of HID class descriptors to follow*/
  33.           0x22,         /*bDescriptorType*/
  34.           HID_MOUSE_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/
  35.           0x00,
  36.           /******************** Descriptor of Mouse endpoint ********************/
  37.           /* 27 */
  38.           0x07,          /*bLength: Endpoint Descriptor size*/
  39.           USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/
  40.           HID_EPIN_ADDR,     /*bEndpointAddress: Endpoint Address (IN)*/
  41.           0x03,          /*bmAttributes: Interrupt endpoint*/
  42.           HID_EPIN_SIZE, /*wMaxPacketSize: 4 Byte max */
  43.           0x00,
  44.           0x0A,          /*bInterval: Polling Interval (10 ms)*/
  45. } ;

Linijka o numerze 24 pozwala na określenie jakiego typu jest to urządzenie, czy klawiatura czy myszka czy żadne z wymienionych. Jest to ważne gdy wykorzystuje się deskryptor dla pojedynczego urządzenia. Wtedy ta opcja powinna być odpowiednio zaznaczona.

W tym samym pliku będzie znajdował się także deskryptor wygenerowany przez CubeMx, z domyślą aplikacją dla myszki, który wygląda następująco:

  1. __ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE]  __ALIGN_END =
  2. {
  3.   0x05,   0x01,
  4.   0x09,   0x02,
  5.   0xA1,   0x01,
  6.   0x09,   0x01,
  7.   0xA1,   0x00,
  8.   0x05,   0x09,
  9.   0x19,   0x01,
  10.   0x29,   0x03,
  11.   0x15,   0x00,
  12.   0x25,   0x01,
  13.   0x95,   0x03,
  14.   0x75,   0x01,
  15.   0x81,   0x02,
  16.   0x95,   0x01,
  17.   0x75,   0x05,
  18.   0x81,   0x01,
  19.   0x05,   0x01,
  20.   0x09,   0x30,
  21.   0x09,   0x31,
  22.   0x09,   0x38,
  23.   0x15,   0x81,
  24.   0x25,   0x7F,
  25.   0x75,   0x08,
  26.   0x95,   0x03,
  27.   0x81,   0x06,
  28.   0xC0,   0x09,
  29.   0x3c,   0x05,
  30.   0xff,   0x09,
  31.   0x01,   0x15,
  32.   0x00,   0x25,
  33.   0x01,   0x75,
  34.   0x01,   0x95,
  35.   0x02,   0xb1,
  36.   0x22,   0x75,
  37.   0x06,   0x95,
  38.   0x01,   0xb1,
  39.   0x01,   0xc0
  40. };

Aby wykorzystywać urządzenie, ten deskryptor należy zmodyfikować. Będzie on zlepkiem kilku deskryptorów dla innych urządzeń,

  1. __ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE]  __ALIGN_END =
  2. {
  3.         /* 47 */    //Keyboard
  4.         0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
  5.         0x09, 0x06,                    // USAGE (Keyboard)
  6.         0xa1, 0x01,                    // COLLECTION (Application)
  7.         0x85, 0x01,                    //   REPORT_ID (1)
  8.         0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
  9.         0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)
  10.         0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)
  11.         0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
  12.         0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
  13.         0x75, 0x01,                    //   REPORT_SIZE (1)
  14.         0x95, 0x08,                    //   REPORT_COUNT (8)
  15.         0x81, 0x02,                    //   INPUT (Data,Var,Abs)
  16.         0x95, 0x01,                    //   REPORT_COUNT (1)
  17.         0x75, 0x08,                    //   REPORT_SIZE (8)
  18.         0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)
  19.         0x95, 0x06,                    //   REPORT_COUNT (6)
  20.         0x75, 0x08,                    //   REPORT_SIZE (8)
  21.         0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
  22.         0x25, 0x65,                    //   LOGICAL_MAXIMUM (101)
  23.         0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
  24.         0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))
  25.         0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)
  26.         0x81, 0x00,                    //   INPUT (Data,Ary,Abs)
  27.         0xc0,                          // END_COLLECTION
  28.         /* 54 */    //Mouse
  29.         0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
  30.         0x09, 0x02,                    // USAGE (Mouse)
  31.         0xa1, 0x01,                    // COLLECTION (Application)
  32.         0x09, 0x01,                    //   USAGE (Pointer)
  33.         0xa1, 0x00,                    //   COLLECTION (Physical)
  34.         0x85, 0x02,                    //     REPORT_ID (2)
  35.         0x05, 0x09,                    //     USAGE_PAGE (Button)
  36.         0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
  37.         0x29, 0x03,                    //     USAGE_MAXIMUM (Button 3)
  38.         0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
  39.         0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
  40.         0x95, 0x03,                    //     REPORT_COUNT (3)
  41.         0x75, 0x01,                    //     REPORT_SIZE (1)
  42.         0x81, 0x02,                    //     INPUT (Data,Var,Abs)
  43.         0x95, 0x01,                    //     REPORT_COUNT (1)
  44.         0x75, 0x05,                    //     REPORT_SIZE (5)
  45.         0x81, 0x03,                    //     INPUT (Cnst,Var,Abs)
  46.         0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
  47.         0x09, 0x30,                    //     USAGE (X)
  48.         0x09, 0x31,                    //     USAGE (Y)
  49.         0x09, 0x38,                    //     USAGE (Wheel)
  50.         0x15, 0x81,                    //     LOGICAL_MINIMUM (-127)
  51.         0x25, 0x7f,                    //     LOGICAL_MAXIMUM (127)
  52.         0x75, 0x08,                    //     REPORT_SIZE (8)
  53.         0x95, 0x03,                    //     REPORT_COUNT (3)
  54.         0x81, 0x06,                    //     INPUT (Data,Var,Rel)
  55.         0xc0,                          //   END_COLLECTION
  56.         0xc0,                          // END_COLLECTION
  57.         /* 48 */    //Game Pad
  58.         0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
  59.         0x09, 0x05,                    // USAGE (Game Pad)
  60.         0xa1, 0x01,                    // COLLECTION (Application)
  61.         0xa1, 0x00,                    //   COLLECTION (Physical)
  62.         0x85, 0x03,                    //     REPORT_ID (3)
  63.         0x05, 0x09,                    //     USAGE_PAGE (Button)
  64.         0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
  65.         0x29, 0x10,                    //     USAGE_MAXIMUM (Button 16)
  66.         0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
  67.         0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
  68.         0x95, 0x10,                    //     REPORT_COUNT (16)
  69.         0x75, 0x01,                    //     REPORT_SIZE (1)
  70.         0x81, 0x02,                    //     INPUT (Data,Var,Abs)
  71.         0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
  72.         0x09, 0x30,                    //     USAGE (X)
  73.         0x09, 0x31,                    //     USAGE (Y)
  74.         0x09, 0x32,                    //     USAGE (Z)
  75.         0x09, 0x33,                    //     USAGE (Rx)
  76.         0x15, 0x81,                    //     LOGICAL_MINIMUM (-127)
  77.         0x25, 0x7f,                    //     LOGICAL_MAXIMUM (127)
  78.         0x75, 0x08,                    //     REPORT_SIZE (8)
  79.         0x95, 0x04,                    //     REPORT_COUNT (4)
  80.         0x81, 0x02,                    //     INPUT (Data,Var,Abs)
  81.         0xc0,                          //   END_COLLECTION
  82.         0xc0,                          // END_COLLECTION
  83.         /* 48 */    //Game Pad
  84.         0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
  85.         0x09, 0x05,                    // USAGE (Game Pad)
  86.         0xa1, 0x01,                    // COLLECTION (Application)
  87.         0xa1, 0x00,                    //   COLLECTION (Physical)
  88.         0x85, 0x04,                    //     REPORT_ID (4)
  89.         0x05, 0x09,                    //     USAGE_PAGE (Button)
  90.         0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
  91.         0x29, 0x10,                    //     USAGE_MAXIMUM (Button 16)
  92.         0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
  93.         0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
  94.         0x95, 0x10,                    //     REPORT_COUNT (16)
  95.         0x75, 0x01,                    //     REPORT_SIZE (1)
  96.         0x81, 0x02,                    //     INPUT (Data,Var,Abs)
  97.         0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
  98.         0x09, 0x30,                    //     USAGE (X)
  99.         0x09, 0x31,                    //     USAGE (Y)
  100.         0x09, 0x32,                    //     USAGE (Z)
  101.         0x09, 0x33,                    //     USAGE (Rx)
  102.         0x15, 0x81,                    //     LOGICAL_MINIMUM (-127)
  103.         0x25, 0x7f,                    //     LOGICAL_MAXIMUM (127)
  104.         0x75, 0x08,                    //     REPORT_SIZE (8)
  105.         0x95, 0x04,                    //     REPORT_COUNT (4)
  106.         0x81, 0x02,                    //     INPUT (Data,Var,Abs)
  107.         0xc0,                          //     END_COLLECTION
  108.         0xc0,                          // END_COLLECTION
  109. };


Dzięki takiej operacji urządzenie będzie wykrywane jako każdy z tych elementów. Ostatnim elementem jest zmiana wartośći HID_MOUSE_REPORT_DESC_SIZE, która została zdefiniowana w definicjach preprocesa. Określa ona wielkość deskryptora. Zdefiniowana została w pliku usbd_hid.h. I tam jej wartość trzeba ustawić na 197.

Program:


Co do kodu aplikacji, to przy wykorzystywaniu standardowego deskryptora udostępnionego domyślnie dla myszki od ST. Funkcja przesyłająca dane wygląda następująco:

  1. uint8_t Buffer_Mouse[4] = {0};  //Podawany do funkcji Mouse_Data_Send
  2. static void Mouse_Data_Send(uint8_t* buff, uint8_t button,                                    int8_t Cursor_Move_X, int8_t Cursor_Move_Y, int8_t Scrool_Value)
  3. {
  4.     buff[1] = 0x00; buff[2] = 0x00; buff[3] = 0x00; buff[4] = 0x00;
  5.     //Lub memset memset(buff, 0x00, 4);
  6.     if(Cursor_Move_X <= 127 && Cursor_Move_Y >= (-127)
  7.         Cursor_Move_X <= 127 && Cursor_Move_Y >= (-127))
  8.     {
  9.         pbuf[1]=Cursor_Move_X;
  10.         pbuf[2]=Cursor_Move_Y;
  11.     }
  12.     else
  13.     {
  14.         pbuf[1]=0;
  15.         pbuf[2]=0;
  16.     }
  17.     //Obsluga przycisku
  18.     if(button == 0x01 || button == 0x02 || button == 0x04)
  19.     {
  20.         if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0)==GPIO_PIN_SET)
  21.         {
  22.             pbuf[0] = button
  23.         }
  24.         else { pbuf[0] = 0; }
  25.     }
  26.     if(Scrool_Value >= (-127) && Scrool_Value <= 127)
  27.     {
  28.         pbuf[3] = Scrool_Value;
  29.     }
  30.     else { pbuf[3] = 0; }
  31. }

Przesyłane są 4 bajty danych, które umożliwiają ustawienie odpowiednio przycisku, przesunięcia myszy w osi X, przesunięcia myszy w osi Y oraz przesunięcie scrollem. Dla przycisków dostępne są takie wartości jak 0x01(LPM), 0x02(PPM) oraz 0x04(środkowy). W domyślnie zdefiniowanym deskryptorze nie trzeba określać jakiego typu jest to urządzenie, tylko zostaje przesłane i domyślnie dobrana na podstawie definicji USB_HID_CfgDesc.

Funkcja Mouse_Data_Send przygotowuje bufor z danymi, należy go jeszcze przesłać, całość operacji może wyglądać następująco w pętli while:

  1.   /* USER CODE BEGIN WHILE */
  2.   while (1)
  3.   {
  4.      Mouse_Data_Send(HID_Buffer, 0x02, 0x05, 0x05, 0x00);
  5.      USBD_HID_SendReport(&hUsbDeviceFS,HID_Buffer,4);
  6.       HAL_Delay(500);
  7.   /* USER CODE END WHILE */
  8.   /* USER CODE BEGIN 3 */
  9.   }
  10.   /* USER CODE END 3 */

Poniżej dwie funkcje tworzące bufor oraz przesyłające dane dla deskryptora zmodyfikowanego. W tym przypadku należy zaznaczyć jakiego typu urządzenie będzie wysyłało dane:

  1. void KeyboardWrite(uint8_t Specjalny, uint8_t Pierwszy, uint8_t Drugi, uint8_t Trzeci, uint8_t Czwarty,
  2.                                     uint8_t Piaty, uint8_t Szosty)
  3. {
  4.     uint8_t buff[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; /* 9 bytes long report */
  5.     int i = 0;
  6.     buff[0] = 0x01;
  7.     buff[1] = Specjalny;
  8.     buff[2] = 0x00;
  9.     buff[3] = Pierwszy;
  10.     buff[4] = Drugi;
  11.     buff[5] = Trzeci;
  12.     buff[6] = Czwarty;
  13.     buff[7] = Piaty;
  14.     buff[8] = Szosty;
  15.     USBD_HID_SendReport(&hUsbDeviceFS, buff, 9);
  16.     buff[0] = 0x01; //Identyfikator dla klawiatury
  17.     for(i=1;i<9;i++)
  18.     {
  19.         buff[i] = 0x00;
  20.     }
  21.     USBD_HID_SendReport(&hUsbDeviceFS, buff, 9);
  22. }
  23. void MouseSend(uint8_t Przycisk, uint8_t OsX, uint8_t OsY, uint8_t Scroll)
  24. {
  25.     uint8_t buff[5] = {0, 0, 0, 0, 0};
  26.     buff[0] = 0x02;
  27.     //Lewy przycisk 0x01 | Prawy przycisk 0x02 | Srodkowy przycisk 0x04
  28.     buff[1] = Przycisk;
  29.     buff[2] = OsX;
  30.     buff[3] = OsY;
  31.     buff[4] = Scroll;
  32.     USBD_HID_SendReport(&hUsbDeviceFS, buff, 5);
  33.     buff[0] = 0x02; //Identyfikator myszki
  34.     buff[1] = 0x00;
  35.     buff[2] = 0x00;
  36.     buff[3] = 0x00;
  37.     buff[4] = 0x00;
  38.     USBD_HID_SendReport(&hUsbDeviceFS, buff, 5);
  39. }

Przy pracy z klawiaturą należy pamiętać, że klawisz po kliknięciu musi zostać zwolniony. Taką informację należy przesłać poleceniem SendReport. Pierwszy element bufora zawiera stałe identyfikatory urządzenia 0x01 dla klawiatury oraz 0x02 dla myszki.

Jak już wspomniałem wcześniej klawiatura i mysz mogą działać wspólnie, dzięki czemu można naprzemiennie przesyłać dane.

Definicje znaków dla klawiatury przedstawiłem we wcześniejszym poście link,

Link do programu.

Przydatne linki:


USB in a NutShell
USB Class Definition