Комментарии 55
Не нужно фантазировать. Есть нормальные процессы разработки под железо. И тестирование, и тестирование на железе и жизненные циклы. Пишутся имитаторы окружения, имитаторы железа, делаются тестовые стенды, делается изоляция модулей программы, прогоняются анализаторы кода - это всё есть. Это совсем не так как Web и достаточно сильно отличается от десктопных плюсов, но всё уже давно есть и используется в серьёзных проектах.
А то, что 90% российских разработчиков софта под железо - "ардуинщики" или лабают а Keil MDK, так это особенности деградации отрасли и низкий уровень оплаты. Если платить 30 т.р. разработчику под STM, то естественно получишь ардуинщика, Keil, и хаос вместо нормального процесса разработки.
И, кстати, я пока не видел, чтобы Jenkins использовали - специфика несколько иная. Хотя, можно, наверное. Но после того как написана своя сборка - а она совершенно всегда требуется, так как контрольные суммы, нестандартная упаковка бинарников, внедрение ресурсов, гарантия уникальности версии, загрузки и т.д, - всё это сделать из IDE и стандартные make-файлов или невозможно или очень криво получается. И почти всегда нужно делать какие-то дополнительные нестандартные телодвижения для автоматизации процесса релизной сборки. После того, как создано ПО для прошивки и тестирования, ПО для массового производства, массовой проверки ремонта, наладки/калибровки, и т.п. то оказывается, что автоматизация сборки это наиболее простая и наименее трудоёмкая задача из всего и она уже решена каким-то образом в ходе выполнения остальных работ.
для IoT вполне актуальная схема. вот на одном из проектов запускал в продакшн: само приложение бэкенд и мобильные клиенты, но есть устройства (шлагбаумы), отдельная команда разработки и их прошивка до недавнего времени была даже не в репе!) вобщем, внедрили -- положили в реп, завели постоянный тестовый стенд, к которому дев-сервер приложения обращается, и даже выдали сваггер с этого стенда. смогли тестировать целиком наконец-то. варианты включения сборки и аплоада прошивок в общий процесс CI/CD -- сейчас как раз ресерчим и что-то похожее собираемся "изобрести". )
А где-то можно прочитать про лучшие практики, чтобы не изобретать велосипеды?
Лучшие практики формируются во время работы. Надо походить по граблям.
Можно почитать ISO26262 части 5 и 6
Part 5: Product development at the hardware level
Part 6: Product development at the software level
Есть 2 таких текста
17 атрибутов хорошей PCB
https://habr.com/ru/post/655879/
9 атрибутов хорошего firmware
https://habr.com/ru/post/655641/
И, кстати, я пока не видел, чтобы Jenkins использовали - специфика несколько иная. Хотя, можно, наверное.
Jenkins можно и вовсе локально запустить. Для Jenkins зависти отдельный репозиторий в который только git pull делать (release) .
Для разработки другой отдельный локальный репозиторий workspace. В нем можно и писать и читать.
https://habr.com/ru/articles/695978/
Используем ClearCase, make, cmake, Gdb для отладки, Nunit, Clion для редактирования кода.
Нет только сервера сборки, но спасибо за наводку.
А затем отдельный сервер? У вас сборка идёт несколько часов? КМК достаточно настроить сборку в контролируемом окружении (виртуальная машина, docker), чтобы результаты были воспроизводимы.
Да, сборка идет долго. Сама сборка минут 40 (на дебаг и релиз), юнит тесты час примерно и еще проверка статическим анализатором минут 40. Итого, около двух часов. Там конечно если распараллелить на 8 потоков, быстрее получается, но в целом меньше часа не получается.
Долгая сборка идет по причине использования спец тулов, например clearmake, вместо обычного make. Он дополнительно собирает информацию о том, какие версии файлов, с каких меток были собраны в этой сборке. Потом можно делать аудит и собственно полностью воспроизвести сборку для этой версии.
У нас 55 сборок и каждая 5 минут. Чтобы пересобрать артефакты после 1 комита надо 4 ч 30 мин.
Вот тут рассказывал, как это организовано у нас в wirenboard: https://www.youtube.com/watch?v=HEEVxZ4rBCo&t=1510s

В каком инструменте Вы производите инспекцию программ?
В Gerrit(е)?
github
На самом деле если в организации нет Gerrit(а) или другого инструмента для инспекции программ (например Swarm), то говорить про CodeReview не приходится.
Лучше уж для начала поднять в прошивках UART-CLI и модульными тестами код покрыть. Тогда это хотя бы можно будет на том же Jenkins(е) автоматически дергать.

Где Вы храните конфиги для микроконтроллерных сборок?
В DeviceTree?
нет, в хедерах
Чтобы конфиги из some_config.h файлика применились some_config.h надо include(ить) в каждый файл сорцов, где нужны эти конфиги.
А если передавать конфиги из Make скриптов опцией -DHAS_XXXX, то не нужно будет писать повсюду лишние
#include "some_config.h"
Вы добавляете some_config.h через ключи компилятора в MakeFile(лах)?
-include some_config.h

Как известно при сборке из IDE надо вручную прописывать пути к папкам с исходниками в настройках IDE.
Это весьма утомительный и рутийный процесс.
Эти пути потом отражаются в файле .cproject
<listOptionValue builtIn="false" value=""${workspace_loc}/control/generic""/>
<listOptionValue builtIn="false" value=""${workspace_loc}/components/Circular_Buffer""/>
<listOptionValue builtIn="false" value=""${workspace_loc}/adt/array""/>
<listOptionValue builtIn="false" value=""${workspace_loc}/adt""/>
<listOptionValue builtIn="false" value=""${workspace_loc}/computing/math""/>
<listOptionValue builtIn="false" value=""${workspace_loc}/control/free_rtos""/>
<listOptionValue builtIn="false" value=""${workspace_loc}/control/task""/>
<listOptionValue builtIn="false" value=""${workspace_loc}/control/super_cycle""/>
<listOptionValue builtIn="false" value=""${workspace_loc}/control""/>
<listOptionValue builtIn="false" value=""${workspace_loc}/common/code_generator""/>
<listOptionValue builtIn="false" value=""${workspace_loc}/mcal/mcal_common/timer""/>
<listOptionValue builtIn="false" value=""${workspace_loc}/mcal/mcal_common""/>
<listOptionValue builtIn="false" value=""${workspace_loc}/mcal""/>
Есть ли способ при сборке из плагинов Eclipse импортировать пути для GCC извне подобно тому как работает gcc опция -include some_config.h
нет, в хедерах
Делать конфиги в *.h файликах это очень мило и наивно.
Сразу урок информатики в 8м классе вспоминается....
Дело в том, что нормальный программный компонент должен быть масштабируемый.
Чтобы можно было объявить несколько экземпляров микросхем или интерфейсов.
Поэтому конфиг для программного компонента надо хранить в константном массиве структур.
А массивы структур обычно определяют в *.с файлах.
--Архитектура Хорошо Поддерживаемого драйвера
https://habr.com/ru/articles/683762/
--Архитектура Xорошего Кода Прошивки (Массив-Наше Всё)
Проблема *.h файликов в том, что у Вас получается прибитый гвоздями программный компонент, который позволяет сконфигурировать только один экземпляр чего-либо. Это по сути бомба с часовым механизмом.
Со временем даст о себе знать как недостаток архитектуры.
Если уж говорить про то, как надо в идеале, то это DeviceTree.
Набор констант в *.h файлике это уровень школьника.
Надо определять константные массивы конфигурационных структур в *.c файлах.
А в идеале компилировать конфиги из специального языка для конфигов - DevicеTree.
Извините, но это лютейший бред. Конфигурация настраивается в хедерах, а переключается настройками компилятора (объявлением макросов). Сугубо потому, что хедеры уже могут изменять структуру C-файлов, вплоть до отключения/подключения целых наборов файлов.
Для простых прошивок да. Для сложных проектов надо массивы структур. Или DevTree.
А как вы условную сборку своим массивов структур сделаете?
Ifdef(aами)
А как вы условную сборку своим массивов структур сделаете?
Вот так.
C:\projects\workspace\board_name\source\boards\at_start_f437\uart_config.c
#include "uart_config.h"
#include "data_utils.h"
#include "log_config.h"
#ifdef HAS_UART1
static uint8_t Uart1TxArray[UART_TX_FIFO_SIZE];
static uint8_t Uart1RxArray[16];
#endif
#ifdef HAS_UART2
static uint8_t Uart2TxArray[16];
static uint8_t Uart2RxArray[16];
#endif
#ifdef HAS_UART6
static uint8_t Uart6TxArray[UART_TX_FIFO_SIZE];
static uint8_t Uart6RxArray[16];
#endif
const UartConfig_t UartConfig[] = {
#ifdef HAS_UART1
{
.num = 1,
.baud_rate = 460800,
.name = "UART1",
.tx_buff_size = sizeof(Uart1TxArray),
.TxFifoArray = Uart1TxArray,
.rx_buff_size = sizeof(Uart1RxArray),
.RxFifoArray = Uart1RxArray,
.valid = true,
.dma =
{
.tx = false,
.rx = false,
},
},
#endif
#ifdef HAS_UART2
{
.num = 2,
.baud_rate = 9600,
.tx_buff_size = sizeof(Uart2TxArray),
.TxFifoArray = Uart2TxArray,
.rx_buff_size = sizeof(Uart2RxArray),
.RxFifoArray = Uart2RxArray,
.name = "UART2",
.rx_buff_size = 0,
.valid = true,
.dma =
{
.tx = false,
.rx = false,
},
},
#endif
#ifdef HAS_UART6
{
.num = 6,
.baud_rate = 460800,
.name = "UART6",
.tx_buff_size = sizeof(Uart6TxArray),
.TxFifoArray = Uart6TxArray,
.rx_buff_size = sizeof(Uart6RxArray),
.RxFifoArray = Uart6RxArray,
.rx_buff_size = 0,
.valid = true,
.dma =
{
.tx = false,
.rx = false,
},
},
#endif
};
UartHandle_t UartInstance[] = {
#ifdef HAS_UART1
{
.num = 1,
.valid = true,
.TxFifo =
{
.err_cnt = 0,
.initDone = true,
.array = (char*)Uart1TxArray,
.fifoState =
{
.size = sizeof(Uart1TxArray),
.start = 0,
.end = 0,
.count = 0,
.errors = false,
},
},
},
#endif
#ifdef HAS_UART2
{
.num = 2,
.valid = true,
},
#endif
#ifdef HAS_UART6
{
.num = 6,
.valid = true,
.TxFifo =
{
.err_cnt = 0,
.initDone = true,
.array = (char*)Uart6TxArray,
.fifoState =
{
.size = sizeof(Uart6TxArray),
.start = 0,
.end = 0,
.count = 0,
.errors = false,
},
},
},
#endif
};
uint32_t uart_get_cnt(void) {
uint32_t cnt = 0;
uint32_t cnt_conf = ARRAY_SIZE(UartConfig);
uint32_t cnt_ints = ARRAY_SIZE(UartInstance);
if(cnt_conf == cnt_ints) {
cnt = cnt_ints;
}
return cnt;
}
а переключается настройками компилятора (объявлением макросов).
Тут Вы, Артемий, @Sap_ruсовершенно правильно заметили.
Макросы можно и нужно передавать, пучком опций компилятора.
Это ключ -DOPT += -DHAS_GPIO
OPT += -DHAS_FLASH
Плюс в том, что такие макросы будут видны глобально во всех файлах проекта!
Поэтому определять макросы в *.h файликах - это уровень зумеров GUI-IDE поколения и школьников.
Всё верно, но кроме вот таких глобальных переключений в серьёзный embedded проектах есть ещё куча копиляторо0заивисмых настроек, библиотеко-зависимых (мы можем собираться с различными libc/libc++ библиотеками), toolchain-зависмых (привет всем этим китайским колхозным системам сборки), ОС-зависимых, и ещё чёрте-от-чего-зависмых настроек. Выносить всё это в глобальные макросы не нужно, так как на практике это очень редко меняется (только при портировании на новую платформу/окружение). А ещё если из одного дерева исходников собираются сильно разные вещи, то возникает здоровое желание переключать глобальные настройки удобным образом в исходниках, чтобы это было лучше видно в системе контроля версий.
Поэтому в нормальных проектах делают три уровня абстракций:
- высокоуровневые макросы, задаваемые через настройки компилятора;
- высокоуровневые макросы и настройки в заголовках, которые переключают какие-то совершенно глобальные вещи, которые нужно править или менять только при портировании на новое железно или окружение или при создании каких-то новых программ на основе того же дерева исходников;
- низкоуровневые макросы, которые завязаны на предыдущие два уровня и проверяют/определяют/настраивают кучу макросов, которые в дальнейшем уже непосредственно влияют на условную компиляцию в файлах исходный текстов кода.
Именно так построены все серьёзные проекты от самого GCC/CLANG, до всех портов libc, и ядра Linux.
И задать всё через макросы, определяемые ключами компилятора, просто не возможно: будет куча мусора и тайных знаний при совершенно шаманской финальной структуре макросов.
Микроконтроллерные прошивки, как правило, - это маленькие Си-программы.
Весь *.bin-арь 300kByte.
Поэтому никогда не было проблем с передачей макросов через ключи компилятора (-Dxxxx).
Если у вас даже 128к ROM, то сложная прошивка в исходных текстах будет весить мегабайты. И есть проект живет долго и эволюционирует, то в какой момент оказывается, что он может собираться тремя разными компиляторами, с четырьмя вариантами libstdc, тремя вариантами OS, и на пяти разных микроконтроллерах, двух разных семейств.
задать всё через макросы, определяемые ключами компилятора, просто не возможно: будет куча мусора и тайных знаний при совершенно шаманской финальной структуре макросов.
В чем тайные знания, сэр?
В компиляторах есть ключ -dM для печати всех переданных макросов через -D.
Вот
https://habr.com/ru/articles/835994/
Так можно отлаживаться.
Успехов в отладке действительно многоплатформенных приложений таким путём. У меня в одном из приложений этот сгенерированный header был полтора мегабайта весом.
Тайные знания в том, что именно, что переключает и от чего зависит. Без исходников все эти макросы - просто мешанина кода. Генерация всю информацию о происхождении макросов убирает.
У меня в одном из приложений этот сгенерированный header был полтора мегабайта весом.
Что Вы такое программируете на си, где у Вас так много макро определений?
Например портирование/кастомизирование FreeRTOS под свою платформу. Или портирование/доработка stdlib. Например переопределние 64-битной арифметики в stdlib позволяет сэкономить от 10 до 30 килобайт FLASH и при этом дать 30% прирост производительности, что может быть критично для многие проектов. А правильный тюнинг сохранения контекста DSP в многозадачных средах позволяет сократить накладные расходны на переключение задач в разы, и при этом практически везде сохранение контекста сделано неоптимально (или даже неправильно).
А ещё можно (и часто нужно) патчить макросы сборки библиотек ST (так как там куча недоработок и ошибок). Только там мегпбайт макросов легко может вывалиться.
Генерация всю информацию о происхождении макросов убирает
Если нужна информация о происхождении макроса, то можно из корня репозитория утилитой grep найти то место, где макрос объявлен.
Grep находит иголку в стоге сена.
Правильное IDE и синтаксический анализатор нужно использовать. А через grep искать это лютая дикость и боль. Попробуйте поискать в исходниках стандартных библиотек и каких-нибудь библиотек от STM. Оно вам сотню вхождений может выдать.
Достоинство grep в том ,что можно делать многоступенчатый grep .
Grep поверх grep.
Правильное IDE и синтаксический анализатор нужно использовать.
Ок, и какая же IDE по вашему является "правильной"?
Попробуйте поискать в исходниках стандартных библиотек и каких-нибудь библиотек от STM. Оно вам сотню вхождений может выдать.
Пробовал сотню+ раз. Никаких проблем не испытывал. Ступенчатый grep находил всё, что надо.
grep -rni gpio | grep "\.c:" | grep -i init | grep -i hal
Как запрос из базы данных выдает нужное место.
Полезные Заготовки Вызова Утилит Командной Строки
https://habr.com/ru/articles/754858/

Почему Вы делаете тестировочные прототипы на фанерке?
Не лучше ли оргстекло? Плексиглас он ведь прозрачный, не перекрывает никаких шильдиков. Прозрачная основа удобнее для отладки.
https://habr.com/ru/articles/709932/
О том что "в embedded DevOps невозможен" мне доказывали не только "очень опытные инженеры" из всяких НИИ “РосПил” но и 28...35 летние Team Lead(ы) некоторых московских контор.
Хороший DevOps не только позволяет уменьшить bus factor до нуля.
Хороший DevOps придает разработке софта положительный азарт, добавляет работе радость и чувство удовлетворения сделанной работой так как всегда видно результат.
Про OpenBMC почитайте. Там сборка под Yocto (ну и гадость эта ваша заливная рыба).
С учетом того, что OpenBMC поддерживают Facebook, Microsoft, Intel и много кто еще - выборка может получиться куда более репрезентативной.
Но очень высокий порог входа туда. Средств отладки в Yocto практически нет. Подключить gdb можно, но столько танцев с саблями придется сделать... То же для IDE - можно, но десять раз вспотеешь.
все производители платформ (intel, AMD, Ampere, etc) к тому же дают свою сборку OpenBMC
Сборки OpenBMC для платформ можно использовать, если они есть. Бывает так, что их пока нет, если платформа совсем новая. Бывает так, что сборки OpenBMC от производителя нет в принципе и появится не скоро, для того же серверного Байкала. Бывает и так, что дизайн платы сделан, как бы это помягче, с особенностями.
Например: вентиляторы висят не на Tach-PWM ногах ASPEED, а на lm63-подобном датчике, подключенном через i2c switch, причем и lm-ка и свич висят на питании хоста, а не на дежурном. И надо думать, как это все прокинуть через конфиг entity-manager. Ну или доработать phosphor-hwmon, чтобы он дергал new_device и delete_device через power events.
драйвера на все популярное есть в ядре
Один из множества примеров. Родной драйвер lm-ки по дефолту PWM не инитит, считая это задачей BIOS (что, в общем, вполне правильно). Т.е. в примере выше его надо дорабатывать, иначе при включении хоста вентиляторы запустятся далеко не сразу. Либо добавлять скрипт инициализации lm в свои рецепты (но проще и надежнее поправить родной драйвер).
Я это все к тому, что как только отходишь от задач типа "а давайте мы на Tioga Pass добавим десяток управляющих релюшек, чтобы конкурс защитить", простота использования OpenBMC куда-то сразу испаряется.
Есть отличный открытый проект flipper zero где можно наслаждаться тем как настроили сборку тесты и кодогенерацию
В репе
https://github.com/flipperdevices/flipperzero-firmware
всё намешано. И USB и UART всё в одной папке валяется.
Сумбурный репозиторий.
Не все в нем идеально, если не сталкивались до этого с проектом, рекомендую как минимум глянуть на их инструмент fbt. Сборка сделана через TaskRunner в VSCode, туда же притянут OpenOCD, инструментарий для тестов и проверки кода. Пользоваться удобнее чем IAR.
С точки зрения embed-ера этот проект новый и свежий.
У самого Флиппера есть и gui и cli и файловая система, и грамотная и удобная система лога.
Сам проект развивается гигантскими шагами и конечно не идеален.
Зачем flipper zero в качестве системы сборки выбрали SCons?
Чем им Make CMake не нравится?
По правде, говоря все мы немного dev ops(ы).
Мы же не вручную код на assembler(е) пишем.
Мы компилятором из абстрактного языка Си генерируем артефакты (*.bin файл).
A это значит что мы DevOps(еры) все.
DevOps для Производства Firmware