poniedziałek, 4 czerwca 2018

[35] STM32F4 - Klawiatura, custom HID

W tym poście chciałbym przedstawić projekt układu z tzw Custom HID. Czyli układem komunikującym się poprzez USB z komputerem.


[Źródło: http://www.st.com/en/evaluation-tools/stm32f4discovery.html]

Poniżej przedstawię dwa projekty, jeden pozwalający na komunikację z programem STM USB HID Demonstrator v 1.02 oraz drugi działający jako klawiatura z opcją zmiany dźwięku.

Napisanie własnego deskryptora:


Tutaj opiszę w jaki sposób wykonać przykładowy deskryptor. Najpierw opiszę przykładowy deskryptor dla klawiatury. Po tym przejdę do opisania deskryptora customowego dla programu Demonstrator.

Poniżej przedstawiony jest przykładowy deskryptor HID:

  1. __ALIGN_BEGIN static uint8_t USBD_HID_Desc[USB_HID_DESC_SIZ]  __ALIGN_END  =
  2. {
  3.   /* 18 */
  4.   0x09,                         /*bLength: HID Descriptor size*/
  5.   HID_DESCRIPTOR_TYPE,          /*bDescriptorType: HID*/
  6.   0x11,                         /*bcdHID: HID Class Spec release number*/
  7.   0x01,
  8.   0x00,                         /*bCountryCode: Hardware target country*/
  9.   0x01,                         /*bNumDescriptors: Number of HID class descriptors to follow*/
  10.   0x22,                         /*bDescriptorType*/
  11.   HID_CUSTOM_REPORT_DESC_SIZE,  /*wItemLength: Total length of Report descriptor*/
  12.   0x00,
  13. };

W nim zawarte są takie informacje jak np. Rodzaj klas HID, typ deskryptora, wielkość ramki.

Drugi tzw deskryptor urządzenia wygląda następująco:

  1. __ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END =
  2.   {
  3.     0x12,                       /*bLength */
  4.     USB_DESC_TYPE_DEVICE,       /*bDescriptorType*/
  5. #if (USBD_LPM_ENABLED == 1)
  6.     0x01,                       /*bcdUSB */ /* changed to USB version 2.01
  7.                                                in order to support LPM L1 suspend
  8.                                                resume test of USBCV3.0*/
  9. #else  
  10.     0x00,                       /* bcdUSB */
  11. #endif
  12.     0x02,
  13.     0x00,                       /*bDeviceClass*/
  14.     0x00,                       /*bDeviceSubClass*/
  15.     0x00,                       /*bDeviceProtocol*/
  16.     USB_MAX_EP0_SIZE,          /*bMaxPacketSize*/
  17.     LOBYTE(USBD_VID),           /*idVendor*/
  18.     HIBYTE(USBD_VID),           /*idVendor*/
  19.     LOBYTE(USBD_PID_FS),           /*idVendor*/
  20.     HIBYTE(USBD_PID_FS),           /*idVendor*/
  21.     0x00,                       /*bcdDevice rel. 2.00*/
  22.     0x02,
  23.     USBD_IDX_MFC_STR,           /*Index of manufacturer  string*/
  24.     USBD_IDX_PRODUCT_STR,       /*Index of product string*/
  25.     USBD_IDX_SERIAL_STR,        /*Index of serial number string*/
  26.     USBD_MAX_NUM_CONFIGURATION  /*bNumConfigurations*/
  27.   } ;

W nim zawarte są takie elementy jak wersja standardu USB, numer identyfikacyjny producenta czyli VendorID oraz urządzenia ProductID. Te numery nadawane są przez USB-IF i do aplikacji wprowadzanych na rynek nie należy ich modyfikować. W każdym systemie może spokojnie pracować kilka urządzeń o jednakowych numer VID oraz PID, gdzie każde z tych urządzeń może pełnić różne funkcje. Z tego powodu nie ma w zasadzie potrzeby ich modyfikowania.

Dla klawiatury możliwe jest kliknięcie klawisza specjalnego czyli ctrl, alt itp. oraz 6 klawiszy określających wprowadzony przycisk. Poniżej przedstawię przykładowy deskryptor raportów dla tego urządzenia.

Najpierw deskryptor musi zostać obudowany w informacje o tym że korzystamy z klawiatury:

  1. 0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
  2. 0x09, 0x06,        // Usage (Keyboard)
  3. 0xA1, 0x01,        // Collection (Application)
  4. 0x85, 0x01,        //   Report ID (1)
  5.         /*
  6.             Main descriptor part
  7.         */
  8. 0x05, 0x07,        //   Usage Page (Keyboard)
  9. 0xC0,                // End Collection

Kolejnym krokiem jest definiowanie wielkości raportu oraz ilości danych jakie mają zostać przesłane z raportem:

  1. 0x75, 0x01,        //   Report Size (1)
  2. 0x95, 0x08,        //   Report Count (8)

Jak widać powyżej przesyłany będzie jeden raport wraz z ośmioma bajtami.

Następnie definiowane jest pole informujące o numerach dotyczących klawiszy specjalnych:

  1. 0x19, 0xE0,        //   Usage Minimum (0xE0 224)
  2. 0x29, 0xE7,        //   Usage Maximum (0xE7 231)

Jak widać tutaj wartości minimalne i maksymalne. Nie odnoszą się one do przesyłanych wartości a jedynie do zakresu jaki ma być przyjmowany. Tutaj widać, do dyspozycji klawiszy specjalnych przeznaczony jest jeden bajt danych. Dzięki temu nie ma konieczności przesyłania większej paczki danych.

Klawisze specjalne przyjmują następującą postać:

  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)

Przyciski mogą przyjmować dwa stany wysoki oraz niski:

  1. 0x15, 0x00,        //   Logical Minimum (0)
  2. 0x25, 0x01,        //   Logical Maximum (1)

Następna linia informuje o tym, że pakiet danych zostanie przesłany do komputera:

  1. 0x81, 0x02,        //   Input (Data,Var,Abs)

Jak już opis klawiszy specjalnych został zrealizowany to zostało przygotowanie klawiszy głównych:

  1. 0x95, 0x06,        //   Report Count (6)
  2. 0x75, 0x08,        //   Report Size (8)
  3. 0x15, 0x00,        //   Logical Minimum (0)
  4. 0x25, 0x65,        //   Logical Maximum (101)
  5. 0x05, 0x07,        //   Usage Page (Keyboard)
  6. 0x19, 0x00,        //   Usage Minimum (Reserver)
  7. 0x29, 0x65,        //   Usage Maximum (Keyboard aplication)
  8. 0x81, 0x00,        //   Input (Data,Array,Abs)

W tym przypadku jak wspomniałem wcześniej do wysłania będzie 6 klawiszy wobec tego cały raport wyniesie 8 bajtów.

Pełny deskryptor dla klawiatury wygląda następująco:

  1. 0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
  2. 0x09, 0x06,        // Usage (Keyboard)
  3. 0xA1, 0x01,        // Collection (Application)
  4. 0x85, 0x01,        //   Report ID (1)
  5. 0x05, 0x07,        //   Usage Page (Keyboard)
  6. 0x75, 0x01,        //   Report Size (1)
  7. 0x95, 0x08,        //   Report Count (8)
  8. 0x19, 0xE0,        //   Usage Minimum (0xE0 224)
  9. 0x29, 0xE7,        //   Usage Maximum (0xE7 231)
  10. 0x15, 0x00,        //   Logical Minimum (0)
  11. 0x25, 0x01,        //   Logical Maximum (1)
  12. 0x81, 0x02,        //   Input (Data,Var,Abs)
  13. 0x95, 0x06,        //   Report Count (6)
  14. 0x75, 0x08,        //   Report Size (8)
  15. 0x15, 0x00,        //   Logical Minimum (0)
  16. 0x25, 0x65,        //   Logical Maximum (101)
  17. 0x05, 0x07,        //   Usage Page (Keyboard)
  18. 0x19, 0x00,        //   Usage Minimum (Reserver)
  19. 0x29, 0x65,        //   Usage Maximum (Keyboard aplication)
  20. 0x81, 0x00,        //   Input (Data,Array,Abs)
  21. 0xC0,                // End Collection

Przykładowa funkcja przesyłająca dla klawiatury wygląda następująco:

  1. void sendKeyboardData(uint8_t keySpecial, uint8_t key1, uint8_t key2, uint8_t key3, uint8_t key4,
  2.         uint8_t key5, uint8_t key6)
  3. {
  4.     keyboard_Typedef keyboardReport;
  5.     keyboardReport.id = 0x01;
  6.     keyboardReport.speciakKey = keySpecial;
  7.     keyboardReport.key1 = key1;
  8.     keyboardReport.key2 = key2;
  9.     keyboardReport.key3 = key3;
  10.     keyboardReport.key4 = key4;
  11.     keyboardReport.key5 = key5;
  12.     keyboardReport.key6 = key6;
  13.     USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&keyboardReport, sizeof(keyboard_Typedef));
  14.     HAL_Delay(100);
  15.     keyboardReport.id = 0x01;
  16.     keyboardReport.speciakKey = 0;
  17.     keyboardReport.key1 = 0;
  18.     keyboardReport.key2 = 0;
  19.     keyboardReport.key3 = 0;
  20.     keyboardReport.key4 = 0;
  21.     keyboardReport.key5 = 0;
  22.     keyboardReport.key6 = 0;
  23.     USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&keyboardReport, sizeof(keyboard_Typedef));
  24. }

Dokładniejszy opis można znaleźć we wcześniejszym poście (34).

Kolejny opisywany deskryptor dotyczy klawiszy multimedialnych. W tym przypadku należy dołączyć specjalny opis klawiszy wraz z raportem.

Na samym początku informacje dla urządzenia, że będziemy wykorzystywać customową klasę:

  1. 0x05, 0x0C,        // Usage Page (Consumer)
  2. 0x09, 0x01,        // Usage (Consumer Control)
  3. 0xA1, 0x01,        // Collection (Application)
  4. 0x85, 0x02,        //Report ID (2)
  5. 0x05, 0x0C,        //Usage Page (Consumer)

Teraz stany przycisków:

  1. 0x15, 0x00,        //   Logical Minimum (0)
  2. 0x25, 0x01,        //   Logical Maximum (1)

Wielkość raportu:

  1. 0x75, 0x01,        //   Report Size (1)
  2. 0x95, 0x08,        //   Report Count (7)

Przesłanie pojedynczego raportu z siedmioma bitami dla dla klawiszy specjalnych.

Każdy z klawiszy musi zostać odpowiednio zdefiniowany w raporcie:

  1. 0x09, 0xE9,      //  Usage (Volume Increment),
  2. 0x09, 0xEA,      //  Usage (Volume Decrement),
  3. 0x09, 0xE2,      //  Usage (Mute),
  4. 0x09, 0xCD,      //  Usage (Play/Pause),
  5. 0x09, 0xB5,      //  Usage (Scan Next Track),
  6. 0x09, 0xB6,      //  Usage (Scan Previous Track),
  7. 0x09, 0xB7,      //  Usage (Stop),
  8. 0x09, 0xB8,      //  Usage (Eject),

Na samym końcu zdefiniowanie ramki do wysłania. Przesyłany będzie numer ID wraz z wprowadzonym klawiszem:

  1. 0x81, 0x02,        //  Input (Data,Var,Abs)
  2. 0xC0,              //End Collection

Cały deskryptor wygląda następująco:

  1. 0x05, 0x0C,        //Usage Page (Consumer)
  2. 0x09, 0x01,        //Usage (Consumer Control)
  3. 0xA1, 0x01,        //Collection (Application)
  4. 0x85, 0x02,        //  Report ID (2)
  5. 0x05, 0x0C,        //  Usage Page (Consumer)
  6. 0x15, 0x00,        //  Logical Minimum (0)
  7. 0x25, 0x01,        //  Logical Maximum (1)
  8. 0x75, 0x01,        //  Report Size (1)
  9. 0x95, 0x08,        //  Report Count (7)
  10. 0x09, 0xB5,        //   Usage (Scan Next Track)
  11. 0x09, 0xB6,        //   Usage (Scan Previous Track)
  12. 0x09, 0xB7,        //   Usage (Stop)
  13. 0x09, 0xB8,        //   Usage (Eject)
  14. 0x09, 0xCD,        //   Usage (Play/Pause)
  15. 0x09, 0xE2,        //   Usage (Mute)
  16. 0x09, 0xE9,        //   Usage (Volume Increment)
  17. 0x09, 0xEA,        //   Usage (Volume Decrement)
  18. 0x81, 0x02,        //  Input (Data,Var,Abs)
  19. 0xC0,              //End Collection

Teraz ostatni z opisywanych deskryptorów czyli customowy pod aplikację Demonstrator.

Ten deskryptor można znaleźć w dokumentach do programu Demonstrator bądź na różnych stronach w internecie.

Na samym początku otwarcie deskryptora:

  1. 0x06, 0x00, 0xff,              // USAGE_PAGE (Generic Desktop)
  2. 0x09, 0x01,                    // USAGE (Vendor Usage 1)
  3. 0xa1, 0x01,                    // COLLECTION (Application)

W środku definiujemy deskryptory dla przycisków:

  1. 0x85, 0x01,                    //   REPORT_ID (1)
  2. 0x09, 0x01,                    //   USAGE (Vendor Usage 1)
  3. 0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
  4. 0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
  5. 0x75, 0x08,                    //   REPORT_SIZE (8)
  6. 0x95, 0x01,                    //   REPORT_COUNT (1)
  7. 0xB1, 0x82,                    //   FEATURE (Data,Var,Abs,Vol)
  8. 0x85, 0x01,                    //   REPORT_ID (1)
  9. 0x09, 0x01,                    //   USAGE (Vendor Usage 1)
  10. 0x91, 0x82,                    //   OUTPUT (Data,Var,Abs,Vol)
  11.  
  12. 0x85, 0x02,                    //   REPORT_ID (2)
  13. 0x09, 0x02,                    //   USAGE (Vendor Usage 2)
  14. 0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
  15. 0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
  16. 0x75, 0x08,                    //   REPORT_SIZE (8)
  17. 0x95, 0x01,                    //   REPORT_COUNT (1)
  18. 0xB1, 0x82,                    //   FEATURE (Data,Var,Abs,Vol)
  19. 0x85, 0x02,                    //   REPORT_ID (2)
  20. 0x09, 0x02,                    //   USAGE (Vendor Usage 2)
  21. 0x91, 0x82,                    //   OUTPUT (Data,Var,Abs,Vol)
  22.  
  23. 0x85, 0x03,                    //   REPORT_ID (3)
  24. 0x09, 0x03,                    //   USAGE (Vendor Usage 3)
  25. 0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
  26. 0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
  27. 0x75, 0x08,                    //   REPORT_SIZE (8)
  28. 0x95, 0x01,                    //   REPORT_COUNT (1)
  29. 0xB1, 0x82,                    //   FEATURE (Data,Var,Abs,Vol)
  30. 0x85, 0x03,                    //   REPORT_ID (3)
  31. 0x09, 0x03,                    //   USAGE (Vendor Usage 3)
  32. 0x91, 0x82,                    //   OUTPUT (Data,Var,Abs,Vol)
  33.  
  34. 0x85, 0x04,                    //   REPORT_ID (4)
  35. 0x09, 0x04,                    //   USAGE (Vendor Usage 4)
  36. 0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
  37. 0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
  38. 0x75, 0x08,                    //   REPORT_SIZE (8)
  39. 0x95, 0x01,                    //   REPORT_COUNT (1)
  40. 0xB1, 0x82,                    //   FEATURE (Data,Var,Abs,Vol)
  41. 0x85, 0x04,                    //   REPORT_ID (4)
  42. 0x09, 0x04,                    //   USAGE (Vendor Usage 4)
  43. 0x91, 0x82,                    //   OUTPUT (Data,Var,Abs,Vol)

Dla wartości USAGE oraz RAPORT_ID wprowadzany jest identyfikator raporty. Diody przyjmują wartości 0 lub 1. Czyli świeci albo nie świeci.

Teraz deskryptor dla dwóch przycisków:

  1. 0x85, 0x05,              //   REPORT_ID (5)
  2. 0x09, 0x05,              //   USAGE (Vendor Usage 5)
  3. 0x75, 0x08,              //   REPORT_SIZE (8)
  4. 0x95, 0x04,              //   REPORT_COUNT (4)
  5. 0x81, 0x82,              //   INPUT (Data,Var,Abs,Vol)
  6.  
  7. 0x85, 0x06,              //   REPORT_ID (6)
  8. 0x09, 0x06,              //   USAGE (Vendor Usage 6)
  9. 0x75, 0x08,              //   REPORT_SIZE (8)
  10. 0x95, 0x04,              //   REPORT_COUNT (4)
  11. 0x81, 0x82,              //   INPUT (Data,Var,Abs,Vol)

Ostania część deskryptora pozwala na przesłanie danych do urządzenia w wartościach od 0 do 255:

  1. 0x85, 0x09,                     //  REPORT_ID (9)
  2. 0x09, 0x09,                     //  USAGE (someData)
  3. 0x15, 0x00,                     //  LOGICAL_MINIMUM (0)
  4. 0x26, 0xFF, 0x00,               //  LOGICAL_MAXIMUM (255)
  5. 0x75, 0x08,                     //  REPORT_SIZE (8)
  6. 0x81, 0x82,                     //  INPUT (Data,Var,Abs,Vol)
  7. 0x85, 0x09,                     //  REPORT_ID (9)
  8. 0x09, 0x09,                     //  USAGE (someData)
  9. 0xB1, 0x82,                     //  FEATURE (Data,Var,Abs,Vol)

Wartość maksymalna Logical_Maximum wartość należy wprowadzić w dwóch polach. Ponieważ dane w polu są typu signed. Co oznacza, że pominięcie wartości 0x00 spowoduje interpretację danych jako -128.

Pełny deskryptor wygląda następująco:

  1. 0x06, 0x00, 0xff,              // USAGE_PAGE (Generic Desktop)
  2. 0x09, 0x01,                    // USAGE (Vendor Usage 1)
  3. 0xa1, 0x01,                    // COLLECTION (Application)
  4.  
  5. 0x85, 0x01,                    //   REPORT_ID (1)
  6. 0x09, 0x01,                    //   USAGE (Vendor Usage 1)
  7. 0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
  8. 0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
  9. 0x75, 0x08,                    //   REPORT_SIZE (8)
  10. 0x95, 0x01,                    //   REPORT_COUNT (1)
  11. 0xB1, 0x82,                    //   FEATURE (Data,Var,Abs,Vol)
  12. 0x85, 0x01,                    //   REPORT_ID (1)
  13. 0x09, 0x01,                    //   USAGE (Vendor Usage 1)
  14. 0x91, 0x82,                    //   OUTPUT (Data,Var,Abs,Vol)
  15.  
  16. 0x85, 0x02,                    //   REPORT_ID (2)
  17. 0x09, 0x02,                    //   USAGE (Vendor Usage 2)
  18. 0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
  19. 0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
  20. 0x75, 0x08,                    //   REPORT_SIZE (8)
  21. 0x95, 0x01,                    //   REPORT_COUNT (1)
  22. 0xB1, 0x82,                    //   FEATURE (Data,Var,Abs,Vol)
  23. 0x85, 0x02,                    //   REPORT_ID (2)
  24. 0x09, 0x02,                    //   USAGE (Vendor Usage 2)
  25. 0x91, 0x82,                    //   OUTPUT (Data,Var,Abs,Vol)
  26.  
  27. 0x85, 0x03,                    //   REPORT_ID (3)
  28. 0x09, 0x03,                    //   USAGE (Vendor Usage 3)
  29. 0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
  30. 0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
  31. 0x75, 0x08,                    //   REPORT_SIZE (8)
  32. 0x95, 0x01,                    //   REPORT_COUNT (1)
  33. 0xB1, 0x82,                    //   FEATURE (Data,Var,Abs,Vol)
  34. 0x85, 0x03,                    //   REPORT_ID (3)
  35. 0x09, 0x03,                    //   USAGE (Vendor Usage 3)
  36. 0x91, 0x82,                    //   OUTPUT (Data,Var,Abs,Vol)
  37.  
  38. 0x85, 0x04,                    //   REPORT_ID (4)
  39. 0x09, 0x04,                    //   USAGE (Vendor Usage 4)
  40. 0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
  41. 0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
  42. 0x75, 0x08,                    //   REPORT_SIZE (8)
  43. 0x95, 0x01,                    //   REPORT_COUNT (1)
  44. 0xB1, 0x82,                    //   FEATURE (Data,Var,Abs,Vol)
  45. 0x85, 0x04,                    //   REPORT_ID (4)
  46. 0x09, 0x04,                    //   USAGE (Vendor Usage 4)
  47. 0x91, 0x82,                    //   OUTPUT (Data,Var,Abs,Vol)
  48.  
  49. 0x85, 0x05,                    //   REPORT_ID (5)
  50. 0x09, 0x05,                    //   USAGE (Vendor Usage 5)
  51. 0x75, 0x08,                    //   REPORT_SIZE (8)
  52. 0x95, 0x04,                    //   REPORT_COUNT (4)
  53. 0x81, 0x82,                    //   INPUT (Data,Var,Abs,Vol)
  54.  
  55. 0x85, 0x06,                    //   REPORT_ID (6)
  56. 0x09, 0x06,                    //   USAGE (Vendor Usage 6)
  57. 0x75, 0x08,                    //   REPORT_SIZE (8)
  58. 0x95, 0x04,                    //   REPORT_COUNT (4)
  59. 0x81, 0x82,                    //   INPUT (Data,Var,Abs,Vol)
  60.  
  61. 0x85, 0x09,                     //  REPORT_ID (9)
  62. 0x09, 0x09,                     //  USAGE (someData)
  63. 0x15, 0x00,                     //  LOGICAL_MINIMUM (0)
  64. 0x26, 0xff, 0x00,               //  LOGICAL_MAXIMUM (255)
  65. 0x75, 0x08,                     //  REPORT_SIZE (8)
  66. 0x81, 0x82,                     //  INPUT (Data,Var,Abs,Vol)
  67. 0x85, 0x09,                     //  REPORT_ID (9)
  68. 0x09, 0x09,                     //  USAGE (someData)
  69. 0xb1, 0x82,                     //  FEATURE (Data,Var,Abs,Vol)
  70.  
  71. 0xC0

Działanie z przykładowym programem:


Teraz kilka funkcji pozwalających na przesłanie customowych danych:

Na samym początku przygotowanie ramki danych do wysłania:

  1. static void sendData(uint8_t reportId, uint8_t data1, uint8_t data2,
  2.                                             uint8_t data3, uint8_t data4)
  3. {
  4.     uint8_t dataToSend[5];
  5.    
  6.     dataToSend[0]=reportId;
  7.     dataToSend[1]=data1;
  8.  
  9.     dataToSend[2]=data2;
  10.     dataToSend[3]=data3;
  11.     dataToSend[4]=data4;
  12.     USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,dataToSend,5);
  13. }

Z wykorzystaniem tej funkcji można wysłać większość pakietów. Np uruchomienie przycisku:

  1. sendData(0x05, 0x01, 0xFF, 0xFF, 0xFF); //Enable btn
  2. sendData(0x05, 0x00, 0xFF, 0xFF, 0xFF); //Disable btn
  3.  
  4. sendData(0x06, 0x01, 0xFF, 0xFF, 0xFF); //Enable second btn
  5. sendData(0x06, 0x00, 0xFF, 0xFF, 0xFF); //Disable second btn

Przesłanie danych z pełnymi wartościami dla jednego bajtu:

  1. sendData(0x09, 0xAA, 0xFF, 0xFF, 0xFF);

Dekodowanie odebranych danych wygląda następująco:

  1. static int8_t CUSTOM_HID_OutEvent_FS  (uint8_t event_idx, uint8_t state)
  2. {
  3.   /* USER CODE BEGIN 6 */
  4.        USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef*)hUsbDeviceFS.pClassData;
  5.  
  6.       for (uint8_t i = 0; i < 2; i++)
  7.       {
  8.           hidRecData[i] = hhid->Report_buf[i];
  9.       }
  10.  
  11.       if(hidRecData[0]==1)
  12.       {
  13.           if (hidRecData[1]==1)
  14.           {
  15.             HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);
  16.           }
  17.           else if (hidRecData[1]==0)
  18.           {
  19.             HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_RESET);
  20.           }
  21.       }
  22.       else if(hidRecData[0]==2)
  23.       {
  24.           if (hidRecData[1]==1)
  25.           {
  26.             HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_SET);
  27.           }
  28.           else if (hidRecData[1]==0)
  29.           {
  30.             HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_RESET);
  31.           }
  32.       }
  33.       else if(hidRecData[0]==3)
  34.       {
  35.           if (hidRecData[1]==1)
  36.           {
  37.             HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_SET);
  38.           }
  39.           else if (hidRecData[1]==0)
  40.           {
  41.             HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_RESET);
  42.           }
  43.       }
  44.       else if(hidRecData[0]==4)
  45.       {
  46.           if (hidRecData[1]==1)
  47.           {
  48.             HAL_GPIO_WritePin(GPIOD, GPIO_PIN_15, GPIO_PIN_SET);
  49.           }
  50.           else if(hidRecData[1]==0)
  51.           {
  52.             HAL_GPIO_WritePin(GPIOD, GPIO_PIN_15, GPIO_PIN_RESET);
  53.           }
  54.       }
  55.       else if(hidRecData[0]==7)
  56.       {
  57.           if(hidRecData[1]==0xAA)
  58.           {
  59.               HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);
  60.               HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_SET);
  61.               HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_SET);
  62.               HAL_GPIO_WritePin(GPIOD, GPIO_PIN_15, GPIO_PIN_SET);
  63.           }
  64.           else
  65.           {
  66.               HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_RESET);
  67.               HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_RESET);
  68.               HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_RESET);
  69.               HAL_GPIO_WritePin(GPIOD, GPIO_PIN_15, GPIO_PIN_RESET);
  70.           }
  71.       }
  72.  
  73.       return (0);
  74.   /* USER CODE END 6 */
  75. }

Na samym początku kopiuje otrzymaną ramkę danych składającą się z dwóch bajtów. Następnie sprawdzam otrzymane dane i zapalam odpowiednie diody na płytce discovery.

Po wgraniu danych i uruchomienie programu powinny wyświetlić się parametry połączenia:


Okno aplikacji Demonstrator gdzie istnieje możliwość odczytu danych z mikrokontrolera (Var Input 1 Report ID), przesłania danych z komputera do układu (Variable Output Report ID), sprawdzenie stanu przycisku (Button Report ID) oraz ustawienie diod (Led Report ID)

 

Można też obejrzeć przesyłaną ramkę danych:


Zwiększanie dźwięku:


Aktualnie korzystam z laptopa z systemem Windows 7. Na nim zmiana dźwięku za pomocą standardowych zestawów klawiszy (czyli 0x7F, 0x80 czy 0x81) okazała się niemożliwa. Mogłem użyć ich jedynie na komputerze stacjonarnym (testowałem tylko na pod Windows 7). Aby użyć tych funkcji na laptopie musiałem wykorzystać lekkie obejście. Najpierw należy utworzyć skrót na pulpicie z lokalizacją: %windir%\System32\SndVol.exe -f 49825268. Będzie on otwierał okno regulacji dźwięku. Dalej klikamy na skrót->właściwości i wprowadzamy klawisz skrótu (ja wykorzystałem F7). Jego kliknięcie wyświetli na ekranie okno regulacji dźwięku. Teraz aby zmienić głośność należy kliknąć strzałkę w górę lub w dół, bądź prawo oraz lewo czy PgUp bądź PdDown czy End w celu kompletnego wyciszenia.

Czyli aby była możliwość zmiany dźwięku na laptopie należy użyć klawisz skrótu, po czym wybrać jeden z klawiszy odpowiedzialnych za operacje do wykonania.

Wobec tego po wykonaniu skrótów głośność można zmodyfikować za pomocą takiej funkcji:

Dla komputera obsługującego klawisze multimedialne można posłużyć się takimi kodami jak:

  1. #define USB_MEDIA_SCAN_NEXT 0x01
  2. #define USB_MEDIA_SCAN_PREV 0x02
  3. #define USB_MEDIA_STOP      0x04
  4. #define USB_MEDIA_EJECT     0x08
  5. #define USB_MEDIA_PAUSE     0x10
  6. #define USB_MEDIA_MUTE      0x20
  7. #define USB_MEDIA_VOL_UP    0x40
  8. #define USB_MEDIA_VOL_DEC   0x80

Struktura przechowująca dane:

  1. typedef struct mediaHidData {
  2.   uint8_t reportId;
  3.   uint8_t specialKeyData;
  4. }media_Typedef;

Funkcja przesyłająca dane do ustawiania:

  1. void sendMediaData(uint8_t dataSend)
  2. {
  3.     media_Typedef mediaReport;
  4.  
  5.     mediaReport.reportId = 0x02;
  6.     mediaReport.specialKeyData = dataSend;
  7.  
  8.     USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&mediaReport, sizeof(media_Typedef));
  9.  
  10.     HAL_Delay(30);
  11.  
  12.     mediaReport.reportId = 0x02;
  13.     mediaReport.specialKeyData = 0x00;
  14.  
  15.     USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&mediaReport, sizeof(media_Typedef));
  16.  
  17.     HAL_Delay(30);
  18. }
  19.  
  20. void soundUP()
  21. {
  22.     sendMediaData(USB_MEDIA_VOL_UP);
  23. }
  24.  
  25. void soundDown()
  26. {
  27.     sendMediaData(USB_MEDIA_VOL_DEC);
  28. }

Tutaj wybierana jest wartość danych do przesłania, które dodawane są jako argument do struktury przesyłanej w paczce danych do komputera.

Sposób z ustawionym skrótem wygląda następująco:

  1. typedef struct keyboardHidData {
  2.     uint8_t id;
  3.     uint8_t speciakKey;
  4.     uint8_t key1;
  5.     uint8_t key2;
  6.     uint8_t key3;
  7.     uint8_t key4;
  8.     uint8_t key5;
  9.     uint8_t key6;
  10. }keyboard_Typedef;
  11.  
  12. void sendKeyboardData(uint8_t keySpecial, uint8_t key1, uint8_t key2, uint8_t key3, uint8_t key4,
  13.         uint8_t key5, uint8_t key6)
  14. {
  15.     keyboard_Typedef keyboardReport;
  16.  
  17.     keyboardReport.id = 0x01;
  18.     keyboardReport.speciakKey = keySpecial;
  19.     keyboardReport.key1 = key1;
  20.     keyboardReport.key2 = key2;
  21.     keyboardReport.key3 = key3;
  22.     keyboardReport.key4 = key4;
  23.     keyboardReport.key5 = key5;
  24.     keyboardReport.key6 = key6;
  25.  
  26.     USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&keyboardReport, sizeof(keyboard_Typedef));
  27.  
  28.     HAL_Delay(80);
  29.  
  30.     keyboardReport.id = 0x01;
  31.     keyboardReport.speciakKey = 0;
  32.     keyboardReport.key1 = 0;
  33.     keyboardReport.key2 = 0;
  34.     keyboardReport.key3 = 0;
  35.     keyboardReport.key4 = 0;
  36.     keyboardReport.key5 = 0;
  37.     keyboardReport.key6 = 0;
  38.  
  39.     USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&keyboardReport, sizeof(keyboard_Typedef));
  40.     HAL_Delay(30);
  41. }
  42.  
  43. void updateVolumeCombinationKeys_VolumeUp()
  44. {
  45.     sendKeyboardData(0x00, F7_Key, 0x00, 0x00, 0x00, 0x00, 0x00);
  46.     sendKeyboardData(0x00, Arrow_Up_Key, 0x00, 0x00, 0x00, 0x00, 0x00);
  47. }
  48.  
  49. void updateVolumeCombinationKeys_VolumeDown()
  50. {
  51.     sendKeyboardData(0x00, F7_Key, 0x00, 0x00, 0x00, 0x00, 0x00);
  52.     sendKeyboardData(0x00, Arrow_Down_Key, 0x00, 0x00, 0x00, 0x00, 0x00);
  53. }

W tym przypadku przesyłamy dwa razy pakiet danych do komputera, każdy z osobnym klawiszem.

Projekty do tego postu można pobrać z dysku Google pod tym linkiem.

Bibliografia:


[1] Device Class Definition for Human Interface Devices (HID)
[2] USB HID Descriptor
[3] UM0424 - STM32 USB-FS-Device development kit
[4] EP - Wykorzystaj USB Implementacja klasy HID interfejsu USB w STM32