poniedziałek, 7 listopada 2016

[24] STM32F4 - Biblioteka HID, klawiatura oraz myszka

W tym poście chciałbym opisać sposób wykonania oraz zastosowanie biblioteki HID (Human Interface Device) w mikrokontrolerze STM32F4. Można ją włączyć tylko dla mikrokontrolerów posiadających wbudowany interfejs USB. W przykładach będę przesyłał określony ciąg znaków poprzez USB do komputera. Dane zostaną wysłane po naciśnięciu klawisza,

Wspomniana powyżej biblioteka czy też klasa została opracowana dla urządzeń typu klawiatura czy myszka. Ma ona tą zaletę, że dzięki temu sterowniki zostały zawarte w każdym komputerze i urządzenie jest gotowe do działania zaraz po jego podłączeniu do komputera.

Aby urządzenie działało w nieco inny sposób to należałoby przygotować odpowiedni program na komputer PC np. w języku Delphi czy C#. I bibliotekę aktywować jako Custom HID. Program dla mikrokontrolera jest bardzo prosty w implementacji zwłaszcza z wykorzystaniem bibliotek HAL-a oraz programu Cube Mx.

Bardzo ważne jest także dobranie taktowania. Zegar dla USB powinien wynosić 48MHz.

Opis biblioteki:


Jednym z czołowych plików w bibliotece jest usbd_desc.c. W nim zawarte są deskryptory za pomocą których opisano konfigurację urządzenia poprzez USB.

Pierwszym z nich jest deskryptor urządzenia w którym zawarto takie informacje jak wersję standardu USB, numery pozwalające na identyfikację producenta oraz urządzenia.


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

Kolejnym elementem jest deklaracja adresu urządzenia oraz pozostałych parametrów:

  1. #define USBD_VID                        0x0483
  2. #define USBD_PID                        0x5710
  3. #define USBD_LANGID_STRING              0x409
  4. #define USBD_MANUFACTURER_STRING        "STMicroelectronics"
  5. #define USBD_PRODUCT_HS_STRING          "USB HID device HS"
  6. #define USBD_SERIALNUMBER_HS_STRING     "000000000111"
  7. #define USBD_PRODUCT_FS_STRING          "USB HID device FS"
  8. #define USBD_SERIALNUMBER_FS_STRING     "000000000112"
  9. #define USBD_CONFIGURATION_HS_STRING    "HID Config"
  10. #define USBD_INTERFACE_HS_STRING        "HID Interface"
  11. #define USBD_CONFIGURATION_FS_STRING    "HID Config"
  12. #define USBD_INTERFACE_FS_STRING        "HID Interface"

Biblioteki API klawiatura


Drugi przykład będzie zawierał wykonanie klawiatury za pomocą biblioteka HID oraz API. W pierwszej kolejności należy wgrać bibliotekę USBLib.

Następnie idąc od początku należy zdeklarować zmienną zewnętrzną na początku funkcji głównej, któa będzie informowała o definicji struktury:

  1. extern USB_OTG_CORE_HANDLE USB_OTG_dev;

Kolejnym elementem jest funkcja przesyłająca dane z STM-a na komputer:

Do przesyłania danych musi zostać przygotowany buffor zawierający 9 bajtów. Podczas przesyłania danych na linii może się pojawić jeden klawisz specjalny oraz 6 klawiszy znakowych.

Włączenie biblioteki dla USB_HID:

  1. void USB_HID_Init(void)
  2. {
  3.     USBD_Init(&USB_OTG_dev,
  4.     #ifdef USE_USB_OTG_HS
  5.             USB_OTG_HS_CORE_ID,
  6.     #else            
  7.             USB_OTG_FS_CORE_ID,
  8.     #endif
  9.             &USR_desc,
  10.             &USBD_HID_cb,
  11.             &USR_cb);
  12. }

Poniżej lista wskaźników i ich numer jaki należy wprowadzić do bufora na pozycji 1. Poniżej znajduje się informacja, który z bitów ma być ustawiony aby była możliwość zaznaczenia klawisza jako włączonego:

  • Lewy Control = 0x01;
  • Lewy Shift = 0x02;
  • Lewy Alt = 0x04;
  • Klawisz Systemowy = 0x08;
  • Prawy Control = 0x10;
  • Prawy Shift = 0x20;
  • Prawy Alt = 0x40;
  • Prawy Systemowy = 0x80;

Kody wszystkich klawiszy jakie są dostępne z tą biblioteką zostały przedstawione pod tym linkiem linkiem lub tutaj. Dodatkowo na internecie można jeszcze znaleźć taką książkę:

  • a - 0x04
  • b - 0x05
  • c - 0x06
  • d - 0x07
  • e - 0x08
  • f - 0x09
  • g - 0x0A
  • h - 0x0B
  • i - 0x0C
  • j - 0x0D
  • k - 0x0E
  • l - 0x0F
  • m - 0x10
  • n - 0x11
  • o - 0x12
  • p - 0x13
  • q - 0x14
  • r - 0x15
  • s - 0x16
  • t - 0x17
  • u - 0x18
  • v - 0x19
  • w - 0x1A
  • x - 0x1B
  • y - 0x1C
  • z - 0x1D

Klawisze numeryczne wraz ze znakami specjalnymi:

  • 1 ! -  0x1E
  • 2 @ - 0x1F
  • 3 # - 0x20
  • 4 $ - 0x21
  • 5 % - 0x22
  • 6 ^ - 0x23
  • 7 & - 0x24
  • 8 * - 0x25
  • 9 ( - 0x26
  • 0 ) - 0x27

Dane dla poszczególnych klawiszy można zdefiniować przy użyciu #define:

  1. #define CONTROL_LEFT    (1<<0)
  2. #define SHIFT_LEFT      (1<<1)
  3. #define ALT_LEFT        (1<<2)
  4. #define GUI_LEFT        (1<<3)
  5. #define CONTROL_RIGHT   (1<<4)
  6. #define SHIFT_RIGHT     (1<<5)
  7. #define ALT_RIGHT       (1<<6)
  8. #define GUI_RIGHT       (1<<7)
  9. #define KEY_A       0x04
  10. #define KEY_B       0x05
  11. #define KEY_C       0x06
  12. #define KEY_D       0x07
  13. #define KEY_E       0x08
  14. #define KEY_F       0x09
  15. #define KEY_G       0x0A
  16. #define KEY_H       0x0B
  17. #define KEY_I       0x0C
  18. #define KEY_J       0x0D
  19. #define KEY_K       0x0E
  20. #define KEY_L       0x0F
  21. #define KEY_M       0x10
  22. #define KEY_N       0x11
  23. #define KEY_O       0x12
  24. #define KEY_P       0x13
  25. #define KEY_Q       0x14
  26. #define KEY_R       0x15
  27. #define KEY_S       0x16
  28. #define KEY_T       0x17
  29. #define KEY_U       0x18
  30. #define KEY_V       0x19
  31. #define KEY_W       0x1A
  32. #define KEY_X       0x1B
  33. #define KEY_Y       0x1C
  34. #define KEY_Z       0x1D
  35. #define KEY_1       0x1E
  36. #define KEY_2       0x1F
  37. #define KEY_3       0x20
  38. #define KEY_4       0x21
  39. #define KEY_5       0x22
  40. #define KEY_6       0x23
  41. #define KEY_7       0x24
  42. #define KEY_8       0x25
  43. #define KEY_9       0x26
  44. #define KEY_0       0x27
  45. #define KEY_F1      0x3A
  46. #define KEY_F2      0x3B
  47. #define KEY_F3      0x3C
  48. #define KEY_F4      0x3D
  49. #define KEY_F5      0x3E
  50. #define KEY_F6      0x3F
  51. #define KEY_F7      0x40
  52. #define KEY_F8      0x41
  53. #define KEY_F9      0x42
  54. #define KEY_F10     0x43
  55. #define KEY_F11     0x44
  56. #define KEY_F12     0x45

Funkcja wysyłająca oraz kasująca przyciski wygląda następująco:

  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.    
  7.     buff[0] = 0x01;
  8.    
  9.     buff[1] = Specjalny;
  10.    
  11.     buff[2] = 0x00;
  12.    
  13.     buff[3] = Pierwszy;
  14.     buff[4] = Drugi;
  15.     buff[5] = Trzeci;
  16.     buff[6] = Czwarty;
  17.     buff[7] = Piaty;
  18.     buff[8] = Szosty;
  19.    
  20.     USBD_HID_SendReport(&USB_OTG_dev, buff, 9);
  21.    
  22.     buff[0] = 0x01;
  23.    
  24.     for(i=1;i<9;i++)
  25.     {
  26.         buff[i] = 0x00;
  27.     }
  28.    
  29.     USBD_HID_SendReport(&USB_OTG_dev, buff, 9);
  30. }

Obie muszą być zdeklarowane, spowodowane jest to tym, że komputer musi być informowany o tym że dany klawisz został wciśnięty oraz, że został puszczony. Jeśli dane zostaną tylko wysłane bez skasowania, to będą one cały czas odczytywane jako włączone.

Biblioteki API myszka


W tym przypadku włączenie biblioteki jest takie samo jak dla klawiatury. Dane wysyła się na pięciu bajtach:

  1. void MouseSend(uint8_t Przycisk, uint8_t OsX, uint8_t OsY, uint8_t Scroll)
  2. {
  3.     uint8_t buff[5] = {0, 0, 0, 0, 0};
  4.    
  5.     buff[0] = 0x02;
  6.    
  7.     //Lewy przycisk 0x01 | Prawy przycisk 0x02 | Srodkowy przycisk 0x04
  8.     buff[1] = Przycisk;
  9.    
  10.     buff[2] = OsX;
  11.     buff[3] = OsY;
  12.    
  13.     buff[4] = Scroll;
  14.    
  15.     USBD_HID_SendReport(&USB_OTG_dev, buff, 5);
  16.    
  17.     buff[0] = 0x02;
  18.    
  19.     buff[1] = 0x00;
  20.    
  21.     buff[2] = 0x00;
  22.     buff[3] = 0x00;
  23.    
  24.     buff[4] = 0x00;
  25.    
  26.     USBD_HID_SendReport(&USB_OTG_dev, buff, 5);
  27. }

Jeśli chodzi o ustawianie i wywoływanie danych, to można to robić np. poprzez wciśnięcie przycisku systemowego w pętli np. tak:

Deklaracja przycisku:

  1. void ButtonInit(void)
  2. {
  3.  GPIO_InitTypeDef GPIO_InitDef;
  4.  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  5.  GPIO_InitDef.GPIO_Pin = GPIO_Pin_0;
  6.  GPIO_InitDef.GPIO_OType = GPIO_OType_PP;
  7.  //Ustawienie jako wejście
  8.  GPIO_InitDef.GPIO_Mode = GPIO_Mode_IN;
  9.  GPIO_InitDef.GPIO_PuPd = GPIO_PuPd_NOPULL;
  10.  GPIO_InitDef.GPIO_Speed = GPIO_Speed_100MHz;
  11.  GPIO_Init(GPIOA, &GPIO_InitDef);
  12. }

Teraz czas na pętlę główną programu, w pętli będzie wywoływane przesunięcie myszki oraz wpisanie danych poprzez klawiaturę:

  1. int main(void)
  2. {  
  3.     uint8_t i = 0;
  4.     SystemInit();
  5.     ButtonInit();
  6.    
  7.     USB_HID_Init();
  8.     while(1)
  9.     {
  10.         if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0))
  11.         {
  12.             delayms(70);
  13.             if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0))
  14.             {
  15.                 delayms(100);
  16.                 if(i==0)
  17.                 {
  18.                     KeyboardWrite(0x00, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15);
  19.                     KeyboardWrite(0x00, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17);
  20.                     i++;
  21.                 }
  22.                 else if(i==1)
  23.                 {
  24.                     MouseSend(0x01, 0x05, 0x10, 0x10);
  25.                     MouseSend(0x02, 0x05, 0x08, 0x06);
  26.                     i=0;
  27.                 }
  28.             }
  29.         }
  30.     }
  31. }

Ja wykorzystałem wyzwalanie przyciskiem, ale równie dobrze można wykonać w pętli, przez co dane będą cyklicznie wysyłane na komputer od razu po podłączeniu do USB.