Два сигнала телеметрии, формируемые в микроконтроллере библиотекой uP7 и отображаемые на компьютере в утилите-клиенте Baical
Два сигнала телеметрии, формируемые в микроконтроллере библиотекой uP7 и отображаемые на компьютере в утилите-клиенте Baical

Зачастую разработчику, или даже пользователю, требуется посмотреть, что происходит внутри устройства. Обычно в таких ситуациях используют либо текстовой вывод в терминал (через голый UART или самописный протокол гарантированной доставки), либо пишут свои собственные системы логирования. Однако, всегда ли оправдан такой подход? Есть ли решение проще и производительнее? В данной статье мы рассмотрим одно из таких - библиотеку логирования uP7.

Предисловие

За свою карьеру я несколько раз встречался с проблемой отсутствия возможности логирования. Работая над своими проектами, я либо отказывался от него вовсе, либо отправлял данные по самописному протоколу гарантированной доставки по UART на компьютер, где их принимал python скрипт и строил графики. Оба варианта меня не удовлетворяли как по времени, так и по качеству.

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

Сейчас же мне захотелось довести до ума один из своих проектов, в котором применена достаточно нетривиальная система автоматического управления. Для разработки данного проекта изучение внутреннего поведения сигналов в реальном времени жизненно необходимо. 

Писать свою систему сбора телеметрии мне не хотелось, поскольку это достаточно тяжелая задача и я не был уверен, что смог бы сделать лучше, чем то, что я уже видел. И тут я вспомнил, что на habr имеется статья о сравнении компьютерных библиотек логирования. Просматривая их, я обнаружил, что у самой производительной из них (P7) имеется версия под микроконтроллер (uP7)! 

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

Что из себя представляет uP7?

uP7 - это библиотека логирования данных, разработанная для очень мелких встраиваемых систем. Из зависимостей ей нужно от пользователя всего три обязательных метода. А потребление ресурсов очень небольшое (на сайте имеется статистика потребления оперативной памяти и приведены замеры по скорости). Она позволяет отправлять как данные телеметрии, так и отладочные сообщения разного уровня важности. 

В дополнение, к данной библиотеке имеется клиент для ПК - Baical, который отображает полученную телеметрию в реальном времени или из файла. Клиент доступен под Windows и Linux.

Все так просто?

Не совсем. Чтобы интегрировать данную библиотеку себе в проект, а потом наслаждаться красивыми (или не очень) графиками со своего ПК, придется приложить немного усилий. А именно:

  1. Включить в состав своего проекта библиотеку uP7

  2. Включить в состав проекта препроцессор для uP7.

  3. Реализовать ряд функций, требуемых библиотеке от разработчика.

  4. Написать/использовать протокол гарантированной доставки и реализовать на его основе proxy-сервер на стороне компьютера.

Разберем всю эту последовательность на рабочем примере.

Интеграция на стороне микроконтроллера

Для простоты возьмем за основу готовый пример, написанный для одной из прошлый статей. В нем уже реализован протокол гарантированной доставки внутри одного пакета - UDP. Проект, после всех описных изменений находится здесь.

Для интеграции uP7 на стороны микроконтроллера необходимо:

  1. Включить код библиотеки uP7 в состав проекта. Я поместил код библиотеки в отдельный субмодуль lib_up7 и включил его в директорию submodules, расположенную в корне проекта. Далее прописал пути до него в CMakeLists.txt.

  2. Собрать препроцессор для uP7 и поместить исполняемый файл в удобную директорию проекта (или вообще поместить вне проекта и прописать в PATH). Что за препроцессор такой? Дело в том, что для того, чтобы избежать динамической инициализации, требуется на этапе компиляции знать, сколько будет сигналов телеметрии или сообщений пользователю. Для этого, перед каждой сборкой проекта, требуется запускать препроцессор, который анализирует использование библиотечных методов в проекте и изменяет значение define-ов, участвующих в инициализации библиотеки уже на этапе выполнения кода. Получить все исходники проекта uP7 можно на официальном сайте.  Внутри имеется CMakeLists.txt, воспользовавшись которым можно получить исполняемый файл препроцессора. Выполняется это следующей командой (находясь в директории со скачанным проектом):

    $ mkdir build && cd build && cmake .. && make

    Файл будет располагаться по пути ./build/uP7preProcessor/uP7preProcessor. Для своего удобства я поместил его прямо в субмодуль lib_up7.

  3. Добавить в проект конфигурационные файлы для препроцессора и библиотеки. Оба файла поместил в корневую директорию up7, находящуюся в корне проекта:

    1. up7_preprocessor.xml - взят из демонстрационного проекта. Частично заполняется пользователем, частично меняется препроцессором. От пользователя требуется указать поля:

      1. Name - имя проекта, под который собирается приложение.

      2. Bits - x32 или x64. Разрядность процессора, под который собирается библиотека.

      3. wchar_t - 16 или 32 бита приходится на wchar_t переменную.

      4. IDsHeader - генерировать или нет дополнительный заголовок c телеметрией и ID или нет.

      5. Pattern Mask -  в каких файлах будет происходить поиск. Тут следует вписать расширения файлов, в которых вы применяете библиотечные методы: .c, .h, .hpp

    2. uP7platform.h - тут нужно выбрать максимальный размер потребляемого стека и стека из него, доступного для трассировки. По умолчанию это 1024 и 512 байт соответственно. Также тут нужно выбрать тип переменной телеметрии. Доступны следующие варианты: double, float, int64_t, int32_t. Поскольку используемый микроконтроллер на ядре Cortex-M0, то я выбрал int32_t. Для этого задал значение define-а UP7_PRECISE_TYPE_INDEX равным 3.

  4. Добавить в систему сборки вызов препроцессора перед каждой сборкой. Поскольку в проекте используется cmake, то добавление выглядит следующим образом:

    add_custom_command(TARGET ${PROJ_NAME}.elf
    	PRE_BUILD
      COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/submodules/lib_up7/uP7preProcessor ${CMAKE_CURRENT_SOURCE_DIR}/up7/up7_preprocessor.xml ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/up7
    )

    Здесь:

    1. ${CMAKE_CURRENT_SOURCE_DIR}/submodules/lib_up7/uP7preProcessor - путь до файла препроцессора.

    2. ${CMAKE_CURRENT_SOURCE_DIR}/up7/up7_preprocessor.xml - путь до файла конфигурации препроцессора.

    3. ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/up7 - путь до директории, в которую будут генерироваться временные файлы для сборки.

  5. Если предполагается, что телеметрия будет отправляться более чем из одного потока, то в системе сборки нужно добавить определение -DUP7_LOCK_AVAILABLE, которое разрешает блокировку ресурса посредством методов, предоставляемых пользователем.

  6. Требуется описать необходимые для библиотеки методы:

    1. cbTimerFrequency - возвращает частоту системного таймера. 

    2. cbTimerValue - возвращает текущее значение системного таймера.

    3. cbSendPacket - отправляет все чанки, которые подготовила библиотека для отправки (например, на компьютер). Об этом методе следует сделать пару замечаний:

      1. Метод может быть вызван для отправки сразу нескольких сообщений подряд.

      2. Метод предполагает, что пользователь предоставляет гарантированную доставку.

    Реализацию методов можно посмотреть здесь.

  7. В случае использования многопоточной отправки (-DUP7_LOCK_AVAILABLE был задан), требуется еще определить методы p7_lock и p7_unlock, для предоставления эксклюзивного доступа. Реализацию методов можно также здесь.

  8. Инициализировать библиотеку до первого ее использования. Для этого в методе init_up7, расположенным в файле telem.c, создается временная структура типа stuP7config, заполняемая пользователем. По ней метод uP7initialize производит инициализацию внутренних переменных. В дальнейшем заполненная структура не нужна и по выходе из функции она уничтожается. Метод init_up7 вызывается в main до запуска планировщика.

  9. Зарегистрировать отправляемые сигналы и сообщения. В примере происходит регистрация двух телеметрических переменных:

    1. В потоке led_blink_thread, расположенном в субмодуле lib_service, файле led_blink.c, переменной led_blink_state, которая отображает текущее состояние светодиода, меняющего свое состояние раз в секунду в диапазоне от 0 до 1.

    2. В потоке eth_thread, расположенном в файле eth.c в корне проекта, sin_test, которая будет изменяться пилой от -10000, до 10000. Ее показания будут отправляться раз в миллисекунду (на деле немного меньше, из-за того, что ОС таймер ос тоже с периодом одна миллисекунда).

    Для регистрации переменной требуется указать:

    1. Имя переменной

    2. Минимальное значение переменной

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

    4. Максимальное значение

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

    6. Состояние счетчика по умолчанию. 

    В случае успешной регистрации переменной будет получен дескриптор, с помощью которого будет происходить отправка. 

10. Начать отправлять требуемую статистику. Для телеметрического кадра требует только передать в метод uP7TelCreateCounter дескриптор переменной и ее новое значение.

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

...
b'13c15f071800053b8d5c020000000000111c000002000000'
b'13c15f071800053b8f5c020000000000121c000002000000'
b'13c15f071800053b915c020000000000131c000002000000'
b'13c15f071800053b935c020000000000141c000002000000'
b'13c15f071800053b955c020000000000151c000002000000'
b'13c15f071800053b975c020000000000161c000002000000'
b'13c15f071800053b995c020000000000171c000002000000'
b'13c15f071800053b9b5c020000000000181c000002000000'
b'13c15f071800053b9d5c020000000000191c000002000000'
b'13c15f071800053b9f5c0200000000001a1c000002000000'
b'13c15f071800053ba15c0200000000001b1c000002000000'
b'13c15f071800053ba35c0200000000001c1c000002000000'
b'13c15f071800053ba55c0200000000001d1c000002000000'
b'13c15f071800053ba75c0200000000001e1c000002000000'
b'13c15f071800053ba95c0200000000001f1c000002000000'
b'13c15f071800053bab5c020000000000201c000002000000'
...

Однако, для того чтобы отображать эти данные в формате графиков, требуется провести интеграцию и на стороне компьютера.

Интеграция на стороне компьютера

uP7 отвязана от транспортных протоколов сама по себе и использует транспортный протокол пользователя. Утилита отображения телеметрии Baical работает только с библиотекой P7. А значит нужно звено, которое бы приняло данные от транспортного протокола пользователя и преобразовало бы их в протокол P7, необходимый для отображения в графической утилите. 

Этим звеном служит proxy-библиотека для uP7. Нам же необходимо воспользоваться ею и написать свой proxy-server. 

Данная библиотека требует предварительной инициализации. А именно - ей нужно передать параметры подключения к слушающему серверу и файл с описанием используемого uP7 протокола. Данный файл генерируется препроцессором перед компиляцией прошивки для микроконтроллера. Его наличие позволяет не вкладывать в прошивку лишнюю информацию о зарегистрированных сигналах телеметрии или сообщения пользователя.

Также библиотеке нужно знать частоту системного таймера в микроконтроллере. 

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

В примере я использую локальный адрес 127.0.0.1, а номер порта получаю из консоли. Данный порт нужно будет указать в утилите Baical, в настройках локального порта.

Настройка приема транзакций от proxy сервера в утилите Baical
Настройка приема транзакций от proxy сервера в утилите Baical

Запускать Baical надо перед запуском сервера. Для удобства можно написать скрипт.

Пример запуска сервера:

$ ./w7500p_udp_proxy /home/vadim/projects/w7500p_up7_test/up7/ 5270 6720 1000
  • /home/vadim/projects/w7500p_up7_test/up7/ - путь до директории с файлами конфигурации библиотеки в составе проекта для микроконтроллера

  • 5270 - порт, с которого приходят сообщения от микроконтроллера

  • 6720 - порт, в которых уходят P7 пакеты для Baical

  • 1000 - частота системного таймера микроконтроллера

После успешного соединения, в листе соединений Baical-а будет наш сервер.

Итоги

В этой статье было показано, как можно достаточно просто интегрировать библиотеку логирования внутрь своего проекта. Библиотека является достаточно небольшой по размеру и потребляет относительно немного RAM, однако предоставляет высокую гибкость для интегратора.

Комментарии (8)


  1. Spym
    01.08.2021 21:21

    Очень занятный проект. Необходимость генерации профиля под конкретное приложение напрягает, потому что несколько усложняет цикл отладки в процессе разработки. Я пару лет назад запилил свой похожий велосипед на C++17, который даже в одном продукте работает; более богатая система типов современного C++ позволяет избавиться от препроцессинга и в то же время не гонять строки через интерфейс трассировки целиком. В итоге я, правда, всё равно им не доволен: реализация вышла сложной, есть потребность в нетривиальной статической инициализации, API неудобный, да и от макросов не получилось избавиться. Сижу ищу альтернативы получше, и в связи с этим вопрос:

    Есть ли решение проще и производительнее? В данной статье мы рассмотрим одно из таких

    Можете огласить весь список конкурирующих решений, пожалуйста?


    1. Vadimatorikda Автор
      01.08.2021 21:55
      +3

      Необходимость генерации профиля под конкретное приложение напрягает

      Можно использовать сразу P7. На сайте так и написано) Моей целью было изучить решения, что позволят мне в дальнейшем получать телеметрию с минимальной тратой ресурсов (да, преждевременная оптимизация и попытка залезть в бутылку).

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


      1. meda1ex
        04.08.2021 14:00

        Для микроконтроллеров ST имеется программа STM Studio, которая позволяет смотреть графики изменения внутренних переменных с периодом от 1мс, обращаясь напрямую по адресу


        1. ostryh
          04.08.2021 15:57
          +1

          Увы я не могу оценить Вашу ссылку без регистрации st.com. Для дебага это может быть удобно - бесспорно.

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

          Т.е. вы приводите пример хорошего дебаггера, а автор пример инструментов мониторинга систем в дикой среде и без привязки к конкретному производителю.


          1. meda1ex
            05.08.2021 07:45

            Согласен с вами, немного попутал.

            Увы я не могу оценить Вашу ссылку без регистрации st.com. Для дебага это может быть удобно - бесспорно.

            STM Studio - программа помогающая отлаживать исходный код, выводя на график значения переменных, при этом необходимо быть подключенным к отлаживаемому устройству через SWD и соответствующий программатор. Согласно информации с st.com сейчас STM Studio not recommended for new design. Новая, похожая, разработка называется STM32CubeMonitor.

            Для телеметрии подходит, мягко говоря, слабо


    1. sami777
      02.08.2021 12:23

      Поменять камень. Для стм32 все это есть из коробки ide. И городить дополнительный порт из уарта для отладки не надо - есть свой специальный.


      1. Spym
        02.08.2021 12:43
        +3

        Это всё имело бы смысл, если бы речь шла об отладке на столе. У меня (и у автора публикации) задача шире, и включает в себя сбор телеметрии с устройства в полевых условиях для диагностики и тонкой доводки контуров управления. Одним лишь UART дело тоже редко ограничивается.


    1. Lau
      02.08.2021 23:37
      +2

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