Пролог

При разработке программного обеспечения (особенно для микроконтроллеров) рано или поздно придется столкнуться с тем, что надо как-то передавать конфигурации для данного программного проекта.

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

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы собираете код из скриптов?
51.85%да14
48.15%нет13
Проголосовали 27 пользователей. Воздержался 1 пользователь.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы пользуетесь механизмами автоматической расстановки конфигураций?
31.82%да7
68.18%нет15
Проголосовали 22 пользователя. Воздержался 1 пользователь.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вы пользуетесь KConfig?
8%да2
92%нет23
Проголосовали 25 пользователей. Воздержался 1 пользователь.