STM Урок 35. HAL. USB. Custom HID

 

 

 

Урок 35

HAL. USB. Custom HID

 

 

Продолжаем работать с шиной USB и также продолжаем работать с классом USB HID.

Только сегодня мы уже будем пытаться поработать с классом нестандартным и написать какой-то свой класс HID.

Проект создаём из проекта TEST002. Назовем его USB_DEVICE_CUSTOM_HID. Запустим проект в Cube, ничего не отключаем в т.ч. и таймеры

Включим USB_OTG_FS в режим Device_Only.

 

image00

 

В USB Device выбираем Custom Human Interface Device Class (HID)

 

image02

 

В Clock Configuration выберем следующие делители (нажмите на картинку для увеличения размера)

 

image01_0500

 

В Configuration таймер сделаем немного помедленнее.

 

image04

 

Сгенерируем и запустим проект.

Соберем проект и настроим программатор на авторезет.

Прошьем контроллер и увидим, что устройство установилось с ошибкой (код 10).

Прежде чем писать дескрипторы, давайте повторим, каких видов они бывают

Посмотрим здесь http://www.keil.com/pack/doc/mw/usb/html/index.html

Откроем на страничке слева USB Concepts->USB Descriptors

Посмотрим дескрипторы по порядку.

Также скачаем удобную утилиту для формирования дескриптора репорта HID Descriptor Tool на сайте http://www.usb.org/developers/hidpage/

Запустим ее и посмотрим ее способности

В файле usbd_customhid.c посмотрим дескрипторы интерфейса, конфигурации и др.

В файле usbd_custom_hid_if.c мы видим пустой дескриптор репорта.

 

 __ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =

{

  /* USER CODE BEGIN 0 */

  0x00,

  /* USER CODE END 0 */

  0xC0    /*     END_COLLECTION                     */

 

Напишем дескриптор репорта от нашего устройства.

 

  /* USER CODE BEGIN 0 */

0x06, 0x00, 0xff,              // USAGE_PAGE (Generic Desktop)

    0x09, 0x01,                    // USAGE (Vendor Usage 1)

    0xa1, 0x01,                    // COLLECTION (Application)

    0x85, 0x01,                    //   REPORT_ID (1)

    0x09, 0x01,                    //   USAGE (Vendor Usage 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 (Vendor Usage 1)

    0x91, 0x82,                    //   OUTPUT (Data,Var,Abs,Vol)

 

    0x85, 0x02,                    //   REPORT_ID (2)

    0x09, 0x02,                    //   USAGE (Vendor Usage 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 (Vendor Usage 2)

    0x91, 0x82,                    //   OUTPUT (Data,Var,Abs,Vol)

 

    0x85, 0x03,                    //   REPORT_ID (3)

    0x09, 0x03,                    //   USAGE (Vendor Usage 3)

    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)

    0x25, 0x01,                                      //   LOGICAL_MAXIMUM (255)

    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 (Vendor Usage 3)

    0x91, 0x82,                    //   OUTPUT (Data,Var,Abs,Vol)

 

    0x85, 0x04,                    //   REPORT_ID (4)

    0x09, 0x04,                    //   USAGE (Vendor Usage 4)

    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)

    0x25, 0x01,                                      //   LOGICAL_MAXIMUM (255)

    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 (Vendor Usage 4)

    0x91, 0x82,                    //   OUTPUT (Data,Var,Abs,Vol)

 

    0x85, 0x05,                    //   REPORT_ID (5)

    0x09, 0x05,                    //   USAGE (Vendor Usage 5)

    0x75, 0x08,                    //   REPORT_SIZE (8)

    0x95, 0x04,                    //   REPORT_COUNT (4)

    0x81, 0x82,                    //   INPUT (Data,Var,Abs,Vol)

 

    0x85, 0x06,                    //   REPORT_ID (6)

    0x09, 0x06,                    //   USAGE (Vendor Usage 6)

    0x75, 0x08,                    //   REPORT_SIZE (8)

    0x95, 0x04,                    //   REPORT_COUNT (4)

    0x81, 0x82,                    //   INPUT (Data,Var,Abs,Vol)

  /* USER CODE END 0 */

  0xC0    /*     END_COLLECTION                     */

 

После этого мы видим ошибку в компиляции, потому что нужно изменить размер нашего дескриптора. Изменим его в Cube. Закроем проект и в Configuration USB DEVICE внесем 108.

Опять сгенерируем код, откроем проект, соберем его и прошьем контроллер.

Как мы видим, ошибка (код 10) в диспетчере устройств исчезла.

Также скачаем утилитку USB HID Demonstrator с сайта st.com для удобного просмотра сообщений от девайза, введя в поисковой строке данную строку (USB HID Demonstrator), установим его и запустим. Увидим, что наше самодельное устройство там нашлось и поддерживается. Это уже хорошо.

Настроим утилиту следующим образом

 

image03

 

 

Добавим подключение файла с функциями в main.c

 

/* USER CODE BEGIN Includes */

#include "main.h"

#include "usbd_customhid.h"

Еще добавим переменные

/* USER CODE BEGIN PV */

/* Private variables ———————————————————*/

extern USBD_HandleTypeDef  hUsbDeviceFS;

uint8_t dataToSend[5];

uint8_t btn_stat=0;

Запустим таймер

  /* USER CODE BEGIN 2 */

        HAL_TIM_Base_Start_IT(&htim6);

  /* USER CODE END 2 */

 

Добавим функцию-обработчик прерываний от таймера.

 

/* USER CODE BEGIN 4 */

void HAL_TIM_PeriodElapsedCallback (TIM_HandleTypeDef *htim6)

{

        if(btn_stat==0)        {dataToSend[0] = 6;dataToSend[1] = 1;}

        else if(btn_stat==1)        {dataToSend[0] = 6;dataToSend[1] = 0;}

        else if(btn_stat==2)        {dataToSend[0] = 5;dataToSend[1] = 1;}

        else if(btn_stat==3)        {dataToSend[0] = 5;dataToSend[1] = 0;}

        dataToSend[2] = 'H';

  dataToSend[3] = 'I';

        dataToSend[4] = 'D';

  USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, dataToSend, 5);

        btn_stat++;

        if(btn_stat>3) btn_stat=0;

}

 

Уберем всё из бесконечного цикла

 

  /* USER CODE BEGIN 3 */

                if(HAL_GPIO_ReadPin (GPIOA, GPIO_PIN_0)==GPIO_PIN_SET)

                {

                        HAL_TIM_Base_Start(&htim6);

                        HAL_TIM_Base_Start_IT(&htim6);

                }

                else

                {

                        tim6_counter=0;

                        HAL_TIM_Base_Stop(&htim6);

                        HAL_TIM_Base_Stop_IT(&htim6);

                        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET);

                }

  }

  /* USER CODE END 3 */

 

Также уберем все из файла

 

void TIM6_DAC_IRQHandler(void)

{

  /* USER CODE BEGIN TIM6_DAC_IRQn 0 */

 

  /* USER CODE END TIM6_DAC_IRQn 0 */

  HAL_TIM_IRQHandler(&htim6);

  /* USER CODE BEGIN TIM6_DAC_IRQn 1 */

        switch(tim6_counter)

        {

                case 0:

                        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET);

                        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);

                        break;

                case 1:

                        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_RESET);

                        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_SET);

                        break;

                case 2:

                        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_RESET);

                        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_SET);

                        break;

                case 3:

                        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_RESET);

                        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_15, GPIO_PIN_SET);

                        break;

        }

        if(tim6_counter<3) tim6_counter++;

        else tim6_counter=0;

  /* USER CODE END TIM6_DAC_IRQn 1 */

}

 

Также уберем оттуда вот эту переменную

 

/* USER CODE BEGIN 0 */

extern uint8_t tim6_counter;

/* USER CODE END 0 */

 

Соберем код. Прошьем контроллер и проверим с помощью утилиты.

В файле usbd_custom_hid_if.c добавим буфер

 

/* USER CODE BEGIN PRIVATE_VARIABLES */

uint8_t dataToReceive[2];

/* USER CODE END PRIVATE_VARIABLES */

 

Также внесем изменения в функции

 

static int8_t CUSTOM_HID_OutEvent_FS  (uint8_t event_idx, uint8_t state)

{

  /* USER CODE BEGIN 6 */

  USBD_CUSTOM_HID_HandleTypeDef     *hhid = (USBD_CUSTOM_HID_HandleTypeDef*)hUsbDeviceFS.pClassData;

  for (uint8_t i = 0; i < 4; i++)

  {

    dataToReceive[i] = hhid->Report_buf[i];

  }        

        if ((dataToReceive[0]==1)&&(dataToReceive[1]==1))

  {

    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);

  }

        if ((dataToReceive[0]==1)&&(dataToReceive[1]==0))

  {

    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_RESET);

  }

        if ((dataToReceive[0]==2)&&(dataToReceive[1]==1))

  {

    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_SET);

  }

        if ((dataToReceive[0]==2)&&(dataToReceive[1]==0))

  {

    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_RESET);

  }

        if ((dataToReceive[0]==3)&&(dataToReceive[1]==1))

  {

    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_SET);

  }

        if ((dataToReceive[0]==3)&&(dataToReceive[1]==0))

  {

    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_RESET);

  }

        if ((dataToReceive[0]==4)&&(dataToReceive[1]==1))

  {

    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_15, GPIO_PIN_SET);

  }

        if ((dataToReceive[0]==4)&&(dataToReceive[1]==0))

  {

    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_15, GPIO_PIN_RESET);

  }

  return (0);

  /* USER CODE END 6 */

}

 

Проверим на практике с помощью утилиты.

 

Предыдущий урок Программирование МК STM32 Следующий урок

 

Исходный код

 

 

Купить отладочную плату можно здесь STM32F4-DISCOVERY

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM32 HAL. USB. HID

5 комментариев на “STM Урок 35. HAL. USB. Custom HID
  1. Desa:

    I wrote time callback into while to make an endless loop. I gave 5 ms between the sender. But I can see that the message comes at 100 ms in hid terminal.What is the real speed of HID?How long should I wait between 2 Custom Hıd Messages?

  2. Владимир:

    Добрый день.
    Скажите, мне кажется Вы не подключили обработчик прерывания (callback) для таймера 6 (tim6).

    Вот это место:

    HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim6)

    не нашёл, где оно привязывается к таймеру 6.

    P.S.

    Очень хорошая статья.
    Заработало сразу.
    Большое спасибо.

  3. Владимир:

    А, не посмотрел, не из пустого проекта создаём.
    Большое спасибо.

    Скажите, пожалуйста, а своё GUI на C# возможно самому написать?
    Скачал исходники USB HID Demonstrator, но они на С++ (я его хорошо знаю, но С++ от микрософта это та ещё вещь, особенно если графическая).

    Более, того при сборке под вижал студию 2015 постоянно пишет — «не та версия windows».
    Пробовал и на 7-ке и на 10-ке.

    Мой проект осциллографа (GUI) уже начат на c#, не хочется смешивать языки.
    Большое спасибо.

    • Наверно, можно. Я не пишу на C#. Я, наоборот, пользуюсь C++, он как-то ближе к железу и к функциям WinAPI. Можно конечно достучаться до WinAPI и в C#, только кода многовато получится. Зато у данного языка свои плюсы. Он мусор сам за собой чистит.

  4. Владимир:

    Большое спасибо на уроки!
    Скажите пожалуйста, никак не могу написать свой ГУИ для управления светодиодами и опроса кнопок.
    Юзаю libUSB для C# (могу перейти на с++)

    Какие ID должны быть в посылках со стороны ПК на МК ?

    Хотя бы на С++ для начала…

    Сложно понять-разобрать хид дескриптор…

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*