В период с 199x по 202x на территории РФ развелось порядка двадцати тысяч программистов-микроконтроллеров, которые никогда в своей жизни не вылазили из всяческих GUI IDE (IAR, KEIL, Code Composer Studio, Atolic True Studio, CodeVision AVR, Segger Embedded Studio и прочие). Как по мне, дак это очень печально. Во многом потому, что специалист в Keil не сможет сразу понять как работать в IAR и наоборот. Другие файлы для настроек компоновщика. Другая xml настройки проекта. Миграция на другую IDE тоже вызывает большую трудность, так как это сводится к мышковозне в IDE-GUI. Каждая версия IAR не совместима с более новой версией IDE.
Дело в том, что GUI IDE появились в 199x...201x, когда не было расцвета DevOps(а), программист работал один и все действия выполнялись вручную. Мышкой. В то время работа в GUI казалась программистам-микроконтроллеров веселее, ведь в IDE много стразиков.
Но с усложнением кодовой базы, с увеличением сборок, с увеличением команд разработчиков появилась нужда в переиспользовании кода, нужда в автосборках, в авто тестах. Появилась методология
код отдельно, конфиги отдельно
и работа с IDE стала только тормозить процессы. Ведь конфиги хранятся в IDE-шной XML(ке). Приходилось дублировать конфиги платы для каждой сборки, что использовала эту плату. Пришлось дублировать код конфигов и этот процесс сопровождался ошибками, из-за человеческого фактора. При масштабировании работы с IDE кодовая база фактически превращалась в зоопарк в болоте.
Подробнее про этот колхоз можно почитать тут: Почему сборка из-под IDE это тупиковый путь
Какие недостатки сборки исходников из-под IDE?
IDE монолитные и неделимые. Если Вы захотите поменять какую-то часть ToolChain(а): препроцессор, компилятор или компоновщик, а остальные фазы ToolChain(а) оставить как есть, то ничего из этого у вас не выйдет, так как капот IDE наглухо закрыт на замок.
IDE стоят дорого, порядка 3500 EUR на один компьютер. Это лишняя дань и оброк для вашей компании. Оно вам надо?
IDE(шные) xml очень слабо документированы или не документированы вовсе. У всех вендоров xml имеет свой особенный снобский xml-like язык разметки для конфигурации проекта. При внесении незначительных изменений появляется огромный git diff.
Затруднена сборка из консоли. В основном инициировать сборку в IDE можно мышкой или горячими клавишами. Либо надо делать refresh мышкой из-под IDE.
Обратная несовместимость с новыми версиями IDE.
В условиях технологического Эмбарго и Санкций законно купить IDE европейского (Германия, Швеция, США) вендора невозможно. Они вас просто пошлют, так как у них Ваша территория числится как criminal state
IDE отжирают какие-никакие но ресурсы компьютера, как RAM как и CPU, IDE же надо много оперативки, чтобы отрисовывать окошки со стразиками. IAR и Code Composer, например, раз в день стабильно напрочь зависают, что помогает лишь перезагрузка розеткой.
В общем распространение IDE это яркий пример известного ныне "технологического диктата" (vendor locking) запада для низкосортных народов из стран второго и третьего мира.
Навязывание GUI-IDE (Keil, IAR, CCS, плагинов Eclipse) это форма технологического диктата со стороны стран бывших метрополий. Они привыкли столетиями помыкать своими колониями и до сих пор продвигают эту подлую циничную политику подсовывая vendor loсking средств разработки во всякие страны как Кракожия, Такистан, Элбония, Западно-Африканская республика и прочее.
Сами они там у себя этим суррогатом нелепы не пользуются. Понимаете? У них там CMake, Make, Ninja скрипты сборки и полный DevOps в Docker контейнерах. Я работал в одной английской конторе и видел это всё своими глазами.
А туземцам в СНГ они суют эту тухлую песочницу в которой только кривые куличи лепить возможно.
Мы вам продаём песочницу (IDE), а вы сидите там за бортиками, улыбаетесь и лепите свои, никому даром не нужные, куличики (прошивки-паршивки).
Понятное дело, что разработчики IDE во всю пользуются ситуацией и добавляют всяческие программные закладки, такие как слив исходников в здание с пятью углами, удаленное отключение функционала, ограничение размера выходного бинарного файла до 32kByte и всё на, что им там только хватит их извращённой фантазии!
Понятное дело, что в таких жестких рамках на "сделать что-то серьезное" туземцам рассчитывать не приходится. Сборка в IDE это всегда мелкая серия. Всегда малый ассортимент. Всегда ручное развертывание.
А когда запад нас в очередной раз кинул пришлось начать думать как теперь дальше жить... Хорошим решением оказалось сделать шаг назад в 197x 198x когда на компьютерах всё делали из консоли. Даешь сборку сорцов из скриптов! Можно вообще *.bat файл написать и он, в общем-то, инициирует запуск нужных утилит, однако исторически Си-код собирали культовой утилитой make.
Дело в том, что makefile придумали в 1976 (Stuart Feldman), тогда, когда к компьютеры были дорогие (65k USD только за несколько сотен килобайт RAM) и к компьютерам подпускали только PhD профессоров из топовых университетов мира. Поэтому и появились такие гениальнейшие утилиты как awk, make, grep, find, gdb, sed, sort, tsort, uniq, tr и прочие.
Тогда в далеких 197x у школоты в принципе не было возможности хоть как-то влиять на ход развития софта и генерировать спагетти код и программные смеси как сейчас в 200x...202x.
Сейчас же в 201x...202x какой-нибудь 43-летний Junior-embedded программист-микроконтроллеров, привыкший к GUI-IDE, может логично провозгласить:
Я вообще не представляю, как без помощи IDE и зелёного треугольника вверху производить пошаговую отладку программы?
Тут есть 4+ ответа:
1--> Использовать связку GDB Client + GDB Server и отлаживать код из командной строки.
https://habr.com/ru/post/694708/
2--> Отлаживать код через интерфейс командной строки CLI поверх UART.
https://habr.com/ru/post/694408/
3--> В коем-то веке покрывать свой код модульными тестами
https://habr.com/ru/post/698092/
4--> Использовать другие косвенные признаки отладки кода: HeartBeat LED, Утилита arm-none-eabi-addr2line.exe, Assert(ы), DAC, STM Studio (Аналог ArtMoney из GameDev(a)), Health Monitor
https://habr.com/ru/post/681280/
В чем достоинства сборки С-кода из Make файлов?
Makefile это самый гибкий способ управлять модульностью кодовой базы. Можно буквально одной строчкой (например FREE_RTOS=Y) добавлять или исключать один конкретный программный компонент (десятки файлов) из десятков сборок. В случае же сборки из-под IDE вам бы пришлось вручную мышкой редактировать .xml для каждой отдельной сборки.
Сборку из Makefile очень легко автоматизировать. Достаточно в консоли выполнить make all и у вас инициируется процесс сборки, а через 3 мин в соседней папке будут лежать артефакты.
После сборки из скриптов вы получите полный лог сборки, в то время как IDE обычно показывают последние 3-4 экрана сообщений компилятора.
В MakeFile очень просто менять компиляторы. Это, буквально, заменить одну строчку. С GCC на Clang или на GHS. Вот типичный основной makefile для любой сборки на ARM Cortex-Mxx
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
$(info Build $(mkfile_path) )
BUILD_DIR = build
#@echo $(error SOURCES_C= $(SOURCES_C))
INCDIR := $(subst /cygdrive/c/,C:/, $(INCDIR))
#@echo $(error INCDIR=$(INCDIR))
SOURCES_C := $(subst /cygdrive/c/,C:/, $(SOURCES_C))
#@echo $(error SOURCES_C=$(SOURCES_C))
SOURCES_ASM := $(subst /cygdrive/c/,C:/, $(SOURCES_ASM))
LIBS := $(subst /cygdrive/c/,C:/, $(LIBS))
LDSCRIPT := $(subst /cygdrive/c/,C:/, $(LDSCRIPT))
#@echo $(error SOURCES_ASM=$(SOURCES_ASM))
# binaries
PREFIX = arm-none-eabi-
GCC_PATH="C:/Program Files (x86)/GNU Arm Embedded Toolchain/10 2021.10/bin"
$(info GCC_PATH=$(GCC_PATH))
# The gcc compiler bin path can be either defined in make command via GCC_PATH variable (> make GCC_PATH=xxx)
# either it can be added to the PATH environment variable.
ifdef GCC_PATH
CC = $(GCC_PATH)/$(PREFIX)gcc
AS = $(GCC_PATH)/$(PREFIX)gcc -x assembler-with-cpp
CP = $(GCC_PATH)/$(PREFIX)objcopy
SZ = $(GCC_PATH)/$(PREFIX)size
else
CC = $(PREFIX)gcc
AS = $(PREFIX)gcc -x assembler-with-cpp
CP = $(PREFIX)objcopy
SZ = $(PREFIX)size
endif
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S
# float-abi
ifeq ($(NRF5340), Y)
ifeq ($(CORE_NET), Y)
FLOAT-ABI = -mfloat-abi=soft
OPT += -fsingle-precision-constant
endif
ifeq ($(CORE_APP), Y)
FLOAT-ABI = -mfloat-abi=hard
endif
else
FLOAT-ABI = -mfloat-abi=hard
endif
# mcu
MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)
# macros for gcc
#CSTANDARD = -std=c11
CSTANDARD = -std=gnu99
# AS defines
AS_DEFS =
# AS includes
AS_INCLUDES =
ifeq ($(DEBUG), Y)
#@echo $(error DEBUG=$(DEBUG))
CFLAGS += -g3 -gdwarf-2 -ggdb
OPT += -O0
else
OPT += -Os
endif
OPT += -fmessage-length=0
OPT += -fsigned-char
OPT += -fno-common
OPT += -fstack-usage
OPT += -finline-small-functions
#Perform dead code elimination
OPT += -fdce
#Perform dead store elimination
OPT += -fdse
# compile gcc flags
ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections
CFLAGS += $(CSTANDARD)
CFLAGS += -Wall
#CFLAGS += -Wformat-overflow=1
CFLAGS += $(MCU) $(OPT) -fdata-sections -ffunction-sections $(INCDIR)
# Generate dependency information
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"
# LDFLAGS
# libraries
LINKER_FLAGS += -Xlinker --gc-sections
ifeq ($(MBR), Y)
#@echo $(error MBR=$(MBR))
LIBS += -lnosys
LDFLAGS += -specs=nano.specs
else
LINKER_FLAGS += -u _scanf_float
LINKER_FLAGS += -u _printf_float
endif
#LINKER_FLAGS += -lrdimon --specs=rdimon.specs
ifeq ($(LIBC), Y)
#@echo $(error LIBC=$(LIBC))
LIBS += -lc
endif
ifeq ($(MATH), Y)
#@echo $(error MATH=$(MATH))
LIBS += -lm
endif
#@echo $(error LDSCRIPT=$(LDSCRIPT))
LIBDIR =
LDFLAGS += $(MCU) -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections $(LINKER_FLAGS)
# default action: build all
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin
# build the application
# list of objects
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(SOURCES_C:.c=.o)))
vpath %.c $(sort $(dir $(SOURCES_C)))
# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(SOURCES_ASM:.S=.o)))
vpath %.S $(sort $(dir $(SOURCES_ASM)))
$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR)
$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@
$(BUILD_DIR)/%.o: %.S Makefile | $(BUILD_DIR)
$(AS) -c $(CFLAGS) $< -o $@
$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
$(CC) $(OBJECTS) $(LDFLAGS) -o $@
$(SZ) $@
$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
$(HEX) $< $@
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
$(BIN) $< $@
$(BUILD_DIR):
mkdir $@
# clean up
clean:
-rm -fR $(BUILD_DIR)
# dependencies
-include $(wildcard $(BUILD_DIR)/*.d)
# *** EOF ***
5--Когда вы собираете из Make вы можете не только собирать исходники, но и
№ | Что можно делать из скриптов сборки |
1 | |
2 | |
3 | собирать документацию (вызвать Latex, Doxygen) |
4 | |
7 | |
5 | можете прямо из make отправлять файлы прошивок потребителям |
6 | вызывать процедуру пере прошивки консольными утилитами программатора |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
... | и многое другое |
Утилите make всё равно какие консольные утилиты вызывать. Понимаете? Это универсальный способ определения программных конвейеров.
Утилиту make можно использовать не только для дирижирования процессом сборки кода программ. Утилита make может также управлять авто генерацией преобразования расширений файлов для черчения или управлять сборкой документации, управлять DevOps(ом). Make можно использовать по-разному, как только фантазии хватит. Ибо Make совершенно инвариантен и абстрагируется от языка программирования как такового.
Для каждой сборки надо самим писать крохотный Makefile
MK_PATH:=$(dir $(realpath $(lastword $(MAKEFILE_LIST))))
#@echo $(error MK_PATH=$(MK_PATH))
WORKSPACE_LOC:=$(MK_PATH)../../
INCDIR += -I$(MK_PATH)
INCDIR += -I$(WORKSPACE_LOC)
#@echo $(error SOURCES_C=$(SOURCES_C))
include $(MK_PATH)config.mk
include $(MK_PATH)cli_config.mk
include $(MK_PATH)diag_config.mk
include $(MK_PATH)test_config.mk
include $(WORKSPACE_LOC)code_base.mk
include $(WORKSPACE_LOC)rules.mk
и конфиг для сборки.
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
$(info Build $(mkfile_path) )
TARGET=pastilda_r1_1_generic
#@echo $(error TARGET=$(TARGET))
AES256=Y
ALLOCATOR=Y
......
USB_HOST_HS=Y
USB_HOST_PROC=Y
UTILS=Y
XML=Y
Для каждого компонента *.mk файл. Язык make простой. Он, в сущности, очень похож на bash. Вот типичный *.mk файл для драйвера UWB радио-трансивера DW1000.
ifneq ($(DWM1000_MK_INC),Y)
DWM1000_MK_INC=Y
DWM1000_DIR = $(DRIVERS_DIR)/dwm1000
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
$(info Build $(mkfile_path) )
$(info + DWM1000)
INCDIR += -I$(DWM1000_DIR)
OPT += -DHAS_DWM1000
OPT += -DHAS_DWM1000_PROC
OPT += -DHAS_UWB
DWM1000_RANGE_DIAG=Y
DWM1000_RANGE_COMMANDS=Y
DWM1000_OTP_COMMANDS=Y
DWM1000_OTP_DIAG=Y
SOURCES_C += $(DWM1000_DIR)/dwm1000_drv.c
include $(DWM1000_DIR)/otp/dwm1000_otp.mk
include $(DWM1000_DIR)/registers/dwm1000_registers.mk
ifeq ($(DWM1000_RANGE),Y)
include $(DWM1000_DIR)/range/dwm1000_range.mk
endif
ifeq ($(DIAG),Y)
ifeq ($(DWM1000_DIAG),Y)
$(info +DWM1000_DIAG)
OPT += -DHAS_DWM1000_DIAG
SOURCES_C += $(DWM1000_DIR)/dwm1000_diag.c
endif
endif
ifeq ($(CLI),Y)
ifeq ($(DWM1000_COMMANDS),Y)
$(info +DWM1000_COMMANDS)
OPT += -DHAS_DWM1000_COMMANDS
SOURCES_C += $(DWM1000_DIR)/dwm1000_commands.c
endif
endif
endif
Параллельное написание Make файлов и С-кода стимулирует придерживаться модульности, изоляции программных компонентов и к прослеживанию зависимостей между компонентами. Если вы пишите код и make файлы примерно параллельно, то очень вероятно, что у вас получится чистый, аккуратный репозиторий сам собой.
Makefile(лы) хороши тем, что можно добавить много проверок зависимостей и assert(ов) на фазе отработки Make-скриптов прямо в *.mk файлах еще до компиляции самого кода, даже до запуска препроцессора, так как язык программирования make поддерживает условные операторы и функции. Можно очень много ошибок отловить на этапе отработки утилиты make.
Язык make очень прост. Вся спека GNU Make это всего 226 страниц. Cтю Фельдман (автор make) просто гений.
Makefile(лы) прозрачные потому что текстовые. Всегда видно, где опции препроцессора, где ключи для компилятора, а где настройки для компоновщика. Всё, что нужно можно найти утилитой grep в той же консоли от GIT-bash даже при работе в Windows 10.
Конфиг для сборки можно формировать как раз на стадии make файлов и передавать их как ключи для препроцессора. Таким образом конфиги будут видны в каждом *.с файле проекта и не надо вставлять #include(ы) c конфигами. Всё можно передать как опции утилите cpp (препроцессора).
При сборке из makefile вам вообще всё равно для какой целевой платформы собирать код прошивки. Вы можете минимальными изменениями в makefile собрать прошивку и крутить её даже на x86. Вместо UART имитировать CLI в Windows консольном приложении на PC .
Внутри makefile вы можете выполнить какой-нибудь полезный скрипт. Например подписать прошивку состоянием репозитория
GIT_SHA := $(shell git rev-parse --short HEAD)
OPT += -DGIT_SHA=0x0$(GIT_SHA)
затем в коде написать
LOG_INFO(SYS,"GitSha: 0x%08x", GIT_SHA);
таким образом 3мя строчками вы сделаете то, что отдельными скриптами заняло бы 50+ строк.
При сборке из скриптов (например из Make) очень легко добавить новую сборку. Достаточно только написать конфиг и 3 строчки в отдельном Makefile и вот у вас новая специфическая сборка. Одновременно с этим создание новой сборки в GUI-IDE сопряжено с многочисленной мышкавозней и дублированием конфигов!
Вывод
В наше время бахвалиться навыками пользования всяческими IDE должно быть уже стыдно. Надо признать что программирование внутри IDE - это уровень кружка робототехники 8-9го класса средней общеобразовательной школы.
Сборка прошивок GUI-IDE плагинами, а также из-под Keil, IAR ССS - это признак Junior разработчика.
А сборка из скриптов - это фундаментальная технология, которая по плечу только программистам с качественным опытом.
Санкционные реалии таковы, что настало время, чтобы российских программистов-микроконтроллеров из детского садика под названием "GUI-IDE" перевести, наконец, в школу (т.е. приучить к makefile или хотя бы к CMake). А дальше приобщать к полноценному DevOps(у).
При этом надо смотреть в сторону Make, CMake. Как вариант Ninja. Make это как пуговицы. Старая, простая и очень полезная вещь.
Откровенно говоря, только тех, кто умеет собирать проекты из скриптов и можно считать настоящими программистами. Те кто "программисты" которые не умеет читать писать скрипты сборки, это либо самозванцы, либо обыкновенная шко-ло-та. Без обид, но что есть, то есть.
При сборке из makefile прошивки для микроконтроллеров всех vendor(ов) собираются одинаково. Будь-то stm32, nrf53, cc26x42, atmega8 или spc58nn. Надо просто открыть консоль и набрать make all. Easy!
Собираете свои прошивки из make в этом нет ничего сложного.
Links
Так как make появился ещё в 197x, то это пожалуй самая изученная тема во всем Computer Science. Материалов для обучения make просто немерено.
№ | Пояснение | URL |
1 | Эффективное использование GNU Make | |
14 | CI/CD прошивок для микроконтроллеров в Wiren Board ( начало на 25:20) | |
2 | Конвеерум #30: Эволюция рабочего окружения для embedded разработки | |
3 | GNU Make может больше чем ты думаешь | |
4 | Пример Makefile | |
5 | Настройка ToolChain(а) для Win10+GCC+С+Makefile+ARM Cortex-Mx+GDB | |
6 | Компактный make для STM32 с USB | |
7 | Генерация Зависимостей Внутри Программы | |
8 | Эффективное использование GNU Make | |
9 | GNU Make | |
10 | How To Create A Makefile (C/C++) | Makefile Tutorial | Linux | |
11 | GNU Make | https://rus-linux.net/nlib.php?name=/MyLDP/algol/gnu_make/gnu_make_3-79_russian_manual.html |
12 | Техникум: Автоматическое Aрхивирование Aртефактов | |
13 | https://runebook.dev/ru/docs/gnu_make/selective-search |