
Пролог
При разработке программного обеспечения (особенно для микроконтроллеров) рано или поздно придется столкнуться с тем, что надо как-то передавать конфигурации для данного программного проекта.
В своем опыте я пришел к выводу, что с точки зрения масштабирования кодовой базы, конфиги проще всего передавать через переменные окружения. Да.. Плюс в том, что переменные окружения можно определять прописывая прямо в скриптах (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/
