本文将介绍乐鑫 IoT 开发框架的编译器系统和其中”组件“的概念。

如果您想知道如何组织新的 ESP-IDF 项目,阅读本文档即可。

我们建议您使用 esp-idf-template 项目作为您新项目的起点。

使用编译系统

esp-idf 的 README 文件包含有关如何使用编译系统构建项目的说明。

概述

一个 ESP-IDF 项目可以看作是许多组件的集合,例如对于一个显示当前湿度的网站服务器来说,可能会包括如下一些组件:

  • ESP32 基础库(libc,rom binding等)
  • WiFi 驱动
  • TCP/IP 协议栈
  • FreeRTOS 操作系统
  • 网站服务器
  • 湿度传感器的驱动
  • 将上述组件捆绑在一起的主代码

ESP-IDF 使这些组件具有显式性和可配置性。为此,在编译项目时,编译环境将查找 ESP-IDF 目录,项目目录和其他自定义组件目录(可选)中的所有组件,然后,它允许用户使用基于文本的菜单系统配置 ESP-IDF 项目,以自定义每个组件。在配置完项目中的组件之后,编译系统将开始编译当前项目。

概念

  • ”项目“是一个目录,其中包含构建单个”app“(可执行文件)的所有文件和配置,以及其他支持型输出文件,比如分区表,数据/文件系统分区和引导程序。
  • ”系统配置“保存在项目根目录中名为 sdkconfig 的单个文件中,可以通过 make menuconfig 来修改此配置文件以自定义项目的配置。一个项目只包含一个项目配置。
  • ”app“是由esp-idf构建的可执行文件。一个项目通常会构建两个应用程序——一个”项目应用程序“(主可执行文件,即用户自定义的固件),和一个”引导程序”(启动项目应用程序的初始化引导程序)。
  • ”组件“是独立代码的模块化部分,他们被编译成静态库(.a文件)并链接到应用程序。有些组件是 esp-idf 本身提供的,有些则可能来自其他地方。

有些东西并不是项目的一部分:

  • ”ESP-IDF“不是项目的一部分,相反它是独立的,并通过 IDF_PATH 环境变量链接到项目,该变量保存 esp-idf 目录的路径。这允许 IDF 框架与您的项目分离。
  • 用于编译的工具链不是项目的一部分。工具链应安装在系统命令行 PATH 中,或者将工具链的路径设置为项目配置中编译器的前缀部分。

示例项目

示例项目的目录树结构可能如下所示:

- myProject/
             - Makefile
             - sdkconfig
             - components/ - component1/ - component.mk
                                         - Kconfig
                                         - src1.c
                           - component2/ - component.mk
                                         - Kconfig
                                         - src1.c
                                         - include/ - component2.h
             - main/       - src1.c
                           - src2.c
                           - component.mk

             - build/

此示例”myProject“包含以下元素:

  • 顶层项目Makefile。这个Makefile设置了 PROJECT_NAME 变量,并定义了项目范围内的其他make变量(可选)。它还导入了核心Makefile文件: $(IDF_PATH)/make/project.mk ,它实现了 ESP-IDF 编译系统的其余部分。
  • 项目配置文件sdkconfig。当 make menuconfig 运行时,将创建/更新此文件,并保存项目中所有组件的配置(包括 esp-idf 本身)。”sdkconfig“文件可能会也可能不会被添加到项目的源代码管理系统中。
  • 可选的 ”components“ 目录包含了属于项目一部分的组件,项目不必包含此类自定义组件,但它可用于构造可重用代码或包括不属于 ESP-IDF 的第三方组件。
  • ”main“目录是一个特殊的”伪组件“,它包含项目本身的源代码。”main“是默认名称,Makefile变量 COMPONENT_DIRS 包含此组件,但您可以修改此变量(或者设置t EXTRA_COMPONENT_DIRS) 以查找其他位置的组件。
  • ”build“ 目录是编译输出的时候创建的目录,该目录将包含临时目标文件和库以及最终的二进制输出文件。此目录通常不会添加到源代码管理中,也不会随着项目源代码一起分发。

组件目录包含组件 makefile —— component.mk, 里面可能会定义一些变量来控制组件的编译过程,以及它与整个项目的集成。有关更多详细信息,请参阅组件 组件 Makefiles

每个组件还可以包括一个 Kconfig 文件,用于定义可以通过项目配置设置的组件配置选项。某些组件还可能包含 Kconfig.projbuildMakefile.projbuild 文件,这些文件是用于覆盖部分项目的特殊文件。

项目 Makefiles

每个项目都有一个 Makefile ,其中包含整个项目的构建设置。默认情况下,项目 Makefile 可以非常小。

最小示例 Makefile

PROJECT_NAME := myProject

include $(IDF_PATH)/make/project.mk

必须的项目变量

  • PROJECT_NAME: 项目名称,最终输出的二进制文件将使用此名称,即myProject.bin,myProject.elf。

可选的项目变量

这些变量都有默认值,用户可以重写这些变量以实现自定义的编译行为。查看 make/project.mk 以获得所有的实现细节。

  • PROJECT_PATH:顶层项目目录。默认是包含Makefile的目录。许多其他项目变量都基于此变量。项目路径中不能包含有空格。
  • BUILD_DIR_BASE:所有对象/库/二进制文件的构建目录,默认为 $(PROJECT_PATH)/build
  • COMPONENT_DIRS:组件的搜索目录,默认为 $(IDF_PATH)/components$(PROJECT_PATH)/components$(PROJECT_PATH)/mainEXTRA_COMPONENT_DIRS。如果您不想从这些目录搜索组件,请重写此变量。
  • EXTRA_COMPONENT_DIRS:组件额外的搜索目录,可选。
  • COMPONENTS:要编译到项目中的组件名称列表,默认为COMPONENT\_DIRS指定的目录中所有的组件。
  • EXCLUDE_COMPONENTS: 在编译的过程中需要排除的组件名称的列表,可选。请注意这会减少编译的时间,但不会减少二进制文件的大小。
  • TEST_EXCLUDE_COMPONENTS: 在单元测试的编译过程中需要排除的组件名称的列表,可选。

以上这些Makefile变量中的任何路径都应该使用绝对路径,您可以使用 $(PROJECT_PATH)/xxx$(IDF_PATH)/xxx,或者使用Make内置函数$(abspath xxx)来将相对路径转换为绝对路径。

这些变量应该在Makefile中 include $(IDF_PATH)/make/project.mk 的前面进行设置。

组件 Makefiles

每个项目都包含一个或者多个组件,这些组件可以是 esp-idf 的一部分,也可以从其他组件目录添加。

组件是包含 component.mk 文件的任何目录。

搜索组件

搜索 COMPONENT_DIRS 指定的目录以查找项目使用到的组件,此列表中的目录可以是组件本身(即他们包含component.mk文件),也可以是子目录是组件的顶层目录。

用户运行 make list-components 命令可以获得这些变量的值,这有助于调试组件的搜索路径是否正确。

具有相同名字的多个组件

当 esp-idf 收集要编译的所有组件时,它将按照 COMPONENT_DIRS指定的顺序依次执行,默认情况下,这意味着首先是idf组件,第二个是项目组件,最后是EXTRA_COMPONENT_DIRS 中的组件。如果这些目录中的两个或者多个包含具有相同名字的组件子目录,则使用搜索到的最后一个位置的组件。例如,这允许通过简单地将组件目录复制到项目组件树中,然后在那里修改来覆盖esp-idf组件。如果使用这种方式,esp-idf目录本身可以保持不变。

最小组件 Makefile

最小的 component.mk 文件是一个空文件,如果文件为空,则组件的默认行为会被设置为:

  • 与makefile在相同目录中的所有源文件(*.c*.cpp*.cc*.S)将会被编译到组件库中。
  • 子目录 “include” 将被添加到其他组件的全局头文件搜索路径中。
  • 组件库将会被链接到项目的应用程序中。

更完整的组件makefile可以查看组件Makefile示例

请注意,空的 component.mk 文件和没有 component.mk 文件之间存在本质差异,前者会调用默认的组件编译行为,后者不会发生默认的组件编译行为。一个组件中如果只包含影响项目配置或编译过程的文件,那么它可以没有 component.mk 文件。

预设的组件变量

以下特定于组件的变量可以在 component.mk 中使用,但不应该修改。

  • COMPONENT_PATH:组件的目录,计算包含 component.mk 的目录的绝对路径,路径中不能包含空格。
  • COMPONENT_NAME:组件的名称,默认为组件目录的名称。

一下变量在项目顶层中设置,被导出到组件中编译时使用:

  • PROJECT_NAME:项目名称,在项目的Makefile中设置。
  • PROJECT_PATH: 包含项目Makefile的项目目录的绝对路径。
  • COMPONENTS:此次编译中包含的所有组件的名字。
  • CONFIG_*: 项目配置中的每个值在make中都有一个相应的变量,它们以CONFIG_开头。
  • CC, LD, AR, OBJCOPY: gcc xtensa 交叉编译工具链中每个工具的完整路径。
  • HOSTCC, HOSTLD, HOSTAR: 主机本地工具链中每个工具的全名。
  • IDF_VER: ESP-IDF的版本,可以通过检索 $(IDF_PATH)/version.txt 文件(假如存在的话)或者使用git命令 git describe 来获得。这里推荐的格式是在一行中指定主IDF发布版本,例如用于标记发布本版的 v2.0 或者是标记任意一次提交的 v2.0-275-g0efaa4f 。应用程序可以通过调用 :cpp[esp_get_idf_version]{role=”func”} 函数来使用它。

如果您修改 component.mk 中的任何一些变量,那么这不会阻碍编译其他组件,但这可能会使您的组件难以编译或调试。

可选的项目通用组件变量

可以在 component.mk 中设置以下变量来控制整个项目的编译行为:

  • COMPONENT_ADD_INCLUDEDIRS: 相对于组件目录的路径,将被添加到项目中所有组件的头文件搜索路径中。如果未被覆盖,则默认为 include。如果一个包含路径仅仅被当前的组件使用到,那么将该路径添加到 COMPONENT_PRIV_INCLUDEDIRS 即可。
  • COMPONENT_ADD_LDFLAGS: 添加链接参数到 LDFLAGS 中用以指导生成最终的应用程序可执行文件,默认为 -l$(COMPONENT_NAME)。 如果将预编译库添加到此目录,请将它们添加为绝对路径,即 $(COMPONENT_PATH)/libwhatever.a
  • COMPONENT_DEPENDS: 应在此组件之前编译的组件列表,这对于链接时依赖不是必需的,因为所有组件的头文件目录始终可用。如果一个组件生成一个头文件,然后另外一个组件需要包含它,那么该变量就有必要进行设定。大多数的组件不需要设置此变量。
  • COMPONENT_ADD_LINKER_DEPS: 保存一些文件的路径,当这些文件发生改变,会触发ELF文件的重新链接。该变量通常用于链接脚本文件和二进制文件,大多数的组件不需要设置此变量。

一下变量仅适用于属于esp-idf的组件:

  • COMPONENT_SUBMODULES: 组件使用的git子模块的路径列表(相对于COMPONENT\_PATH)。这些路径会在编译的过程中被检查(并在必要的时候初始化)。如果组件位于IDF_PATH目录之外,则忽略此变量。

可选的特定组件变量

一下变量可以在 component.mk 中被设置,用以控制该组件的编译行为:

  • COMPONENT_PRIV_INCLUDEDIRS:目录路径,必须相对于组件目录。该目录仅会被添加到此组件源文件的头文件搜索路径中。
  • COMPONENT_EXTRA_INCLUDES:编译组件的源文件时需要指定的额外的头文件搜索路径,这些路径将以”-l“为前缀传递给编译器。这和 COMPONENT_PRIV_INCLUDEDIRS 变量的功能有点类似,但是这些路径不会相对于组件目录进行扩展。
  • COMPONENT_SRCDIRS: 目录路径,必须相对于组件目录,这些路径用于搜索源文件(*.cpp*.c*.S)。默认为”.”,即组件目录本身。重写该变量可以指定包含源文件的不同目录列表。
  • COMPONENT_OBJS: 要编译生成的目标文件,默认是COMPONENT_SRCDIRS中每个源文件的.o文件。重写此列表将允许您排除 COMPONENT_SRCDIRS 中的源文件,否则他们将会被编译。请参阅指定源文件
  • COMPONENT_EXTRA_CLEAN:相对于组件编译目录的路径,指向component.mk文件中自定义make规则生成的任何文件,他们也是make clean命令需要删除的文件。有关示例,请参阅 源代码生成
  • COMPONENT_OWNBUILDTARGET & COMPONENT_OWNCLEANTARGET: 这些目标允许您完全覆盖组件的默认编译行为。有关详细信息,请参阅完全覆盖组件的Makefile
  • COMPONENT_CONFIG_ONLY:如果设置了此标志,表示组件根本不会产生编译输出(即不会编译得到 COMPONENT_LIBRARY),并且会忽略大多数其他组件变量。此标志用于IDF内部组件,其中仅包含 KConfig.projbuild 和/或 Makefile.projbuild 文件来配置项目,但是没有源文件。
  • CFLAGS:传递给C编译器的标志。根据项目设置定义一组默认的 CFLAGS。可以通过 CFLAGS +=为组件添加特定的标志,也可以完全重写该变量(尽管不推荐)。
  • CPPFLAGS:传递给C预处理器的标志(用于.c.cpp.S文件)。根据项目设置定义一组默认的 CPPFLAGS 。可以通过 CPPFLAGS +=为组件添加特定的标志,也可以完全重写该变量(尽管不推荐)。
  • CXXFLAGS:传递给C++编译器的标志。根据项目设置定义一组默认的 CXXFLAGS 。可以通过 CXXFLAGS +=为组件添加特定的标志,也可以完全重写该变量(尽管不推荐)。

如果要将编译标志应用于单个源文件,您可以将该源文件的变量覆盖,例如:

1
apps/dhcpserver.o: CFLAGS += -Wno-unused-variable

如果存在发出警告的上游代码,这么做可能很有用。

配置组件

每个组件都可以包含一个Kconfig文件, 和 component.mk放在同一个目录下。其中包含要添加到此组件的“make menuconfig”的相关配置的设置。

运行menuconfig时,可以在”Component Settings“菜单栏下找到这些设置。

要创建一个组件的Kconfig文件,最简单的方法就是使用esp-idf中已有的Kconfig文件为模板,再做修改。

有关示例,请参阅添加条件配置.

预处理器定义

ESP-IDF编译系统会在命令行中添加以下C预处理定义:

  • ESP_PLATFORM — 可以用来检测在ESP-IDF内发生的编译行为。
  • IDF_VER — ESP-IDF的版本,请参阅预设的组件变量