Ряд микроконтроллеров STM32 имеют на борту USB интерфейс для связи с компьютерами. Как правило, удобнее всего использовать предоставляемый компаний ST Microelectronics драйвер класса CDC (Communication Device Class ). Он позволяет использовать на стороне компьютера UART через USB и не требует установки драйверов. Со стороны STM32 при этом требуется только поменять операции вывода данных, остальное делается самостоятельно. Причём скорость такого соединения может быть практически любой, поддерживаемой компьютером.
Однако ряд разработок, особенно, когда приходишь в другую компанию, где используется HID Class (Human Interface Device), в случае разработки новой версии устройства требуется поддерживать ранее выбранный интерфейс. Что, собственно, и случилось. Примеры проектов от самой ST, которые они дают при загрузке STM32 Cube MX и IDE, как обычно, дали только минимальное понимание, но не раскрыли, что и как надо делать. Я когда-то разбирался с USB, даже писал собственный драйвер, но это было так давно… Остались только общие воспоминания. Посему пришлось искать дополнительную информацию, чтобы получить стартовую точку.
Первое найденное было видеороликом на youtube в стиле HID за 5 минут 🙂 Автор даёт доступ к своему коду на GitHub. Всё, типа круто, красиво, просто вставляйте к себе и всё будет чудесно. Судя по отзывам под роликом, некоторым этого хватило. Изучив исходники понял, что минимального прозрения не наступило, да и уровень полученной информации мал для того, чтобы решить поставленную задачу. Но закомство с этим материалом было явно полезным. Решение вопроса с использованием кубика (STM32Cube MX) мне лично импонирует больше, чем другие подходы, поскольку позволяет отвлечься от ряда низкоуровневых операций и генерация проекта всегда происходит в одном стиле. Соответственно, изучение этого примера показало, на какие файлы надо обратить внимание, где и что надо поменять или добавить, какие функции использовать для получения и отправки данных именно для нашей выбранной среды программирования.
Следующий поиск оказался весьма удачным. Хабр — известный сайт, на котором можно найти много полезного по разной электронной тематике. Нашлась там и статья STM32 и USB-HID — это просто. Я не являюсь постоянным клиентом Хабра и не знаю автора этой статьи RaJa, но на мой взгляд это очень хорошая статья, описывающая основные положения работы HID интерфейся. Без её прочтения читать дальше здесь бессмысленно, поскольку далее будут, в основном, комментарии для адаптации кода к среде разработки STM32IDE/STM32CubeMX + Atollic TrueStudio. (Далее STM32IDE). Да и столь популярный в 2014 году и реально очень неплохой проект EmBlocks, увы, умер.
Первое, что необходимо решить — как тестировать вновь создаваемое устройство. Лет… дцать назад я использовал для этого анализатор и синтезатор трафика USB — очень полезные, но дорогие игрушки 🙂 Сейчас у меня такой возможности нет, да и должен же быть более простой путь. Тем более для простого стандартного интерфейса без написания собственного драйвера. Авторы обоих рассмотренных выше проектов пошли самы простым для них путём — написание простой программы на известных им языках. Но автор статьи на Хабре сделал очень правильный шаг — он написал свой проект, совместимый с программой ST HID Demonstrator (ссылка есть в статье), позволяющей поуправлять нашим устройством, как графически, так и послать свои данные и посмотреть, что пришло от нашего устройства. Фактически программа может использоваться и в дальнейшем для отладки будущей программы на выбранном микроконтроллере.
Своё ознакомление с проектом для HID я осуществлял с платой STM32L476 Discovery. Плата, вообще говоря, может быть любой, где USB интерфейс микроконтроллера физически подключён к отдельному разъёму USB. Есть у меня и Nucleo 32 с STM32L4, но там один разъём USB тспользуется и для программирования/отладки, и для связи с хостом, что добавляет интриги в интерфейс и может служить источником дополнительных непоняток. Оно нам надо?
Итак, комментарии и дополнения к статье по привязке HID к STM32IDE примерно по тем же шагам, как и в хабровской статье.
В STM32IDE структура всех проектов задаётся при генерации проекта из среды назначения функциональности пинов и пользователю о том заботиться не надо. В частности, в кубике (что отдельном STM32Cube MX, что в встроенном в STM32IDE) активируем USB, как Device, и добавляем Middleware USB Custom HID.
Рис.1 Выбор USB интерфейсаРис.2 Выбор и первичная настройка MiddlewareНадо заметить, что несмотря на установку размера буфера в 64 байта, эта величина не вносится по #define. Видимо баг текущей версии кубика. Далее покажем, где надо пофиксить. Указанный резмер дескриптора равный 79 — это значение для данного конретного стартового проектаЗаходим в Clock Configuration. Вполне вероятно, что могут быть проблемы с системными частотами, которые маркируются малиновым цветом.
Рис. 3 Возможные проблемы по установке частотЕсли так, нажимаем Resolve Clock Issues и, скорее всего, всё будет настроено на максимальные частоты. Главное — USB Clock будет выставлен на 48 МГц. Надо заметить, что в семействе STM32L4 генератор на 48МГц имеет автоподстройку по SOF (Start Of Frame), что позволяет создавать USB устройства без внешнего кварца/генератора. Если, конечно, остальной дизайн допускает использование некварцованных генераторов. Для других семейств не проверял, поскольку для моего текущего проекта был выбран именно L4. Только надо отметить, что при использовании USB есть некоторая минимальная частота работы микроконтроллера. Я делал прикидку для другого проекта, где надо общаться с хостом и при этом потреблять минимум тока. Задачи простые, не требуют большой скорости и я хотел запустить МК на 8МГц. Оказалось, что меньше 14МГц при подключении USB ставить не могу, RCC не позволяет. Пришлось остановиться на следующем круглом значении 16МГц.
Собственно, настройка аппаратной части USB и выбор файлов, отвечающих за базовую функциональность этого интерфейса на на этом закончены. Вся остальная периферия, находящаяся на выбранной плате настраивается автоматически при её выборе на старте проекта. Сохраняем, генерим проект и переходим к «программированию» в сравнении с описанным на Хабре проектом.
Стандартные массивы данных для передачи информации хосту, с чем он будет иметь дело. Для интереса можно посмотреть дескрипторы устройства и конфигурации. Сейчас их можно оставить такими, как получились, но в дальнейшем они наверняка потребуют редактирования. Впрочем, не исключено, что они будут генериться по тем параметрам, что ставятся в кубике. Что не может не радовать. А вот Report Descriptor стоит изучить получше — это фактически основное, что придётся в дальнейшем править ручками. Не знаю, откуда RaJa взял его дескрипторы, в нашём случае они генерируются кубиком и располагаются в следующих файлах проекта:
Дескриптор от Raja | Дескриптор от ST | Файл в проекте |
RHID_DeviceDescriptor | USBD_FS_DeviceDesc | usbd_desc. c |
RHID_ConfigDescriptor | USBD_CUSTOM_HID_CfgFSDesc | usbd_customhid.c |
RHID_ReportDescriptor | CUSTOM_HID_ReportDesc_FS | usbd_custom_hid_if.c |
Поскольку для простоты сейчас будем работать только с ST HID Demonstrator, то не мудрствуя лукаво я просто скопировал содержимое RHID_ReportDescriptor в соответствующее место моего проекта. Только подставил свои константы на место длины. Надо отметить, что надо точно посчитать количество байтов в этом дескрипторе (в этом проекте 79) и убедиться, что именно это значение стоит в Class Parameters. Не больше и не меньше. Иначе хост не опознает подключённое устройство. Проверено 🙂
Далее заходим в файл usbd_customhid.h и меняем значения CUSTOM_HID_EPIN_SIZE и CUSTOM_HID_EPOUT_SIZE на 0x40U. Честно говоря, немного напрягает то, что ST не даёт альтернатив смене значения по умолчанию 2 на другое значение и далее в коде с использованием этих констант стоит комментарий, что не более 2х байт. Но, с другой стороны, это было рекомендовано в первом найденном описании и, вообще говоря, установка такого значения выглядит достаточно логично. Иначе в чём отличие CustomHID от обычного? Проблема в том, что при регенерации проекта из кубика, что на этапе первичного кода происходит довольно часто, это значение не сохраняется и его надо восстанавливать ручками. Для этого я себе в main вывел строку warning, чтобы не забывать проверить эти константы. Возможно я ошибаюсь, и в дальнейшем всё окажется проще. Но в такой конфигурации работает 🙂
Для выдачи данных на хост всё достаточно аналогично описанию на Хабре. Только название функции другое: USBD_CUSTOM_HID_SendReport(). Все остальные реомендации из той статьи подходят по полной программе.
А вот чтение здесь интереснее, чем на Хабре. И на самом деле несколько проще. Обработка принятого массива происходит в usbd_custom_hid_if.c / static int8_t CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state).
В этом тестовом проекте я не заморачивался с обработкой входных параметров и следуя своей обычной практике минимальности времени обработки прерываний, просто копирую полученные данные в заранее определённый массив и устанавливаю флаг готовности данных от USB
Ну, а собственно «сбор данных» (нажатие кнопок джойстика) и реакция на полученные от хоста данные в этом прото проекте делаю внутри бесконечного цикла в main.c Всё просто 🙂 В этом прото проекте нет разделения реакции на SET_FEATURE и SET_REPORT, с этим надо будет разобраться далее, в реальном проекте. Компилируем, запускаем, подключаем к хосту и там должен появиться новый CustomHID от STMicroelectronics.
Звпускаем на хосте USB HID Demonstrator. На плате, с которой я запускал этот проект, не имеет органов для работы с Variable Inputs/Outputs, поэтому в разделе Graphic customization были убраны соответствующие назначениями, оставлено 5 кнопок и назначены ID, определённые в проекте: 1, 2 для Output report (входные данные для ST) и 4 для Input Report (выход от ST).
Рис. 4 Настройка демонстратораМоей задачей для этого проекта было управлять парой светодиодов на плате, что стало работать сразу, как эта программа обнаружила подключенную плату, и включать «лампочки» этой платы при нажатии различных кнопок джойстика на плате, а вот здесь сразу не получилось. При указанных настройках все пять лампочек одновременно зажигались при нажатии на центр джойстика. Остальные кнопки не отображались. При этом, если перейти на Input/Otput transfer, то данные были вполне ожидаемы. Т.е. сам интерфейс работает, но отображение в программе на хосте не отвечает моим запросам. Слава богу ST предоставляетс исходники, а в соседнем кубике сидит программист нашей группы, пишущий в том числе и софт для компьютеров. В общем, он подправил одну функцию и сгенерил исполняемую программу. Всё стало работать, как хотелось. Конечно, можно было бы на каждую кнопку создать свой report с уникальным номером, что исходно и предусмотрено. В этом случае было бы достаточно посылать по одному байту для каждой кнопки, но мой проект предусматривает многобайтный отчёт. Исходник подправленной функции и подправленный исполняемый файл можно скачать по ссылке ниже.
На этом, пожалуй, всё. Если у Вас есть такая же плата 32L476GDISCOVERY, то для начала можно просто скачать мой прото проект, адаптированный для него демонстратор и исходник изменённой функции по этой ссылке. Исходный USB HID Demonstrator скачивается с сайта STM, инсталлируется и его исполняемый файл заменяется моим. Импортируете в STM32IDE мой проект, компилируете и должны получить работающую базу для своих проектов. Если у Вас другая плата, то адаптируете «сбор информации» и включение светодиодов под свою плату.
Для дальнейшей работы обязательно прочтите указанную статью RaJa с Хабра. Она даст понимание того, что и как должно быть сделано для других проектов с USB HID интерфейсом. А ещё лучше начать с неё 🙂
И при выборе класса устройства для Вашего проекта надо учитывать следующее: минимальный период опроса HID устройств — 1ms. И если я правильно помню, это скорее пожелание системе от внешнего устройства. В стандартном HID устройстве за один кадр (frame) передаётся только два байта, т.е. скорость обмена не более 2 кбайт/с. В Custom HID на
Full Speed (12 мбит/с) объём данных отчёта (report) — не более 64 байт, т.е. скорость обмена с Вашим HID не более 64 кбайт/с. Для High Speed (480 мбит/с) — максимальный объём данных 512 байт (512 кбайт/с). Не будь у меня ограничения совместимости с предыдущим софтом, используемым в компании, использовал хотя бы CDC.
У меня изучение статей и адаптация под мои хотелки заняло три дня. Описание заняло больше 🙂 Надеюсь, что у тех, кто воспользуется этой статьёй, аналогичный процесс займёт не более одного дня. Комментируйте, спрашивайте. Что смогу — отвечу. Что не смогу, вместе поищем решение.
Доброго дня всем посетителям и читателям нашего сайта! Наконец-то, после длительного летнего перерыва, возобновляется работа над новыми статьями ) Тем накопилось очень много, темы абсолютно разные, так что в ближайшее время, надеюсь каждый найдет что-то интересное для себя. И сегодня мы начнем обсуждать реализацию класса USB HID для наших любимых микроконтроллеров STM32. Почему начнем? Просто я решил, что для одной статьи тут будет многовато всего, поэтому их будет две или три, так что давайте приступать.
Итак, по старой доброй традиции разберемся с инструментарием… Я буду использовать отладочную плату с микроконтроллером STM32F103VET6, а для создания проекта мы задействуем набирающий все большую популярность STM32CubeMx.
Собственно, запускаем, создаем новый проект (на этих шагах я не останавливаюсь, все уже разобрано в мини-цикле статей, посвященных этой теме на нашем сайте — их можно найти в рубрике STM32CubeMx). Включаем только то, что нам понадобится для решения непосредственно сегодняшней задачи, ничего лишнего:
Кроме того, на моей плате есть вот такая фишка:
Поэтому для того, чтобы подключение по USB в принципе могло работать мне надо подать на PC13 напряжение низкого уровня, чтобы открыть транзистор.
В настройках тактирования нам необходимо обеспечить 48 МГц для работы USB. Тут ничего сложного, просто подбираем нужные значения умножителей и делителей частоты и не забываем активировать внешний кварц, который мы уже подключили в предыдущем окне:
Переходим на вкладку Configuration, но там все оставляем в первозданном виде. Разве что можно зайти в настройки USB_Device и посмотреть значения PID и VID, они могут нам пригодиться впоследствии для того, чтобы проверить, действительно ли наше устройство успешно подключилось к ПК. Но не будем забегать вперед, а просто запомним или запишем эти значения. По умолчанию они такие:
Работу в CubeMx на этом заканчиваем, генерируем файлы проекта и переходим в IAR (ну или Keil, например, у каждого своя IDE). Мы можем прошить полученную программу в микроконтроллер, но, к сожалению, это нам ничего не даст — устройство определится в системе, но работать не будет, а в диспетчере устройств мы увидим ошибку — «Сбой запроса дескриптора устройства». И эта ситуация как ни парадоксально абсолютно правильна и логична. Дело в том, что дескриптор для нашего Custom HID устройства мы должны создать самостоятельно и добавить в наш сгенерированный проект. Этим мы сейчас и займемся.
Всего есть три USB дескриптора, которые отвечают за подключение к ПК:
Дескриптор устройства описан в файле usbd_desc.c и выглядит следующим образом:
/* USB Standard Device Descriptor */ __ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = { 0x12, /*bLength */ USB_DESC_TYPE_DEVICE, /*bDescriptorType*/ 0x00, /*bcdUSB */ 0x02, 0x00, /*bDeviceClass*/ 0x00, /*bDeviceSubClass*/ 0x00, /*bDeviceProtocol*/ USB_MAX_EP0_SIZE, /*bMaxPacketSize*/ LOBYTE(USBD_VID), /*idVendor*/ HIBYTE(USBD_VID), /*idVendor*/ LOBYTE(USBD_PID_FS), /*idVendor*/ HIBYTE(USBD_PID_FS), /*idVendor*/ 0x00, /*bcdDevice rel. 2.00*/ 0x02, USBD_IDX_MFC_STR, /*Index of manufacturer string*/ USBD_IDX_PRODUCT_STR, /*Index of product string*/ USBD_IDX_SERIAL_STR, /*Index of serial number string*/ USBD_MAX_NUM_CONFIGURATION /*bNumConfigurations*/ }; /* USB_DeviceDescriptor */
В принципе, здесь все снабжено комментариями, все значения стандартные, ну и мы собственно, здесь ничего трогать сейчас не будем, а двинемся дальше. Дескриптор конфигурации мы можем найти в файле usbd_customhid.c:
/* USB CUSTOM_HID device Configuration Descriptor */ __ALIGN_BEGIN static uint8_t USBD_CUSTOM_HID_CfgDesc[USB_CUSTOM_HID_CONFIG_DESC_SIZ] __ALIGN_END = { 0x09, /* bLength: Configuration Descriptor size */ USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */ USB_CUSTOM_HID_CONFIG_DESC_SIZ, /* wTotalLength: Bytes returned */ 0x00, 0x01, /* bNumInterfaces: 1 interface */ 0x01, /* bConfigurationValue: Configuration value */ 0x00, /* iConfiguration: Index of string descriptor describing the configuration */ 0xC0, /* bmAttributes: bus powered */ 0x32, /* MaxPower 100 mA: this current is used for detecting Vbus */ /************** Descriptor of CUSTOM HID interface ****************/ /* 09 */ 0x09, /*bLength: Interface Descriptor size*/ USB_DESC_TYPE_INTERFACE, /*bDescriptorType: Interface descriptor type*/ 0x00, /*bInterfaceNumber: Number of Interface*/ 0x00, /*bAlternateSetting: Alternate setting*/ 0x02, /*bNumEndpoints*/ 0x03, /*bInterfaceClass: CUSTOM_HID*/ 0x00, /*bInterfaceSubClass : 1=BOOT, 0=no boot*/ 0x00, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/ 0, /*iInterface: Index of string descriptor*/ /******************** Descriptor of CUSTOM_HID *************************/ /* 18 */ 0x09, /*bLength: CUSTOM_HID Descriptor size*/ CUSTOM_HID_DESCRIPTOR_TYPE, /*bDescriptorType: CUSTOM_HID*/ 0x11, /*bCUSTOM_HIDUSTOM_HID: CUSTOM_HID Class Spec release number*/ 0x01, 0x00, /*bCountryCode: Hardware target country*/ 0x01, /*bNumDescriptors: Number of CUSTOM_HID class descriptors to follow*/ 0x22, /*bDescriptorType*/ USBD_CUSTOM_HID_REPORT_DESC_SIZE, /*wItemLength: Total length of Report descriptor*/ 0x00, /******************** Descriptor of Custom HID endpoints ********************/ /* 27 */ 0x07, /*bLength: Endpoint Descriptor size*/ USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/ CUSTOM_HID_EPIN_ADDR, /*bEndpointAddress: Endpoint Address (IN)*/ 0x03, /*bmAttributes: Interrupt endpoint*/ CUSTOM_HID_EPIN_SIZE, /*wMaxPacketSize: 2 Byte max */ 0x00, 0x20, /*bInterval: Polling Interval (20 ms)*/ /* 34 */ 0x07, /* bLength: Endpoint Descriptor size */ USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: */ CUSTOM_HID_EPOUT_ADDR, /*bEndpointAddress: Endpoint Address (OUT)*/ 0x03, /* bmAttributes: Interrupt endpoint */ CUSTOM_HID_EPOUT_SIZE, /* wMaxPacketSize: 2 Bytes max */ 0x00, 0x20, /* bInterval: Polling Interval (20 ms) */ /* 41 */ };
В общем-то, здесь тоже инженеры и программисты ST позаботились о том, чтобы хорошенько все прокомментировать, поэтому я не буду останавливаться на каждом поле (если возникнут вопросы или что-то не будет работать, пишите в комментариях, обязательно помогу).
Отдельно стоит обратить внимание на CUSTOM_HID_EPOUT_SIZE. В файле usbd_customhid.h находим:define CUSTOM_HID_EPOUT_SIZE 0x02
Эта константа определяет максимальный размер пакетов, которые будут использоваться при обмене данными с ПК. Давайте поставим 4 байта. Но не забывайте, что по стандарту максимальный размер пакета составляет 64 байта (0x40).
С двумя из упомянутых USB дескрипторов разобрались, теперь настала очередь третьего. Он определен в файле 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 */ };
Как видите он абсолютно пуст, поэтому наше устройство и не смогло определиться в системе как положено. Так что давайте разберемся и реализуем дескриптор репорта.
Что такое в принципе репорт?
Все дело в том, что ПК и наше устройство не просто кидают друг другу байты данных, а обмениваются пакетами, имеющими четко определенную структуру. Идентификация пакета происходит по первому байту, который представляет из себя ID репорта. Перед нами стоит задача отправлять данные хосту, а также принимать данные, передающиеся в обратном направлении. Поэтому мы определим два репорта — на прием и на передачу. Пусть на передачу у нас будет репорт с ID = 1, а на прием репорт с ID = 2:
__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END = { /* USER CODE BEGIN 0 */ 0x06, 0x00, 0xff, // USAGE_PAGE (Generic Desktop) 0x09, 0x01, // USAGE (Vendor Usage 1) // System Parameters 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, 4, // REPORT_COUNT (4) 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) 0x75, 0x08, // REPORT_SIZE (8) 0x95, 4, // REPORT_COUNT (4) 0x81, 0x82, // INPUT (Data,Var,Abs,Vol) /* USER CODE END 0 */ 0xC0 /* END_COLLECTION */ };
Не забываем изменить размер массива дескриптора — USBD_CUSTOM_HID_REPORT_DESC_SIZE, а заодно и USBD_CUSTOMHID_OUTREPORT_BUF_SIZE:
#define USBD_CUSTOMHID_OUTREPORT_BUF_SIZE 4 #define USBD_CUSTOM_HID_REPORT_DESC_SIZE 38
Ну и в файле usbd_customhid. h также устанавливаем значения, соответствующие нашему режиму передачи и приема (а именно передача и прием по 4 байта):
#define CUSTOM_HID_EPIN_ADDR 0x81 #define CUSTOM_HID_EPIN_SIZE 4 #define CUSTOM_HID_EPOUT_ADDR 0x01 #define CUSTOM_HID_EPOUT_SIZE 4
И вот теперь уже мы можем попробовать собрать проект и запрограммировать микроконтроллер. В результате устройство должно успешно определиться в диспетчере устройств:
Соответственно, значения PID и VID у этого устройства соответствуют тем, которые мы задали в STM32CubeMx при создании и настройке проекта.
Таким образом, на сегодня мы на этом заканчиваем. Сразу расскажу, чем мы будем заниматься в следующей статье. Во второй части, посвященной USB Custom HID мы разберемся как принимать и передавать данные, а в третьей части мы напишем программку для ПК, которая собственно и будет осуществлять подключение и обмен данными с нашим устройством. До скорой встречи!
Я разработал собственный геймпад, используя STM32. Он имеет четыре аналоговые оси и 22 кнопки. Я написал для него прошивку, включая дескриптор USB (это заняло несколько попыток).
Он работает точно так, как ожидалось, в Windows, но в Linux (в частности, Raspbian) последние 5 кнопок инвертированы («вкл» по умолчанию, «выкл» при нажатии)
Для моего продукта важно, чтобы он работал на обеих платформах (Mac тоже, но у меня сейчас нет его для тестирования)
Есть идеи, почему это может быть и что с этим делать?
Вот вывод lsusb -v для этого устройства:
Шина 001 Устройство 003: ID 0483:5750 STMicroelectronics Дескриптор устройства: бДлина 18 bDescriptorType 1 bcdUSB 2. 00 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 64 idVendor 0x0483 STMicroelectronics idProduct 0x5750 bcdDevice 2.00 iManufacturer 1 STMicroelectronics Пользовательский человеческий интерфейс iProduct 2 STM32 iSerial 3 74E79C303734 bNumConfigurations 1 Дескриптор конфигурации: bДлина 9 bDescriptorType 2 wTotalLength 0x0029 bNumИнтерфейсы 1 bConfigurationValue 1 iКонфигурация 0 бматрибутес 0xc0 Автономный Максимальная мощность 100 мА Дескриптор интерфейса: bДлина 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 3 Устройство пользовательского интерфейса bInterfaceSubClass 0 bInterfaceProtocol 0 интерфейс 0 Дескриптор HID-устройства: bДлина 9бдескриптортип 33 bcdHID 1.11 bCountryCode 0 Не поддерживается bNumDescriptors 1 bDescriptorType 34 Отчет wDescriptorLength 53 Дескриптор отчета: (длина 53) Элемент (глобальный): страница использования, данные = [0x01] 1 Общие элементы управления рабочим столом Элемент (локальный): использование, данные = [0x05] 5 Геймпад Элемент (основной): коллекция, данные = [0x01] 1 Приложение Элемент (локальный): использование, данные = [0x01] 1 Указатель Элемент (основной): коллекция, данные = [0x00] 0 Физический Элемент (локальный): Использование, данные = [0x30] 48 Направление-X Элемент (локальный): Использование, данные = [0x31] 49Направление-Y Элемент (локальный): использование, данные = [0x32] 50 Направление-Z Элемент (локальный): использование, данные = [0x33] 51 Повернуть-X Элемент (глобальный): логический минимум, данные = [0x00] 0 Элемент (глобальный): логический максимум, данные = [0xff 0x0f] 4095 Элемент (глобальный): размер отчета, данные = [0x10] 16 Элемент (глобальный): количество отчетов, данные = [0x04] 4 Элемент (основной): ввод, данные = [0x02] 2 Переменная данных Абсолютная No_Wrap Линейная Preferred_State No_Null_Position Non_Volatile Битовое поле Элемент (основной): конец коллекции, данные = нет Элемент (глобальный): Страница использования, данные = [ 0x09] 9 Кнопки Элемент (локальный): минимум использования, данные = [0x01] 1 Кнопка 1 (основная) Элемент (локальный): максимальное использование, данные = [0x16] 22 (нулевой) Элемент (глобальный): логический минимум, данные = [0x00] 0 Элемент (глобальный): логический максимум, данные = [0x01] 1 Элемент (глобальный): размер отчета, данные = [0x01] 1 Элемент (глобальный): количество отчетов, данные = [0x16] 22 Элемент (основной): ввод, данные = [0x02] 2 Переменная данных Абсолютная No_Wrap Линейная Preferred_State No_Null_Position Non_Volatile Битовое поле Элемент (глобальный): размер отчета, данные = [0x01] 1 Элемент (глобальный): количество отчетов, данные = [0x02] 2 Элемент (основной): ввод, данные = [0x03] 3 Постоянная Переменная Абсолютная No_Wrap Линейная Preferred_State No_Null_Position Non_Volatile Битовое поле Элемент (основной): конец коллекции, данные = нет Дескриптор конечной точки: bДлина 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmАтрибуты 3 Прерывание по типу передачи Тип синхронизации Нет Данные о типе использования wMaxPacketSize 0x0002 1x 2 байта бИнтервал 10 Дескриптор конечной точки: bДлина 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmАтрибуты 3 Прерывание по типу передачи Тип синхронизации Нет Данные о типе использования wMaxPacketSize 0x0002 1x 2 байта бИнтервал 10 Статус устройства: 0x0001 Автономный
Я также только что заметил, что положение джойстика отличается (опять же, правильное в Windows и неправильное в Linux. )
Я читал что-то о том, что в Linux есть драйвер ядра, который что-то делает с этими значениями? jstest-gtk позволяет мне контролировать некоторые аспекты осей (масштабирование, мертвые зоны, инверсию), может быть, это подсказка?
Класс USB HID Device (HID) предлагает самый простой способ включить USB-связь между встроенным устройством и хост-компьютер без написания каких-либо драйверов устройств. Однако мы должны написать приложение пользовательского пространства, если устройство не подпадает ни под одно из существующих определений использования в классе HID.
Прежде чем идти дальше, предполагается, что у вас есть некоторые практические знания спецификации USB 2.0 [1] , вам не нужно быть экспертом в USB-протоколах, так как библиотеки промежуточного программного обеспечения, поставляемые поставщиками микроконтроллеров, всех ключевых частей реализации прошивки USB. Если вы никогда раньше не читали спецификацию USB, то я посоветовал бы прочитать USB в двух словах [2] прежде чем погрузиться в фактические стандартные документы.
Устройство класса HID и хост взаимодействуют с помощью канала управления (конечная точка 0), Канал Interrupt IN и дополнительный канал Interrupt OUT. СПРЯТАННЫЙ класс спецификация [3] есть применимо к устройствам с низкой, полной и высокой скоростью. В качестве примера на максимуме доступная пропускная способность, на полной скорости с максимальной полезной нагрузкой данных 64 байта, канал прерывания может передавать 1216 байт полезных данных на кадр [4] . Поскольку каналы прерываний имеют ограниченную задержку, такое частое использование максимальной полосы пропускания будет задушить весь автобус, по этой причине разные типы передачи определяются в спецификации. Если требования к передаче данных устройства могут быть встретился с двумя каналами прерываний, мы можем перейти к классу HID.
Прежде чем хост сможет общаться с устройством, ему необходимо знать каково использование или применение этого устройства? Как организованы его данные? и Что на самом деле измеряют данные?
Давайте рассмотрим простой пример, чтобы понять вышеприведенные вопросы: Допустим, у нас есть Система управления питанием HID-устройство, при нажатии кнопки на устройстве хост-компьютер должен войти в спящий режим. Все это без написания какой-либо программы на стороне хоста.
Для возникновения вышеуказанных событий драйвер класса HID должен знать:
Вся эта информация описана в дескрипторе отчета . Как только драйвер класса проанализирует дескриптор отчета, он может интерпретировать данные, поступающие в каналы прерываний, и передавать их соответствующим приложение. Дескриптор отчета не возвращается вместе с дескриптором конфигурации при перечислении, только его длина включается в дескриптор класса HID, как только хост знает, что это устройство имеет интерфейс HID, драйвер класса HID будет отправить запрос GET_DESCRIPTOR на соответствующий интерфейс, чтобы получить его дескриптор отчета.
Дескриптор отчета описывается последовательностью элементов, вместе эти элементы описывают данные (также называемые отчетом), которые будут передаваться по каналам. Элемент начинается с 1-байтового префикса, указывающего роль элемента и его длину. который может быть коротким или длинным. Короткий элемент может иметь 0, 1, 2 или 4 байта данных. Длинный элемент может содержать 255 байт данных, длинные элементы не определены в спецификации класса HID.
Короткие элементы бывают трех типов: Глобальные, Локальные и Основные. Каждый тип далее делится на теги, давайте рассмотрим некоторые из них:
Фрагмент кода в листинге 1 показывает дескриптор отчета для системы В примере управления питанием обратите внимание, что в канале Interrupt IN отображается только один байт данных отчета. отправил. Из которых только бит-0 имеет значение.
Листинг-1: sys_ctrl_hid_report_desc.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1920 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | /* 1-байтовый префикс элемента Биты: 7654 | 32 | 10 бтег | bТип | bРазмер bSize: длина данных после префикса 0= 0 байт 1= 1 байт 2= 2 байта 3= 4 байта bType: Типы элементов 0= Основной 1= глобальный 2= местный 3= зарезервировано bTag: см. 4-битные старшие биты в макросах ниже. */ // Основные элементы #define HID_Input(x) 0x81,x #define HID_Output(x) 0x91,х #define HID_Feature(x) 0xB1,x #define HID_Collection(x) 0xA1,x # определить HID_EndCollection 0xC0 // Глобальные элементы #define HID_UsagePage(x) 0x05,x #define HID_LogicalMin(x) 0x15,x #define HID_LogicalMax(x) 0x25,x #define HID_ReportSize(x) 0x75,x #define HID_ReportCount(x) 0x95,x // Локальные элементы #define HID_Usage(x) 0x09,x const uint8_t HID_ReportDescriptor[] = { // Коллекция приложений принадлежит универсальной странице рабочего стола. HID_UsagePage(HID_USAGE_PAGE_GENERIC), // Тег локального элемента далее классифицирует его как системный элемент управления. HID_Usage(HID_USAGE_GENERIC_SYSTEM_CTL), HID_Collection (HID_Application), HID_LogicalMin(0), HID_LogicalMax(1), // 1-битные данные отчета Sleep Control HID_ReportSize(1), HID_ReportCount(1), // Вход Основной элемент, который следует за этим, относится к управлению спящим режимом, переход 0 -> 1 инициирует спящий режим. HID_Usage(HID_USAGE_GENERIC_SYSCTL_SLEEP), // Кнопка ввода имеет предпочтительное состояние 0, HID_Input(HID_Data | HID_Variable | HID_Relative | HID_PreferredState), // 7-битное заполнение для выравнивания отчета по границе байта. HID_ReportSize(7), HID_ReportCount(1), HID_Input(HID_Constant), HID_EndCollection, }; |
Спецификация класса HID позволяет использовать страницу использования, определенную поставщиком. для реализации собственного пользовательского протокола с упрощенным дескриптором отчета. Драйвер класса HID игнорирует набор приложений, определенный поставщиком, и ожидает приложение поставщика для управления устройством. Возьмем пример ARM Cortex CMSIS DAP [6] Блок отладчика, его версия прошивки V1 реализовано с классом HID мы можем использовать инструмент USB HID Report Descriptor Viewer [11] чтобы изучить его дескриптор отчета, вот вывод средства просмотра из модуля отладчика ULink2:
Usage Page(0xff00) // Страница, определенная поставщиком. Usage(0x1) // Использование, определенное поставщиком. Collection(0x1) // Коллекция приложений. Логический минимум (0x0) Логический максимум (0xff) Размер отчета (0x8) // Все данные имеют размер в байтах. Report Count(0x40) // Общий размер входного отчета = 64 байта. Использование (0x1) Input(0x2) // Данные имеют переменный тип. Report Count(0x40) // Общий размер выходного отчета = 64 байта. Использование (0x1) Output(0x2) // Данные имеют переменный тип. Report Count(0x1) // Общий размер отчета о функциях = 1 байт. Использование (0x1) Feature(0x2) // Данные имеют переменный тип. Завершить сбор
Если мы контролируем и прошивку устройства, и хост-приложение, то нет необходимости даже проанализировать дескриптор отчета. Давайте используем описанный выше дескриптор отчета, определенный поставщиком. в качестве общего шаблона и создайте еще один пример, чтобы поймать событие нажатия кнопки и светодиоды управления над СПРЯТАННЫМ. Фрагмент кода в листинге 2 показывает дескриптор отчета. использовано:
Листинг-2: led_ctrl_over_hid_report_desc.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 2930 31 32 33 34 35 36 37 | #define HID_INPUT_REPORT_BYTES 1 /* размер отчета в байтах */ #define HID_OUTPUT_REPORT_BYTES 1 /* размер отчета в байтах */ #define HID_FEATURE_REPORT_BYTES 1 /* размер отчета в байтах */ /** * Дескриптор отчета HID */ const uint8_t HID_ReportDescriptor[] = { HID_UsagePageVendor (0x00), HID_Usage(0x01), HID_Collection (HID_Application), HID_LogicalMin(0), HID_LogicalMax(0xFF), HID_ReportSize(8), // 8 бит HID_ReportCount (HID_INPUT_REPORT_BYTES), HID_Usage(0x01), // Бит-0 1-байтового ввода указывает на событие перехода кнопки. // 1 = 0 -> 1, кнопка нажата HID_Input(HID_Data | HID_Variable | HID_Absolute), HID_ReportCount (HID_OUTPUT_REPORT_BYTES), HID_Usage(0x01), // бит-0 управляет платой LED5 // 1 = включить светодиод // 0 = выключить светодиод HID_Output(HID_Data | HID_Variable | HID_Absolute), // Частота мигания светодиода, от 1 до 20 миганий в секунду. HID_LogicalMin(1), HID_LogicalMax(20), // Частота мигания светодиода 4 на плате контролируется отчетом о функции. HID_ReportCount(HID_FEATURE_REPORT_BYTES), HID_Usage(0x01), HID_Feature(HID_Data | HID_Variable | HID_Absolute), HID_EndCollection, }; |
Для приведенных выше примеров я использую макетную плату NGX LPC4357 Xplorer++ [7] . LPC4357 [8] микроконтроллер основан на ядрах ARM Cortex-M4/M0, он также поддерживается Библиотека LPCOpen Software [9] , которая включает в себя стек промежуточного программного обеспечения USB.
Теперь нам нужно хост-приложение для связи с USB-устройством, проще всего можно использовать PyUSB [10] . Если у вас есть указанная выше плата разработки, вы можете попробовать из этих примеров они были протестированы на Ubuntu 16.04.
Снимок экрана — Хост-приложение
Исходный код и инструкции доступны в общедоступном репозитории GitHub: https://github.