Obsahem dnešního dílu je USB HID. HID jsme už probírali, je to jedna ze tříd USB zařízení. Výhodou je to, že HID je genericky součástí Windows, tedy že nepotřebujete nějaký ovladač a zařízení jen připojíte k PC. Proto jsem jako druhý praktický příklad realizace USB po Virtuálním sériovém portu zvolil právě HID.

Úvod

HID, jak jsme probírali mí různé typy zařízení. Můžete si vyrobit z kitu myš, která bude rejdit kurzorem po obrazovce (jak je v demo příkladu pro kit) nebo klávesnici, která vám do otevřeného dokumentu rovnou zapíše naměřené hodnoty. Nicméně v praxi častěji budete asi používat HID tzv. Custom Page, tedy, že nepůjde o nějaké "běžné zařízení", ale sami si nedefinujete, jak se bude chovat a PC nechá ovládání a přijímání dat z tohoto zařízení na Vás.

HID má kromě výhod, také několik nevýhod. Nevýhody vyplývají z definicí této třídy USB zařízení. HID primárně má sloužit pro komunikaci s člověkem - a to je hrozně pomalá periférie v porovnání s dnešním počítačem. Takže HID zařízení předává jen malé množství dat a pomalu. Nicméně, pro mnoho našich aplikací to může být dostatečné. Upozorňuji na to dopředu, aby se někdo nesnažil zrealizovat pomocí HID Class například logický analyzátor nebo osciloskop a pak by se divil, že nasbíraná data nedokáže přenést do PC. Naopak HID je vhodné pro zařízení které třeba měří teplotu, nebo napětí a čas od času se jej zeptáte na aktuální hodnotu, nebo třeba USB znakový displej, na který čas od času pošlete nějakou hodnotu. Aplikace od ST "USB HID Demonstrator" celkem pěkně ukazuje možnosti komunikace s HID. Na PC spustíte USB HID Demonstrator aplikaci a klikáním na ikonky LED je rozsvítíte i na kitu a naopak, když na kitu stisknete tlačítko, tak se vám na obrazovce objeví příslušná změna. Rovněž se posílá jedna hodnota z AD převodníku. Takže údajů nic moc, ale když budete psát vlastní program na PC, tak nemusíte se zajímat o nějaký speciální USB driver a při psaní aplikace pro PC využijete jednoduchou univerzální knihovnu, kterou si přilinkujete ke svému programu. To ale zjistíte až v následujícím dílu. V tomto dílu si ověříte funkčnost pomocí USB HID Demonstratoru.


Realizace

Jak bylo výše napsáno, využívám v tomto dílu software od ST (USB HID Demonstrator), který ale musíte nastavit. Nejdříve si USB HID Demonstrator stáhnete (odkaz na konci) a nainstalujete. Po prvním spuštění (před tím než přeložíte demo aplikaci a kit připojíte druhým USB kabelem k PC), je nastavíte následovně:



Bacha na to VariableInput1, aby za ním byla 06! Na VariableOutput realizaci jsem se vykašlal, ale je možné pochopitelně využít D/A převodník a generovat odpovídající napětí. Každopádně to můžete zkusit za domácí úkol a kdo to zvládne první, tak dostane vzorek některého z STM32F.

Po nastavení můžete přepnout do normálního grafického módu a pak už můžete přeložit demo. Jsou v tom ještě nějaké chybky (nějaké hlášky make pointer from integer without a cast, atd. Ale jak víte, pojídači koláčů warnigy nečtou, tudíž to nějaký poctivec mezi vámi může upravit) ale hlavní je, že to funguje. Tedy doufám. Protože jsem nevyzkoušel to měření A/D převodníkem na pinu PA1, ale doufám že to chodí (a prosím o potvrzení do komentáře, pokud to někdo odzkouší).

Co se týče realizace na straně mcu, tak pochopitelně využijeme opět kitu STM32F4 Discovery, který propojíme pomocí dvou USB kabelů s PC, tak jak bylo popsáno v dílu s USB Virtuálním sériovým portem. I tato demo aplikace je napsána v Atollic TrueSTUDIO, takže si jí můžete rozbalit a naimportovat do vašeho prostředí. Snažil jsme se aplikaci psát tak, aby jste jí mohli použít i v IAR a Keil, ale protože ani jedno z těchto prostředí nemám aktuálně v PC, tak jsem funkčnost importu neověřoval.

Po přeložení aplikace s spuštění debug v TrueSTUDIO, čímž se nahraje firmware do mcu, dáte F8, což je Resume, aby se vám program rozběhl. Na PC by měla vyskočit ikona, že byl připojen nový HW (HID) a po dokončení enumerace by jste měli vidět v "Ovládací panely - Systém - Hardware - Správce zařízení", jak vám naskočí nové USB zařízení na konec seznamu. To je jen pro ověření, že se USB HID správně zavedl. No a pak už můžete klikat na USB HID Demonstrator a mačkat USER tlačítko (to modré) na STM32F4 Discovery kitu a mělo by to fungovat. Tím myslím že když stisknete USER tlačítko, tak na formuláři USB HID Demonstratoru uvidíte jak se zelené kolečko vlevo rozsvítí a naopak, jak pomocí zaškrtávání na formuláři na PC obrazovce budete rozsvěcet LED na kitu. Na obrazovce to pak bude vypadat jako na následujícím obrázku.




Podrobnosti k SW

Vyšel jsem z pochopitelně z existujícího dema k STM32F4 Discovery, které realizuje HID mouse. Nicméně myš jen data posílá a nic nepřijímá, proto jsem musel zaregistrovat a napsat novou funkci USBD_HID_DataOut. Registraci jsem provedl takto:

USBD_Class_cb_TypeDef  USBD_HID_cb =
{
  USBD_HID_Init,
  USBD_HID_DeInit,
  USBD_HID_Setup,
  NULL, /*EP0_TxSent*/  
  NULL, /*EP0_RxReady*/
  USBD_HID_DataIn, /*DataIn*/
  /*  NULL,*/
  USBD_HID_DataOut, /*DataOut*/
  NULL, /*SOF */
  NULL,
  NULL,      
  USBD_HID_GetCfgDesc,
#ifdef USB_OTG_HS_CORE  
  USBD_HID_GetCfgDesc, /* use same config as per FS */
#endif  
};


A funkce vypadá následovně:
/**
  * @brief  USBD_HID_DataOut
  *         handle data OUT Stage
  * @param  pdev: device instance
  * @param  epnum: endpoint index
  * @retval status
  */

static uint8_t  USBD_HID_DataOut (void *pdev, uint8_t epnum)
{
        uint16_t USB_RecData_Cnt;
        BitAction Led_State;
        if (epnum == HID_OUT_EP)
        {
                /* Get the received data buffer and update the counter */
                USB_RecData_Cnt = ((USB_OTG_CORE_HANDLE*)pdev)->dev.out_ep[epnum].xfer_count;
                /* USB data will be immediately processed, this allow next USB traffic being
                   NAKed till the end of the application Xfer */

                if (((USB_OTG_CORE_HANDLE*)pdev)->dev.device_status == USB_OTG_CONFIGURED )
                {
                        /* predpokladame ze delka = 2 bajty */
                        USB_OTG_ReadPacket((USB_OTG_CORE_HANDLE*)pdev, *Buffer, HID_OUT_PACKET);
                        /* process the report setting */
                        if (Buffer[1] == 0)
                                Led_State = Bit_RESET;
                        else
                                Led_State = Bit_SET;
                        switch (Buffer[0])
                        {
                            case 1: /* Led 1 */
                             if (Led_State != Bit_RESET)
                                 STM32F4_Discovery_LEDOn(LED3);
                             else
                                 STM32F4_Discovery_LEDOff(LED3);
                             break;
                            case 2: /* Led 2 */
                             if (Led_State != Bit_RESET)
                                 STM32F4_Discovery_LEDOn(LED4);
                             else
                                 STM32F4_Discovery_LEDOff(LED4);
                              break;
                            case 3: /* Led 3 */
                             if (Led_State != Bit_RESET)
                                 STM32F4_Discovery_LEDOn(LED5);
                             else
                                 STM32F4_Discovery_LEDOff(LED5);
                              break;
                            case 4: /* Led 4 */
                             if (Led_State != Bit_RESET)
                                 STM32F4_Discovery_LEDOn(LED6);
                             else
                                 STM32F4_Discovery_LEDOff(LED6);
                              break;
                          default:
                            STM32F4_Discovery_LEDOff(LED3); /* oranzova */
                            STM32F4_Discovery_LEDOff(LED4); /* zelena */
                            STM32F4_Discovery_LEDOff(LED5); /* cervena */
                            STM32F4_Discovery_LEDOff(LED6); /* modra */
                            break;
                        }
                        /* Prepare Out endpoint to receive next packet */
                        DCD_EP_PrepareRx(pdev,
                                           HID_OUT_EP,
                                           (uint8_t*)(Buffer),
                                           HID_OUT_PACKET);
                }
        }
        return USBD_OK;
}


Jak vidíte sami z kódu, tak funkce rovnou zpracovává přijaté reporty od PC a podle nic nastavuje LED na kitu. To je ale jen jedna stránka problému, tedy směr dat z PC do USB zařízení. Ale také chceme odesílat data z kitu do PC (moc jich není, kde jen o stisk USER tlačítka, ale stejně to musíme napsat ). Celé se to chovává v main().

/**
  * @brief  Main program.
  * @param  None
  * @retval None
  */

int main(void)
{
  STM32F4_Discovery_LEDInit(LED3);
  STM32F4_Discovery_LEDInit(LED4);
  STM32F4_Discovery_LEDInit(LED5);
  STM32F4_Discovery_LEDInit(LED6);
  STM32F4_Discovery_PBInit(BUTTON_USER, BUTTON_MODE_GPIO);

  /* zapneme LED3 */
  STM32F4_Discovery_LEDOn(LED3);   /* oranzova */
  Delay(0xFFFF);
  STM32F4_Discovery_LEDOff(LED3);   /* oranzova */

  USBD_Init(&USB_OTG_dev,
#ifdef USE_USB_OTG_HS
  USB_OTG_HS_CORE_ID,
#else
  USB_OTG_FS_CORE_ID,
#endif
  &USR_desc,
  &USBD_HID_cb,
  &USR_cb);

  Init_ADC_Reading();
  while (1)
  {
          if (STM32F4_Discovery_PBGetState(BUTTON_USER) == Bit_SET)
          {
                  /* STM32F4_Discovery_LEDOn(LED3);  oranzova - debug only */
                  if (UserButtonPressed != 0x01)
                  {
                          /* new action */
                          UserButtonPressed = 0x01;
                          Delay(0xFF);
                          Buffer[0]=0x05; /* report cislo 5 */
                          Buffer[1]=0x01;
                          USBD_HID_SendReport (&USB_OTG_dev, Buffer, 2);
                  }
          }
          else
          {
                  /* STM32F4_Discovery_LEDOff(LED3);  oranzova - debug only*/
                  if (UserButtonPressed != 0x00)
                  {
                          /* new action */
                          UserButtonPressed = 0x00;
                          Delay(0xFF);
                          Buffer[0]=0x05; /* report cislo 5 */
                          Buffer[1]=0x00;
                          USBD_HID_SendReport (&USB_OTG_dev, Buffer, 2);
                  }
          }
  }
}



Aby fungovala komunikace, tak se rovněž musel napsat nový Report Descriptor, místo původního. Nový vypadá následovně.

__ALIGN_BEGIN static uint8_t CustomHID_ReportDescriptor[CUSTOMHID_SIZ_REPORT_DESC] __ALIGN_END =
{
  0x06, 0xFF, 0x00,      /* USAGE_PAGE (Vendor Page: 0xFF00) */
  0x09, 0x01,            /* USAGE (Demo Kit) */
  0xA1, 0x01,            /* COLLECTION (Application)       */
  /* 6 */  
  /* Led 1 */
  0x85, 0x01,            /*     REPORT_ID (1)                */
  0x09, 0x01,            /*     USAGE (LED 1)                */
  0x15, 0x00,            /*     LOGICAL_MINIMUM (0)        */
  0x25, 0x01,            /*     LOGICAL_MAXIMUM (1)        */
  0x75, 0x08,            /*     REPORT_SIZE (8)            */
  0x95, 0x01,            /*     REPORT_COUNT (1)           */
  0xB1, 0x82,             /*    FEATURE (Data,Var,Abs,Vol) */
  0x85, 0x01,            /*     REPORT_ID (1)              */
  0x09, 0x01,            /*     USAGE (LED 1)              */
  0x91, 0x82,            /*     OUTPUT (Data,Var,Abs,Vol)  */
  /* 26 */  
  /* Led 2 */
  0x85, 0x02,            /*     REPORT_ID 2                  */
  0x09, 0x02,            /*     USAGE (LED 2)                */
  0x15, 0x00,            /*     LOGICAL_MINIMUM (0)        */
  0x25, 0x01,            /*     LOGICAL_MAXIMUM (1)        */
  0x75, 0x08,            /*     REPORT_SIZE (8)            */
  0x95, 0x01,            /*     REPORT_COUNT (1)           */
  0xB1, 0x82,             /*    FEATURE (Data,Var,Abs,Vol) */
  0x85, 0x02,            /*     REPORT_ID (2)              */
  0x09, 0x02,            /*     USAGE (LED 2)              */
  0x91, 0x82,            /*     OUTPUT (Data,Var,Abs,Vol)  */
  /* 46 */  
  /* Led 3 */
  0x85, 0x03,            /*     REPORT_ID (3)                */
  0x09, 0x03,            /*     USAGE (LED 3)                */
  0x15, 0x00,            /*     LOGICAL_MINIMUM (0)        */
  0x25, 0x01,            /*     LOGICAL_MAXIMUM (1)        */
  0x75, 0x08,            /*     REPORT_SIZE (8)            */
  0x95, 0x01,            /*     REPORT_COUNT (1)           */
  0xB1, 0x82,             /*    FEATURE (Data,Var,Abs,Vol) */
  0x85, 0x03,            /*     REPORT_ID (3)              */
  0x09, 0x03,            /*     USAGE (LED 3)              */
  0x91, 0x82,            /*     OUTPUT (Data,Var,Abs,Vol)  */
  /* 66 */  
  /* Led 4 */
  0x85, 0x04,            /*     REPORT_ID 4)                 */
  0x09, 0x04,            /*     USAGE (LED 4)                */
  0x15, 0x00,            /*     LOGICAL_MINIMUM (0)        */
  0x25, 0x01,            /*     LOGICAL_MAXIMUM (1)        */
  0x75, 0x08,            /*     REPORT_SIZE (8)            */
  0x95, 0x01,            /*     REPORT_COUNT (1)           */
  0xB1, 0x82,            /*     FEATURE (Data,Var,Abs,Vol) */
  0x85, 0x04,            /*     REPORT_ID (4)              */
  0x09, 0x04,            /*     USAGE (LED 4)              */
  0x91, 0x82,            /*     OUTPUT (Data,Var,Abs,Vol)  */
  /* 86 */
  /* key USER Button */
  0x85, 0x05,            /*     REPORT_ID (5)              */
  0x09, 0x05,            /*     USAGE (USER Button)        */
  0x15, 0x00,            /*     LOGICAL_MINIMUM (0)        */
  0x25, 0x01,            /*     LOGICAL_MAXIMUM (1)        */
  0x75, 0x01,            /*     REPORT_SIZE (1)            */
  0x81, 0x82,            /*     INPUT (Data,Var,Abs,Vol)   */  
  0x09, 0x05,            /*     USAGE (USER Button)        */
  0x75, 0x01,            /*     REPORT_SIZE (1)            */
  0xb1, 0x82,            /*     FEATURE (Data,Var,Abs,Vol) */
  0x75, 0x07,            /*     REPORT_SIZE (7)            */
  0x81, 0x83,            /*     INPUT (Cnst,Var,Abs,Vol)   */
  0x85, 0x05,            /*     REPORT_ID (5)              */
  0x75, 0x07,            /*     REPORT_SIZE (7)            */
  0xb1, 0x83,            /*     FEATURE (Cnst,Var,Abs,Vol) */
  /* 114 */
  /* ADC IN */
  0x85, 0x06,            /*     REPORT_ID (6)              */
  0x09, 0x07,            /*     USAGE (ADC IN)             */
  0x15, 0x00,            /*     LOGICAL_MINIMUM (0)        */
  0x26, 0xff, 0x00,      /*     LOGICAL_MAXIMUM (255)      */
  0x75, 0x08,            /*     REPORT_SIZE (8)            */
  0x81, 0x82,            /*     INPUT (Data,Var,Abs,Vol)   */
  0x85, 0x06,            /*     REPORT_ID (6)              */
  0x09, 0x06,            /*     USAGE (ADC in)             */
  0xb1, 0x82,            /*     FEATURE (Data,Var,Abs,Vol) */
  /* 133 */
  0xc0            /*     END_COLLECTION              */
}; /* CustomHID_ReportDescriptor */


Jak z popisu reportu vidíte, tak každý prvek, který chcete ovládat, musí mít příslušný popis v reportu. Existuje pochopitelně i jiné řešení - napsat obecný prvek, který bude mít třeba jen jeden report s 30-ti bajtovou délkou a obsah paketu si nadefinujete sami. Nicméně, pokud jsem chtěl využít USB HID Demonstrator, tak jsem se musel držet norem, které vyžadují report, jak je uveden výše ve výpisu.

Závěr

Jsem se s tímhle programem dost nadřel. A jediný důvod byla moje blbost, kdy jsem přehlédl že v definici descriptoru zařízení jsem nechal nesprávnou délku descriptoru, což pochopitelně vedlo k pádu enumerace v jeho polovině. Ach jo. No nakonec jsem to po dvou dnech utírání zpoceného čela objevil. Doufám tedy že aspoň malou odměnou bude to, že tohle je doufám první realizace USB HID Custom Page pro STM32F4 Discovery kit na světě.

Příště si ukážeme jak pomocí Visual Studia v C# napsat něco podobného jako ten USB HID Demonstrator od ST.

Pokud se domníváte, že si text a přiložený demoprogram zaslouží Vaše ocenění, tak můžete poslat svou dotaci. Předem děkuji všem! Podpoříte tím vznik dalších článků.






Odkazy

Materiál k HID naleznete zde.
USB HID Demonstrator si stáhnete s webu ST zde.

Zazipovaný projekt k tomuto dílu naleznete zde.

Začínáme s STM32F4 kitem 1. odkaz (vývojové prostředí, atd.)
Začínáme s STM32F4 kitem 2. odkaz (USB Úvod)
Začínáme s STM32F4 kitem 3. odkaz (USB Enumerace)
Začínáme s STM32F4 kitem 4. odkaz (USB Virtual COM port)
Modifikce USB HID pro STM32F103 odkaz
Začínáme s STM32F4 kitem 5. odkaz (označení portů, alternativní funkce)
Začínáme s STM32F4 kitem 6. odkaz (VGA monitor)

Dodatek

Ještě jsem do textu zapomněl napsat že ta hodnota z A/D převodníku se z kitu do PC odesílá při přerušení, které vyvolá ADC při dokončení převodu. Takže to najdete mezi handlery pro přerušení.