Люблю слушать музыку, чтобы сконцентрироваться или расслабиться. Но вот управлять громкостью и переключать треки было ой как неудобно на моей китайской механике. В один прекрасный день работа не сильно давила на голову и я решил сделать себе примочку к компу похожую на те, которые я видел на Алике.

В первую очередь, определимся, что нам нужно от готового устройства:
- Поворотная голова для регулировки громкости
- Кнопки Пуск/Пауза, Следующий трек, Предыдущий
- Какая-нибудь световая индикация – что устройство вообще еще живое
- Питание от USB
- Отсутствие драйверов
- Небольшой размер – по желанию
До глобального кризиса микросхем (2020-2021) я закупал пачками STM32F103 (в народе “BluePill“). Весной 2021 цена BluePill’ы стала на 30% выше, чем BlackPill’ы. Которая сделана намного более аккуратно, с TypeC и с STM32F4 на борту.
Но у меня завалялось много F103, даже в виде чипов, поэтому решил делать на этом камушке.
В первую очередь – нужно развести плату. Прошли те веселые годы, когда позволить себе печатку промышленного качества могли лишь богатые любители попаять. Более того, даже необязательно использовать какой-нибудь полу-легальный САПР для проектирования своих поделок. Уже давно есть отличный сервис проектирования печатных плат EasyEda, который интегрирован с JLCPCB. EasyEda есть и оффлайн-версия, которую я использую – лагов намного меньше. Да и весит она примерно 500 Мб. Да и свободные проекты можно открывать и копировать – как в гитхабе. Это не реклама – сервис действительно хорош. Особенно наличием огромной библиотеки ПОСТОЯННО ПОПОЛНЯЕМЫХ компонентов. Порог вхождения для комфортного использования, требовательность к ресурсам ПК намного ниже, чем в Altium. Altium хорош, но для быстрых самоделок для неспециалиста, по моему мнению, слишком серьезен.
Итак. Вся суть платы – упрощенная BluePill с энкодером и какой-никакой защитой по USB.


На данном МК есть возможность аппаратной работы с энкодером. Выбирается тут:

Более подробное видео по подключению энекодера
https://www.youtube.com/watch?v=xqzWQgpqHmI
Оставим энкодеры и кнопки, т.к. это самая неинтересная часть данного поста. Для тестов нам нужна будет лишь одна кнопка на вход. Приступим к USB устройству. USB во вкладке Connectivy просто включается буз изменений. Далее подключаем библиотеку, которая как раз и содержит в себе дескриптор устройства. Выберем класс устройства HID. Такие устройства не требуют установки дополнительных драйверов и работают на практически любых машинах.

Во время разводки первой платы проворонил некоторый момент с энкодером и пришлось писать свой, достаточно специфичный, обработчик сигналов с него. Но – еще раз – энкодер в данном случае не проблема, самое сложное, обычно, разобраться в дескрипторах и найти пример с которого можно начать эксперименты.
Посе успешной генерации кода кубом начинаем его редактировать. Нас интересуют следующие файлы

Куб генерирует рабочий дескриптор устройства – мышь, который можно сразу же использовать. Мы будем делать клавиатуру. Причем, добавим нормальную клавиатуру + спец-клавиши. Таким образом можно совмещать несколько устройств в одном – например мышь+клавиатура и т.д.
В файле usbd_hid.c найдем строку
__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] __ALIGN_END = {
Именно в данном массиве описывается что у нас за устройство такое. Очень подробно разбирать каждую строку не буду – это уже сделано на многих ресурсах(например, хабр). Стоит лишь обратить внимание что РАЗНЫЕ УСТРОЙСТВА ОТЛИЧАЮТСЯ ПО Report ID. И описано более 1 устройства, то добавляется самым первым байтом его идентификатор.
Для составления собственного дескриптора имеет смысл воспользоваться программой HID Descriptor Tool.

Мой дескриптор следующий (спасибо доброму человеку, который его сделал и дописал комментарии. В первый раз такое было тяжело переварить):
__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_CUSTOM_REPORT_DESC_SIZE] __ALIGN_END = { // 78 bytes 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) 0x09, 0x06, // Usage (Keyboard) 0xA1, 0x01, // Collection (Application) 0x85, 0x01, // Report ID (1) 0x05, 0x07, // Usage Page (Kbrd/Keypad) 0x75, 0x01, // Report Size (1) 0x95, 0x08, // Report Count (8) 0x19, 0xE0, // Usage Minimum (0xE0) 0x29, 0xE7, // Usage Maximum (0xE7) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0x95, 0x03, // Report Count (3) 0x75, 0x08, // Report Size (8) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x64, // Logical Maximum (100) 0x05, 0x07, // Usage Page (Kbrd/Keypad) 0x19, 0x00, // Usage Minimum (0x00) 0x29, 0x65, // Usage Maximum (0x65) 0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) 0xC0, // End Collection 0x05, 0x0C, // Usage Page (Consumer) 0x09, 0x01, // Usage (Consumer Control) 0xA1, 0x01, // Collection (Application) 0x85, 0x02, // Report ID (2) 0x05, 0x0C, // Usage Page (Consumer) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x75, 0x01, // Report Size (1) 0x95, 0x08, // Report Count (8) 0x09, 0xB5, // Usage (Scan Next Track) 0x09, 0xB6, // Usage (Scan Previous Track) 0x09, 0xB7, // Usage (Stop) 0x09, 0xB8, // Usage (Eject) 0x09, 0xCD, // Usage (Play/Pause) 0x09, 0xE2, // Usage (Mute) 0x09, 0xE9, // Usage (Volume Increment) 0x09, 0xEA, // Usage (Volume Decrement) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0xC0, // End Collection };
Тут стоит остановиться и внимательно посмотреть на комментарии. Видно, что тут есть Report ID 1 и Report ID 2. Это значит, что при вызове функции отправки данных в USB, я добавлю первым байтом этот ID. Устройство Report ID 1 – это очень усечённая клавиатура.
Я не стал менять название массива, а лишь поменял дефайн размера массива на соответствующий данному дескриптору.
Везде в данном файле меняем все HID_MOUSE_REPORT_DESC_SIZE на HID_CUSTOM_REPORT_DESC_SIZE.
Так же стоит найти данные строчки (их 3)
0x00, /* nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse */
И выставить в 0x01. У нас жеж клавиатура.
В файле usbd_hid.h добавляем новый дефайн, который отвечает за размер нового массива
define HID_CUSTOM_REPORT_DESC_SIZE 78U
Так же имеет смысл увеличить максимальный размер пакета. (Максимум 0x40)
#define HID_EPIN_SIZE 0x08U
Кстати, все ваши изменения CubeMX сотрёт при перегенерации проекта. Так что аккуратней с этим. На этом работа с модификацией HID устройства закончена. Можно приступать непосредственно к коду. Переходим в main.c. После #include’ов добавляем описание структуры.
struct mediaHID_t { uint8_t id; // Report ID uint8_t keys; //Key };
Так, с Report ID, более менее понятно, а как понять что писать в key? Добавим дефайнов.
#define USB_HID_SCAN_NEXT 0x01 #define USB_HID_SCAN_PREV 0x02 #define USB_HID_STOP 0x04 #define USB_HID_EJECT 0x08 #define USB_HID_PAUSE 0x10 #define USB_HID_MUTE 0x20 #define USB_HID_VOL_UP 0x40 #define USB_HID_VOL_DEC 0x80
Как можно заметить, каждый из дефайнов для управления звуком – это 1, которая сдвигается влево (если смотреть сверху вниз). То есть функциональное назначение действия кнопки это единичка в массиве. Если записать две рядом – то и будут нажаты 2 кнопки.
Я думаю, следующая картинка поможет пониманию того, что я написал.

Добавим еще дефайнов для нашей мини-клавиатуры
// USB keyboard codes #define USB_HID_MODIFIER_LEFT_CTRL 0x01 #define USB_HID_MODIFIER_LEFT_SHIFT 0x02 #define USB_HID_MODIFIER_LEFT_ALT 0x04 #define USB_HID_MODIFIER_LEFT_GUI 0x08 // (Win/Apple/Meta) #define USB_HID_MODIFIER_RIGHT_CTRL 0x10 #define USB_HID_MODIFIER_RIGHT_SHIFT 0x20 #define USB_HID_MODIFIER_RIGHT_ALT 0x40 #define USB_HID_MODIFIER_RIGHT_GUI 0x80 #define USB_HID_KEY_L 0x0F
Обязательно стоит указать, что хэндлер usb уже где-то есть.
extern USBD_HandleTypeDef hUsbDeviceFS;
А он объявлен в файле usbdevice.c
В мэйне создаем объявленную ранее структуру и выставляем id в 2. Ща будем звуком управлять
int main(void) { /* USER CODE BEGIN 1 */ struct mediaHID_t mediaHID; mediaHID.id = 2; mediaHID.keys = 0;
В цикле делаем следующее:
while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET){ mediaHID.keys = USB_HID_VOL_UP; USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&mediaHID, sizeof(struct mediaHID_t)); HAL_Delay(20); mediaHID.keys = 0; USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&mediaHID, sizeof(struct mediaHID_t)); HAL_Delay(2000); } }
Что тут? Если нажата кнопка – отправим сообщение ” нажата кнопка прибавить звук”, чуть ждем, отправим сообщение “кнопка прибавить звук отжата”.
Заливаем, подключаем. Если всё сделано правильно, то в диспетчере устройств будет добавлено новое HID-устройство. Пробуем нажать кнопку – звук должен увеличится.
Далее ваш полет фантазии ничем не ограничен. У меня получилось такое

Ссылка на GitHub: (Только как пример – у меня был энкодер с приколом, пришлось делать всякие гадости, чтобы он заработал)
Литература:
https://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/
Если ссылка отвалится – сохранил в pdf
https://programel.ru/files/Tutorial about USB HID Report Descriptors _ Eleccelerator.pdf
Лучшее, что есть по дескрипторам человеческим языком
Отличный конфигуратор устройств HID для STM32
Замечательная литература для более глубокого изучения работы USB
Comment