
Пролог
При разработке программного обеспечения (особенно для микроконтроллеров) рано или поздно придется столкнуться с тем, что надо как-то передавать конфигурации для данного программного проекта.
В своем опыте я пришел к выводу, что с точки зрения масштабирования кодовой базы, конфиги проще всего передавать через переменные окружения. Да.. Плюс в том, что переменные окружения можно определять прописывая прямо в скриптах (Make, CMake и т.п.).
Выглядит это так. У каждой сборки есть файл config.mk в котором перечислены программные компоненты из которых должна собираться эта конкретная сборка. Содержимое этого файла обычно выглядит так.
ADC=Y ADC_ISR=Y ADT=Y ALLOCATOR=Y ARRAY=Y ASICS=Y .... TASK=Y TBFP=Y TERMINAL=Y TIME=Y UART0=Y UART2=Y UNIT_TEST=Y UTILS=Y UWB=Y
Это просто атомарные строчки. Тут происходит определение переменных окружения. Декларативно перечисляется из чего собирать прошивку. У другой сборки есть свой такой же декларативный config.mk файл и свой собственный набор переменных окружения.
Именно эти самые переменны окружения решают какие файлы добавить в компиляцию а какие исключить. Вот например переменная окружения SD_CARD=Y добавит в сборку вот этот make скрипт
ifneq ($(SD_CARD_MK_INC),Y) SD_CARD_MK_INC=Y $(info + SD card SPI driver) SD_CARD_DIR = $(ASICS_DIR)/sd_card #@echo $(error SD_CARD_DIR= $(SD_CARD_DIR)) INCDIR += -I$(SD_CARD_DIR) OPT += -DHAS_CRC7 OPT += -DHAS_CRC16 OPT += -DHAS_SD_CARD OPT += -DHAS_SD_CARD_CRC7 OPT += -DHAS_SD_CARD_CRC16 SOURCES_C += $(SD_CARD_DIR)/sd_card_drv.c SOURCES_C += $(SD_CARD_DIR)/sd_card_crc.c SOURCES_C += $(SD_CARD_DIR)/sd_card_crc16.c ifeq ($(DIAG),Y) ifeq ($(SD_CARD_DIAG),Y) $(info + SD card diag) OPT += -DHAS_SD_CARD_DIAG SOURCES_C += $(SD_CARD_DIR)/sd_card_diag.c endif endif ifeq ($(CLI),Y) ifeq ($(SD_CARD_COMMANDS),Y) $(info + SD card commands) OPT += -DHAS_SD_CARD_COMMANDS SOURCES_C += $(SD_CARD_DIR)/sd_card_commands.c endif endif endif
Достаточно внутри config.mk прописать SD_CARD=Y и скрипты сами добавят всё что надо для сборки Си кода: исходный код, пути к исходному коду и директивы препроцессора. Вы заменили в скриптах один ��имвол c "Y" на "N" и скрипты make автоматически исключили из сборки исходный код, пути к исходному коду и директивы препроцессора для этого конкретного программного компонента. Easy!
Переменные окружения внутри config.mk, внимание, отсортированы! И конфиги двух разных сборок очень удобно сравнивать в утилите WinMerge.

Как известно, компьютерные программы (в частности прошивки для MCU) по-хорошему строятся иерархично. Вот, например, программный компонент интерфейса командной строки CLI нуждается в том чтобы в программе уже были реализованы такие программные компоненты как UART, TIMER, распознаватель чисел из строки и прочее по мелочи MISC.
В чем проблема?
Проблема в том, что переменных окружения (конфигов) становится много. Если Вы собираете через make скрипты, то у Вас будет много переменных окружения для каждой конкретной сборки. В каждом config.mk будет по 100 ....200 строчек и можно нечаянно забыть прописать какую-то важную переменную окружения, которая активирует какой-то конфиг. И без которой прошивка будет неправильно работать в run-time. Вот пример типичного config.mk
ADC=Y ADC_ISR=Y ALLOCATOR=Y ARRAY=Y ASICS=Y AUDIO=Y BIN_2_STR=Y BOARD=Y BOARD_INFO=Y BOARD_UTILS=Y BUTTON=Y CLI=Y CLOCK=Y CMSIS=Y COMMON=Y COMPLEX=Y COMPONENTS=Y CONNECTIVITY=Y CORE=Y CORE_APP=Y CORE_EXT=Y CORTEX_M33=Y CRC16=Y CRC8=Y CRC=Y CSV=Y CUSTOM_PRINTF=Y DATA_POC=Y DEBUG=Y DEBUGGER=Y DFT=Y DIAG=Y DRIVERS=Y DSP=Y DYNAMIC_SAMPLES=Y FIFO=Y FIFO_CHAR=Y FIFO_INDEX=Y FLASH=Y FLASH_EX=Y FLASH_FS=Y FLASH_FS_WRITE=Y FLASH_WRITE=Y GENERIC=Y GPIO=Y HEALTH_MONITOR=Y I2C1=Y I2C=Y I2S0=Y I2S0_MASTER=Y I2S=Y I2S_ISR=Y INDICATION=Y INTERFACES=Y LED=Y LED_MONO=Y LED_VERIFY=Y LIMITER=Y LOG=Y LOG_COLOR=Y LOG_DIAG=Y LOG_TIME_STAMP=Y LOG_UTILS=Y MATH=Y MATH_VECTOR=Y MCAL=Y MCAL_NRF5340=Y MICROCONTROLLER=Y MISCELLANEOUS=Y MULTIMEDIA=Y NORTOS=Y NRF5340=Y NRF5340_APP=Y NRF5340_DK=Y NRFX=Y NVIC_COMMANDS=Y NVS=Y NVS_WRITE=Y PARAM=Y PARAM_SET=Y PCM_16_BIT=Y PINS=Y PROTOCOLS=Y REAL_SAMPLE_ARRAY=N SENSITIVITY=Y SOFTWARE_TIMER=Y STORAGE=Y STR2_DOUBLE=Y STREAM=Y STRING=Y STRING_PARSER=Y SUPER_CYCLE=Y SW_DAC=Y SW_DAC_STATIC_SAMPLES=Y SYSTEM=Y SYSTICK=Y SYS_INIT=Y TABLE_UTILS=Y TASK=Y TERMINAL=Y TEST_HW=Y TEST_SW=Y THIRD_PARTY=Y TIME=Y TIMER0=Y TIMER1=Y TIMER2=Y TIMER=Y UART0=Y UART2=Y UART=Y UART_INTERRUPT=Y UART_ISR=Y UNIT_TEST=Y WM8731=Y WM8731_I2S_SLAVE=Y WM8731_USB_MODE=Y WM8731_VERIFY=Y WRITE_ADDR=Y
В Zephyr Project проблему забытых конфигов частично решает такой механизм как KConfig. Если Вы не прописали конфиг, то KConfig выдаст ошибку сборки или сам автоматически молча добавит нужный конфиг и продолжит сборку в записимости от скриптов Kconfig для каждого программного компонента. Однако, к сожалению, не существует stand alone утилиты KConfig.exe, которую можно было бы использовать в любой сборке на Windows без отношения к Zephyr Project. Вот так...

Решение
Очевидно, что надо сделать так, чтобы на стадии отработки make скриптов как-то автоматический волшебным образом прописывались забытые конфиги для зависимостей тех программных компонентов, которые мы первоначально выбрали в файле config.mk.
Надо сделать так, чтобы конфиги прописывались автоматически.
Прежде всего нужно для каждого программного компонента создать отдельный make файл, который будет содержать информацию про его зависимости. Иначе откуда с��стема сборки догадается, что надо ещё подключить? Назвать этот файл можно xxx_preconfig.mk. Вот, например, файл nvram_preconfig.mk. Очевидно, что для работы кода on-chip NVRAM необходимы такие программные компоненты как CRC8 и MCAL для Flash периферии. В связи с этим и определяются нужные переменные окружения.
ifneq ($(NVRAM_PRECONFIG_INC),Y) NVRAM_PRECONFIG_INC=Y NVRAM=Y FLASH=Y NVS=Y NVRAM_PROC=Y CRC=Y CRC8=Y endif
Если в скриптах сборки отработает скрипт nvram_preconfig.mk, то вам не придется в файле config.mk прописывать CRC8=Y, FLASH=Y, NVS=Y и прочее. Вам будет достаточно лишь только прописать NVRAM=Y. Далее всё остальное выставится само собой.
А вот это preconfig для SD-карты в режиме SPI.
ifneq ($(SD_CARD_PRECONFIG_MK_INC),Y) SD_CARD_PRECONFIG_MK_INC=Y CRC7=Y SPI=Y GPIO=Y CRC16=Y SD_CARD=Y SD_CARD_CRC7=Y SD_CARD_CRC16=Y endif
На самом деле основная идея этого трюка взята из идеологии CMake. CMake собирает конфиг, далее на сцену выходит система сборки (make, ninja или IDE) собирает сам проект. Только в этом случае всё много проще. И конфиг, и проект собирает сама система сборки! Тут это делает всеядная утилита make.
Вот упрощенный корневой файл code_base.mk сборки пере используемого репозитория
ifneq ($(CODE_BASE_MK),Y) CODE_BASE_MK=Y include $(WORKSPACE_LOC)/code_base_preconfig.mk #preconfig/presets done! ifeq ($(CORE),Y) include $(WORKSPACE_LOC)/core/core.mk endif ifeq ($(MICROCONTROLLER),Y) include $(WORKSPACE_LOC)/microcontroller/microcontroller.mk endif ifeq ($(BOARD),Y) include $(WORKSPACE_LOC)/boards/boards.mk endif ifeq ($(THIRD_PARTY),Y) include $(WORKSPACE_LOC)/third_party/third_party.mk endif ifeq ($(APPLICATIONS),Y) include $(WORKSPACE_LOC)/applications/applications.mk endif ifeq ($(MCAL),Y) include $(WORKSPACE_LOC)/mcal/mcal.mk endif ifeq ($(ADT),Y) include $(WORKSPACE_LOC)/adt/adt.mk endif ifeq ($(CONNECTIVITY),Y) include $(WORKSPACE_LOC)/connectivity/connectivity.mk endif ifeq ($(CONTROL),Y) include $(WORKSPACE_LOC)/control/control.mk endif ifeq ($(COMPONENTS),Y) include $(WORKSPACE_LOC)/components/components.mk endif ifeq ($(COMPUTING),Y) include $(WORKSPACE_LOC)/computing/computing.mk endif ifeq ($(SENSITIVITY),Y) include $(WORKSPACE_LOC)/sensitivity/sensitivity.mk endif ifeq ($(STORAGE),Y) include $(WORKSPACE_LOC)/storage/storage.mk endif ifeq ($(SECURITY),Y) include $(WORKSPACE_LOC)/security/security.mk endif ifeq ($(ASICS),Y) include $(WORKSPACE_LOC)/asics/asics.mk endif ifeq ($(UNIT_TEST),Y) include $(WORKSPACE_LOC)/unit_tests/unit_test.mk endif ifeq ($(MISCELLANEOUS),Y) include $(WORKSPACE_LOC)/miscellaneous/miscellaneous.mk endif endif
Обратите внимание, что code_base_preconfig.mk
include $(WORKSPACE_LOC)/code_base_preconfig.mk
вызывается до сборки самого проекта. Что такое code_base_preconfig.mk? Это как раз тот самый скрипт для автоматического расставления конфигов про которые мы забыли составляя config.mk.
ifneq ($(CODE_BASE_PRECONFIG_MK),Y) CODE_BASE_PRECONFIG_MK=Y ifeq ($(BOARD),Y) include $(WORKSPACE_LOC)/boards/boards_preconfig.mk endif ifeq ($(MICROCONTROLLER),Y) include $(WORKSPACE_LOC)/microcontroller/microcontroller_preconfig.mk endif ifeq ($(CORE),Y) include $(WORKSPACE_LOC)/core/core_preconfig.mk endif ifeq ($(MCAL),Y) include $(WORKSPACE_LOC)/mcal/mcal_preconfig.mk endif ifeq ($(ADT),Y) include $(WORKSPACE_LOC)/adt/adt_preconfig.mk endif ifeq ($(CONNECTIVITY),Y) include $(WORKSPACE_LOC)/connectivity/connectivity_preconfig.mk endif ifeq ($(CONTROL),Y) include $(WORKSPACE_LOC)/control/control_preconfig.mk endif ifeq ($(COMPONENTS),Y) include $(WORKSPACE_LOC)/components/components_preconfig.mk endif ifeq ($(COMPUTING),Y) include $(WORKSPACE_LOC)/computing/computing_preconfig.mk endif ifeq ($(SENSITIVITY),Y) include $(WORKSPACE_LOC)/sensitivity/sensitivity_preconfig.mk endif ifeq ($(STORAGE),Y) include $(WORKSPACE_LOC)/storage/storage_preconfig.mk endif ifeq ($(SECURITY),Y) include $(WORKSPACE_LOC)/security/security_preconfig.mk endif include $(WORKSPACE_LOC)/asics/asics_preconfig.mk endif
Те же скрипты nvram_preconfig.mk и sd_card_preconfig.mk будут вызываться где-то внутри storage_preconfig.mk. И т. д.
Таким образом ваш изначальный config.mk можно упростить до вот такого вида
CLI=Y COMPLEX=Y CSV=Y DEBUG=Y NVRAM=Y DEBUGGER=Y DFT=Y DIAG=Y DSP=Y DYNAMIC_SAMPLES=Y GENERIC=Y I2C1=Y I2S0_MASTER=Y NRF5340_DK=Y NORTOS=Y TASK=Y TIMER0=Y TIMER1=Y TIMER2=Y UART0=Y UART2=Y UNIT_TEST=Y WM8731_I2S_SLAVE=Y WM8731_USB_MODE=Y
Всё остальное расставить preconfig автоматически! Корневой конфиг config.mk стал проще в 10 раз! Успех!
Итог
Как видите сборка из скриптов дает такие бонусы, как возможность автоматически расставлять конфигурации!
Разработана технология простого автоматического переносимого прописывания конфигов зависимостей программных компонентов на основе зависимостей указанных в фалах xxx_preconfig.mk
Надеюсь этот текст поможет другим программистам в разработке программ.
Links
Почему Важно Собирать Код из Скриптовhttps://habr.com/ru/articles/723054/
Сортировка Конфигов для Make Сборок https://habr.com/ru/articles/745244/
Настройка ToolChain(а) для Win10+GCC+С+Makefile+ARM Cortex-Mx+GDBhttps://habr.com/ru/articles/673522/
Сборка firmware для CC2652 из Makefilehttps://habr.com/ru/articles/726352/
Генерация зависимостей внутри программыhttps://habr.com/ru/articles/765424/
ToolChain: Настройка сборки прошивок для микроконтроллеров Artery из Makefile https://habr.com/ru/articles/792590/
Автоматическое Обновление Версии Прошивки https://habr.com/ru/articles/791768/
