SOC Project: Each SOC project root directory contains a CMakeLists.txt as the build entry point. All MCU projects under it are added here.
MCU Project: Each MCU project uses unified toolchain and configuration parameters (some configurations may vary due to firmware or ROM). Different MCU projects may use different toolchains. An MCU project includes builds of one or more firmwares.
Firmware: Mainly includes image1, image2, image3, with differences as follows:
image1: Defines the common infrastructure for bootloader and system flash layout in MCU systems, providing a secure bootloader with convenient software upgrade support.
image2: The main application firmware of the SOC, typically containing a real-time OS (FreeRTOS) and application tasks.
image3: Highly configurable, allowing users to include only required security services. Protected by Read Protection (RDP), decrypted in the secure bootloader, and loaded into TrustZone-protected SRAM.
Component: Components are organized under the component directory at the same level as SOC projects. Each subdirectory represents a functional module with its own CMakeLists.txt defining compilation and linking configurations.
Firmware Build: In each MCU project’s [asdk|vsdk]/make/[image1|image2|image3]/CMakeLists.txt, add required components and define generation rules.
The order of components’ appended configurations in the final global compilation settings (e.g., the order of header file paths) depends on the sequence in which components are added to the firmware
This ordering is unreliable. For order-sensitive configurations (like header file paths), it’s recommended to handle them through alternative approaches. For details see Component Private Part
The global compilation configuration applies to each MCU project. For example, RTL8721Dx has separate configurations for km0 and km4, while RTL8730E has configurations for km0, km4, and ca32. These configurations are isolated. Components under the same MCU project use the same global compilation configuration. See the following diagram:
Key points:
Each MCU project contains an independent compilation scope.
Different firmwares (image1, image2, image3) under the same MCU project share the same compilation config.
The same component is compiled with different global configurations across MCU projects (e.g., at_cmd in project_mcu1 vs. project_mcu2).
Include directories: Added to global if other components need headers from this component.
Definitions: Global preprocessor macros added to global when this component is added (rarely used).
Link libraries: Required if the component is provided as a prebuilt library (not source). Use ameba_add_external_app_library to build the library.
Configuration variables: public_includes, public_definitions, public_libraries. Update these in the highlighted section of code block below. See examples in cmake/CMakeLists-template.cmake and APIs in List Operations.
1########################################################################################## 2## * This part defines public part of the component 3## * Public part will be used as global build configures for all component 4 5set(public_includes)#public include directories, NOTE: relative path is OK 6set(public_definitions)#public definitions 7set(public_libraries)#public libraries(files), NOTE: linked with whole-archive options 8 9#------------------------------------------------------------------#10# Component public part, user config begin(DO NOT remove this line)1112# set public_includes, public_definitions, public_libraries in this section1314# Component public part, user config end(DO NOT remove this line)15#------------------------------------------------------------------#1617#WARNING: Fixed section, DO NOT change!18ameba_global_include(${public_includes})19ameba_global_define(${public_defines})20ameba_global_library(${public_libraries})#default: whole-achived
Note
If there’s no need to update the three types of configurations mentioned above for the current component, the highlighted code section can be left blank.
Private compile options: Only for current component
Configuration variables: private_sources, private_includes, private_definitions, private_compile_options. Update these in the highlighted section of code block below. See examples in cmake/CMakeLists-template.cmake and APIs in List Operations.
Attention
The final compilation configuration of a component (particularly include directories, definitions and compile options) is composed of both Component Private Part and Component Public Part, with the former taking higher priority for include directories and compile options . For example, the actual header search paths for a component include:
Private include directories added by the current component.
Global include directories from Global Compilation Configuration, which includes paths appended by both the current component and other components.
Prefer placing include directories in the Component Private Part to avoid header file pollution.
Exceptions: For generic or low-level components used by many others, placing includes in Component Public Part is preferred to improve reusability.
1########################################################################################## 2## * This part defines private part of the component 3## * Private part is used to build target of current component 4## * NOTE: The build API guarantees the global build configures(mentioned above) 5## * applied to the target automatically. So if any configure was already added 6## * to public above, it's unnecessary to add again below. 7 8#NOTE: User defined section, add your private build configures here 9# You may use if-else condition to set these predefined variable10# They are only for ameba_add_internal_library/ameba_add_external_app_library/ameba_add_external_soc_library11set(private_sources)#private source files, NOTE: relative path is OK12set(private_includes)#private include directories, NOTE: relative path is OK13set(private_definitions)#private definitions14set(private_compile_options)#private compile_options1516#------------------------------#17# Component private part, user config begin1819# set private_sources, private_includes, private_definitions, private_compile_options in this section2021# Component private part, user config end22#------------------------------#2324#WARNING: Select right API based on your component's release/not-release/standalone2526###NOTE: For open-source component, always build from source27ameba_add_internal_library(foo28p_SOURCES29${private_sources}30p_INCLUDES31${private_includes}32p_DEFINITIONS33${private_definitions}34p_COMPILE_OPTIONS35${private_compile_options}36)
Add public header file paths. It is recommended to use relative paths:
ameba_list_append(public_includes${CMAKE_CURRENT_SOURCE_DIR}# Access current dir by CMAKE_CURRENT_SOURCE_DIR, same as .${CMAKE_CURRENT_SOURCE_DIR}/foo# Access sub dir by CMAKE_CURRENT_SOURCE_DIR, same as ./foofoo# Access sub dir directly)
For third-party code with independent build systems (e.g., CMake/Makefile-based components), non-intrusive integration solutions can be implemented to link them into the firmware. Specific adaptation strategies should be selected based on their build system types:
Create a wrapper CMakeLists.txt in some directory using the template cmake/CMakeLists-template.cmake.
ameba_add_subdirectory(path/to/your/cmakelists)# Add the real CMakeLists.txt dir of the wrapped componentameba_port_standalone_internal_library(foo)# Add the real target of the wrapped component to link
add_custom_target(fooALL# Replace foo with new name as you wishCOMMAND${CMAKE_COMMAND}-Eecho"Building foo project"# Some prompt message while buildingCOMMANDmake-j-C/path/to/your/makefileall# Set your makefile pathCOMMENT"Building foo project using make")ameba_target_depend(${c_CURRENT_IMAGE}foo)# Add dependency, foo MUST be same as in add_custom_target above
Adjusting Submodule Organization Relationships in Components
When a component is complex and contains multiple subcomponents, the CMakeLists.txt in the component root directory should manage their organizational relationships. Pay attention to:
Configuration Checks: Since MCU projects generally don’t check switches and directly add components, configuration checks must be performed here. Example from component/bluetooth/CMakeLists.txt:
if(NOTCONFIG_BT)ameba_info("CONFIG_BT is off, bluetooth will not be built")return()endif()
Subcomponent Addition Logic: When splitting subcomponents, handle the addition logic using Useful Types for Complex Logic Processing Example from component/bluetooth/CMakeLists.txt:
ameba_add_subdirectory_ifnot(CONFIG_BT_INICapi)ameba_add_subdirectory_if(CONFIG_BT_AUDIO_CODEC_SUPPORTbt_audio)ameba_add_subdirectory(bt_mp)ameba_add_subdirectory(driver)ameba_add_subdirectory_ifnot(CONFIG_BT_INICexample)ameba_add_subdirectory(osif)ameba_add_subdirectory(rtk_coex)if(CONFIG_BT_ZEPHYR)ameba_add_subdirectory(zephyr)elseif(NOTCONFIG_BT_INIC)ameba_add_subdirectory(rtk_stack)# refer to ble_mesh_stackendif()
Add component path variables in cmake/global_define.cmake as below:
ameba_set(c_CMPT_WIFI_DIR${c_COMPONENT_DIR}/wifi)ameba_set(c_CMPT_WPAN_DIR${c_COMPONENT_DIR}/wpan)ameba_set(c_CMPT_CRASHDUMP_DIR${c_COMPONENT_DIR}/soc/common/crashdump)ameba_set(c_CMPT_LZMA_DIR${c_COMPONENT_DIR}/soc/common/lzma)ameba_set(c_CMPT_FOO_DIR${c_COMPONENT_DIR}/foo)# new component
Attention
For SOC/MCU-related components, add them in the ameba_reset_global_define() macro:
macro(ameba_reset_global_define)ameba_set(c_CMPT_USRCFG_DIR${c_COMPONENT_DIR}/soc/usrcfg/${c_SOC_TYPE})ameba_set(c_CMPT_BOOTLOADER_DIR${c_CMPT_SOC_DIR}/bootloader)ameba_set(c_CMPT_FOO_DIR${c_CMPT_SOC_DIR}/foo)# new componentendmacro()
The following APIs are used to compile code into static libraries or to perform further processing on existing targets.
Note
These APIs have the following characteristics:
The firmware that links the static library depends on which firmware’sCMakeLists.txt it is added in. For example, if added in image2/CMakeLists.txt, the static library will be linked to firmware image2.
The actual target name generated by the name parameter in these APIs is combined with other information such as c_MCU_PROJECT_NAME and c_CURRENT_IMAGE. Users can obtain the real target name using the variable c_CURRENT_TARGET_NAME after API calls.
Adds a static library, compiles it, and automatically links the target to the current firmware. The library file is generated under the build directory. Parameter descriptions:
name: Target name. The actual library file is lib_${name}.a.
p_SOURCES: Source files for the target.
p_INCLUDES: Include directories for the target.
p_COMPILE_OPTIONS: Compile options for the target.
p_DEFINITIONS: Preprocessor definitions for the target.
p_DEPENDENCIES: Dependencies for the target.
Attention
The library file generated by this API resides in the build directory. It does not check CONFIG_AMEBA_RLS internally and is always active.
Adds a static library, compiles it, but does not automatically link the target to the current firmware. It must be specified in Component Public Part. The library file is output to the ${c_SDK_LIB_APP_DIR} directory (see MCU Project-related Constants) and undergoes objcopy-g-D processing. Parameter descriptions:
name: Target name. The actual library file is lib_${name}.a.
p_SOURCES: Source files for the target.
p_INCLUDES: Include directories for the target.
p_COMPILE_OPTIONS: Compile options for the target.
p_DEFINITIONS: Preprocessor definitions for the target.
p_DEPENDENCIES: Dependencies for the target.
Attention
This API does nothing and returns immediately if CONFIG_AMEBA_RLS is TRUE.
Add a directory for compilation. Refer to add_subdirectory. When dir is an external path, the last-level directory name will be used as binary_dir. Parameter description:
When path doesn’t exist: silently return if CONFIG_AMEBA_RLS is TRUE, otherwise throw error. When path exists, behaves same as ameba_add_subdirectory. Parameter description:
dir: Directory to add for compilation when path exists
View Detailed Compilation Parameters of a Source File
Add the following code at the beginning of the target source file or introduce syntax errors, then compile. The compiler will report errors at the corresponding source file location and print detailed compilation parameters.
This error typically occurs during the linking stage when generating axf files. Common causes include:
Cause 1: The library containing the symbol is not linked
Cause 2: The source file containing the symbol is not compiled
Cause 3: The code block containing the symbol is not compiled
The following uses RTL8721Dx as an example to demonstrate troubleshooting steps for the above causes. Below is a sample link error output (partial information retained for demonstration):
if(CONFIG_AMEBADPLUS)# Add code for amebadplus hereelseif(CONFIG_AMEBALITE)# Add code for amebalite hereelseif(CONFIG_AMEBASMART)# Add code for amebasmart hereendif()
Attention
SOC type distinctions should be minimized. Prefer feature-based configuration switches instead.
If such usage is mandatory, adhere to the principle of upward compatibility to ensure future SOC type additions require no modification of this logic (e.g., avoid adding new elseif clauses). Example implementation in component/rtk_coex/CMakeLists.txt:
Typically caused by incorrect parameters in COMMAND sections of add_custom_target() or add_custom_command(), often due to empty CMake variables. Sample error output showing CMake’s -E usage:
This error occurs when ${output_path} is empty, resulting in incomplete copy command as shown in line 2.
Copied and replaced a source file but did not trigger recompilation
The CMake build system uses incremental compilation by default, meaning it only recompiles when source code or header files change. Its mechanism for detecting changes is **based on whether the file’s modification timestamp is newer than the previous build**.
When replacing a file with an older version (via copy-paste) while keeping its original modification timestamp (default behavior in Windows Explorer), the incremental compilation **will not be triggered** because the timestamp remains unchanged.
Recommended solutions for Windows environments:
1. **Create a new file** and copy the content into it (generates a new timestamp)
2. Use the build.py-c command to clean the build directory
To set compilation configurations, such as predefined macros, for specific source files. For example, adding a predefined macro __FILE_Z_STR__ to each source file that expands to the filename string of the corresponding source file. Implementation is as follows:
ameba_list_append(private_compile_options-Wno-multichar# for both asm/c/cpp language$<$<COMPILE_LANGUAGE:C>:-Wno-pointer-sign># for c language only$<$<COMPILE_LANGUAGE:CXX>:-Wno-narrowing># for cpp language only$<$<COMPILE_LANGUAGE:CXX>:-std=c++17># for cpp language only)
The global compilation configuration in cmake/flags/common/compile_options.cmake enables -Werror by default, which treats all compilation warnings as errors. To disable this behavior, use one of the following methods:
Method 1: Component-Specific Disable. Add compilation options in the target component’s CMakeLists.txt:
ameba_list_append(private_compile_options-Wno-error# Disable warning-as-error for this component)
Method 2: Global Disable. Modify the global configuration file cmake/flags/common/compile_options.cmake and comment out the -Werror line
The configuration system uses Kconfig to organize. Top Kconfig file under amebaxxx_gcc_project references sub-Kconfig under each component, generating files for CMake. As following figure show:
Mutual references between Kconfig files can use following syntax:
source :
The source statement can reference a path relative to the top-level Kconfig. For example, a top-level Kconfig loacated at ${top_kconfig_path} can use the following statement:
source"subdir/Kconfig"
This will incorporate {top_kconfig_path}/subdir/Kconfig file into the top-level Kconfig. If {top_kconfig_path}/subdir/Kconfig file does not exist, a Kconfig error will pop.
osource :
The file path referenced after osource will not cause an error even if it does not exist.
rsource :
The path referenced by rsource is relative to the Kconfig file using the rsource statement, rather than the top-level Kconfig.
orsource :
This statement is based on rsource, allowing the referenced file path to not exist.
Defining a Kconfig symbol follows the below format:
configFOObool"choose FOO"defaultn
The above Kconfig structure defines a bool type config symbol, which appears in the visualization interface as:
[]chooseFOO
"chooseFOO" is the prompt text, which will be displayed in the visualization configuration interface. If "chooseFOO" is removed, the configuration item will not appear in the visualization interface. Besides bool type, other types such as string, hex, int, etc., can also be defined.
defaultn indicates “no”, meaning this item will not be selected by default. defaultn can be omitted. Note that the default value defined by default only takes effect if the user has never touched this config item.
Further, conditional expressions can expand Kconfig functionality. For example, the following writing means that when BAR is selected, the FOO item will be displayed in the menu, and when both BAR and TIZ are selected, the default value of FOO will be y.
configFOObool"choose FOO"ifBARdefaultyifBAR&&TIZ
Dependencies in Kconfig
Adding dependencies with dependson
Through dependson, all entries under this config item can be added with dependencies. The following two writings are equivalent:
select forces another config to be selected when a config is chosen, regardless of whether this config has other dependency relationships. For example, in the following example, when BUZZ is selected, FOO will definitely be selected regardless of the value of BAR, and it cannot be deselected through the visualization interface.
select is commonly used for invisible configs or those without dependencies.
Invisible Config Items
When no prompt text is defined under a config, this config item is invisible, meaning it cannot be manually selected or deselected by the user. Its value is generally selected through dependency items or defined by default values. For example:
Conf files replace the UI-based menuconfig approach by writing user-defined configuration items into them. Conf files consist of multiple configuration items, each following this format:
CONFIG_<name1>=<value>CONFIG_<name2>=<value>...
There should be no spaces around the = sign.
Writing conf Files
The writing style of conf files is similar to .config files, but note that .config files contain all config items, whether they are visible to the user or not. However, conf files can only set config items visible to the user.
Similar to UI menuconfig, this direct configuration method essentially selects/deselects some items defined in Kconfig files, replacing interactive input with parameter input.
For example, consider the following Kconfig:
configSUPPORT_ATCMDbool"Enable ATCMD"defaultnifSUPPORT_ATCMDchoicedefaultATCMD_MANUAL_TESTprompt"ATCMD Mode"configATCMD_MANUAL_TESTbool"Manual Test Mode"configATCMD_HOST_CONTROLbool"Host Control Mode"endchoiceconfigATCMD_NETWORKbool"Enable Network"defaultnconfigATCMD_SOCKETbool"Enable Socket"endif
Since ATCMD_MANUAL_TEST is the default choice value, it can be omitted in the conf file as CONFIG_ATCMD_MANUAL_TEST=y.
In the {SDK}/amebaxxx_gcc_project/utils/confs_daily_build directory, various common configuration collection files are provided, which users can refer to for creating their own conf files.
Note
Users can save the current configurations as a conf file using menuconfig.py -s.
default.conf
In {SDK}/amebaxxx_gcc_project directory, there is a configuration file named default.conf, defining the initial configuration for building this SOC project.
Specifically, menuconfig.py-f implicitly includes the rule of menuconfig.py-fdefault.conf, so when users write conf files, they can omit config items already configured in the default.conf file, meaning they only need to write incremental configurations compared to default.conf. If certain options in default.conf need to be disabled, set the corresponding config items to n.
Default values in Kconfig only take effect if a specific config item is not touched, while default.conf files act as a series of default inputs, thus having higher priority than default values in Kconfig.
prj.conf
prj.conf is located under example or the user-created project path, recording the configuration items needed for this example or external project. Users can configure the project using menuconfig.py-f/.../prj.conf. Additionally, when users have not configured through the UI or specified other conf files, prj.conf will be used as the initial configuration.
The priority of prj.conf is higher than that of default.conf.
When the Kconfig file or default.conf file is updated (e.g., pulled from a remote repository), but the files in the menuconfig folder are still based on the previous Kconfig, running build.py directly can lead to unexpected behavior. To prevent this, a check for Kconfig updates is performed before each compilation.
The anchor file for this check is menuconfig/.config_default, which is generated based on default.conf before each compilation. It contains the default configuration values for the SOC. If the newly generated menuconfig/.config_default differs from an existing file in the menuconfig folder, the console will display the differences and prompt the user to decide:
If user determines that the Kconfig update can be ignored, press Enter to continue using the current .config configuration.
If user believes ignoring the Kconfig update may affect the compilation results, press Ctrl+C to exit. After exiting, the user can reconfigure using the visualization interface or via menuconfig.py-f, or clean the menuconfig folder using menuconfig.py-c or build.py-p, and then compile using the default configuration.