概述
概念
SOC 项目:每个 SOC 项目根目录都包含一个
CMakeLists.txt
作为编译入口,其下所有 MCU 项目都是在这里添加的。MCU 项目:每个 MCU 项目使用统一的工具链和配置参数(个别配置会因固件或 ROM 有差异),不同 MCU 项目可能使用不同的工具链,MCU 项目下又包括了一个或多个固件的编译。
固件:主要包括
image1
,image2
,image3
,三者区别如下:image1
: 定义了 MCU 系统中引导加载程序(bootloader)和系统闪存布局的通用基础架构,并提供了一个安全引导加载程序,支持便捷的软件升级。image2
: 是该 SOC 的主应用固件,通常包含实时操作系统(FreeRTOS)和应用程序任务。image3
: 具有高度可配置性,允许用户仅包含所需的安全服务与功能。它受到读保护(RDP)的保护,将在安全引导加载程序中解密,并被加载至由 TrustZone 技术保护的安全静态随机存储器(SRAM)中。
组件:组件是在和 SOC 项目同级的
component
目录下,按照不同的功能划分为不同的子目录,每个组件都有相应的CMakeLists.txt
定义其编译及被链接等配置。固件编译:在各自 MCU 项目下
[asdk|vsdk]/make/[image1|image2|image3]/CMakeLists.txt
中根据需要添加所需组件并定义编译规则。
SDK CMake 结构图
整个项目 CMakeLists.txt
目录结构和调用关系如下图所示:
注意
在上图中, image1
,image2
,image3
目录中的 CMakeLists.txt
中可以为 各自固件添加相应的组件,他们是相互独立的。
cmake
目录结构如下:
cmake
├── flags # Global compile and link options
│ ├── ca32
│ │ ├── compile_options.cmake
│ │ └── link_options.cmake
│ ├── common
│ │ ├── compile_options.cmake
│ │ └── link_options.cmake
│ ├── km0
│ │ ├── compile_options.cmake
│ │ └── link_options.cmake
│ ├── km4
│ │ ├── compile_options.cmake
│ │ └── link_options.cmake
│ └── kr4
│ ├── compile_options.cmake
│ └── link_options.cmake
├── CMakeLists-template.cmake # CMakeLists.txt template for component
├── common.cmake # Project related APIs
├── global_define.cmake # Global defined parameters
├── utility_base.cmake # Utility APIs (lower level)
├── utility.cmake # Utility APIs (upper level)
└── toolchain # Toolchain defines
├── ameba-toolchain-asdk-10.3.1.cmake
├── ameba-toolchain-asdk-12.3.1.cmake
├── ameba-toolchain-check.cmake
├── ameba-toolchain-common.cmake
└── ameba-toolchain-vsdk-10.3.1.cmake
全局编译配置
全局编译配置指在一定范围内,所有的组件共享的编译配置内容。
编译配置主要内容
源文件 (sources)
头文件路径 (include directories)
编译选项 (compile options)
预定义 (definitions)
链接选项 (link options)
链接库 (link libraries)
备注
源文件、头文件路径、编译选项、预定义 用于组件编译。
链接选项 和 链接库 用于固件链接。
全局编译配置来源
主要有两部分来源:
cmake/flags
中的设置,参考 SDK CMake 结构图各个组件 组件 public 部分 中追加的内容
注意
最终全局编译配置中各组件追加部分的顺序(如头文件路径的顺序),取决于组件被固件 add 的顺序。
这个顺序是不可靠的,在顺序敏感的配置中(如头文件路径),最好通过别的方式处理,详见 组件 private 部分。
全局编译配置作用域
全局编译配置的作用域为各 MCU 项目,如 RTL8721Dx
中 km0
和 km4
各有一套全局编译配置, RTL8730E
中 km0
,km4
及 ca32
各有一套全局编译配置,它们之间相互隔离。
同一 MCU 项目下的组件编译时使用相同的全局编译配置,参考如下示意图:
其中:
可以看到每个 MCU 项目都各有一个独立的编译配置作用域。
同一个 MCU 项目下的不同固件
image1
,image2
,image3
使用相同的编译配置。同一个组件在不同的 MCU 项目中会使用 不同的全局编译配置 分别编译,如上图中的
at_cmd
在project_mcu1
和project_mcu2
的image2
中分别被编译。
组件编译 CMakeLists.txt
由 组件 public 部分 和 组件 private 部分 两部分组成:
前者描述了该组件向 全局编译配置 中追加的内容
后者描述了该 组件库文件 的编译配置。
组件 public 部分
该部分用于向 全局编译配置 中 追加 如下编译配置:
头文件路径:如果其他组件要使用当前组件的头文件,则可以将当前组件头文件目录加入全局。
预定义:如果当前组件被添加编译时,需要同步在全局加入某些预定义(使用场景较少)。
链接库:如果 当前组件给下游客户时不提供源码,只提供库文件,则链接时需要链接库文件而不是 target,所以需要将库文件路径加入全局以便链接时取出。库文件的编译可以在 组件 private 部分 中调用 ameba_add_external_app_library 完成。
上述三类配置在如下代码块中分别由 public_includes
,public_definitions
,public_libraries
三个变量设置,用户可以在如下高亮代码行区域内对其进行更新。
可以参考模板 cmake/CMakeLists-template.cmake
中的一些示例,相关 API 可参考 list 操作,具体示例可以参考 修改现有组件的编译配置。
##########################################################################################
## * This part defines public part of the component
## * Public part will be used as global build configures for all component
set(public_includes) #public include directories, NOTE: relative path is OK
set(public_definitions) #public definitions
set(public_libraries) #public libraries(files), NOTE: linked with whole-archive options
#------------------------------------------------------------------#
# Component public part, user config begin(DO NOT remove this line)
# set public_includes, public_definitions, public_libraries in this section
# Component public part, user config end(DO NOT remove this line)
#------------------------------------------------------------------#
#WARNING: Fixed section, DO NOT change!
ameba_global_include(${public_includes})
ameba_global_define(${public_defines})
ameba_global_library(${public_libraries}) #default: whole-achived
备注
如果当前组件没有必要对上述三类配置进行更新,上述高亮代码部分可以留空。
组件 private 部分
该部分描述了该组件库文件的编译配置,主要包括:
源文件:当前组件编译的源文件
头文件路径:仅对当前组件有效
预定义:仅对当前组件有效
编译选项:仅对当前组件有效
上述四类配置在如下代码块中分别由 private_sources
,private_includes
,
private_definitions
,private_compile_options
四个变量设置,用户可以在高亮代码行区域内对其进行更新。
可以参考模板 cmake/CMakeLists-template.cmake
中的一些用法,相关 API 可参考 list 操作。
注意
组件最终的编译配置(特别是 头文件路径 , 预定义 及 编译选项)是由 组件 private 部分 和 组件 public 部分 组成,头文件路径和编译选项前者优先级高于后者,例如组件实际头文件搜索路径包括:
当前组件 private 部分添加的头文件路径。
全局编译配置 中的头文件路径, 包含 当前组件 的和 其他组件 追加的。
所以在 组件 public 部分 已经添加的编译配置就无需在 组件 private 部分 重复添加。
一般情况下建议:
倾向于将头文件路径放在 组件 private 部分,以避免头文件污染。
除非是比较通用或底层组件,会被很多其他组件用到,此时放在 组件 public 部分 可以提高复用性。
##########################################################################################
## * This part defines private part of the component
## * Private part is used to build target of current component
## * NOTE: The build API guarantees the global build configures(mentioned above)
## * applied to the target automatically. So if any configure was already added
## * to public above, it's unnecessary to add again below.
#NOTE: User defined section, add your private build configures here
# You may use if-else condition to set these predefined variable
# They are only for ameba_add_internal_library/ameba_add_external_app_library/ameba_add_external_soc_library
set(private_sources) #private source files, NOTE: relative path is OK
set(private_includes) #private include directories, NOTE: relative path is OK
set(private_definitions) #private definitions
set(private_compile_options) #private compile_options
#------------------------------#
# Component private part, user config begin
# set private_sources, private_includes, private_definitions, private_compile_options in this section
# Component private part, user config end
#------------------------------#
#WARNING: Select right API based on your component's release/not-release/standalone
###NOTE: For open-source component, always build from source
ameba_add_internal_library(foo
p_SOURCES
${private_sources}
p_INCLUDES
${private_includes}
p_DEFINITIONS
${private_definitions}
p_COMPILE_OPTIONS
${private_compile_options}
)
备注
上述代码块中使用 ameba_add_internal_library 编译库文件,此外还可以根据需要选择其他 API,详见 添加库。上述高亮代码部分修改可参考 修改现有组件的编译配置。
如果组件已经有独立编译脚本的目录,则参考 适配具备独立编译系统的代码。
最佳实践
修改现有组件的编译配置
可以参考 组件编译 CMakeLists.txt 相关说明,也可以使用 一些可用于复杂逻辑处理的判断类型,并且 list 操作 API 可以多次调用。
以下是一些具体的例子:
添加 public 头文件路径,建议使用相对路径:
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 ./foo foo # Access sub dir directly )
添加链接库路径,这里用到的变量可以参考 MCU 项目相关常量:
ameba_list_append(public_libraries ${c_SDK_LIB_APP_DIR}/lib_foo.a )
添加源文件,建议使用相对路径:
ameba_list_append(private_sources foo/foo.c bar/bar.c )
添加 private 头文件路径:
ameba_list_append(private_includes ../common # Access parent dir foo bar )
添加预定义:
ameba_list_append(private_definitions __RTOS__ MBEDTLS_CONFIG_FILE="mbedtls/config.h" )
添加编译选项:
ameba_list_append(private_compile_options -Wno-unused-parameter )
快速将一组源文件编译并加入固件进行测试
参考如下步骤:
拷贝模板
cmake/CMakeLists-template.cmake
到源文件目录下,并改名为CMakeLists.txt
参考 修改现有组件的编译配置 设置 组件 public 部分 和 组件 private 部分
组件 private 部分 选择 ameba_add_internal_library 作为编译的 API
参考 CMakeLists.txt 调用关系图 在相应 MCU 项目下对应固件的
CMakeLists.txt
中添加上述源码目录进行编译ameba_add_subdirectory(path/to/your/cmakelists)
编译,测试
适配具备独立编译系统的代码
对于具备独立编译系统(如采用 CMake/Makefile 的第三方组件),可通过非侵入式集成方案实现链接进入固件,具体实施可依据其编译系统类型选择适配策略。
拷贝模板
cmake/CMakeLists-template.cmake
到一个合适的位置(比如为该代码组件新建一个 wrap 目录),并改名为CMakeLists.txt
参考 修改现有组件的编译配置 设置 组件 public 部分
在 组件 private 部分 通过 ameba_add_subdirectory 添加原有
CMakeLists.txt
编译,然后使用 ameba_port_standalone_internal_library 进行适配ameba_add_subdirectory(path/to/your/cmakelists) # Add the real CMakeLists.txt dir of the wrapped component ameba_port_standalone_internal_library(foo) # Add the real target of the wrapped component to link
参考 CMakeLists.txt 调用关系图 在相应固件的
CMakeLists.txt
中添加上述CMakeLists.txt
所在目录进行编译ameba_add_subdirectory(path/to/your/wrap/cmakelists)
编译,测试
拷贝模板
cmake/CMakeLists-template.cmake
到一个合适的位置(比如为该代码组件新建一个 wrap 目录),并改名为CMakeLists.txt
参考 修改现有组件的编译配置 设置 组件 public 部分,特别是是设置
public_libraries
为最终库文件的路径组件 private 部分 使用如下代码,注意修改其中的路径或名称
add_custom_target(foo ALL # Replace foo with new name as you wish COMMAND ${CMAKE_COMMAND} -E echo "Building foo project" # Some prompt message while building COMMAND make -j -C /path/to/your/makefile all # Set your makefile path COMMENT "Building foo project using make" ) ameba_target_depend(${c_CURRENT_IMAGE} foo) # Add dependency, foo MUST be same as in add_custom_target above
参考 CMakeLists.txt 调用关系图 在相应固件的
CMakeLists.txt
中添加上述CMakeLists.txt
所在目录进行编译ameba_add_subdirectory(path/to/your/wrap/cmakelists)
编译,测试
调整组件子模块组织关系
当一个组件很复杂,其中包含了多个子组件,此时组件根目录下的 CMakeLists.txt
通常用于添加各个子组件的组织关系,注意如下两点:
因为 MCU 项目中原则上不检查开关,直接 add 组件,所以组件的开关检查需要在这里完成,例如
component/bluetooth/CMakeLists.txt
开头:if(NOT CONFIG_BT) ameba_info("CONFIG_BT is off, bluetooth will not be built") return() endif()
当拆分子组件时,组件根目录下的
CMakeLists.txt
要处理好底层子组件的添加逻辑,可以使用 一些可用于复杂逻辑处理的判断类型,例如component/bluetooth/CMakeLists.txt
:ameba_add_subdirectory_ifnot(CONFIG_BT_INIC api) ameba_add_subdirectory_if(CONFIG_BT_AUDIO_CODEC_SUPPORT bt_audio) ameba_add_subdirectory(bt_mp) ameba_add_subdirectory(driver) ameba_add_subdirectory_ifnot(CONFIG_BT_INIC example) ameba_add_subdirectory(osif) ameba_add_subdirectory(rtk_coex) if(CONFIG_BT_ZEPHYR) ameba_add_subdirectory(zephyr) elseif(NOT CONFIG_BT_INIC) ameba_add_subdirectory(rtk_stack) # refer to ble_mesh_stack endif()
component 中新增组件
当需要新增一个独立组件时,按如下步骤操作:
在
cmake/global_define.cmake
中添加组件的路径变量,参考如下组件: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
注意
如果组件路径与 SOC 类型 或 MCU 类型 相关,则应将其加入到
cmake/global_define.cmake
的ameba_reset_global_define()
宏中,例如: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 component endmacro()
在
component
下新建组件目录,并参考 组件编译 CMakeLists.txt 添加相关CMakeLists.txt
参考 CMakeLists.txt 调用关系图 在相应固件的
CMakeLists.txt
中添加ameba_add_subdirectory(${c_CMPT_FOO_DIR})
常用的 CMake 接口和预设常量
list 操作
ameba_list_append
ameba_list_append(<list_name> [<value> ...])
向 list 中追加元素,支持追加多个,参数说明:
- list_name:
list 变量名
- value:
待追加的值
ameba_list_append_if
ameba_list_append_if(<condition> <list_name> [<value> ...] [p_ELSE <else value> ...])
基于一定的条件向 list 中追加元素,支持追加多个,参数说明:
- condition:
判定条件的变量名
- list_name:
list 变量名
- value:
当
condition
成立时向 list 中追加的值- p_ELSE:
可选的关键字参数,其后面紧跟的值会在
condition
不成立时追加到 list 中- else value:
当
condition
不成立时向 list 中追加的值
注意
condition
所表示的变量未定义或定义了但布尔值为 FALSE
都会被认为不成立
ameba_list_append_ifnot
ameba_list_append_ifnot(<condition> <list_name> [<value> ...] [p_ELSE <else value> ...])
基于一定的条件向 list 中追加元素,支持追加多个,与 ameba_list_append_if()
功能相反,参数说明:
- condition:
判定条件的变量名
- list_name:
list 变量名
- value:
当
condition
不成立时向 list 中追加的值- p_ELSE:
可选的关键字参数,其后面紧跟的值会在
condition
成立时追加到 list 中- else value:
当
condition
成立时向 list 中追加的值
注意
condition
所表示的变量未定义或定义了但布尔值为 FALSE
都会被认为不成立
ameba_list_append_ifdef
ameba_list_append_ifdef(<condition> <list_name> [<value> ...] [p_ELSE <else value> ...])
基于一定的条件向 list 中追加元素,支持追加多个,参数说明:
- condition:
判定条件的变量名
- list_name:
list 变量名
- value:
当
condition
被定义时向 list 中追加的值( 注意:此时condition
可以是FALSE
)- p_ELSE:
可选的关键字参数,其后面紧跟的值会在
condition
未定义时追加到 list 中- else value:
当
condition
未定义时向 list 中追加的值
添加库
如下 API 用于将代码编译为静态库或者对已有 target 进行进一步处理
备注
这些 API 具有如下特点:
静态库被 哪个固件链接取决于是在哪个固件 的
CMakeLists.txt
中添加,例如在 image2/CMakeLists.txt 中添加,则相应静态库会被固件image2
链接如下 API 中的 target 实际名称是由
name
参数及其他信息如 c_MCU_PROJECT_NAME,c_CURRENT_IMAGE 组合而成,用户可以在 API 调用过后使用变量c_CURRENT_TARGET_NAME
来获取真实的 target 名称这些 API 内部会自动使用 全局编译配置 来编译当前 target
ameba_add_internal_library
ameba_add_internal_library(<name>
[p_SOURCES <sources> ...]
[p_INCLUDES <include dirs> ...]
[p_COMPILE_OPTIONS <compile options> ...]
[p_DEFINITIONS <definitions> ...]
[p_DEPENDENCIES <dependencies> ...]
)
添加一个静态库,执行编译且 相应 target 会被自动链接到当前固件,库文件输出到默认的 build
目录下,参数说明:
- name:
target 名称,实际完整的库文件为
lib_${name}.a
- p_SOURCES:
target 源文件
- p_INCLUDES:
target 头文件路径
- p_COMPILE_OPTIONS:
target 编译选项
- p_DEFINITIONS:
target 预定义
- p_DEPENDENCIES:
target 依赖
注意
该 API 生成的库文件在 build 目录下,其内部不会检查 CONFIG_AMEBA_RLS
,始终生效
ameba_port_standalone_internal_library
ameba_port_standalone_internal_library(<name>)
将一个 已有 静态库 target 添加到当前固件的链接 :
- name:
target 名称
小技巧
特别适用于非侵入性的适配第三方库的 CMakeLists.txt
,参考 适配具备独立编译系统的代码
注意
该 API 其内部不会检查 CONFIG_AMEBA_RLS
,始终生效
ameba_add_external_app_library
ameba_add_external_app_library(<name>
[p_SOURCES <sources> ...]
[p_INCLUDES <include dirs> ...]
[p_COMPILE_OPTIONS <compile options> ...]
[p_DEFINITIONS <definitions> ...]
[p_DEPENDENCIES <dependencies> ...]
)
添加一个静态库,执行编译但 相应 target 不会被自动链接到当前固件,需要在 组件 public 部分 中指定,库文件会被输出到 ${c_SDK_LIB_APP_DIR}
(参考 MCU 项目相关常量)目录中,且会执行 objcopy -g -D
操作,参数说明:
- name:
target 名称,实际完整的库文件为
lib_${name}.a
- p_SOURCES:
target 源文件
- p_INCLUDES:
target 头文件路径
- p_COMPILE_OPTIONS:
target 编译选项
- p_DEFINITIONS:
target 预定义
- p_DEPENDENCIES:
target 依赖
注意
当 CONFIG_AMEBA_RLS
为 TRUE
时该该 API 什么也不做直接返回
添加子目录
ameba_add_subdirectory
ameba_add_subdirectory(<dir>)
添加一个目录用于编译,可参考 add_subdirectory,此外,当 dir
为外部路径时会取其最后一级目录名作 binary_dir
,参数说明:
- dir:
要添加编译的目录
ameba_add_subdirectory_if
ameba_add_subdirectory_if(<condition> <dir>)
基于一定的条件添加一个目录用于编译,其他同 ameba_add_subdirectory,参数说明:
- condition:
判定条件的变量名
- dir:
条件成立时要添加编译的目录
ameba_add_subdirectory_ifnot
ameba_add_subdirectory_ifnot(<condition> <dir>)
基于一定的条件添加一个目录用于编译,其他同 ameba_add_subdirectory,参数说明:
- condition:
判定条件的变量名
- dir:
条件不成立时要添加编译的目录
注意
condition
所表示的变量未定义或定义了但布尔值为 FALSE
都会被认为不成立
ameba_add_subdirectory_if_exist
ameba_add_subdirectory_if_exist(<dir>)
当路径不存在时,如果 CONFIG_AMEBA_RLS
为 TRUE
时直接静默返回,否则会报错,路径存在时同 ameba_add_subdirectory,参数说明:
- dir:
条件不成立时要添加编译的目录
常量定义
CMake 脚本 cmake/global_define.cmake
中定义了一些常量可以直接用于编写脚本,列举部分如下:
常量类型 |
变量名 |
值 |
---|---|---|
通用常量 |
|
仓库根目录 |
|
|
|
|
|
|
|
|
|
SoC 项目相关常量 |
|
[“amebadplus”, “amebalite”, “amebasmart”] 之一 |
|
[“AMEBADPLUS”, “AMEBALITE”, “AMEBASMART”] 之一 |
|
|
[“AebaDplus”, “AmebaLite”, “AmebaSmart”] 之一 |
|
|
|
|
MCU 项目相关常量 |
|
[“km0”, “km4”, “kr4”, “ca32”, …] 之一 |
|
如果 MCU 项目文件夹名称为 |
|
|
当前固件 target 名称,如 |
|
|
当前 MCU 项目 Kconfig 文件路径 |
|
|
|
|
|
|
|
|
|
|
组件相关常量 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
常见问题与建议
查看某个源文件的详细编译参数
在相应源文件开头加入如下代码或者加入一些语法错误然后进行编译,编译器会在相应源文件处出错并打印详细编译参数
#error debug
Undefined Reference 错误
该错误一般发生在链接生成 axf 文件阶段,常见原因有如下几类:
原因一:符号所在库文件未被链接
原因二:符号所在源文件未被编译
原因三:符号所在代码块未被编译
下面以 RTL8721Dx
为例,针对上述原因依次进行排查,如下是一个链接错误的输出(这里仅保留了部分信息用于展示):
[5/42] Linking C executable project_km4/asdk/make/image2/target_img2_km4.axf
FAILED: project_km4/asdk/make/image2/target_img2_km4.axf
ccache /opt/rtk-toolchain/asdk-10.3.1-4354/linux/newlib/bin/arm-none-eabi-gcc
-O2
-o project_km4/asdk/make/image2/target_img2_km4.axf
-Wl,--whole-archive
project_km4/asdk/make/image2/at_cmd/lib_at_cmd.a
project_km4/asdk/make/image2/swlib/lib_swlib.a
project_km4/asdk/make/image2/file_system/fatfs/lib_fatfs.a
project_km4/asdk/make/image2/file_system/littlefs/lib_littlefs.a
project_km4/asdk/make/image2/file_system/kv/lib_kv.a
project_km4/asdk/make/image2/file_system/vfs/lib_vfs.a
project_km4/asdk/make/image2/fwlib/lib_fwlib.a
project_km4/asdk/make/image2/hal/lib_hal.a
project_km4/asdk/make/image2/misc/lib_misc.a
project_km4/asdk/make/image2/lwip/lib_lwip.a
-Wl,--no-whole-archive
-lm
-O2
-o project_km4/asdk/make/image2/target_img2_km4.axf
-Wl,--whole-archive
project_km4/asdk/make/image2/at_cmd/lib_at_cmd.a
project_km4/asdk/make/image2/cmsis-dsp/lib_cmsis_dsp.a
-Wl,--no-whole-archive
-lm
-lstdc++
ld: amebadplus_gcc_project/project_km4/asdk/lib/soc/lib_chipinfo.a(ameba_rom_patch.o): in function `io_assert_failed':
(.text.io_assert_failed+0x12): undefined reference to `rtk_log_write_nano'
针对上述错误首先确认基本信息,通过高亮行 1,2,33,34 可知:
出现链接问题的 MCU 项目 是:
km4
出现链接问题的 固件 是:image2,文件名 是:
target_img2_km4.axf
未定义的符号是:
rtk_log_write_nano
,其是在函数io_assert_failed()
中被调用的进一步确认:
包含该符号定义的 源文件:
log.c
包含该源文件的 组件:
swlib
该组件的生成的 库文件:
lib_swlib.a
然后依次排查上述原因:
符号所在库文件未被链接
排查述错误信息中的链接参数是否包含 库文件:
lib_swlib.a
通过上述错误信息第 9 行可知
project_km4/asdk/make/image2/swlib/lib_swlib.a
已经被 image2 加入了链接,如果没有则需要排查:image2 的
CMakeLists.txt
中是否 addswlib
组件检查组件的
CMakeLists.txt
,如果使用了 ameba_add_external_app_library 等 API 要检查其 组件 public 部分 中的public_libraries
是否正确添加库文件
符号所在源文件未被编译
在 CMake 的
build
目录中的 MCU 项目目录project_km4
查找文件是否存在log.o
:
执行命令:
find build/project_km4 -name log.o
结果如下:
build/project_km4/asdk/make/image1/swlib/CMakeFiles/swlib_target_loader_km4.dir/log.o
build/project_km4/asdk/make/image2/swlib/CMakeFiles/swlib_target_img2_km4.dir/log.o
执行命令:
dir /s build\project_km4\log.o
结果如下:
C:\Users\sdk\amebadplus_gcc_project\build\project_km4\asdk\make\image1\swlib\CMakeFiles\swlib_target_loader_km4.dir 的目录
2025/03/12 14:56 21,096 log.o
1 个文件 21,096 字节
C:\Users\sdk\amebadplus_gcc_project\build\project_km4\asdk\make\image2\swlib\CMakeFiles\swlib_image2_km4.dir 的目录
2025/03/12 14:56 21,096 log.o
1 个文件 21,096 字节
查看黄色标记部分,如果未出现表示
log.c
未被编译,此时需要排查swlib
的CMakeLists.txt
中的问题如: 遗漏源文件,逻辑错误等
符号所在代码块未被编译
参考 原因二 中步骤找到
log.o
路径使用如下命令(这里需要用到工具链, 具体路径请参考 安装工具链)查看
log.o
中是否包含符号rtk_log_write_nano
:执行命令:
/opt/rtk-toolchain/asdk-10.3.1/linux/newlib/bin/arm-none-eabi-nm build/project_km4/asdk/make/image2/swlib/CMakeFiles/swlib_target_img2_km4.dir/log.o | grep -w rtk_log_write_nano #Or use default nm: nm build/project_km4/asdk/make/image2/swlib/CMakeFiles/swlib_target_img2_km4.dir/log.o | grep -w rtk_log_write_nano
执行命令:
C:\rtk-toolchain\asdk-10.3.1-4354\mingw32\newlib\bin\arm-none-eabi-nm.exe build\project_km4\asdk\make\image2\swlib\CMakeFiles\swlib_image2_km4.dir\log.o | findstr /R "\<rtk_log_write_nano\>"
如果输出 不包含 如下内容(注意其中的
T
)则表明log.a
未包含符号,此时需要排查log.c
中的条件编译是否正确00000000 T rtk_log_write_nano
一些可用于复杂逻辑处理的判断类型
SOC 类型
SOC 类型如 amebadplus
,amebalite
,amebasmart
可以通过如下变量判断: CONFIG_AMEBADPLUS
,CONFIG_AMEBALITE
,CONFIG_AMEBASMART
:
if(CONFIG_AMEBADPLUS)
# Add code for amebadplus here
elseif(CONFIG_AMEBALITE)
# Add code for amebalite here
elseif(CONFIG_AMEBASMART)
# Add code for amebasmart here
endif()
注意
SOC 类型的区分应当尽可能消除,更好的方法是使用特性之类的开关配置替代
如果一定要使用应秉持向上兼容的原则,即未来有新增 SOC 类型时 无需改动此处逻辑 (比如增加新的
elseif
),例如:file:component/rtk_coex/CMakeLists.txt
中的写法:if(NOT CONFIG_AMEBAD) if (CONFIG_COEXIST_HOST) include(rtk_coex_api.cmake) endif() endif()
MCU 类型
SOC 类型如 km0
,km4
,kr4
,ca32
等,可以通过 STR 类型变量 c_MCU_TYPE
获取:
if(${c_MCU_TYPE} STREUQAL "km0")
# Add code for km0 here
elseif(${c_SOC_TYPE} STREUQAL "km4")
# Add code for km4 here
elseif(${c_SOC_TYPE} STREUQAL "ca32")
# Add code for ca32 here
endif()
CMake 常见的一些 debug 方法
日志
可以使用 CMake 内置 message()
或可读性更好的 ameba_debug()
,ameba_info()
,ameba_warning()
,ameba_fatal()
使用如下方式打印日志, CMake 在配置阶段执行到代码处会停止,通常可以用于分析代码执行情况
message(FATAL_ERROR "stop here")
常见 CMake 错误排查
小技巧
排查 CMake 错误要从终端输出中的 第一个 错误开始看起
执行外部命令参数错误
通常是在调用 CMake 接口 add_custom_target()
或 add_custom_command()
中 COMMAND
中的命令参数有误导致,而根源更多是因为某些 CMake 变量为空导致。典型错误输出如下, 特征是会包括 CMake 的 -E
参数的 usage:
FAILED: build/lib_atcmd.a
/usr/bin/cmake -E copy build/lib_atcmd.a
CMake Error: cmake version 3.30.2
Usage: /usr/bin/cmake -E <command> [arguments...]
Available commands:
capabilities - Report capabilities built into cmake in JSON format
cat [--] <files>... - concat the files and print them to the standard output
chdir dir cmd [args...] - run command in a given directory
...
出现此错误对应的 CMake 代码如下:
add_custom_command(
OUTPUT lib/lib_atcmd.a
COMMAND ${CMAKE_COMMAND} -E copy build/lib_atcmd.a ${output_path}
DEPENDS build/lib_atcmd.a
)
在上述代码中,如果变量 output_path
为空,拷贝命令就会变成了上述错误信息第 2 行缺少目的路径从而导致报错
拷贝替换了一个源文件,但没有重新编译
CMake 编译系统默认是增量编译,即源码或头文件发生变化时才会重新编译,而其检测变化的依据是 文件的修改时间是否比上次更新
当从别处拷贝了一个修改时间更老的文件,如果文件修改时间戳保持不变(Windows 下资源管理器默认行为)就不会触发增量编译
解决方法:
方法一:Windows 环境中新建文件,然后拷贝内容
方法二:执行
build.py -c
进行清理
使用 no-whole-archive 的方式链接某个静态库
关于 no-whole-archive 的介绍参考 静态链接,大多数 API 处理的静态库都是 whole-archive 的方式被固件链接的,如果需要强制静态库使用 no-whole-archive 针对不同的情况方案如下:
情况一:使用 ameba_add_internal_library 编译的静态库
在
ameba_add_internal_library()
函数中增加参数p_NO_WHOLE_ARCHIVE
:ameba_add_internal_library(at_cmd p_NO_WHOLE_ARCHIVE p_SOURCES ${private_sources} p_INCLUDES ${private_includes} p_DEFINITIONS ${private_definitions} p_COMPILE_OPTIONS ${private_compile_options} p_DEPENDENCIES ${c_BUILD_INFO} )
情况二: 使用
public_libraries
加入链接的静态库在调用
ameba_global_library()
时加入参数p_NO_WHOLE_ARCHIVE
ameba_global_library(${public_libraries} p_NO_WHOLE_ARCHIVE)
进阶阅读
特殊的编译配置
以源文件为单位设置编译配置
仅对某个或某些源文件设置编译配置,如预定义等,例如为每个源文件添加一个预定义 __FILE_Z_STR__
,该预定义展开为对应源文件的文件名字符串,实现如下:
ameba_list_append(private_sources
foo.c
bar.c
)
foreach(src ${private_sources})
cmake_path(GET src FILENAME filename)
set_source_files_properties(${src} PROPERTIES COMPILE_DEFINITIONS "__FILE_Z_STR__=\"${filename}\"")
endforeach()
使用 CMake 内置接口 set_source_files_properties 可以精确对每个源文件设置编译配置
按源文件类型设置编译选项
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
)
阻止编译警告错误
全局编译配置中 cmake/flags/common/compile_options.cmake
默认使用了 -Werror
选项,即所有编译警告都会当作编译错误处理,如需解除该设定可参考下列方法:
方法一:仅对当前组件生效,在当前组件的
CMakeLists.txt
添加编译选项:ameba_list_append(private_compile_options -Wno-error )
方法二:对所有组件生效,修改全局编译配置
cmake/flags/common/compile_options.cmake
注释掉-Werror
即可