Pull to refresh

Что Должно Быть в Каждом FirmWare Pепозитории

Reading time10 min
Views7.3K

В этом тексте я предлагаю порассуждать, что же должно быть в нормальном взрослом firmware репозитории (репе/общаке) безотносительно к конкретному проекту. То есть самые универсальные и переносимые программные компоненты (кирпичики/SubSystems), которые могут пригодиться в практически любой сборке.

Загрузчик (BootLoader)

Загрузчик нужен для обновления прошивки без специализированного оборудования типа программаторов. Это очень важно для пользователей. Загрузчик обязательно должен уметь обновлять по UART так как драйвер UART занимает мизерное количество On-Chip NorFlash памяти. Остальные интерфейсы обновления по обстоятельствам или поддержка этих тяжеловесных интерфейсов (CAN, USB, LoRa, TCP, WiFI) должна быть в приложении так как там больше Flash памяти. Задача минимального загрузчика это найти в NorFlash приложение и прыгнуть в него. А если приложения нет, то просить прошивку в UART, чтобы совсем в тыкву не превращаться. Тем более, что переходники с USB-UART (например на основе чипа CP2102) самые дешевые из всех возможных интерфейсов. Всего порядка 6 EUR.

Компонент управления логированием

Должен быть программный компонент для управления логированием в UART. Раскраска логов в TeraTerm/Putty, выбор/отмена TimeStamp выбор отмена логирования для конкретного компонента. Это поможет анализировать лог загрузки устройства и следить за событиями внутри прошивки в run-time(е), просто глядя в UART.

фрагмент цветного лога загрузки в UART
фрагмент цветного лога загрузки в UART

CLI (Command Line Interface) он же Shell он же консоль он же TUI (Text User Interface)

CLI(шка) обязательный компонент для отладки и тестирования прошивок. С этим компонентом можно общаться с устройством на человеческом языке. Многие успешные продукты обладают CLI (российский Flipper-Zero, китайский NanoVNA V2, швейцарский U-Blox ODIN C099-F9P, канадский Bluetooth модуль BC127 от Sierra Wireless).

*ADT: Самые Базовые абстрактные структуры данных

a) Универсальная FIFO (очередь)

В любом Firmware проекте точно понадобится надежная FIFO(шка). Например, FIFO нужна для предварительного хранения данных приходящих из UART Rx, для хранения кнопок с HID клавиатуры, для хранения принятых пакетов из LoRa или TCP. FIFO нужна для того же CLI. Причем реализация FIFOшки должна подстраиваться под любой тип данных: char, uint16_t, array[N].

b) Универсальная LIFO (стек)

Если ваша прошивка парсит XML файл с конфигами на SD карте, то вам точно понадобится реализация LIFO.

с) Циклический массив

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

d) Связанный список

Linked List в прошивке может вам понадобится для организации списка команд CLI.

e) HashSet

Также понадобится для разбора XML.

Вычислители контрольных сумм CRC

При реализации протоколов понадобится целая куча разнообразных контрольных сумм (CRC8, CRC16, CRC24, CRC32). Также CRC нужны для энергонезависимых файловых систем. Сюда же относятся алгоритмы Hash функций (например SHA256) и цифровых подписей.

*MCAL: MicroController Abstraction Layer для каждого семейства микроконтроллеров

Кодовая база должна быть универсальной так в AUTOSAR, однако для каждого семейства микроконтроллеров будет своя уникальная реализация MCAL. В MCAL надо прописать реализации универсального API. Например для управления GPIO, прописи on-chip FLASH, запуска таймеров, пуляния пакетов в I2C/SPI/I2S/UART, вычитывания ADC и прочего.
Если вендор поставляет свой HAL то вы должны написать Binding(и) для этого HAL чтобы вызывать его из универсальных функций MCAL.

*ASICs: Драйверы периферийных чипов с управлением по I2C/SPI/MDIO

В каждой электронной плате есть множество умных навороченных чипов с конфигурацией по I2C/SPI/MDIO, например DS3231. Я видел ASIC чип у которого 936х 32-битных регистров конфигурации. В репозитории должна быть папка "asics" для складирования драйвов для каждого такого чипа и отдельная папка с модульными тестами для чипа. Напишите в комментариях драйверы каких умных SPI/I2C/MDIO чипов писали Вы.

*core: Компонент для поддержки каждого конкретного процессорного ядра

Для каждого конкретного процессорного ядра (Cortex-M3/Cortex-M33/PowerPC/Tensilica Xtensa и про) должна быть папка с сорцами описания этого ядра. Это функции вкл/откл прерываний, проверка прерывание ли сейчас, перезагрузка ядра, печать таблицы прерываний, управление системным таймером и прочее.

*microcontroller: Компонент для поддержки каждого конкретного микроконтроллера

Для каждого конкретного MCU должна быть папка с описанием этого MCU. Это прежде всего перечень пинов, описание ядра, памяти и доступной периферии.

*boards: Компонент для поддержки каждой конкретной платы (platform code)

Для каждой платы должна быть создана отдельная папка в репозитории. Там должны быть исходники конфигурации, которые говорят сколько в этой конкретной плате кнопок, LED(ов), SPI, I2C, SDIO. Какие там есть драйверы чипов и прочее.

*projects: Отдельная папка для каждой конкретной сборки

Все сборки должны делить общую кодовую базу. Поэтому в папке с проектом в принципе не должно быть *.с файлов. Максимум один Makefile, парочка конфигов в *.h, и еще пара скриптов.

Программный Таймер

Программный таймер это способ из одного аппаратного таймера сделать сотни программных таймеров. Очень полезно, если все аппаратные таймеры исчерпаны или их настройка на неизвестном SoC(е) очень трудна и непонятна. Хороший программный таймер позволяет настраивать период и фазу счета и полностью конфигурируется в run-time из CLI.

Синтаксический разбор строчек

При реализации CLI нужны функции для синтаксического разбора чисел всех типов данных из текстовых ASCII строк. Поэтому для каждого типа данных (float, int32_t, string, hexArray) следует реализовать парсеры типов данных. Своего рода интерпретаторы чисел. Это очень большая часть кода порядка 2k строк.

Компонент компрессии-декомпрессии

Может случится, что битовая скорость трансивера очень низкая (LoRa, BLE), а данных надо передавать много. В этом случае может спасти сжатие или компрессия данных (например кодек LC3, SBC или GZip) и декомпрессия на стороне приемника. В репе должен быть надежный и протестированный программный компонент кодек сжатия данных.

Драйвер подавление дребезга контактов

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

Программный Триггер Шмитта

Это звено для реализации гистерезиса. Очень полезно для подавления аналогового шума с датчиков при релейном управлении чем-либо.

Компонент limiter

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

Папка miscellaneous

В эту папку надо складывать те компоненты, которые сложно классифицировать во что-то конкретное. Функций Base64 кодека, функции реверса порядка слов/байт/бит, функции swap для всех типов данных, сравнение float(ов) и прочее. Всё это как правило пригождается в разработке firmware от проекта к проекту. В идеале папка miscellaneous должна быть пустая.

Реализация механизма выделения и освобождения памяти

Весьма вероятно, что придется накропать собственную надежную детерминированную и переносимую версию функций malloc и free, c возможностью расширенной диагностики и сборки мусора.

Компонент с математикой (сomputing)

Немного линейной алгебры и численных методов. Например, в системах автоматического управления на MCU очень нужна функция для вычисления угла между векторами (с учетом знака, естественно). А это сразу подтягивает реализацию скалярного и векторного умножения. При работе с ЦАП/DAC(ами) пригодится вычисление семпла синуса, семпла PWM, Chirp(а), Saw(а), Fence(а). Еще может понадобится целочисленное возведение в степень и вычисление квадратного корня. Бывает надо 7-битное знаковое число или 13-битное знаковое число преобразовать в стандартный int32_t. Для этого тоже нужны свои математические алгоритмы.

Какую математику приходилось реализовывать в микроконтроллерах вам?

Программная реализация календаря

Если в проекте предусмотрены часы реального времени, на плате есть кварц и батарейка, а аппаратные часы внутри MCU могут только увеличивать каждую секунду счетчик, то придется найти и протестировать надежный программный календарь. Даешь количество секунд, получаешь дату и время и наоборот.

Цифровые фильтры

Цифровые фильтры (ЦФ) нужны не только для обработки аудиопотока и радарных данных. ЦФ понадобится для реализации подавления дребезга контактов, для вычисления направлений движения RFID маяков и прочего.

Шифровальщик AES

Рано или поздно передаваемую прошивку по загрузчику придется шифровать или хранить в памяти в шифрованном виде. Поэтому надо выбрать доверенный и протестированный алгоритм шифрования данных, например AES256. В TI чипах cc26x2 AES и вовсе реализован аппаратно, и этот компонент можно отнести вообще в BSP.

Парсеры протоколов

Устройство будет взаимодействовать с внешним миров. Ту же прошивку надо передавать по какому-то протоколу (ModBus, x/yModem, MQTT, TFTP, CCP, XCP). Надо парсить протоколы умных датчиков (NMEA, UBX, RDS, RTCM). Плюс есть протоколы управления исполнительными механизмами (Pelco-D). Должна быть программная реализация всех этих протоколов или серии протоколов. Больше чем уверен, что в вашей компании ещё есть какой-то собственный компанейский бинарный проприетарный протокол.

NVRAM: или энергонезависимая Key-Value-Map(ка)

В любой крупной Си программе накопится очень много параметров и констант, которые придется варьировать, настраивать, калибровать. Чтобы не пере собирать и пере прошивать каждый раз гаджет, надо реализовать энергонезависимый журнал прямо на Target(е). Вот вам War Story. Устройство висит под потолком. Подключился через LoRa к CLI. Прописал новый параметр через CLI, reset(нул) прошивку через CLI и у тебя новая прошивка. Easy.

Если памяти мало, то берём эту https://habr.com/ru/articles/706972/
Если памяти много, то берём эту https://habr.com/ru/articles/732442/

Third Party код

Наверняка в вашей прошивке будет код от других Onen Source проектов. Это может быть FreeRTOS, LwIP, FatFs. Этот код тоже надо подвергнуть версионному контролю. Однако ни в коем случае не надо этот код менять как бы плохо но бы не был написан. Иначе вы станете владельцем этого кода и понесете за него ответственность. Очень важно также не переформатировать этот Third party код, как как это позволит делать сравнение используемого кода с новой его версией от официального вендора.

Статические библиотеки

Некоторые программные компоненты распространяются не в виде исходников, а в виде предварительно скомпилированных библиотек (например кодек lc3). Поэтому их *.a файлы надо тоже подвергать версионному контролю.

Код генераторы

В программировании микроконтроллеров часто много повторяющегося по структуре кода. Например синтаксический разбор CAN матриц (8 байт полезных данных в пакете). Поэтому в репозиторий можно добавить утилиты кодогенераторы для генерации C-функций делающих синтаксический разбор пакетов с известной структурой.

Модульные тесты

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

Какие папки должны быть в репозитории?

Код любой прошивки можно условно разделить на 5 фракций. Это sensitivity, connectivity, computing, storage, control, security. Поэтому для каждого понятия должна быть папка.

Вот например рассмотрим папку computing. В ней следует складировать все компоненты которые так или иначе связаны с вычислением чего-либо. Это вычислители CRC, вычислители календаря, ЦОС, решатели уравнений, генераторы QR кодов, обработчики интервалов, арифметика над GNSS координатами, астрономические вычислители, триггер Шмитта, программный DAC и тому подобное. Всё это сохраняем в папку computing.

Сборка из Make

Утилита Make это самый гибкий способ управлять циклопическими репозиториями. Можно одной строчкой добавлять/исключать тысячи файлов для тысяч сборок. Поэтому для каждой сборки надо самим писать Makefile для каждого компонента *.mk файл. Сборка из Make стимулирует придерживаться модульности и изоляции компонентов. Make идеален для масштабирования кодовой базы.

Makefile(лы) хороши тем, что можно добавить кучу проверок зависимостей и assert(ов) на фазе bash-скриптов прямо в *.mk файлах еще до компиляции самого кода, так как язык программирования make поддерживает условные операторы и функции. Можно очень много ошибок отловить на этапе отработки утилиты make. При этом make очень прост. Вся спека GNU Make это 226 странички. Cтю Фельдман (автор make) просто гений.

Сборка из CMake

Вероятно придется собирать компанейский репозиторий в составе Cmake проектов. Например в Zephyr Project(е). Поэтому каждый компонент должен содержать также *.cmake скрипт с правилами сборки

Скрипты автосборки

Каждая сборка должна собираться из скриптов. Скрипты должны записывать во On-Chip Flash hash последнего коммита и название GIT ветки. Это потом позволит откатиться назад и выявить причину бага в конкретный момент истории.

Буквально открыл папку с проектом, жмакнул *.bat файл и максимум через 2 минуты получил полный комплект артефактов *.hex *.bin *.elf *.map. Easy! Также сборка из скриптов позволит вам добавить сборки на сервер сборки Jenkins и каждое утро следить за тем, что собирается, а что нет.

Скрипты авто прошивки

Должна быть возможность автоматически прошивать Target. Буквально жмакнул скрипт flash.bat из папки с проектом и скормил прошивку микроконтроллеру. И еще и лог обновления сохранился в текстовый файлик.

Этот же скрипт с артефактами можно будет отгрузить клиенту, так как установку IDE для обновления прошивки от user(ов) какого-н фитнес браслета ожидать точно не стоит.

Скрипты отчистки и автоматического форматирования

Это для причесывания кода утилитой clang-format. Чтобы отступы были предсказуемыми по всему проекту. Это позволит писать более просты регулярные выражения для навигации по коду утилитой grep.

Batch cкрипт авто очистки в корне репозитория позволит удалить временные файлы c расширениям *.d *.o *.obj *.bak *.i *.pp и высвободить тонну места на диске. Тем более, что у вас на диске будет как минимум 2 экземпляра репозитория: workspcaсe для работы и release для сервера сборки типа Jenkins(а).

Авто генерация документации

Надо относится как программированию не только к созданию артефактов. Надо относится как к программированию также для создания документации. При разработке Firmware документацией является схема toolchain, блок схемы плат, блок схемы комплексов, инструкции. Все эти *.pdf, *.svg можно синтезировать из кода на языке dot. Поэтому должны быть makefile(ы) для сборки документации. Текстовые документы можно авто генерировать на LaTeX.

Вывод

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

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

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

Опасайтесь пользоваться всякого рода проприетарными GUI-IDE(шкам), так как вы потеряете возможность автоматизировать процессы производства firmware и вы будете зависеть от политики разработчика IDE.

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

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

Давайте строить хорошие репозитории.

Only registered users can participate in poll. Log in, please.
Вы используете системы контроля версий?
92.78% да90
7.22% нет7
97 users voted. 10 users abstained.
Only registered users can participate in poll. Log in, please.
Вы используете сервера сборки?
45.35% да39
54.65% нет47
86 users voted. 13 users abstained.
Only registered users can participate in poll. Log in, please.
Вы в общем согласны с представленной структурой репозитория?
52% да39
48% нет36
75 users voted. 23 users abstained.
Only registered users can participate in poll. Log in, please.
Вы собираете сборки из скриптов?
70% да56
30% нет24
80 users voted. 14 users abstained.
Only registered users can participate in poll. Log in, please.
Вы практикуете коллективное владение кодовой базой?
68.75% да55
31.25% нет25
80 users voted. 9 users abstained.
Only registered users can participate in poll. Log in, please.
Вы практикуете инспекцию программ?
60.61% Да40
39.39% Нет26
66 users voted. 19 users abstained.
Only registered users can participate in poll. Log in, please.
Вы разрабатывали firmware по правилам AUTOSAR?
10.71% да3
89.29% нет25
28 users voted. 5 users abstained.
Only registered users can participate in poll. Log in, please.
Вы собираете прошивки из-под CMake скриптов?
40% да4
60% нет6
10 users voted. 1 user abstained.
Tags:
Hubs:
Total votes 18: ↑14 and ↓4+10
Comments17

Articles