В прошлый раз показал один из способов распределение ресурсов между конечными точками, а именно регистров EPnR, памяти под дескрипторы буферов и под сами буферы. Предлагаю продолжить начатое и рассмотреть написанную библиотеку на примере создания простого HID-устройства, позволяющего управлять светодиодом. Кроме обозначенных ранее ресурсов конечные точки также делят единственное прерывание. Соответственно, общий (главный) обработчик должен корректно передать управление обработчику прерывания нужной конечной точки. Номер конечной точки, к которой обращается хост, записано в битах EP_ID регистра ISTR. Придерживаясь линии реализации полностью шаблонной библиотеки, получил следующий класс: Ключевым элементом класса является массив _handlersIndexes, который отображает номер конечной точки и направление в конкретный обработчик. Для получения данного массива реализован специальный класс: Из такой реализации, кстати, вытекает рекомендация объявлять конечные точки с номерами по порядку, потому что размер массива индексов обработчиков равен удвоенному максимальному номеру конечной точки. Из-за необходимости распределить ресурсы между конечными точками пришлось разделить код на два класса: базовый, который и нужно инстанцировать: А также расширенный, с адресами буферов и регистра (которые назначаются менеджером, описанным в прошлой статье). Из-за различий между конечными точками разных типов/направлений (например, у однонаправленной конечной точки типа Interrupt будет один буфер, а у двунаправленной или Bulk с двойной буферизацией - два) для каждого типа объявлен свой класс: Конечная точка на текущий момент реализована простой: экспортирует метод инициализации (в котором заполняется регистр EPnR), метод заполнения дескриптора, методы управления битами регистра (Очистка битов CTR_TX/RX, установка битов TX/RX_STATUS), а также отправку данных. Следующей сущностью в иерархии является интерфейс, который, по сути (как я понимаю) есть просто контейнер для конечных точек, поэтому реализующий его класс очень простой (прикладываю код полностью, потому что здесь применяется мощь variadic-шаблонов, который позволил исключить лишние зависимости): То же самое можно сказать о конфигурации, которая есть контейнер для интерфейсов. Читая описание USB я понял, что все на самом деле сложнее и хост вправе выбирать конфигурации/интерфейсы, однако на данный момент не осознал сути и не поддерживаю подобные механизмы. В то же время предполагаю, что для реализации взаимодействия хоста с конфигурациями и интерфейсами нужно будет сделать немного - добавить диспетчеризацию обращений к ним аналогично диспетчеризации обращений к конечным точкам. Наконец, в вершине иерархии находится непосредственно само устройство. В моей реализации соответствующий класс является производным от нулевой конечной точки, поскольку она особая и обмен данными с нулевой конечной точкой - это обмен данными (как правило, управляющими) с самим девайсом. Класс содержит главный обработчик прерывания, диспетчеризуя запросы по различными конечным точкам: Являясь одновременно и нулевой конечной точкой, класс Device содержит обработчик обращений к нулевой точке, реализуя, на данный момент, процесс нумерации: HID-устройство - это устройство как минимум с одним интерфейсом типа HID, поэтому в библиотеке класс HID - это производный от интерфейса: Собственно, библиотечная часть на этом окончена, в дальнейшем, конечно, к классу HidInterface будут добавлены другие стандартные классы, что представляет собой задачу чисто техническую, ничего изобретать уже не нужно (надеюсь). Теперь давайте из всего этого сделаем устройство, которое будет содержать один светодиод (потому что так удобно, он есть на плате BluePill) и поддерживать возможность управления этим светодиодом с компьютера (через USB HID Demonstrator). Основной любого HID-устройства является Report, определяющий порядок взаимодействия. В нашем случае он будет достаточно простым: Далее нужно определить всю ирерахию: конечные точки, интерфейс, конфигурацию и устройство: В общем-то всё, осталось написать обработчик для конечной точки управления светодиодом: Целиком файл main.c для Stm32f103 выглядит так (по-моему, достаточно компактно): Не совсем очевидная реализация библиотечного кода (в прошлой статье получил заслуженные комментарии в стиле "Не хотел бы увидеть такой код в продакшне", "Как это поддерживать" и т.п.) позволила максимально упростить непосредственно реализацию устройства, не нужно даже вручную объявлять дескрипторы: все генерируется из подставленных в шаблоны аргументов. Использование variadic-шаблонов помогло избавиться от лишних зависимостей. Прошивка тоже получается компактной, код из примера выше с оптимизацией Og вышел в 2360 байтов Flash и 36 байтов RAM (с оптимизацией Os прошивка весит 1712 байтов, но не работает. Пока не разобрался, почему именно), что я считаю неплохим результатом. За замечательный пост про HID благодарен @RaJa. Также менее, чем за неделю до написания этого поста вышел еще крутой материал по HID от @COKPOWEHEU. Без этих постов я бы ничего не осилил. Еще большую помощь оказали пользователи с форума radiokot (COKPOWEHEU и VladislavS), был приятно удивлен оперативностью ответов и желанием помочь.