深入理解ESP-IDF构建系统

本系列文章主要介绍ESP-IDF构建系统的实现原理,以及组件等相关概念。

概述

一个 ESP-IDF 项目可以看作是多个不同组件的集合,例如一个显示当前湿度的网页服务器会包含以下组件:

  • ESP-IDF 基础库,包括 libc、ROM bindings 等
  • Wi-Fi 驱动
  • TCP/IP 协议栈
  • FreeRTOS 操作系统
  • 网页服务器
  • 湿度传感器的驱动
  • 负责将上述组件整合到一起的主程序

ESP-IDF 可以显式地指定和配置每个组件。在构建项目的时候,构建系统会前往 ESP-IDF 目录、项目目录和用户自定义组件目录(可选)中查找所有组件,允许用户通过文本菜单系统配置 ESP-IDF 项目中用到的每个组件。在所有组件配置结束后,构建系统开始编译整个项目。

概念

项目

特指一个目录,其中包含了构建可执行应用程序所需的全部文件和配置,以及其他支持型文件,例如分区表、数据/文件系统分区和引导程序。

项目配置

保存在项目根目录下名为 sdkconfig 的文件中,可以通过 idf.py menuconfig 进行修改,且一个项目只能包含一个项目配置。

应用程序

是由 ESP-IDF 构建得到的可执行文件。一个项目通常会构建两个应用程序:项目应用程序(可执行的主文件,即用户自定义的固件)和引导程序(启动并初始化项目应用程序)。

组件

是模块化且独立的代码,会被编译成静态库(.a 文件)并链接到应用程序。部分组件由 ESP-IDF 官方提供,其他组件则来源于其它开源项目。

目标

特指运行构建后应用程序的硬件设备。运行 idf.py –list-targets 可以查看当前 ESP-IDF 版本中支持目标的完整列表。

请注意,以下内容并不属于项目的组成部分:

ESP-IDF 并不是项目的一部分,它独立于项目,通过 IDF_PATH 环境变量(保存 esp-idf 目录的路径)链接到项目,从而将 IDF 框架与项目分离。

交叉编译工具链并不是项目的组成部分,它应该被安装在系统 PATH 环境变量中。

使用构建系统

idf.py

idf.py 命令行工具提供了一个前端,可以帮助你轻松管理项目的构建过程,它管理了以下工具:

  • CMake,配置待构建的项目
  • Ninja,用于构建项目
  • esptool.py,烧录目标硬件设备

可通过 idf.py 配置构建系统,具体可参考 ESP-IDF 前端工具 idf.py

直接使用CMake

为了方便,idf.py 已经封装了 CMake 命令,但是你愿意,也可以直接调用 CMake

idf.py 在执行某些操作时,它会打印出其运行的每条命令以便参考。例如运行 idf.py build 命令与在 bash shell(或者 Windows Command Prompt)中运行以下命令是相同的:

mkdir -p build
cd build
cmake .. -G Ninja   # 或者 'Unix Makefiles'
ninja

在上面的命令列表中,cmake 命令对项目进行配置,并生成用于最终构建工具的构建文件。在这个例子中,最终构建工具是 Ninja: 运行 ninja 来构建项目。

没有必要多次运行 cmake。第一次构建后,往后每次只需运行 ninja 即可。如果项目需要重新配置,ninja 会自动重新调用 cmake

若在 CMake 中使用 ninjamake,则多数 idf.py 子命令也会有其对应的目标,例如在构建目录下运行 make menuconfigninja menuconfig 与运行 idf.py menuconfig 是相同的。

使用Ninja/Make来烧录

可以直接使用 ninja 或 make 运行如下命令来构建项目并烧录:

ninja flash

或:

make app-flash

可用的目标还包括:flashapp-flash (仅用于 app)、bootloader-flash (仅用于 bootloader)。

以这种方式烧录时,可以通过设置 ESPPORTESPBAUD 环境变量来指定串口设备和波特率。可以在操作系统或 IDE 项目中设置该环境变量,或者直接在命令行中进行设置:

ESPPORT=/dev/ttyUSB0 ninja flash

在命令的开头为环境变量赋值属于 Bash shell 的语法,可在 Linux 、macOS 和 Windows 的类 Bash shell 中运行,但在 Windows Command Prompt 中无法运行。

或:

make -j3 app-flash ESPPORT=COM4 ESPBAUD=2000000

在命令末尾为变量赋值属于 make 的语法,适用于所有平台的 make。

示例项目

一个ESP-IDF项目的目录树结构通常如下所示:

- myProject/
    - CMakeLists.txt
    - sdkconfig
    - components/
        - component1/
            - CMakeLists.txt
            - Kconfig
            - src1.c
        - component2/
            - CMakeLists.txt
            - Kconfig
            - src1.c
            - include/
                - component2.h
    - main/
        - CMakeLists.txt
        - src1.c
        - src2.c
    - build/

该示例项目 “myProject” 包含以下组成部分:

  • 顶层项目 CMakeLists.txt 文件,这是 CMake 用于学习如何构建项目的主要文件,可以在这个文件中设置项目全局的 CMake 变量。顶层项目 CMakeLists.txt 文件会导入 /tools/cmake/project.cmake 文件,由它负责实现构建系统的其余部分。该文件最后会设置项目的名称,并定义该项目。

  • “sdkconfig” 项目配置文件,执行 idf.py menuconfig 时会创建或更新此文件,文件中保存了项目中所有组件(包括 ESP-IDF 本身)的配置信息。 sdkconfig 文件可能会也可能不会被添加到项目的源码管理系统中。

  • 可选的 components 目录中包含了项目的部分自定义组件,并不是每个项目都需要这种自定义组件,但它有助于构建可复用的代码或者导入第三方(不属于 ESP-IDF)的组件。或者,你也可以在顶层 CMakeLists.txt 中设置 EXTRA_COMPONENT_DIRS 变量以查找其他指定位置处的组件。

  • main 目录是一个特殊的组件,它包含项目本身的源代码。main 是默认名称,CMake 变量 COMPONENT_DIRS 默认包含此组件,但你可以修改此变量。有关详细信息,请参阅 重命名 main 组件。如果项目中源文件较多,建议将其归于组件中,而不是全部放在 main 中。

  • build 目录是存放构建输出的地方,如果没有此目录,idf.py 会自动创建。CMake 会配置项目,并在此目录下生成临时的构建文件。随后,在主构建进程的运行期间,该目录还会保存临时目标文件、库文件以及最终输出的二进制文件。此目录通常不会添加到项目的源码管理系统中,也不会随项目源码一同发布。

每个组件目录都包含一个 CMakeLists.txt 文件,里面会定义一些变量以控制该组件的构建过程,以及其与整个项目的集成。

每个组件还可以包含一个 Kconfig 文件,它用于定义 menuconfig 时展示的 组件配置 选项。某些组件可能还会包含 Kconfig.projbuildproject_include.cmake 特殊文件,它们用于 覆盖项目的部分设置

以下是转换为Markdown格式后的内容,放置在Markdown代码块中:

项目 CMakeLists 文件

每个项目都有一个顶层 CMakeLists.txt 文件,包含整个项目的构建设置。默认情况下,项目 CMakeLists 文件会非常小。

最小 CMakeLists 文件示例

最小项目:

cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(myProject)

必要部分

每个项目都要按照上面显示的顺序添加上述三行代码:

  1. cmake_minimum_required(VERSION 3.16) 必须放在 CMakeLists.txt 文件的第一行,它会告诉 CMake 构建该项目所需要的最小版本号。ESP-IDF 支持 CMake 3.16 或更高的版本。

  2. include($ENV{IDF_PATH}/tools/cmake/project.cmake) 会导入 CMake 的其余功能来完成配置项目、检索组件等任务。

  3. project(myProject) 会创建项目本身,并指定项目名称。该名称会作为最终输出的二进制文件的名字,即 myProject.elfmyProject.bin。每个 CMakeLists 文件只能定义一个项目。

可选的项目变量

以下这些变量都有默认值,用户可以覆盖这些变量值以自定义构建行为。

  • COMPONENT_DIRS:组件的搜索目录,默认为 IDF_PATH/componentsPROJECT_DIR/componentsEXTRA_COMPONENT_DIRS。如果你不想在这些位置搜索组件,请覆盖此变量。

  • EXTRA_COMPONENT_DIRS:用于搜索组件的其它可选目录列表。路径可以是相对于项目目录的相对路径,也可以是绝对路径。

  • COMPONENTS:要构建进项目中的组件名称列表,默认为 COMPONENT_DIRS 目录下检索到的所有组件。使用此变量可以“精简”项目以缩短构建时间。请注意,如果一个组件通过 COMPONENT_REQUIRES 指定了它依赖的另一个组件,则会自动将其添加到 COMPONENTS 中,所以 COMPONENTS 列表可能会非常短。

以上变量中的路径可以是绝对路径,或者是相对于项目目录的相对路径。

请使用 cmake 中的 set 命令 来设置这些变量,如 set(VARIABLE "VALUE")。请注意,set() 命令需放在 include(...) 之前,cmake_minimum(...) 之后。

重命名 main 组件

构建系统会对 main 组件进行特殊处理。假如 main 组件位于预期的位置(即 ${PROJECT_PATH}/main),那么它会被自动添加到构建系统中。其他组件也会作为其依赖项被添加到构建系统中,这使用户免于处理依赖关系,并提供即时可用的构建功能。重命名 main 组件会减轻上述这些幕后工作量,但要求用户指定重命名后的组件位置,并手动为其添加依赖项。重命名 main 组件的步骤如下:

  1. 重命名 main 目录。

  2. 在项目 CMakeLists.txt 文件中设置 EXTRA_COMPONENT_DIRS,并添加重命名后的 main 目录。

  3. 在组件的 CMakeLists.txt 文件中设置 COMPONENT_REQUIRESCOMPONENT_PRIV_REQUIRES 以指定依赖项。

覆盖默认的构建规范

构建系统设置了一些全局的构建规范(编译标志、定义等),这些规范可用于编译来自所有组件的所有源文件。

例如,其中一个默认的构建规范是编译选项 -Wextra。假设一个用户想用 -Wno-extra 来覆盖这个选项,应在 project() 之后进行:

cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(myProject)

idf_build_set_property(COMPILE_OPTIONS "-Wno-error" APPEND)

这确保了用户设置的编译选项不会被默认的构建规范所覆盖,因为默认的构建规范是在 project() 内设置的。

公众号 | FunIO
微信搜一搜 “funio”,发现更多精彩内容。
个人博客 | blog.boringhex.top

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值