Вот и подоспела вторая часть из цикла статей, посвященных реализации класса USB Custom HID на микроконтроллерах STM32 при помощи утилиты STM32CubeMx. Как и обещал в конце предыдущей статьи, сегодня мы займемся непосредственно обменом данными между хостом и нашим устройством.
Начнем, пожалуй, с передачи данных хосту. Тут все просто - для передачи в HAL реализована специальная функция:
uint8_t USBD_CUSTOM_HID_SendReport(USBD_HandleTypeDef *pdev, uint8_t *report, uint16_t len)
Аргументы у функции типичные для любой передающей функции - указатель на передаваемые данные и количество байт, которые нужно отправить. Кроме того, мы должны передать в функцию объект USBD_HandleTypeDef. Эта переменная у нас определена в файле usnd_custom_hid_if:
USBD_HandleTypeDef *hUsbDevice_0;
Поскольку передачу мы будем производить из функции main(), то давайте сразу же объявим в файле main.c:
extern USBD_HandleTypeDef *hUsbDevice_0;
Передавать мы будем (как решили в предыдущей статье) по 4 байта, поэтому заодно объявим и массив для хранения передаваемых данных:
uint8_t dataToSend[4];
В принципе с приготовлениями на этом все, теперь просто вызываем функцию отправки, ну и не забываем заполнить массив данных какими-нибудь тестовыми значениями:
/* USER CODE BEGIN 2 */ dataToSend[0] = 'S'; dataToSend[1] = 'n'; dataToSend[2] = 'd'; dataToSend[3] = '\0'; /* USER CODE END 2 */ /* USER CODE BEGIN 3 */ /* Infinite loop */ while (1) { HAL_Delay(1000); USBD_CUSTOM_HID_SendReport(hUsbDevice_0, dataToSend, 4); } /* USER CODE END 3 */
Теперь наше устройство будет отправлять данные хосту раз в секунду. Как видите, с передачей все оказалось совсем несложно.
На самом деле, с приемом тоже нет никаких трудностей и если его организация и сложнее, то ненамного. Итак, предлагаю реализовать следующее - пусть хост у нас отправляет два разных пакета, к примеру строки "Rcv" и "Snd". Мы же будем в зависимости от принятого пакета гасить или зажигать светодиод (на моей плате я использую PC6). Вот в общем-то и все, переходим к реализации...
Объявим в файле usbd_custom_hid_if.c буфер для сохранения принятых данных:
/* USER CODE BEGIN 2 */ uint8_t dataToReceive[4]; /* USER CODE END 2 */
А теперь идем в функцию CUSTOM_HID_OutEvent_FS(), тут то мы и будем принимать и анализировать данные:
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*)hUsbDevice_0->pClassData; for (uint8_t i = 0; i < 4; i++) { dataToReceive[i] = hhid->Report_buf[i]; } if ((dataToReceive[0] == 'R') && (dataToReceive[1] == 'c') && (dataToReceive[2] == 'v')) { HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, GPIO_PIN_SET); } if ((dataToReceive[0] == 'S') && (dataToReceive[1] == 'n') && (dataToReceive[2] == 'd')) { HAL_GPIO_WritePin(GPIOC, GPIO_PIN_6, GPIO_PIN_RESET); } return(0); /* USER CODE END 6 */ }
Собственно, тут нет ничего фантастического и непонятного - все принятые данные сохраняются в наш объект для работы с USB (hUsbDevice_0). Мы просто копируем их оттуда и сохраняем в массив. Ну а после просто анализируем данные и в зависимости от того, какой пакет принят, меняем состояние вывода, к которому подключен светодиод. Вот и все!
Статья получилась небольшая, но все аспекты, которые я хотел описать, я описал ) В следующей статье, посвященной USB Custom HID мы напишем свою собственную программу для хоста (ПК), которая и будет отправлять данные нашему устройству, а на сегодня на этом заканчиваем, до встречи в новых статьях 🤝
Следующая статья по Custom HID - USB Custom HID.
Спасибо! Очень ценно и полезно разобраться в этом! Ждём QT!!!
Уважаемый автор, очень ждём продолжения :))))
Готово =)
Здесь, пожалуй, самое простое и понятное описание как работать с HID. До этого перерыл весь интернет. Но ваши статьи больше всего пригодились.
Спасибо, рад что понравилось!
При считывании dataToReceive в dataToReceive[0] записывается ID репорта. Так что надо не с нулевого элемента данные смотреть, а с первого.
Странно, у меня ID в данных не было..Может разные версии HAL.
Интересен такой вопрос. Можно ли пин перевести из Output состояния в Input и какой вообще функцией смотреть содержимое Input-пинов. Подскажите, если кто знает, пожалуйста! Может в Cube об этом уже позаботились и есть простой набор функций?
Может я что-то не так понял, но HAL_GPIO_ReadPin же
Ага, спасибо)
Но больший интерес вызывает вопрос с изменением инпут на аутпут и наоборот. Например: в Кубе задал пинам аутпут изначально, после компиляции кубом, где-то в полученных сорсах появляется описание пинов и там будет присвоен их Mode -> Output.. Вопрос: если мне теперь надо их читать. Как поменять их мод на инпут. Просто этой структуре, в которой они описаны, присвоить Mode =Input? И если знаете, можете сказать где создается структура пинов и присваивается Mode.
Там наверняка есть функций GPIO_DeInit, затем заполняешь поля структуры и вызываешь Init.
Похоже, что менять можно просто создав две функции инициализации, одну для инпут, другую для аутпут одних и тех же пинов. Вроде как ни на что не ругается, завтра проверю как оно на практике.
К слову сурсы инициализации пинов и всего такого Cube пихает прямо в main.c
Но на всякий случай лучше вызвать функцию DeInit предварительно.
Ну да, в функцию GPIO_Init()
Еще один нюанс по поводу структуры буфера.
При передаче данных хосту в новой версии куба надо так же первым байтом передавать ID репорта!
Buffer[0]=0x03; // номер репорта - 3
Buffer[1] = d; // передаваемый байт
USBD_CUSTOM_HID_SendReport(hUsbDevice_0, Buffer, 2);
Так же будьте бдительны - при тех настройках дескриптора репорта, что выложены на сайте, размер передаваемых данных ограничен 1. Т.е. в моем примере d будет или 0, или 1. Чтобы передавать данные больше 1, надо дескриптор дополнить LOGICAL_MINIMUM и LOGICAL_MAXIMUM
0x85, 0x03, // REPORT_ID (4)
0x09, 0x03, // USAGE (Vendor Usage 4)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x01, // REPORT_COUNT (1)
0x81, 0x82, // INPUT (Data,Var,Abs,Vol)
передача на пк прошла успешно, правда промониторить получилось только Simple USB Logger , а вот чем отправить на контроллер команды??
а так в целом оба урока похожи на статью https://habrahabr.ru/post/208026/ , только в урезанном варианте (если я не ошибаюсь)
Добрый день! Как можно посмотреть на ПК принятые байты с HID? Есть ли такие возможности в Windoцs? Посоветуйте готовый софт?
Спасибо, очень хорошая и полезная статья.
У меня только 2 вопросика
1 USBD_CUSTOM_HID_SendReport(hUsbDevice_0, dataToSend, 4); всегда возвращает usbok, даже когда мк не подключен к компьютеру. это нормально?
2 при однократной отправки данных в мк все нормально, но если Далее ещё раз отправить данные то в QT пишет no sened data. у меня по таймеру уходит массив, затем второй но он уже похоже не доходит, видимо я что-то не так делаю?
У меня нет такой строки в проекте
USBD_HandleTypeDef *hUsbDevice_0;
Немного переделали видимо в новых версиях Cube - так периодичсеки происходит, что появляются небольшие изменения.
Не подскажите как теперь с этим быть, с тем что этого больше нет: USBD_HandleTypeDef *hUsbDevice_0;
возможно надо исправить на USBD_HandleTypeDef *hUsbDevice_FS;
или я не прав?
Ну да, я точно не помню на что именно менять, но там по минимуму - в паре мест надо изменить и все будет работать.
Почините плз. статью, а то новый куб с ней не совместим и начинающим не понять что с этим делать. 🙂
Я не справился.. 🙁
или выложите проект сделанный старым кубом, чтоб по аналогии можно было понять что не так в новом.
В новой версии определено так:
extern USBD_HandleTypeDef hUsbDeviceFS?
Если да, то везде надо hUsbDevice_0 поменять на hUsbDeviceFS. Можете выслать на почту проект - я могу сам там поправить, а то у меня нет под HID просто готового проекта на новой версии.
Вот мой проект, я пытался сделать как Вы говорите, но оно не отправляет данные 🙁
https://cloud.mail.ru/public/HWWF/Cg9Byb45W
Устройство определяется, но никакого обмена данными нет, заранее Спасибо.
Ваш Email не нашел.
Мой p_g_g@mail.ru
В проекте все правильно сделано вроде бы. А чем проверяете отправку?
Вот наваял проект на C# на базе LibUsbDotNet, но явно что то не то. Устройство видится, открывается, но из него ничего не читается.
https://cloud.mail.ru/public/LEvn/Z4XUpeFNV
Можно попробовать USBLyzer поставить - тогда там можно будет увидеть, отсылаются ли реально данные в линию.
еще непонятно с приемом данных, у Вас
USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef*)hUsbDevice_0->pClassData;
for (uint8_t i = 0; i Report_buf[i];
}
но в новом кубе hUsbDeviceFS описан не как указатель, а видимо как объект, не пойму как исправить эту строку..
По идее так должно быть:
USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef*)hUsbDevice_FS.pClassData;
Да, Спасибо, мог бы сам догадаться 🙂 Да мозги уже кипят.. У Вас написано:
#define CUSTOM_HID_EPIN_ADDR 0x81
#define CUSTOM_HID_EPIN_SIZE 4
#define CUSTOM_HID_EPOUT_ADDR 0x01
#define CUSTOM_HID_EPOUT_SIZE 4
но ведь в этом случае у нас на вход и на выход будет EndPoint c номером 1, а енд поинта номер 2 не будет вообще, это правильно?
Вот проект, имхо все правильно, но оно не работает 🙁
Может что скажете..
https://cloud.mail.ru/public/GZN2/P5XZaMWza
Нашел врага, это со стороны компа не работало 🙁
Спасибо за помощь!
Рад, что заработало! )
Возникла проблема, возможно и другим будет интересно. При непрерывном обмене данными с хостом, каждые 5 секунд пропадает одна трансакция со стороны девайса. Причем этот эффект иногда пропадает при перевключении USB кабеля в комп, пропадает до следующего перевключения. В этом случае общая скорость передачи данных увеличивается почти в 2 раза. Дело не в таймауте со стороны приемника... Любопытен факт, что если убрать из описания репорта строку: 0x85, 0x01, // REPORT_ID (1) то начинает теряться одна трансакция в 1 секунду. Может что посоветуете? Обмен идет на максимальной длине репорта 0х40 в обе стороны.
А Вы нашли ответ на этот вопрос? А то я только на это наткнулся и пока идей нет 🙁
Уважаемый автор, подскажите где брали информацию по работе с Custom HID при использовании CubeMX (какие функции дописывать, где лежит дескриптор CUSTOM_HID_ReportDesc_FS и т.д.)?
На сайте STMicroelectronics я не смог найти ответов. Нашел мануал на микроконтроллер (у меня STM32F3Discovery), но там только описание регистров.
Все по коду библиотек в основном.
На компьютере отслеживаю данные приходящие с МК в USBLyzer. С контроллера на комп всё приходит, но как теперь отправить данные на МК?
Есть готовые программы?
Пробовал USB HID Demonstrator Release 1.0.2 от STM. Устройство определяет но не получается изменить длину Output report и ничего не приходит на МК.
Готовых я не видел - свою делал для тестов.
Можно ссылку на обещанную программу для ПК ?
https://microtechnics.ru/qt-usb-custom-hid-biblioteka-libusb/
Здравствуйте!
Вопрос не про HID, но все же задам здесь.
Пытаюсь сделать устройство, которое определялось бы как usb принтер, дабы использовать его в качестве переходника для печати на китайских термопринтерах (для одного импортного прибора).
Создал в кубе Custom HID, переопределил дескрипторы в соответствии с http://www.usb.org/developers/docs/devclass_docs/usbprint11a021811.pdf
VID\PID взял из https://www.xmos.com/download/private/AN00126%3A-USB-Printer-Device-Class%282.0.2rc1%29.pdf
Теперь в диспетчере устройств оно определяется как "поддержка USB принтера" и драйвер устанавливается даже.
К сожалению не смог разобраться как собственно принимать данные от компа, какую функцию использовать и как.
Возможно вы сможете что-то посоветовать?
Немного разобрался.
В файле usbd_customhid.c изменил функцию USBD_CUSTOM_HID_DataOut :
static uint8_t USBD_CUSTOM_HID_DataOut (USBD_HandleTypeDef *pdev,
uint8_t epnum)
{
// USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef*)pdev->pClassData;
// ((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->OutEvent(hhid->Report_buf[0],
// hhid->Report_buf[1]);
USBD_LL_PrepareReceive(pdev, CUSTOM_HID_EPOUT_ADDR , buff,
USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);
for (int i=0; i<USBD_CUSTOMHID_OUTREPORT_BUF_SIZE; i++)
{
if (buff[i]!=0)
{
send_to_uart1(buff[i]);
}
}
Теперь при печати файла с компьютера (или прибора) данные принимаются и пересылаются в serial принтер, но только не первый раз после прошивки или включения.В чем может быть еще дело?
Добрый день!
То есть прошивка начинает работать после ресета?
Нет, после ресета прошивка работает.
При печати в первый раз попадаем в функцию USBD_CUSTOM_HID_DataOut (где стоит точка остановки), но в buff ничего не записывается или записываются нули, если повторить. то все отрабатывает как надо.
День добрый. Отличная статья! Подскажите, пожалуйста, что возвращает функция uint8_t USBD_CUSTOM_HID_SendReport(..., ..., ...) при передаче? Кол-во переданных байт или код ошибки. Заранее спасибо!
Добрый день!
Возвращает статус операции, возможные значения:
typedef enum {
USBD_OK = 0,
USBD_BUSY,
USBD_FAIL,
}USBD_StatusTypeDef;
Это применимо к той версии библиотек, которая в статье использовалась, могли поменять в текущей версии.
Не нашёл третьей части Вашей статьи, там где программа для ПК.
Дайте ссылку пожалуйста.
Добрый день! Вот - https://microtechnics.ru/qt-usb-custom-hid-biblioteka-libusb/