В этом тексте я предлагаю порассуждать, что же должно быть в нормальном взрослом firmware репозитории (репе/общаке) безотносительно к конкретному проекту. То есть самые универсальные и переносимые программные компоненты (кирпичики), которые могут пригодиться в практически любой сборке.
Загрузчик
Загрузчик нужен для обновления прошивки без специализированного оборудования типа программаторов. Загрузчик обязательно должен уметь обновлять по UART. Остальные интерфейсы обновления по обстоятельствам.
Компонент управления логированием
Должен быть программный компонент для управления логированием в UART. Раскраска логов в TeraTerm/Putty, выбор/отмена TimeStamp выбор отмена логирования для конкретного компонента. Это поможет анализировать лог загрузки устройства и следить за событиями внутри прошивки в run-time(е), просто глядя в UART.
CLI (Command Line Interface) или Shell
CLI(шка) обязательный компонент для отладки и тестирования прошивок. С этим компонентом можно общаться с устройством на человеческом языке. Многие успешные продукты обладают CLI (Flipper-Zero, NanoVNA, UbloxODIN).
Универсальная FIFO
В любом Firmware проекте рано или поздно понадобится FIFOшка. Например, FIFO нужна для предварительного хранения данных приходящих из UART Rx. FIFO нужна для того же CLI. Реализация FIFO должна подстраиваться под любой тип данных: char, uint16_t, array[N].
Вычислители контрольных сумм CRC
При реализации протоколов понадобится целая куча разнообразных контрольных сумм (CRC8, CRC16, CRC24, CRC32). Также CRC нужны для энергонезависимых файловых систем.
BSP (Board Support Package) для каждого семейства микроконтроллеров
Кодовая база должна быть универсальной, однако для каждого семейства микроконтроллеров будет своя уникальная реализация BSP. В BSP надо прописать реализации универсального API. Например для управления GPIO, прописи OnChip FLASH, запуска таймеров, пуляния пакетов в I2C/SPI/I2S/UART, вычитывания ADC и прочего.
Циклический массив
Циклический массив понадобится для реализации цифровых фильтров, энергонезависимых журналов.
Драйверы чипов
В каждой плате есть множество умных навороченных чипов с конфигурацией по I2C/SPI/MDIO. В репозитории должна быть папка с драйвером для каждого такого чипа и отдельная папка с модульными тестами для чипа. Напишите в комментариях драйверы каких умных SPI/I2C/MDIO чипов писали вы.
Компонент для поддержки каждого конкретного процессорного ядра
Для каждого конкретного процессорного ядра (Cortex-M3/Cortex-M33/PowerPC/Tensilica Xtensa и про) должна быть папка с сорцами описания этого ядра. Это функции вкл/откл прерываний, перезагрузка, печать таблицы прерываний, управление системным таймером и прочее.
Компонент для поддержки каждого конкретного микроконтроллера
Для каждого конкретного MCU должна быть папка с описанием этого MCU. Это прежде всего перечень пинов, описание ядра, памяти и доступной периферии.
Компонент для поддержки каждой конкретной платы (platform code)
Для каждой платы должна быть создана отдельная папка в репозитории. Там должны быть исходники конфигурации, которые говорят сколько в этой конкретной плате кнопок, LED(ов), SPI, I2C, SDIO. Какие там есть драйверы чипов и прочее.
Отдельная папка для каждой конкретной сборки
Все сборки должны делить общую кодовую базу. Поэтому в папке с проектом в принципе не должно быть *.с файлов. Максимум один Makefile, парочка конфигов в *.h, и еще пара скриптов.
Программный Таймер
Программный таймер это способ из одного аппаратного таймера сделать сотни программных таймеров. Очень полезно, если все аппаратные таймеры исчерпаны или их настройка очень трудна. Хороший программный таймер позволяет настраивать период и фазу счета и полностью конфигурируется в run-time из CLI.
Синтаксический разбор строчек
При реализации CLI нужны функции для синтаксического разбора чисел всех типов данных из текстовых ASCII строк. Поэтому для каждого типа данных следует реализовать парсеры типов данных. Своего рода интерпретатор чисел. Это очень большая часть кода.
Компонент компрессии-декомпрессии
Может случится, что битовая скорость трансивера очень низкая (LoRa), а данных надо передавать много. В этом случае может спасти сжатие или компрессия данных (например кодек LC3) и декомпрессия на стороне приемника. В репе должен быть программный компонент кодек сжатия данных.
Драйвер подавление дребезга кнопок
Часто в электронных платах есть тактовые кнопки и герконы. Они при замыкании создают дребезг. Поэтому в репозитории должен быть драйвер кнопки, который подавляет дребезг контактов, умеет отличать короткое нажатие от длинного, двойного. Всё это пригодится при отладке.
Программный Триггер Шмитта
Это звено для реализации гистерезиса. Очень полезно для подавления аналогового шума с датчиков при релейном управлении чем-либо.
Компонент limiter
Это функция, которая следит за тем, чтобы конкретная функция не вызывалась чаще чем установлено в параметрах. Очень полезно при реализации примитивов RTOS. Можно прямо в суперцикле пропускать все вызовы через limiter и таким образом выставлять период срабатывания каждой функции.
Реализация механизма выделения и освобождения памяти
Весьма вероятно, что придется накропать собственную надежную детерминированную и переносимую версию функций malloc и free, c возможностью расширенной диагностики и сборки мусора.
Компонент с математикой
Немного линейной алгебры и численных методов. Например, в системах автоматического управления на MCU очень нужна функция для вычисления угла между векторами (с учетом знака, естественно). А это сразу подтягивает реализацию скалярного и векторного умножения. При работе с ЦАП/DAC(ами) пригодится вычисление семпла синуса, семпла PWM, Chirp(а), Saw(а), Fence(а). Еще может понадобится целочисленное возведение в степень и вычисление квадратного корня. Бывает надо 7-битное знаковое число или 13-битное знаковое число преобразовать в стандартный int32_t. Для этого тоже нужны свои математические алгоритмы.
Какую математику приходилось реализовывать в микроконтроллерах вам?
Программная реализация календаря
Если в проекте предусмотрены часы реального времени, на плате есть кварц и батарейка, а аппаратные часы внутри MCU могут только увеличивать каждую секунду счетчик, то придется найти и протестировать надежный программный календарь. Даешь количество секунд, получаешь дату и время и наоборот.
Цифровые фильтры
Цифровые фильтры (ЦФ) нужны не только для обработки аудиопотока и радарных данных. ЦФ понадобится для реализации подавления дребезга контактов, для вычисления направлений движения RFID маяков и прочего.
Шифровальщик
Рано или поздно передаваемую прошивку по загрузчику придется шифровать или хранить в памяти в шифрованном виде. Поэтому надо выбрать доверенный и протестированный алгоритм шифрования данных, например AES. В IT чипах AES и вовсе реализован аппаратно и этот компонент можно отнести в BSP.
Парсеры протоколов
Устройство будет взаимодействовать с внешним миров. Ту же прошивку надо передеравать по какому то протоколу (ModBus, yModem). Должна быть программная реализация какого-то протокола или серии протоколов. Больше чем уверен, что в вашей компании есть какой-то собственный компанейский бинарный протокол.
Энергонезависимый журнал
В любой крупной С программе накопится очень много параметров и констант, которые придется варьировать, настраивать, калибровать. Чтобы не пересобирать и перепрошивать каждый раз гаджет надо реализовать энергонезависимый журнал прямо на Target(е). Вот вам War Story. Устройство висит под потолком. Подключился через LoRa к CLI. Прописал новый параметр через CLI, reset(нул) прошивку через CLI и у тебя новая прошивка. Easy.
Код генераторы
В программировании микроконтроллеров часто много повторяющегося по структуре кода. Например синтаксический разбор CAN матриц (8 байт полезных данных в пакете). Поэтому в репозиторий можно добавить утилиты кодогенераторы для генерации C-функций делающих синтаксический разбор пакетов с известной структурой.
Модульные тесты
Вся работа пойдет прахом, если ошибка в очередном коммите тихо останется незамеченной. Грош цена репозиторию без тестов. Поэтому надо покрывать код модульными тестами. Каждая нетривиальная функция должна быть покрыта тестами. Аппаратно-независимый код можно вообще тестировать прямо на PC.
Сборка из Make
Утилита Make это самый гибкий способ управлять циклопическими репозиториями. Можно одной строчкой добавлять/исключать тысячи файлов для тысяч сборок. Поэтому для каждой сборки надо самим писать Makefile для каждого компонента *.mk файл. Сборка из Make стимулирует придерживаться модульности и изоляции компонентов.
Можно добавить кучу проверок зависимостей и assert(ов) на этапе bash скриптов прямо в *.mk файлах так как язык программирования make поддерживает условные операторы и функции. Можно очень много ошибок отловить на этапе отработки утилиты make. Cтю Фельдман (автор make) просто гений.
Скрипты автосборки
Каждая сборка должна собираться из скриптов. Скрипты должны записывать во OnChipFlash hash последнего коммита и название ветки. Это потом позволит откатиться назад и выявить причину бага.
Буквально открыл папку с проектом, жмакнул *.bat файл и максимум через 2 минуты получил артефакты *.hex *.bin *.elf *.map. Easy! Также сборка из скриптов позволит вам добавить сборки на сервер сборки Jenkins и каждое утро следить за тем, что собирается, а что нет.
Скрипты авто прошивки
Должна быть возможность автоматически прошивать Target. Жмакнул скрипт flash.bat из папки с проектом и скормил прошивку микроконтроллеру. И еще и лог сохранился в текстовый файлик.
Скрипты отчистки и автоматического форматирования
Это для причесывания кода утилитой clang-format. Чтобы отступы были предсказуемыми по всему проекту. Это позволит писать более просты регулярные выражения для навигации по коду утилитой grep.
Скрипт автоочистки в корне репозитория позволит удалить временные файлы c расширениям *.d *.o *.obj *.bak *.i *.pp и высвободить тонну места на диске.
Авто генерация документации
Надо относится как программированию не только к созданию артафактов. Надо относится как к программированию также для создания документации. При разработке Firmware документацией является схема toolchain, блок схемы плат, блок схемы комплексов, инструкции. Все эти *.pdf, *.svg можно синтезировать из кода на языке dot. Поэтому должны быть makefile(ы) для сборки документации. Текстовые документы можно авто генерировать на LaTeX
Вывод
Суммирую вышесказанное можно разделить код на аппаратно-зависимый, аппаратно не зависимый и документацию. Плюс часть инфраструктурных сборок для кодогенераторов и модульных тестов на dosktop.
Пользуйтесь CLI, системами контроля версий, системами авто сборок и модульными тестами и все у вас будет хорошо и предсказуемо.
Если вы считаете, что в Firmware репозитории должно быть еще что-то универсальное и полезное, то напишите это в комментариях.
Также буду признателен, если вы отметите в комментах какие экзотические микроконтроллеры и периферийные чипы программировали лично вы. Какие технологии управления репозиториями вы считаете перспективными, а какие у вас в черном списке.
Давайте строить хорошие репозитории.
Автор:
aabzel