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