Урок 35
HAL. USB. Custom HID
Продолжаем работать с шиной USB и также продолжаем работать с классом USB HID.
Только сегодня мы уже будем пытаться поработать с классом нестандартным и написать какой-то свой класс HID.
Проект создаём из проекта TEST002. Назовем его USB_DEVICE_CUSTOM_HID. Запустим проект в Cube, ничего не отключаем в т.ч. и таймеры
Включим USB_OTG_FS в режим Device_Only.
В USB Device выбираем Custom Human Interface Device Class (HID)
В Clock Configuration выберем следующие делители (нажмите на картинку для увеличения размера)
В Configuration таймер сделаем немного помедленнее.
Сгенерируем и запустим проект.
Соберем проект и настроим программатор на авторезет.
Прошьем контроллер и увидим, что устройство установилось с ошибкой (код 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), установим его и запустим. Увидим, что наше самодельное устройство там нашлось и поддерживается. Это уже хорошо.
Настроим утилиту следующим образом
Добавим подключение файла с функциями в 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
Смотреть ВИДЕОУРОК (нажмите на картинку)
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?
Добрый день.
Скажите, мне кажется Вы не подключили обработчик прерывания (callback) для таймера 6 (tim6).
Вот это место:
HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim6)
не нашёл, где оно привязывается к таймеру 6.
P.S.
Очень хорошая статья.
Заработало сразу.
Большое спасибо.
А, не посмотрел, не из пустого проекта создаём.
Большое спасибо.
Скажите, пожалуйста, а своё GUI на C# возможно самому написать?
Скачал исходники USB HID Demonstrator, но они на С++ (я его хорошо знаю, но С++ от микрософта это та ещё вещь, особенно если графическая).
Более, того при сборке под вижал студию 2015 постоянно пишет — «не та версия windows».
Пробовал и на 7-ке и на 10-ке.
Мой проект осциллографа (GUI) уже начат на c#, не хочется смешивать языки.
Большое спасибо.
Наверно, можно. Я не пишу на C#. Я, наоборот, пользуюсь C++, он как-то ближе к железу и к функциям WinAPI. Можно конечно достучаться до WinAPI и в C#, только кода многовато получится. Зато у данного языка свои плюсы. Он мусор сам за собой чистит.
Большое спасибо на уроки!
Скажите пожалуйста, никак не могу написать свой ГУИ для управления светодиодами и опроса кнопок.
Юзаю libUSB для C# (могу перейти на с++)
Какие ID должны быть в посылках со стороны ПК на МК ?
Хотя бы на С++ для начала…
Сложно понять-разобрать хид дескриптор…
на stm32f070k6t6 не взлетает. Сбой запроса дескриптора. Все дескрипторы (и устройства, и конфигурации и репортов) сверил с вашими исходниками — один в один.
Размер репорта изменил согласно статье.
Нога DP подтянута к 3.3В внутри самого кристала (пробовал добавлять 1.5КОм к 3.3В — не изменилось)
Провод прозвонил, все жилы целые.
stm32f070f6p6