Найти в Дзене
ProgramEl

USB-HID на STM32F103. Примочка для управления треками и громкостью

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

В первую очередь, определимся, что нам нужно от готового устройства:

  • Поворотная голова для регулировки громкости
  • Кнопки Пуск/Пауза, Следующий трек, Предыдущий
  • Какая-нибудь световая индикация - что устройство вообще еще живое
  • Питание от USB
  • Отсутствие драйверов
  • Небольшой размер - по желанию

До глобального кризиса микросхем (2020-2021) я закупал пачками STM32F103 (в народе "BluePill"). Весной 2021 цена BluePill'ы стала на 30% выше, чем BlackPill'ы. Которая сделана намного более аккуратно, с TypeC и с STM32F4 на борту.

Но у меня завалялось много F103, даже в виде чипов, поэтому решил делать на этом камушке.

Итак. Вся суть платы - упрощенная BluePill с энкодером и какой-никакой защитой по USB.

-2
-3

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

-4
https://www.youtube.com/watch?v=xqzWQgpqHmI
Более подробное видео по подключению энекодера

Оставим энкодеры и кнопки, т.к. это самая неинтересная часть данного поста. Для тестов нам нужна будет лишь одна кнопка на вход. Приступим к USB устройству. USB во вкладке Connectivy просто включается буз изменений. Далее подключаем библиотеку, которая как раз и содержит в себе дескриптор устройства. Выберем класс устройства HID. Такие устройства не требуют установки дополнительных драйверов и работают на практически любых машинах.

-5

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

Посе успешной генерации кода кубом начинаем его редактировать. Нас интересуют следующие файлы

-6

Куб генерирует рабочий дескриптор устройства - мышь, который можно сразу же использовать. Мы будем делать клавиатуру. Причем, добавим нормальную клавиатуру + спец-клавиши. Таким образом можно совмещать несколько устройств в одном - например мышь+клавиатура и т.д.

В файле usbd_hid.c найдем строку

__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] __ALIGN_END = {

Именно в данном массиве описывается что у нас за устройство такое. Очень подробно разбирать каждую строку не буду - это уже сделано на многих ресурсах(например, хабр). Стоит лишь обратить внимание что РАЗНЫЕ УСТРОЙСТВА ОТЛИЧАЮТСЯ ПО Report ID. И описано более 1 устройства, то добавляется самым первым байтом его идентификатор.

Для составления собственного дескриптора имеет смысл воспользоваться программой HID Descriptor Tool.

HID Descriptor Tool
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 кнопки.

Я думаю, следующая картинка поможет пониманию того, что я написал.

-8

Добавим еще дефайнов для нашей мини-клавиатуры

// 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-устройство. Пробуем нажать кнопку - звук должен увеличится.

Далее ваш полет фантазии ничем не ограничен. У меня получилось такое

-9

Ссылка на GitHub: (Только как пример - у меня был энкодер с приколом, пришлось делать всякие гадости, чтобы он заработал)

GitHub - tunerok/usb_volumer

Литература:

Лучшее, что есть по дескрипторам человеческим языком
https://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/

Если ссылка отвалится - сохранил в pdf
https://programel.ru/files/Tutorial about USB HID Report Descriptors _ Eleccelerator.pdf
Отличный конфигуратор устройств HID для STM32
https://github.com/FreeJoy-Team/FreeJoy
Замечательная литература для более глубокого изучения работы USB
http://microsin.ru/content/view/1107/44/https://www.usbmadesimple.co.uk/