Makefile Analysis
Makefile Analysis
Taking some real code as examples:
Case 1
ifneq ($(MKENV_INCLUDED),1)
include mkenv.mk
endif
subdirs-y :=
include apps.mk
.PHONY: all clean distclean
all: $(subdirs-y)
@echo "Make Applications done."
ifneq ($(strip $(subdirs-y)),)
.PHONY: $(subdirs-y)
$(subdirs-y):
@echo "[BUILD] applications $@"
@$(MAKE) -C $@ all || exit $?;
endif
clean:
@if [ -n "$(subdirs-y)" ]; then \
for dir in $(subdirs-y); do \
echo "[CLEAN] applications $$dir"; \
$(MAKE) -C $$dir clean || exit $?; \
done; \
fi
distclean: clean
@if [ -n "$(subdirs-y)" ]; then \
for dir in $(subdirs-y); do \
echo "[DISTCLEAN] applications $$dir"; \
$(MAKE) -C $$dir distclean || exit $?; \
done; \
fi
.PHONY: gen_kconfig
gen_kconfig:
@echo "# Dynamic generate by Makefile, DO NOT Modify it.\n" > $(SDK_BUILD_DIR)/Kconfig.app; \
for dir in $(shell find $(SDK_APPS_SRC_DIR) -maxdepth 1 -mindepth 1 -type d); do \
echo "source \"$$dir/Kconfig\"" >> $(SDK_BUILD_DIR)/Kconfig.app; \
done; \ifneq ($(MKENV_INCLUDED),1) is used to check whether the variable MKENV_INCLUDED is ; if it is 1, then include mkenv.mk, which prevents multiple files from including mkenv.mk repeatedly during compilation.
It defines the variable subdirs-y (-y is a Linux naming convention, meaning a subdirectory that is included in the build),
subdirs-y :=
include apps.mkThe contents of apps.mk are as follows:
-include $(SDK_SRC_ROOT_DIR)/.config
subdirs-$(CONFIG_APP_ENABLE_HELLOWORLD) += helloworld
subdirs-$(CONFIG_APP_ENABLE_POKEMON_DETECT) += pokemon_detectCase 2:
# Include base configuration
include ../mkenv.mk
include $(SDK_RTSMART_SRC_DIR)/libs/mk/toolchain_riscv64_musl.mk
# Build configuration
DIR := $(shell basename `pwd`)
BUILD := $(SDK_APPS_BUILD_DIR)/$(DIR)
BIN := $(SDK_APPS_IMAGE_DIR)/$(DIR)
# Source files handling
SRC_DIRS := . screens
SRCS := $(shell find $(SRC_DIRS) -maxdepth 1 -type f \( -name "*.c" -o -name "*.cpp" -o -name "*.cc" \))
OBJS := $(addprefix $(BUILD)/, $(addsuffix .o, $(basename $(notdir $(SRCS)))))
DEPS := $(OBJS:.o=.d)
# Directory creation logic - Captured and sorted
OBJ_DIRS := $(sort $(dir $(OBJS) $(BIN)))
# Library configuration
include $(SDK_RTSMART_SRC_DIR)/libs/mk/liblvgl.mk
include $(SDK_RTSMART_SRC_DIR)/libs/mk/libmpp.mk
include $(SDK_RTSMART_SRC_DIR)/libs/mk/libopencv.mk
include $(SDK_RTSMART_SRC_DIR)/libs/mk/libnncase.mk
include $(SDK_RTSMART_SRC_DIR)/libs/mk/librtsmart_hal.mk
include $(SDK_RTSMART_SRC_DIR)/libs/mk/lib3rdparty.mk
# Compiler flags
C_CXX_FLAGS := -fopenmp -march=rv64imafdcv -mabi=lp64d -mcmodel=medany -Os
C_CXX_FLAGS += -I. $(LIB_CFLAGS) -I$(SDK_SRC_ROOT_DIR)/include -include "generated/autoconf.h"
C_CXX_FLAGS += -DLV_LVGL_H_INCLUDE_SIMPLE
C_CXX_FLAGS += -Wall -Wextra -ffunction-sections -fdata-sections
CFLAGS := $(C_CXX_FLAGS) -std=gnu99
CXX_FLAGS := $(C_CXX_FLAGS)
# Linker flags
LDFLAGS := -T $(SDK_RTSMART_SRC_DIR)/libs/mk/link.lds --static -Wl,--gc-sections
LDFLAGS += $(LIB_LDFLAGS)
# Phony targets
.PHONY: all clean distclean
all: $(BIN)
@$(ECHO) [BUILD] applications $(DIR) done.
.PHONY: .FORCE
.FORCE:
# Rule to link the final ELF - Order-only dependency on directory
$(BIN): $(OBJS) .FORCE | $(OBJ_DIRS)
@echo "[LD] $@"
$(Q)$(CXX) $(CFLAGS) $(OBJS) $(LDFLAGS) -o $@
$(Q)$(STRIP) $@
# Rule for C objects - Order-only dependency on directory
vpath %.c $(SRC_DIRS)
$(BUILD)/%.o: %.c | $(OBJ_DIRS)
@echo "[CC] $@"
$(Q)$(CC) $(CFLAGS) -MD -MP -MF $(@:.o=.d) -c $< -o $@
# Rule for C++ objects - Order-only dependency on directory
vpath %.cc $(SRC_DIRS)
$(BUILD)/%.o: %.cc | $(OBJ_DIRS)
@echo "[CXX] $@"
$(Q)$(CXX) $(CXX_FLAGS) -MD -MP -MF $(@:.o=.d) -c $< -o $@
vpath %.cpp $(SRC_DIRS)
$(BUILD)/%.o: %.cpp | $(OBJ_DIRS)
@echo "[CXX] $@"
$(Q)$(CXX) $(CXX_FLAGS) -MD -MP -MF $(@:.o=.d) -c $< -o $@
# Actual directory creation rule
$(OBJ_DIRS):
$(Q)mkdir -p $@
clean:
@rm -rf $(BIN)
distclean: clean
@rm -rf $(BUILD)
-include $(DEPS)Explanation:
# Include base configuration
include ../mkenv.mk
include $(SDK_RTSMART_SRC_DIR)/libs/mk/toolchain_riscv64_musl.mkinclude is used to import files, just like in C — include ../mkenv.mk is equivalent to placing the contents of mkenv.mk at that position.
The contents of mkenv.mk are as follows:
ifneq ($(MKENV_INCLUDED),1)
export SDK_SRC_ROOT_DIR := $(realpath $(dir $(realpath $(lastword $(MAKEFILE_LIST))))/../../)
endif
include $(SDK_SRC_ROOT_DIR)/tools/mkenv.mk
include $(SDK_SRC_ROOT_DIR)/.config
export SDK_APPS_IMAGE_DIR := $(SDK_BUILD_IMAGES_DIR)/sdcard/app/
export MKENV_INCLUDED_APPLICATIONS=1In the code above:
ifneq ($(MKENV_INCLUDED),1)
...
endifis used to prevent the file from being included more than once. export SDK_SRC_ROOT_DIR := $(realpath $(dir $(realpath $(lastword $(MAKEFILE_LIST))))/../../) is a very classic and advanced Makefile idiom for "getting the project's absolute root path". To understand it, we need to peel it apart from the inside out, like an onion:
$(MAKEFILE_LIST): this is a built-in Make variable that records the list of names of all Makefiles loaded so far — right now it holdsMakeFile ../mkenv.mk.lastword: takes the last word of that list. Since the system just loaded the current../mkenv.mk, what it gets is the name of this file itself (including the relative path).realpath(inner): converts the name just obtained into an absolute path (and resolves symlinks along the way). For example, it becomes/home/fs/workspace/K230/rtos/src/applications/mkenv.mk.dir: extracts the directory path where this file lives. The result becomes/home/fs/workspace/K230/rtos/src/applications/../../: goes up two levels from that directory. It becomes `/home/fs/workspace/K230/rtos/../../realpath(outer): finally normalizes the absolute path one more time, erasing the..relative marker, and ends up with the clean SDK root directory:/home/fs/workspace/K230/rtos.
export: declares the computedSDK_SRC_ROOT_DIRas an environment variable, so that every subsequently invoked sub-Makefile or shell script can use this variable directly, and nobody has to guess relative paths anymore.
Next,include $(SDK_SRC_ROOT_DIR)/tools/mkenv.mk— that is, it includes/home/fs/workspace/K230/rtos/tools/mkenv.mk
export SDK_TOOLCHAIN_DIR?=$(shell echo $$HOME)/.kendryte/k230_toolchains
export SDK_TOOLS_DIR=$(SDK_SRC_ROOT_DIR)/tools
# Default target
.DEFAULT_GOAL := all
# Strip quotes and then whitespaces
qstrip = $(strip $(subst ",,$(1)))
define check_build_dir
$(shell \
if [ ! -d $(1) ]; then \
mkdir -p $(1); \
fi; \
)
endef
define del_mark
$(shell find $(SDK_SRC_ROOT_DIR) -type f -name ".parse_config" | xargs rm -rf {})
endef
# 1 src
# 2 dst
define sync_dir
@echo "Source: $(1)"
@echo "Destination: $(2)"
@rm -rf $(2)/*
@mkdir -p $(2) || exit 1
@rsync -aq --delete $(1)/ $(2)/
endef
define gen_kconfig
@if [ -f "$(1)/Kconfig" ]; then \
cp -f $(1)/Kconfig $(SDK_BUILD_DIR)/Kconfig.$(2); \
else \
echo "" > $(SDK_BUILD_DIR)/Kconfig.$(2); \
fi
endef
# Do not print "Entering directory ...",
# but we want to display it when entering to the output directory
# so that IDEs/editors are able to understand relative filenames.
MAKEFLAGS += --no-print-directory
# ifneq ($(MKENV_INCLUDED),1)
# JOBS := $(shell ps -o args= $(MAKEPPID) | grep -o -E -- '-j[0-9]+' | head -n 1 | cut -c3-)
# ifneq ($(JOBS),)
# ifneq ($(JOBS),1)
# $(error not support parallel build now)
# endif
# endif
# endif
# Turn on increased build verbosity by defining BUILD_VERBOSE in your main
# Makefile or in your environment. You can also use V=1 on the make command
# line.
ifeq ("$(origin V)", "command line")
BUILD_VERBOSE=$(V)
endif
ifndef BUILD_VERBOSE
BUILD_VERBOSE = 0
endif
ifeq ($(BUILD_VERBOSE),0)
Q = @
else
Q =
endif
RM = rm -rf
ECHO = echo
CP = cp
MKDIR = mkdir
SED = sed
CAT = cat
TOUCH = touch
PYTHON = python3
ZIP = zip
INSTALL = install
# Check if 'bear' command exists
BEAR_EXISTS := $(shell command -v bear >/dev/null 2>&1 && echo yes || echo no)
BEAR_COMMAND ?= bear
ifeq ($(BEAR_EXISTS),yes)
# Capture the Bear version
BEAR_VERSION := $(shell bear --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
# Convert BEAR_VERSION to a single integer and compare it to 30000
BEAR_VERSION_INT := $(shell echo $(BEAR_VERSION) | awk -F. '{print ($$1*10000 + $$2*100 + $$3)}')
# Check if the Bear version is 3.0.0 or greater
ifneq ($(shell [ $(BEAR_VERSION_INT) -ge 30000 ] && echo true),)
BEAR_COMMAND := bear --
endif
endif
export BEAR_COMMAND
PARALLEL ?= $(shell if command -v nproc > /dev/null 2>&1; then nproc; \
elif [ -f /proc/cpuinfo ]; then grep -c '^processor' /proc/cpuinfo; \
elif command -v sysctl > /dev/null 2>&1; then sysctl -n hw.ncpu; \
else echo 1; fi)
export NCPUS?=$(PARALLEL)
CONFIG_SDK_ENABLE_CANMV ?= n
def_config :=$(filter %_defconfig,$(MAKECMDGOALS))
ifneq ($(def_config),)
ifeq ($(shell if [ -f $(SDK_SRC_ROOT_DIR)/configs/$(def_config) ]; then echo 1; else echo 0; fi;), 0)
$(error "Please specify a valid CONFIG")
endif
CONFIG_BOARD_CONFIG_NAME := $(def_config)
else
-include $(SDK_SRC_ROOT_DIR)/.config
endif
CONFIG_BOARD := $(call qstrip,$(CONFIG_BOARD))
export CONFIG_SDK_ENABLE_CANMV
ifeq ($(strip $(filter $(MAKECMDGOALS),list_def list-def dl_toolchain)),)
ifeq ($(CONFIG_BOARD_CONFIG_NAME),)
$(error "Please run make xxx_defconfig first. Use 'make list-def' to see available configurations.")
endif
endif
MK_LIST_DEFCONFIG?=$(CONFIG_BOARD_CONFIG_NAME)
export MK_LIST_DEFCONFIG
export UBOOT_DEFCONFIG=$(patsubst %,%_defconfig,$(call qstrip,$(CONFIG_UBOOT_CONFIG_FILE)))
export RTSMART_DEFCONFIG=$(patsubst %,%_defconfig,$(call qstrip,$(CONFIG_RTSMART_CONFIG_FILE)))
export SDK_BOARDS_DIR=$(SDK_SRC_ROOT_DIR)/boards
export SDK_BOARD_DIR=$(SDK_BOARDS_DIR)/$(call qstrip,$(CONFIG_BOARD))
export SDK_OPENSBI_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/opensbi
export SDK_RTSMART_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/rtsmart
export SDK_UBOOT_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/uboot
export SDK_CANMV_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/canmv
export SDK_APPS_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/applications
export SDK_BUILD_DIR=$(SDK_SRC_ROOT_DIR)/output/$(call qstrip,$(CONFIG_BOARD_CONFIG_NAME))
export SDK_OPENSBI_BUILD_DIR=$(SDK_BUILD_DIR)/opensbi
export SDK_RTSMART_BUILD_DIR=$(SDK_BUILD_DIR)/rtsmart
export SDK_UBOOT_BUILD_DIR=$(SDK_BUILD_DIR)/uboot
export SDK_CANMV_BUILD_DIR=$(SDK_BUILD_DIR)/canmv
export SDK_APPS_BUILD_DIR=$(SDK_BUILD_DIR)/applications
export SDK_ARDUINO_SDK_BUILD_DIR=$(SDK_SRC_ROOT_DIR)/output/arduino-sdk
export SDK_BUILD_IMAGES_DIR=$(SDK_BUILD_DIR)/images
ifeq ($(strip $(filter $(MAKECMDGOALS),list_def list-def dl_toolchain)),)
$(call check_build_dir, $(SDK_BUILD_IMAGES_DIR))
$(call check_build_dir, $(SDK_OPENSBI_BUILD_DIR))
$(call check_build_dir, $(SDK_RTSMART_BUILD_DIR))
$(call check_build_dir, $(SDK_UBOOT_BUILD_DIR))
$(call check_build_dir, $(SDK_CANMV_BUILD_DIR))
$(call check_build_dir, $(SDK_APPS_BUILD_DIR))
$(call check_build_dir, $(SDK_ARDUINO_SDK_BUILD_DIR))
$(call check_build_dir, $(SDK_TOOLCHAIN_DIR))
endif
export MKENV_INCLUDED = 1
export MPP_FOR_CANMV_SDK = 1
MK_IMAGE_NAME?=$(call qstrip,$(CONFIG_BOARD_NAME))
export MK_IMAGE_NAMEexport SDK_TOOLCHAIN_DIR?=$(shell echo $$HOME)/.kendryte/k230_toolchains
export SDK_TOOLS_DIR=$(SDK_SRC_ROOT_DIR)/tools
# Default target
.DEFAULT_GOAL := allIt exports SDK_TOOLCHAIN_DIR as the toolchain path /home/fs/.kendryte/k230_toolchains. $ is used to declare variables, and $$HOME is the content of a variable.
It exports SDK_TOOLS_DIR as /home/fs/workspace/K230/rtos/tools.DEFAULT_GOAL := all means that if you run make with no arguments, it runs make all
qstrip:qstrip = $(strip $(subst ",,$(1)))Purpose: strip quotes and then whitespace. Because the underlying config file.configoften contains quoted variables (e.g.CONFIG_BOARD="k230_evb"), and quotes would cause errors when used for path assembly in a Makefile, this function exists specifically to peel"k230_evb"down tok230_evb.check_build_dir: uses the shell to check whether a directory exists; if not, itmkdir -ps it into existence.del_mark: searches the whole project for the.parse_configcache file and deletes it (usually for clearing out old configuration).sync_dir: calls the powerfulrsynccommand to sync the source directory to the destination directory, ensuring incremental copying.gen_kconfig: aggregates each module's ownKconfiginto the overall build directory for use by the graphical configuration interface.
MAKEFLAGS += --no-print-directory
...
ifeq ($(BUILD_VERBOSE),0)
Q = @
else
Q =
endif
RM = rm -rf
ECHO = echo
CP = cp
MKDIR = mkdir
SED = sed
CAT = cat
TOUCH = touch
PYTHON = python3
ZIP = zip
INSTALL = install- Anti spam: it turns off Make's default "Entering directory xxx" / "Leaving directory xxx" flood of messages.
- The
V=1mechanism: this is a very classic design from the Linux kernel build. By defaultQ = @, and prefixing a Makefile command with@hides the actual command being executed (for example, hiding an extremely longgcc -c ...), printing only a short message. But if you're troubleshooting a build error, you typemake V=1in the terminal,Qbecomes empty, and all the real compilation commands being executed underneath are exposed. - The file then defines aliases for common commands such as
RM,CP,MKDIRin one place, for cross-platform compatibility.**
# Check if 'bear' command exists
BEAR_EXISTS := $(shell command -v bear >/dev/null 2>&1 && echo yes || echo no)
BEAR_COMMAND ?= bear
ifeq ($(BEAR_EXISTS),yes)
# Capture the Bear version
BEAR_VERSION := $(shell bear --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
# Convert BEAR_VERSION to a single integer and compare it to 30000
BEAR_VERSION_INT := $(shell echo $(BEAR_VERSION) | awk -F. '{print ($$1*10000 + $$2*100 + $$3)}')
# Check if the Bear version is 3.0.0 or greater
ifneq ($(shell [ $(BEAR_VERSION_INT) -ge 30000 ] && echo true),)
BEAR_COMMAND := bear --
endif
endif
export BEAR_COMMAND
PARALLEL ?= $(shell if command -v nproc > /dev/null 2>&1; then nproc; \
elif [ -f /proc/cpuinfo ]; then grep -c '^processor' /proc/cpuinfo; \
elif command -v sysctl > /dev/null 2>&1; then sysctl -n hw.ncpu; \
else echo 1; fi)
export NCPUS?=$(PARALLEL)
CONFIG_SDK_ENABLE_CANMV ?= n- The Bear interceptor:
bearis an extremely powerful tool that intercepts all compilation arguments whilemakeis building and generatescompile_commands.json. That file is the foundation for precise C/C++ code navigation and smart completion in modern IDEs like VSCode and CLion. This code thoughtfully checks whetherbearis installed in your environment, and whether the version is the latest 3.0+. PARALLEL: automatically sniffs out your computer's CPU core count (usingnprocorsysctl) and assigns it toNCPUS, preparing for later parallel builds (such asmake -j8).
def_config :=$(filter %_defconfig,$(MAKECMDGOALS))
ifneq ($(def_config),)
ifeq ($(shell if [ -f $(SDK_SRC_ROOT_DIR)/configs/$(def_config) ]; then echo 1; else echo 0; fi;), 0)
$(error "Please specify a valid CONFIG")
endif
CONFIG_BOARD_CONFIG_NAME := $(def_config)
else
-include $(SDK_SRC_ROOT_DIR)/.config
endif
CONFIG_BOARD := $(call qstrip,$(CONFIG_BOARD))
export CONFIG_SDK_ENABLE_CANMV
ifeq ($(strip $(filter $(MAKECMDGOALS),list_def list-def dl_toolchain)),)
ifeq ($(CONFIG_BOARD_CONFIG_NAME),)
$(error "Please run make xxx_defconfig first. Use 'make list-def' to see available configurations.")
endif
endif
MK_LIST_DEFCONFIG?=$(CONFIG_BOARD_CONFIG_NAME)
export MK_LIST_DEFCONFIGMAKECMDGOALS: a built-in Make variable representing the command goals you typed into the terminal.- Logic branches:
- If you typed
make k230_evb_defconfigin the terminal, it checks whether that config file actually exists under theconfigs/directory. If it does, it stores the name inCONFIG_BOARD_CONFIG_NAME. - If you just typed
make, it quietly doesinclude $(SDK_SRC_ROOT_DIR)/.configto read in the previous configuration.
- If you typed
- Interceptor: if there is no configuration (for example, you just downloaded the code), and the command you're running isn't one of the list (
list_def) or download-toolchain (dl_toolchain) commands, the system errors out and stops you right there ($(error "Please run make xxx_defconfig first...")), forcing you to pick a board.$(filter %_defconfig, ...) - This is Make's string filtering function.
- Syntax:
$(filter <pattern>, <text>) - Purpose: selects from
<text>the words that match the<pattern>. %is the wildcard, matching any sequence of any length.%_defconfigmeans: match strings ending in_defconfig.
The code after that handles def_config — it checks whether that config file exists and then runs the relevant logic: what to do if it exists, and what to do if it doesn't.MK_LIST_DEFCONFIG?=$(CONFIG_BOARD_CONFIG_NAME)sets a default value.
export UBOOT_DEFCONFIG=$(patsubst %,%_defconfig,$(call qstrip,$(CONFIG_UBOOT_CONFIG_FILE)))
export RTSMART_DEFCONFIG=$(patsubst %,%_defconfig,$(call qstrip,$(CONFIG_RTSMART_CONFIG_FILE)))
export SDK_BOARDS_DIR=$(SDK_SRC_ROOT_DIR)/boards
export SDK_BOARD_DIR=$(SDK_BOARDS_DIR)/$(call qstrip,$(CONFIG_BOARD))
export SDK_OPENSBI_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/opensbi
export SDK_RTSMART_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/rtsmart
export SDK_UBOOT_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/uboot
export SDK_CANMV_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/canmv
export SDK_APPS_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/applications
export SDK_BUILD_DIR=$(SDK_SRC_ROOT_DIR)/output/$(call qstrip,$(CONFIG_BOARD_CONFIG_NAME))
export SDK_OPENSBI_BUILD_DIR=$(SDK_BUILD_DIR)/opensbi
export SDK_RTSMART_BUILD_DIR=$(SDK_BUILD_DIR)/rtsmart
export SDK_UBOOT_BUILD_DIR=$(SDK_BUILD_DIR)/uboot
export SDK_CANMV_BUILD_DIR=$(SDK_BUILD_DIR)/canmv
export SDK_APPS_BUILD_DIR=$(SDK_BUILD_DIR)/applications
export SDK_ARDUINO_SDK_BUILD_DIR=$(SDK_SRC_ROOT_DIR)/output/arduino-sdk
export SDK_BUILD_IMAGES_DIR=$(SDK_BUILD_DIR)/images
ifeq ($(strip $(filter $(MAKECMDGOALS),list_def list-def dl_toolchain)),)
$(call check_build_dir, $(SDK_BUILD_IMAGES_DIR))
$(call check_build_dir, $(SDK_OPENSBI_BUILD_DIR))
$(call check_build_dir, $(SDK_RTSMART_BUILD_DIR))
$(call check_build_dir, $(SDK_UBOOT_BUILD_DIR))
$(call check_build_dir, $(SDK_CANMV_BUILD_DIR))
$(call check_build_dir, $(SDK_APPS_BUILD_DIR))
$(call check_build_dir, $(SDK_ARDUINO_SDK_BUILD_DIR))
$(call check_build_dir, $(SDK_TOOLCHAIN_DIR))
endif
export MKENV_INCLUDED = 1
export MPP_FOR_CANMV_SDK = 1
MK_IMAGE_NAME?=$(call qstrip,$(CONFIG_BOARD_NAME))
export MK_IMAGE_NAME- The source area (
src/): it pins down the source locations of OpenSBI (low-level firmware), U-Boot (bootloader), RT-Smart (real-time system), CanMV (AI vision framework), and Applications (application layer). - The output area (
output/): all compiled.ofiles and images are absolutely not allowed to pollute the source directory; they must all be output underoutput/<your board name>/. This also makes it convenient to do a thorough cleanup withmake distclean— just delete theoutputfolder and everything is gone.
Changelog
0ca31-onfd60f-on