STM32Cube. USB Custom HID. Прием и передача данных.

Вот и подоспела вторая часть из цикла статей, посвященных реализации класса USB Custom HID на микроконтроллерах STM32 при помощи замечательной утилиты STM32Cube =) Как и обещал в конце предыдущей статьи, сегодня мы займемся непосредственно обменом данными между хостом и нашим устройством.

Первая часть — ссылка.

USB прием и передача данных

Начнем, пожалуй, с передачи данных хосту ) Тут все просто — для передачи в 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 мы напишем свою собственную программу для хоста (ПК), которая и будет отправлять данные нашему устройству, а на сегодня на этом заканчиваем, до встречи в новых статьях!

Понравилась статья? Поделись с друзьями!

STM32Cube. USB Custom HID. Прием и передача данных.: 47 комментариев
  1. Здесь, пожалуй, самое простое и понятное описание как работать с HID. До этого перерыл весь интернет. Но ваши статьи больше всего пригодились.

  2. При считывании dataToReceive в dataToReceive[0] записывается ID репорта. Так что надо не с нулевого элемента данные смотреть, а с первого.

  3. Интересен такой вопрос. Можно ли пин перевести из Output состояния в Input и какой вообще функцией смотреть содержимое Input-пинов. Подскажите, если кто знает, пожалуйста! Может в Cube об этом уже позаботились и есть простой набор функций?

  4. Ага, спасибо)
    Но больший интерес вызывает вопрос с изменением инпут на аутпут и наоборот. Например: в Кубе задал пинам аутпут изначально, после компиляции кубом, где-то в полученных сорсах появляется описание пинов и там будет присвоен их Mode -> Output.. Вопрос: если мне теперь надо их читать. Как поменять их мод на инпут. Просто этой структуре, в которой они описаны, присвоить Mode =Input? И если знаете, можете сказать где создается структура пинов и присваивается Mode.

  5. Похоже, что менять можно просто создав две функции инициализации, одну для инпут, другую для аутпут одних и тех же пинов. Вроде как ни на что не ругается, завтра проверю как оно на практике.
    К слову сурсы инициализации пинов и всего такого Cube пихает прямо в main.c

  6. Еще один нюанс по поводу структуры буфера.
    При передаче данных хосту в новой версии куба надо так же первым байтом передавать 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)

  7. передача на пк прошла успешно, правда промониторить получилось только Simple USB Logger , а вот чем отправить на контроллер команды??
    а так в целом оба урока похожи на статью https://habrahabr.ru/post/208026/ , только в урезанном варианте (если я не ошибаюсь)

  8. Добрый день! Как можно посмотреть на ПК принятые байты с HID? Есть ли такие возможности в Windoцs? Посоветуйте готовый софт?

  9. Спасибо, очень хорошая и полезная статья.

    У меня только 2 вопросика

    1 USBD_CUSTOM_HID_SendReport(hUsbDevice_0, dataToSend, 4); всегда возвращает usbok, даже когда мк не подключен к компьютеру. это нормально?

    2 при однократной отправки данных в мк все нормально, но если Далее ещё раз отправить данные то в QT пишет no sened data. у меня по таймеру уходит массив, затем второй но он уже похоже не доходит, видимо я что-то не так делаю?

    • Немного переделали видимо в новых версиях 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

        • Можно попробовать USBLyzer поставить — тогда там можно будет увидеть, отсылаются ли реально данные в линию.

          • еще непонятно с приемом данных, у Вас
            USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef*)hUsbDevice_0->pClassData;
            for (uint8_t i = 0; i Report_buf[i];
            }

            но в новом кубе hUsbDeviceFS описан не как указатель, а видимо как объект, не пойму как исправить эту строку..

          • Да, Спасибо, мог бы сам догадаться 🙂 Да мозги уже кипят.. У Вас написано:
            #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 в обе стороны.

    • А Вы нашли ответ на этот вопрос? А то я только на это наткнулся и пока идей нет 🙁

  10. Уважаемый автор, подскажите где брали информацию по работе с Custom HID при использовании CubeMX (какие функции дописывать, где лежит дескриптор CUSTOM_HID_ReportDesc_FS и т.д.)?
    На сайте STMicroelectronics я не смог найти ответов. Нашел мануал на микроконтроллер (у меня STM32F3Discovery), но там только описание регистров.

      • На компьютере отслеживаю данные приходящие с МК в USBLyzer. С контроллера на комп всё приходит, но как теперь отправить данные на МК?
        Есть готовые программы?

        Пробовал USB HID Demonstrator Release 1.0.2 от STM. Устройство определяет но не получается изменить длину Output report и ничего не приходит на МК.

  11. Здравствуйте!
    Вопрос не про 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 ничего не записывается или записываются нули, если повторить. то все отрабатывает как надо.

  12. День добрый. Отличная статья! Подскажите, пожалуйста, что возвращает функция uint8_t USBD_CUSTOM_HID_SendReport(…, …, …) при передаче? Кол-во переданных байт или код ошибки. Заранее спасибо!

    • Добрый день!
      Возвращает статус операции, возможные значения:
      typedef enum {
      USBD_OK = 0,
      USBD_BUSY,
      USBD_FAIL,
      }USBD_StatusTypeDef;

      Это применимо к той версии библиотек, которая в статье использовалась, могли поменять в текущей версии.

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

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