Когда нужна телеметрия: интегрируем в проект библиотеку логирования uP7
Два сигнала телеметрии, формируемые в микроконтроллере библиотекой uP7 и отображаемые на компьютере в утилите-клиенте Baical
Зачастую разработчику, или даже пользователю, требуется посмотреть, что происходит внутри устройства. Обычно в таких ситуациях используют либо текстовой вывод в терминал (через голый UART или самописный протокол гарантированной доставки), либо пишут свои собственные системы логирования. Однако, всегда ли оправдан такой подход? Есть ли решение проще и производительнее? В данной статье мы рассмотрим одно из таких — библиотеку логирования uP7.
Предисловие
За свою карьеру я несколько раз встречался с проблемой отсутствия возможности логирования. Работая над своими проектами, я либо отказывался от него вовсе, либо отправлял данные по самописному протоколу гарантированной доставки по UART на компьютер, где их принимал python скрипт и строил графики. Оба варианта меня не удовлетворяли как по времени, так и по качеству.
В местах работы же, напротив, использовались свои библиотеки и системы сбора телеметрии. Где-то неплохие, где-то похуже. Однако, все они были закрытые и на их поддержание и доработки уходили человека-месяцы.
Сейчас же мне захотелось довести до ума один из своих проектов, в котором применена достаточно нетривиальная система автоматического управления. Для разработки данного проекта изучение внутреннего поведения сигналов в реальном времени жизненно необходимо.
Писать свою систему сбора телеметрии мне не хотелось, поскольку это достаточно тяжелая задача и я не был уверен, что смог бы сделать лучше, чем то, что я уже видел. И тут я вспомнил, что на habr имеется статья о сравнении компьютерных библиотек логирования. Просматривая их, я обнаружил, что у самой производительной из них (P7) имеется версия под микроконтроллер (uP7)!
Я решил изучить этот вопрос и не пожалел, что потратил на это время. Но обо все по порядку.
Что из себя представляет uP7?
uP7 — это библиотека логирования данных, разработанная для очень мелких встраиваемых систем. Из зависимостей ей нужно от пользователя всего три обязательных метода. А потребление ресурсов очень небольшое (на сайте имеется статистика потребления оперативной памяти и приведены замеры по скорости). Она позволяет отправлять как данные телеметрии, так и отладочные сообщения разного уровня важности.
В дополнение, к данной библиотеке имеется клиент для ПК — Baical, который отображает полученную телеметрию в реальном времени или из файла. Клиент доступен под Windows и Linux.
Все так просто?
Не совсем. Чтобы интегрировать данную библиотеку себе в проект, а потом наслаждаться красивыми (или не очень) графиками со своего ПК, придется приложить немного усилий. А именно:
Включить в состав своего проекта библиотеку uP7.
Включить в состав проекта препроцессор для uP7.
Реализовать ряд функций, требуемых библиотеке от разработчика.
Написать/использовать протокол гарантированной доставки и реализовать на его основе proxy-сервер на стороне компьютера.
Разберем всю эту последовательность на рабочем примере.
Интеграция на стороне микроконтроллера
Для простоты возьмем за основу готовый пример, написанный для одной из прошлый статей. В нем уже реализован протокол гарантированной доставки внутри одного пакета — UDP. Проект, после всех описных изменений находится здесь.
Для интеграции uP7 на стороны микроконтроллера необходимо:
Включить код библиотеки uP7 в состав проекта. Я поместил код библиотеки в отдельный субмодуль lib_up7 и включил его в директорию submodules, расположенную в корне проекта. Далее прописал пути до него в CMakeLists.txt.
Собрать препроцессор для uP7 и поместить исполняемый файл в удобную директорию проекта (или вообще поместить вне проекта и прописать в PATH). Что за препроцессор такой? Дело в том, что для того, чтобы избежать динамической инициализации, требуется на этапе компиляции знать, сколько будет сигналов телеметрии или сообщений пользователю. Для этого, перед каждой сборкой проекта, требуется запускать препроцессор, который анализирует использование библиотечных методов в проекте и изменяет значение define-ов, участвующих в инициализации библиотеки уже на этапе выполнения кода. Получить все исходники проекта uP7 можно на официальном сайте. Внутри имеется CMakeLists.txt, воспользовавшись которым можно получить исполняемый файл препроцессора. Выполняется это следующей командой (находясь в директории со скачанным проектом):
$ mkdir build && cd build && cmake .. && make
Файл будет располагаться по пути ./build/uP7preProcessor/uP7preProcessor. Для своего удобства я поместил его прямо в субмодуль lib_up7.
Добавить в проект конфигурационные файлы для препроцессора и библиотеки. Оба файла поместил в корневую директорию up7, находящуюся в корне проекта:
up7_preprocessor.xml — взят из демонстрационного проекта. Частично заполняется пользователем, частично меняется препроцессором. От пользователя требуется указать поля:
Name — имя проекта, под который собирается приложение.
Bits — x32 или x64. Разрядность процессора, под который собирается библиотека.
wchar_t — 16 или 32 бита приходится на wchar_t переменную.
IDsHeader — генерировать или нет дополнительный заголовок c телеметрией и ID или нет.
Pattern Mask — в каких файлах будет происходить поиск. Тут следует вписать расширения файлов, в которых вы применяете библиотечные методы: .c, .h, .hpp
uP7platform.h — тут нужно выбрать максимальный размер потребляемого стека и стека из него, доступного для трассировки. По умолчанию это 1024 и 512 байт соответственно. Также тут нужно выбрать тип переменной телеметрии. Доступны следующие варианты: double, float, int64_t, int32_t. Поскольку используемый микроконтроллер на ядре Cortex-M0, то я выбрал int32_t. Для этого задал значение define-а UP7_PRECISE_TYPE_INDEX равным 3.
Добавить в систему сборки вызов препроцессора перед каждой сборкой. Поскольку в проекте используется 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 )
Здесь:
${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 — путь до директории, в которую будут генерироваться временные файлы для сборки.
Если предполагается, что телеметрия будет отправляться более чем из одного потока, то в системе сборки нужно добавить определение -DUP7_LOCK_AVAILABLE, которое разрешает блокировку ресурса посредством методов, предоставляемых пользователем.
Требуется описать необходимые для библиотеки методы:
cbTimerFrequency — возвращает частоту системного таймера.
cbTimerValue — возвращает текущее значение системного таймера.
cbSendPacket — отправляет все чанки, которые подготовила библиотека для отправки (например, на компьютер). Об этом методе следует сделать пару замечаний:
Метод может быть вызван для отправки сразу нескольких сообщений подряд.
Метод предполагает, что пользователь предоставляет гарантированную доставку.
Реализацию методов можно посмотреть здесь.
В случае использования многопоточной отправки (-DUP7_LOCK_AVAILABLE был задан), требуется еще определить методы p7_lock и p7_unlock, для предоставления эксклюзивного доступа. Реализацию методов можно также здесь.
Инициализировать библиотеку до первого ее использования. Для этого в методе init_up7, расположенным в файле telem.c, создается временная структура типа stuP7config, заполняемая пользователем. По ней метод uP7initialize производит инициализацию внутренних переменных. В дальнейшем заполненная структура не нужна и по выходе из функции она уничтожается. Метод init_up7 вызывается в main до запуска планировщика.
Зарегистрировать отправляемые сигналы и сообщения. В примере происходит регистрация двух телеметрических переменных:
В потоке led_blink_thread, расположенном в субмодуле lib_service, файле led_blink.c, переменной led_blink_state, которая отображает текущее состояние светодиода, меняющего свое состояние раз в секунду в диапазоне от 0 до 1.
В потоке eth_thread, расположенном в файле eth.c в корне проекта, sin_test, которая будет изменяться пилой от -10000, до 10000. Ее показания будут отправляться раз в миллисекунду (на деле немного меньше, из-за того, что ОС таймер ос тоже с периодом одна миллисекунда).
Для регистрации переменной требуется указать:
Имя переменной
Минимальное значение переменной
Минимальное значение, ниже которого, при отображении на графике, значение сигнала будет подчеркиваться как выходящее за пределы допустимых границ
Максимальное значение
Максимальное значение, выше которого, при отображении на графике, значение сигнала будет подчеркиваться как выходящее за пределы допустимых границ
Состояние счетчика по умолчанию.
В случае успешной регистрации переменной будет получен дескриптор, с помощью которого будет происходить отправка.
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
Запускать 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, однако предоставляет высокую гибкость для интегратора.