Составное устройство USB на STM32. Часть 1: Предпосылки



    История эта началась три года назад, когда я осознал, что мне скоро исполнится 50 лет, что я погряз в бумажной работе, и что мне хочется чего-то нового. Работу поменять в моём возрасте уже проблематично, поэтому я решил начать pet-проект.

    Первое, что приходит в таких случаях на ум старому радиолюбителю: новая радиостанция. Стопроцентно аппаратные решения остались в далёком прошлом. Сейчас гораздо более актуальны SDR-трансиверы: решение это программно-аппаратное, есть опубликованные примеры реализации, к некоторым из них даже выложены исходные коды прошивок.

    Основная проблема в разработке заключалась в том, что несложные SDR-радиостанции, работающие в связке со звуковой картой, требуют наличия у компьютера, к которому они подключены, двухканальных линейных входа и выхода для работы приёмо-передающего тракта, а также COM-порта для работы CAT-интерфейса. В современных же ноутбуках аудиовход обычно предназначен для подключения микрофона гарнитуры и бывает только монофоническим.

    Решением проблемы стала реализация составного устройства USB, состоящего из виртуального COM-порта и дуплексной звуковой карты. Кому интересно, как я с этим справился, не имея опыта программирования, милости прошу под cut.

    TL/DR: Как я с этим справился, не имея опыта программирования? Просто начал программировать на C, а остальное приложилось само: MVP проекта реализован, а исходные коды публикуемой реализации составного устройства USB, состоящего из виртуального COM-порта и дуплексной звуковой карты находятся здесь: http://github.com/dmitrii-rudnev/selenite-habr

    … и опыт, сын ошибок трудных...


    И опыт и навыки формируются практикой. Для их формирования необходимо:

    • изучать документацию;
    • проводить анализ существующих решений;
    • разрабатывать собственные решения;
    • воплощать собственные решения в «железе»;
    • возвращаться к началу цикла если не заработало.

    Любую технологию освоить гораздо проще, когда понимаешь, для решения каких проблем она в своё время создавалась.

    Язык C разрабатывался инженерами и для инженеров. Использование языка программирования C дало мне возможность абстрагироваться от ассемблера и машинных кодов с одной стороны, но в тоже время обращаться напрямую к регистрам или к памяти.

    Нелюбимый многими STM32CubeMX с библиотекой HAL значительно облегчил мне процесс разработки хотя бы тем, что не надо было за каждой мелочью заглядывать в Reference Manual.

    Кроме того, я очень многому научился, разбирая сгенерированный STM32CubeMX код:

    • его писали разные люди, но он выдержан в одном стиле;
    • назначение переменных и функций понятно из имени;
    • комментарии к коду позволяют документировать его через Doxygen и т.п.

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

    В силу того, что я не мог гарантировать результат своей программистской деятельности, пришлось вводить градации по функционалу MVP проекта от простого к сложному.

    Минимальный функционал MVP подразумевал подключение приёмной части радиостанции к линейному входу звуковой карты компьютера и приём на фиксированной частоте.

    Следующим шагом планировалась реализация перестройки частоты через CAT-интерфейс, подключение приёмного и передающего трактов радиостанции к линейным входу и выходу звуковой карты и приём-передача на любительских диапазонах.

    И только после этого планировалось подключение SDR-трансивера к компьютеру как звукового устройства USB с управлением по CAT-интерфейсу.

    Такой подход сразу дал плоды: уже к началу 2019 года, всего через шесть месяцев после установки на мой компьютер STM32CubeMX, был реализован минимальный MVP проекта: функциональный аналог SDR-приёмника Softrock Lite II RX уверенно принимал сигналы точного времени на частоте 9996 кГц.

    В настоящее время MVP проекта является функциональным аналогом SDR-трансивера Peaberry SDR V2 и работает как на приём, так и на передачу.

    Структура приёмопередающего тракта SDR-трансивера


    Описываемое в публикации составное устройство USB работает в составе SDR-трансивера. Структура приёмопередающего тракта разрабатываемого SDR-трансивера включает в себя пока только самый необходимый минимум и представлена на рисунке ниже:



    При приёме радиосигнал поступает из антенны через полосовой фильтр (BPF) в квадратурный детектор (QSD). Полученный в результате квадратурный сигнал (IQ) через двухканальный вход дуплексного звукового устройства USB поступает в компьютер для дальнейшей обработки.

    При передаче сформированный в компьютере квадратурный сигнал (IQ) через двухканальный выход дуплексного звукового устройства USB поступает в квадратурный возбудитель (QSE).
    Полученный в результате радиосигнал подаётся через полосовой фильтр (BPF) в антенну.

    Обработка сигналов на стороне компьютера осуществляется программой HDSDR.

    Частота приёма и передачи задаётся настройками генератора плавного диапазона (VFO). Управление VFO и режимом работы (приём-передача) осуществляется из программы HDSDR через CAT-интерфейс, подключенный к виртуальному COM-порту.

    Связь HDSDR с виртуальным COM-портом осуществляется посредством программы OmniRig, созданной канадским радиолюбителем Alex Shovkoplyas (VE3NEA).

    CAT-интерфейс трансивера использует ограниченный набор команд популярного во всём мире трансивера Yaesu FT-817.

    Виртуальный COM-порт и дуплексное звуковое устройство объединены в составное устройство USB, работа которого и будет разобрана в данной публикации. Для облегчения проверки работоспособности публикуемого решения на входные и выходные потоки устройств установлены шлейфы.

    Вся необходимая для разработки описанного в публикации составного устройства USB документация была найдена поиском по сайту usb.org.

    Техническое решение разрабатывалось на основе анализа созданной немецким радиолюбителем Andreas Richter (DF8OE) open source прошивки для трансивера mcHF M0NKA и его клонов. Ряд нюансов был проработан при попытках разобраться в кодах дуплексного звукового устройства USB на базе расширения X-CUBE-AUDIO для STM32CubeMХ.

    Структура составного устройства USB


    Описываемое в публикации составное устройство USB состоит из виртуального COM-порта и дуплексного звукового устройства USB 16 бит 48 кГц. Публикуемое решение реализовано на микроконтроллере STM32F446ZET6 из состава платы NUCLEO-F446ZE.

    Упрощенная структура дескриптора представлена на рисунке ниже:



    Дескриптор составного устройства USB создан по рекомендациям, содержащимся в документе:

    [1] USB Interface Association Descriptor Device Class Code and Use Model. rev.1.0. July 23 2003

    Хотел бы заострить внимание на том, что в структуре дескриптора составного устройства USB важен порядок описания интерфейсов: сначала идёт описание интерфейса 0, затем интерфейса 1 и т.д. Номера используемых интерфейсами конечных точек (EP) могут идти не по порядку.

    При генерации кода STM32CubeMX размещает дескриптор устройства (Device Descriptor) в файле usbd_desc.c. Нужно отметить, что STM32CubeMX при последующей генерации кода не сохранит изменения, вручную внесённые в дескриптор, т.к. они не находятся в области, помеченной как USER CODE.

    Дескрипторы конфигурации и классов устройств размещаются в файлах usbd_cdc.c и usbd_audio.c, размещённых в папках директории Middlewares/ST/Class. Важно помнить, что STM32CubeMX даёт выбрать за раз только один класс устройств. Если ранее был выбран другой класс, при генерации кода файлы с драйверами этого класса из проекта будут удалены.

    От автора


    В следующей части публикации будет разобрана:

    • подготовка проекта в STM32CubeMX;
    • настройка параметров звукового устройства USB;
    • работа звукового устройства USB, которое STM32CubeMX генерирует по умолчанию «из коробки».

    Читайте продолжение:
    Составное устройство USB на STM32. Часть 2: USB Audio Speaker

    Комментарии 40

      +3
      Очень интересно, жду продолжения.
        +2
        Продолжение следует:
        04.03.2021 Часть 2: USB Audio Speaker
        09.03.2021 Часть 3: Звуковое устройство отдельно, виртуальный СОМ-порт отдельно
        11.03.2021 Часть 4: Два-в-одном
        –8

        Печально видеть COM порт в современном изделии. Популярных SDR приёмников ведь много, думаю, можно было выбрать протокол посовременнее. Или сделать кастомный протокол и написать плагин для декодирующей программы (к примеру, в SDRangel это делается несложно).

          0
          Программы управления и всяких контестов заточены под работу с классическими трансиверами, а там — ком-порт. Ну так исторически сложилось. А HAM-радио — вещь изрядно замшелая.
            0

            Как по мне, развитие SDR технологий — хороший повод пересмотреть исторические подходы.

              +4
              Зачем ломать то, что хорошо работает? И плодить сущности, изобретая новые, несовместимые со старыми, интерфейсы?

              image
                0

                Потому, что устройство — приёмник, а не COM-порт и звуковая карта. Мало того, что типы соответствуют функции весьма отдалённо, так ещё и устройство не одно, а два.
                Допускаю, что в данном конкретном случае прямой путь может быть слишком сложен, однако отбрасывать его даже не рассматривая — это странно.
                Аналогия — хранение числовых переменных в памяти в виде строк. Удобно, достаточно выучить, как работать со строками, не нужно думать о порядке байтов, диапазоне значений. Иногда в этом есть смысл, но чаще всего это приводит лишь к дополнительным расходам ресурсов памяти (на хранение) и процессора (на постоянное преобразование типов туда-сюда).

            +1
            Или сделать кастомный протокол и написать плагин для декодирующей программы (к примеру, в SDRangel это делается несложно).

            так автор же написал, что нет опыта программирования.
            А чем плох COM в данном конкретном случае?
              –2

              Так удалось же с USB разобраться.
              COM — это лишняя сущность, не требующаяся ни со стороны ПК, ни со стороны приёмника.
              Раз уже есть USB, то более чистое решение — использовать для связи непосредственно его.
              Возможно, эмуляция какого-то другого приёмника добавит ещё больше лишнего кода, но это можно понять только после анализа.

                0
                Зато под СОМ ненужно переписывать драйвер, когда поменяется операционная система или потребуется работа на разных платформах.
                  –1

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

                    +4
                    Безусловно, обычно драйвера для адаптеров пишет производитель. А т.к. объем рынка весьма велик, то качество и разнообразие поддерживаемых систем обычно весьма велико. И если СОМ позволяет реализовать функционал, то он гораздо удобнее даже в серийных устройствах, и на уровне отладки, и на уровне поддержки.
                  +1
                  Использовать для связи ЮСБ невозможно в принципе, поскольку ЮСБ — это транспортный протокол, который знать не знает, что там ходит между конечными точками — аудио, файлы или команды.
                    +3

                    Для связи между ПК и приёмником. Правила, по которым формируются USB пакеты (то, что я называл протоколом), знают драйвера устройства и программы, которые с этим устройством работают. Чтобы посмотреть на то, как работает "невозможное", достаточно покопаться в исходниках библиотек современных приёмников, к примеру, LimeSDR или RTL-SDR.

                      +4
                      Вы не ответили на вопрос — зачем ломать, то, что работает и чего достаточно? Только потому, что СОМ-порту 100 лет в обед и вам захотелось чего-то новенького???

                      Через СОМ устройством можно управлять с любого устройства, от АТмеги до сервера, напрямую или через долларовый переходник, просто посылая ему текстовые строки. Если же, как вы предлагаете, делать проприетарный протокол, то вы такое устройство не сможете подключить к своему микроконтроллеру, если на протокол нет документации, исходников или времени на разработку.
                        +1

                        Ответил в соответствующей ветке.
                        В статье речь о подключении к ноутбуку. Если есть планы подключать к другим устройствам, то это другой разговор.

                    +2
                    COM — это лишняя сущность


                    COM вас переживёт, могу поспорить. Это промышленный стандарт, возможности которого (особенно гальванически развязанного) и не снились USB. Единственно в чём он проигрывает — скорость.
                    Простота реализации (железо+код), полный дуплекс, помехозащищенность (-12V \+12V против 0\+5V), кабель 3 жилы против 5 у USB, максимальная длина кабеля 100м против 5 у USB.
                    Не смейтесь над старичком, он — Кощей Бессмертный.
                    Вы возможно удивитесь, но почти в каждом роутере, в том числе и бытовом, присутствует железный, распаянный COM-порт, только кабель подключи. Про всякие одноплатные машинки и не говорю.
                      0

                      Речь не о COM вообще, а о его применении в SDR приёмнике. Допустим, COM защищён от помех, защищён ли так же хорошо от них аудиокабель? Допустим, поддержку COM легко реализовать, но так ли это важно, если всё равно надо разбираться с USB?
                      В роутерах, полагаю, для отладочного интерфейса важна надёжность. Чем меньше кода, тем меньше шансов допустить в нём ошибку. Отлаживать же работу USB через USB… думаю приятного мало.

                  0
                  TCI потихоньку поддерживается всё большим количеством программ, и в нем как раз заложено много всего.
                    +1

                    Интересный вариант, особенно если в ноутбуке есть Ethernet разъём.
                    Протягивать же Ethernet через USB не намного лучше, чем COM порт.

                  0
                  А при одновременной передаче данных по COM, вывод звука с STM не хрипит?
                    0
                    Увеличивайте размер буфера аудио, тогда не будет хрипеть.
                      0
                      Да там опустошение происходит, а не переполнение. Хотя потоки входящих/выходящих на устройство и близко не дотягивают до возможного максимума. Если виртуальный COM не активен — тогда все норм.
                        0
                        Вот для этого и надо увеличить буфер, чтобы не происходило опустошение за то время, пока идут данные в СОМ.
                          0
                          С буфером вроде все ОК, это не однократно проверялось и тестировались разные варианты, но да — очевидно теряются пакеты. Делать бесконечно большой буфер тоже не вариант — звук там — не из файл происходит, а из источника в реальном времени и рассинхрон с видеорядом недопустим (допуск 1/10 секунды — что по идее более чем). Создалось впечатление что нет арбитража очередности пакетов при использовании композитных устройств на USB, возможно это проблема USB в целом, а возможно проблема STM в частности, ну или я что-то делаю не так.
                            0
                            Пакеты теряться не могут, т.к. хост будет переотправлять их до тех пор, пока девайс не подтвердит получение, а уже в девайсе их можно потерять только из-за своих ошибок. Если это не изохронный режим, но в нём нежелательно параллельно поднимать что-то ещё, если нет уверенности, что полосы интерфейса и ресурсов контроллера гарантированно хватит на всех.

                            Арбитраж напишите свой, если его нет или работает некорректно. Или вы на уровне ОС работаете?
                              0
                              Понятно, что теряются в девайсе. Может и из-за своих, но я бы не был столь категоричным, может и из-за ошибок разработчиков девайса.
                      +1
                      Нет, не хрипит.
                        0
                        Надо будет ваш вариант попробовать адаптировать. Спасибо!
                        0
                        1. У Вас какой MCU?
                        2. Как организован вывод звука?
                        3. Откуда это все тактируется?
                          0
                          У меня STM32F407G-DESC1 и все по примерам, ну кроме того, что я к ней еще USB3300 ULPI прикрутил.

                          В общем как-нибудь разберусь. Может быть ))
                            +2

                            Обратите внимание:


                            1. На размер приемного буфера USB. Назначается в файле usbd_conf.c функцией HAL_PCDEx_SetRxFifo ( ). Расчет есть в руководстве по HAL для F4.
                            2. На мастерклок. Лучше его не колхозить из тактовой частоты MCU, а взять с калиброванного генератора.
                        +1
                        Господи, спасибо огромное.
                        Вот сейчас лежу, и пытаюсь реализовать абсолютно то же самое, но используя библиотеку libopencm3.
                          +3
                          Всегда пожалуйста!
                          Я для этого и начал публикацию, чтобы другие обошли мои грабли.
                            0

                            Не могу не спросить… А почему лежу ?

                              0
                              Мне почему-то лучше программируется лежа :)
                            +1
                            Почему-то хочется вместо слова «составное» видеть слово «композитное».
                            По крайней мере, слово «композитное» сразу вызывает в голове нужные ассоциации.
                              0
                              Долго я пытался запустить ADTRX UR4QBP. Так ничего и не получилось, к сожалению.
                                +1

                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                  Самое читаемое