IC:

概述

概念

  • SOC 项目:每个 SOC 项目根目录都包含一个 CMakeLists.txt 作为编译入口,其下所有 MCU 项目都是在这里添加的。

  • MCU 项目:每个 MCU 项目使用统一的工具链和配置参数(个别配置会因固件或 ROM 有差异),不同 MCU 项目可能使用不同的工具链,MCU 项目下又包括了一个或多个固件的编译。

  • 固件:主要包括 image1image2image3,三者区别如下:

    • 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 目录结构和调用关系如下图所示:

../../rst_rtos/0_build_system/figures/build_cmake_project_structure.svg

注意

在上图中, image1image2image3 目录中的 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)

备注

  • 源文件、头文件路径、编译选项、预定义 用于组件编译。

  • 链接选项链接库 用于固件链接。

全局编译配置来源

主要有两部分来源:

注意

  • 最终全局编译配置中各组件追加部分的顺序(如头文件路径的顺序),取决于组件被固件 add 的顺序。

  • 这个顺序是不可靠的,在顺序敏感的配置中(如头文件路径),最好通过别的方式处理,详见 组件 private 部分

全局编译配置作用域

全局编译配置的作用域为各 MCU 项目,如 RTL8721Dxkm0km4 各有一套全局编译配置, RTL8730Ekm0km4ca32 各有一套全局编译配置,它们之间相互隔离。

同一 MCU 项目下的组件编译时使用相同的全局编译配置,参考如下示意图:

../../rst_rtos/0_build_system/figures/build_cmake_project_structure_with_config.svg

其中:

  • 可以看到每个 MCU 项目都各有一个独立的编译配置作用域。

  • 同一个 MCU 项目下的不同固件 image1image2image3 使用相同的编译配置。

  • 同一个组件在不同的 MCU 项目中会使用 不同的全局编译配置 分别编译,如上图中的 at_cmdproject_mcu1project_mcu2image2 中分别被编译。

组件编译 CMakeLists.txt

组件 public 部分组件 private 部分 两部分组成:

  • 前者描述了该组件向 全局编译配置 中追加的内容

  • 后者描述了该 组件库文件 的编译配置。

组件 public 部分

该部分用于向 全局编译配置追加 如下编译配置:

  • 头文件路径:如果其他组件要使用当前组件的头文件,则可以将当前组件头文件目录加入全局。

  • 预定义:如果当前组件被添加编译时,需要同步在全局加入某些预定义(使用场景较少)。

  • 链接库:如果 当前组件给下游客户时不提供源码,只提供库文件,则链接时需要链接库文件而不是 target,所以需要将库文件路径加入全局以便链接时取出。库文件的编译可以在 组件 private 部分 中调用 ameba_add_external_app_library 完成。

上述三类配置在如下代码块中分别由 public_includespublic_definitionspublic_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_sourcesprivate_includes , private_definitionsprivate_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}
)

备注

最佳实践

修改现有组件的编译配置

可以参考 组件编译 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/Makefile 的第三方组件),可通过非侵入式集成方案实现链接进入固件,具体实施可依据其编译系统类型选择适配策略。

  1. 拷贝模板 cmake/CMakeLists-template.cmake 到一个合适的位置(比如为该代码组件新建一个 wrap 目录),并改名为 CMakeLists.txt

  2. 参考 修改现有组件的编译配置 设置 组件 public 部分

  3. 组件 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
    
  4. 参考 CMakeLists.txt 调用关系图 在相应固件的 CMakeLists.txt 中添加上述 CMakeLists.txt 所在目录进行编译

    ameba_add_subdirectory(path/to/your/wrap/cmakelists)
    
  5. 编译,测试

调整组件子模块组织关系

当一个组件很复杂,其中包含了多个子组件,此时组件根目录下的 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 中新增组件

当需要新增一个独立组件时,按如下步骤操作:

  1. 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.cmakeameba_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()
    
  2. component 下新建组件目录,并参考 组件编译 CMakeLists.txt 添加相关 CMakeLists.txt

  3. 参考 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_NAMEc_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_RLSTRUE 时该该 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_RLSTRUE 时直接静默返回,否则会报错,路径存在时同 ameba_add_subdirectory,参数说明:

dir:

条件不成立时要添加编译的目录

常量定义

CMake 脚本 cmake/global_define.cmake 中定义了一些常量可以直接用于编写脚本,列举部分如下:

常量类型

变量名

通用常量

c_BASEDIR

仓库根目录

c_CMAKE_FILES_DIR

${c_BASEDIR}/cmake

c_COMPONENT_DIR

${c_BASEDIR}/component

c_EMPTY_C_FILE

${c_CMAKE_FILES_DIR}/empty_file.c

SoC 项目相关常量

c_SOC_TYPE

[“amebadplus”, “amebalite”, “amebasmart”] 之一

c_SOC_TYPE_UPPER

[“AMEBADPLUS”, “AMEBALITE”, “AMEBASMART”] 之一

c_SOC_TYPE_CAMEL

[“AebaDplus”, “AmebaLite”, “AmebaSmart”] 之一

c_SOC_PROJECT_DIR

${c_BASEDIR}/${c_SOC_TYPE}_gcc_project

MCU 项目相关常量

c_MCU_TYPE

[“km0”, “km4”, “kr4”, “ca32”, …] 之一

c_MCU_PROJECT_NAME

如果 MCU 项目文件夹名称为 project_xxx,值为 xxx

c_CURRENT_IMAGE

当前固件 target 名称,如 target_img2_km4

c_MCU_KCONFIG_FILE

当前 MCU 项目 Kconfig 文件路径

c_MCU_PROJECT_DIR

${c_SOC_PROJECT_DIR}/project_${c_MCU_TYPE}

c_SDK_LIB_APP_DIR

${c_MCU_PROJECT_DIR}/[asdk|vsdk]/lib/application

c_SDK_LIB_SOC_DIR

${c_MCU_PROJECT_DIR}/[asdk|vsdk]/lib/soc

组件相关常量

c_CMPT_APP_DIR

${c_COMPONENT_DIR}/application

c_CMPT_AT_CMD_DIR

${c_COMPONENT_DIR}/at_cmd

c_CMPT_AUDIO_DIR

${c_COMPONENT_DIR}/audio

c_CMPT_BLUETOOTH_DIR

${c_COMPONENT_DIR}/bluetooth

c_CMPT_DYN_APP_DIR

${c_COMPONENT_DIR}/dynamic_app

c_CMPT_ETHERNET_DIR

${c_COMPONENT_DIR}/ethernet

c_CMPT_EXAMPLE_DIR

${c_COMPONENT_DIR}/example

c_CMPT_FILE_SYSTEM_DIR

${c_COMPONENT_DIR}/file_system

c_CMPT_LWIP_DIR

${c_COMPONENT_DIR}/lwip

c_CMPT_NETWORK_DIR

${c_COMPONENT_DIR}/network

c_CMPT_OS_DIR

${c_COMPONENT_DIR}/os

c_CMPT_RTK_COEX_DIR

${c_COMPONENT_DIR}/rtk_coex

c_CMPT_SDIO_DIR

${c_COMPONENT_DIR}/sdio

c_CMPT_SSL_DIR

${c_COMPONENT_DIR}/ssl

c_CMPT_UI_DIR

${c_COMPONENT_DIR}/ui

c_CMPT_USB_DIR

${c_COMPONENT_DIR}/usb

c_CMPT_UTILS_DIR

${c_COMPONENT_DIR}/utils

c_CMPT_TFLITE_DIR

${c_COMPONENT_DIR}/tflite_micro

c_CMPT_WIFI_DIR

${c_COMPONENT_DIR}/wifi

c_CMPT_WPAN_DIR

${c_COMPONENT_DIR}/wpan

c_CMPT_CRASHDUMP_DIR

${c_COMPONENT_DIR}/soc/common/crashdump

c_CMPT_LZMA_DIR

${c_COMPONENT_DIR}/soc/common/lzma

c_CMPT_SOC_DIR

${c_COMPONENT_DIR}/soc/${c_SOC_TYPE}

常见问题与建议

查看某个源文件的详细编译参数

在相应源文件开头加入如下代码或者加入一些语法错误然后进行编译,编译器会在相应源文件处出错并打印详细编译参数

#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

然后依次排查上述原因:

符号所在库文件未被链接

  1. 排查述错误信息中的链接参数是否包含 库文件lib_swlib.a

  2. 通过上述错误信息第 9 行可知 project_km4/asdk/make/image2/swlib/lib_swlib.a 已经被 image2 加入了链接,如果没有则需要排查:

一些可用于复杂逻辑处理的判断类型

SOC 类型

SOC 类型如 amebadplusamebaliteamebasmart 可以通过如下变量判断: CONFIG_AMEBADPLUSCONFIG_AMEBALITECONFIG_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()

注意

  1. SOC 类型的区分应当尽可能消除,更好的方法是使用特性之类的开关配置替代

  2. 如果一定要使用应秉持向上兼容的原则,即未来有新增 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 类型如 km0km4kr4ca32 等,可以通过 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 即可