目录结构

创建一个简单的esp_hello_word工程,目录结构如下

├── CMakeLists.txt        
├── main
│   ├── CMakeLists.txt
│   ├── component.mk
│   └── main.c
├── Makefile
└── sdkconfig

   main:工程主代码目录
     CMakeLists.txt:main 文件夹下的的 CMake 配置文件
     component.mk:main 组件的配置文件
     main.c:主代码,程序入口文件
   CMakeLists.txtCMake 配置文件
   Makefilemake 配置文件
   sdkconfig:【自动生成】通过 menuconfig 生成的配置文件

主目录下有CMakeLists.txtMakefile文件,其实这分别对应着两种编译指令。
CMakeLists.txt对应执行CMake指令,已经封装在idf.py指令中。对应文件如下:

├── CMakeLists.txt
└── main
    ├── CMakeLists.txt
    └── main.c

Makefile对应执行make指令。对应文件如下:

├── main
│   ├── component.mk
│   └── main.c
└── Makefile

接下来对文件进行分析。


CMakeLists.txt

主目录esp_hello_word文件夹下的CMakeLists.txt文件内容:

# 以下几行引用必须在您的项目中
# CMakeLists 的正确顺序,以便cmake正常工作
cmake_minimum_required(VERSION 3.5)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(esp_hello_word)

cmake_minimum_required(VERSION 3.5)
定义了 CMake 所支持的最小版本。
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
来包含 esp-idf 工程下的 project.cmake 文件,这个文件包含许多需要加载的组件。
project(esp_hello_word)
工程名为 esp_hello_word。

Makefile

#    这是一个项目生成文件。
#    假设这个Makefile所在的目录是一个项目子目录。

#    项目名称
PROJECT_NAME := esp_hello_word

#    顶层项目目录,默认是包含 Makefile 文件的目录,许多其他的项目变量都基于此变量。
include $(IDF_PATH)/make/project.mk

main目录

CMakeLists.txt

idf_component_register(SRCS "hello_world_main.c"
                    INCLUDE_DIRS "")

最小组件 CMakeLists.txt 文件通过使用 idf_component_register 将组件添加到构建系统中。

  • SRCS 是源文件列表(.c、.cpp、.cc、.S),里面所有的源文件都将会编译进组件库中。
  • INCLUDE_DIRS 是目录列表,里面的路径会被添加到所有需要该组件的组件(包括 main 组件)全局 include 搜索路径中。
  • REQUIRES 实际上并不是必需的,但通常需要它来声明该组件需要使用哪些其它组件,请参考 组件依赖.

上述命令会构建生成与组件同名的库,并最终被链接到应用程序中。
上述目录通常设置为相对于 CMakeLists.txt 文件的相对路径,当然也可以设置为绝对路径。
有关更完整的 CMakeLists.txt 示例,请参阅 组件依赖示例组件 CMakeLists 示例

idf_component_register原型

idf_component_register([[SRCS src1 src2 ...] | [[SRC_DIRS dir1 dir2 ...] [EXCLUDE_SRCS src1 src2 ...]]
                       [INCLUDE_DIRS dir1 dir2 ...]
                       [PRIV_INCLUDE_DIRS dir1 dir2 ...]
                       [REQUIRES component1 component2 ...]
                       [PRIV_REQUIRES component1 component2 ...]
                       [LDFRAGMENTS ldfragment1 ldfragment2 ...]
                       [REQUIRED_IDF_TARGETS target1 target2 ...]
                       [EMBED_FILES file1 file2 ...]
                       [EMBED_TXTFILES file1 file2 ...]
                       [KCONFIG kconfig]
                       [KCONFIG_PROJBUILD kconfig_projbuild])
  • SRCS - 组件的源文件,用于为组件创建静态库;如果没有指定,组件将被视为仅配置组件,从而创建接口库。
  • SRC_DIRS, EXCLUDE_SRCS - 用于通过指定目录来 glob 源文件 (.c、.cpp、.S),而不是通过 SRCS 手动指定源文件。请注意,这受 CMake 中通配符的限制。 在 EXCLUDE_SRCS - 中指定的源文件会从被 glob 的文件中移除。
  • INCLUDE_DIRS - 相对于组件目录的路径,该路径将被添加到需要当前组件的所有其他组件的 include 搜索路径中。
  • PRIV_INCLUDE_DIRS - 必须是相对于组件目录的目录路径,它仅被添加到这个组件源文件的 include 搜索路径中。
  • REQUIRES - 组件的公共组件依赖项。
  • PRIV_REQUIRES - 组件的私有组件依赖项;在仅用于配置的组件上会被忽略。
  • LDFRAGMENTS - 组件链接器片段文件。
  • REQUIRED_IDF_TARGETS - 指定该组件唯一支持的目标。
  • KCONFIG - 覆盖默认的 Kconfig 文件。
  • KCONFIG_PROJBUILD - 覆盖默认的 Kconfig.projbuild 文件。

main.c

#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"

void app_main(void)
{
    printf("Hello world!\n");

    /* 打印芯片信息 */
    esp_chip_info_t chip_info;
    esp_chip_info(&chip_info);
    printf("芯片内核数: %d\n", chip_info.cores);
    printf("芯片修订号: %d\n", chip_info.revision);
    printf("%s flash:%dMB \n", (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "片上" : "片外",
                              spi_flash_get_chip_size() / (1024 * 1024));
    printf("芯片功能标志: %x  ", chip_info.revision);
    printf("[%s%s%s]\n",  (chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "2.4GHz WiFi" : "",
                                (chip_info.features & CHIP_FEATURE_BT) ? "/常规蓝牙" : "",
                                (chip_info.features & CHIP_FEATURE_BLE) ? "/低功耗蓝牙" : "");
    /* 重启 */
    for (int i = 10; i >= 0; i--) {
        printf("%2d 秒后重启...\n", i);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    printf("重启...\n");
    fflush(stdout);
    esp_restart();
}

sdkconfig

sdkconfig 文件由 make menuconfig 自动生成,存储配置参数。


添加组件

传统 GNU Make

目录结构更改如下

├── components            -- 新增 组件目录
│   ├── component.mk      -- 新增 子目录component.mk
│   ├── include           -- 新增 组件头文件目录
│   │   └── chip.h        -- 新增 chip.h 头文件
│   └── src               -- 新增 组件头文件目录
│       └── chip.c        -- 新增 chip.c 源文件           
├── main
│   ├── component.mk
│   └── main.c            -- 修改
├── Makefile              -- 修改
└── sdkconfig

修改主目录下的Makefile

# 将自建组件添加到包含路径
COMPONENT_ADD_INCLUDEDIRS := components/include

components 目录下 component.mk内容如下

#
# Component Makefile
#
# 头文件的包含路径
COMPONENT_ADD_INCLUDEDIRS := include
# 源文件的包含路径
COMPONENT_SRCDIRS := src

chip.c

#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"

void chip_info(void)
{
    /* 打印芯片信息 */
    esp_chip_info_t chip_info;
    esp_chip_info(&chip_info);
    printf("芯片内核数: %d\n", chip_info.cores);
    printf("芯片修订号: %d\n", chip_info.revision);
    printf("%s flash:%dMB \n", (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "片上" : "片外",
                              spi_flash_get_chip_size() / (1024 * 1024));
    printf("芯片功能标志: %x  ", chip_info.revision);
    printf("[%s%s%s]\n",  (chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "2.4GHz WiFi" : "",
                                (chip_info.features & CHIP_FEATURE_BT) ? "/常规蓝牙" : "",
                                (chip_info.features & CHIP_FEATURE_BLE) ? "/低功耗蓝牙" : "");
}

chip.h

#ifndef __CHIP_H__
#define __CHIP_H__

void chip_info(void);

#endif

main.c

#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"
#include "chip.h"
void app_main(void)
{
    printf("Hello world!\n");

    chip_info();
    /* 重启 */
    for (int i = 10; i >= 0; i--) {
        printf("%2d 秒后重启...\n", i);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
    printf("重启...\n");
    fflush(stdout);
    esp_restart();
}

CMake方式

├── CMakeLists.txt
├── components            -- 新增 组件目录
│   ├── CMakeLists.txt    -- 新增 CMakeLists.txt
│   ├── include           -- 新增 组件头文件目录
│   │   └── chip.h        -- 新增 chip.h 头文件
│   └── src               -- 新增 组件头文件目录
│       └── chip.c        -- 新增 chip.c 源文件  
├── main
│   ├── CMakeLists.txt
│   └── main.c
└── sdkconfig

components文件夹下CMakeLists.txt内容如下:

set(srcs "src/chip.c")

idf_component_register(SRCS "${srcs}"
                    INCLUDE_DIRS include
                    REQUIRES spi_flash)

其中chip.c引用了#include "esp_spi_flash.h"文件,所以使用REQUIRES进行声明。
而"esp_spi_flash.h" 文件位于esp-idf->components->spi_flash组件下。


完整目录如下

├── CMakeLists.txt
├── components
│   ├── CMakeLists.txt
│   ├── component.mk
│   ├── include
│   │   └── chip.h
│   └── src
│       └── chip.c
├── main
│   ├── CMakeLists.txt
│   ├── component.mk
│   └── main.c
├── Makefile
└── sdkconfig

最小组件 Makefile

最小的 component.mk 文件是一个空文件。如果文件为空,则设置默认组件行为:与 makefile 在相同的目录中的所有源文件(.c,.cpp,.cc,.S)将被编译到组件库中子目录 “include” 将被添加到所有其他组件的全局 include 搜索路径中。组件库将链接到项目应用程序中。

预设组件变量
COMPONENT_PATH:组件目录.计算包含 component.mk 的目录的绝对路径.组件路径不能包含空格.
COMPONENT_NAME:组件的名称.默认为组件目录的名称.
COMPONENT_BUILD_DIR:组件构建目录.计算 $(BUILD_DIR_BASE) 中要构建此组件源文件的目录的绝对路径.每次构建组件时,这也是当前工作目录,因此 make 等目标中的相对路径都是相对于此目录.
COMPONENT_LIBRARY:将为此组件构建的静态库文件的名称(相对于组件构建目录).默认为 $(COMPONENT_NAME).a.
以下变量在项目级别设置,但会导出在组件构建中使用:

PROJECT_NAME:项目名称,在项目 Makefile 中设置
PROJECT_PATH:包含项目 Makefile 的项目目录的绝对路径.
COMPONENTS:此构建中包含的所有组件的名称.
CONFIG_ *:项目配置中的每个值都有一个 make 中可用的对应变量.所有名称都以 CONFIG_ 开头.
CC,LD,AR,OBJCOPY:gcc xtensa 交叉工具链中每个工具的完整路径.
HOSTCC,HOSTLD,HOSTAR:来自主机本机工具链的每个工具的全名.
IDF_VER:ESP-IDF 版本,使用 git 命令 git describe 从 $(IDF_PATH)/version.txt 文件(如果存在)中检索.这里推荐的格式是单独的一行指定主要 IDF 发布版本,例如标记版本的 v2.0 或任意提交的 v2.0-275-g0efaa4f.应用程序可以通过调用 esp_get_idf_version() 来使用它.
PROJECT_VER: 项目版本
如果 PROJECT_VER 变量在项目 Makefile 文件中设置,则将使用其值。
否则,如果 $PROJECT_PATH/version.txt 存在,其内容将用作 PROJECT_VER。
否则,如果项目位于 Git 存储库中,则将使用git describe的输出。
否则,PROJECT_VER 将为“1”。
如果您修改 component.mk 中的任何这些变量,那么这不会阻止构建其他组件,但它可能使您的组件难以构建或者调试.

可选项目范围的组件变量
可以在 component.mk 中设置以下变量来控制整个项目中的构建设置:

COMPONENT_ADD_INCLUDEDIRS:相对于组件目录的路径,将添加到项目中所有组件的 “include” 搜索路径.如果未被覆盖,则默认include.如果仅需要编译此特定组件的 “include” 目录,请将其添加到 COMPONENT_PRIV_INCLUDEDIRS
COMPONENT_ADD_LDFLAGS:为 LDFLAGS 添加链接器参数以用于应用程序可执行文件.默认为 -l$(COMPONENT_NAME).如果将预编译库添加到此目录,请将它们添加为绝对路径 -e$(COMPONENT_PATH)/libwhatever.a
COMPONENT_DEPENDS:应在此组件之前编译的组件名称的可选列表.对于链接时依赖性,这不是必需的,因为所有组件"include"目录始终可用.如果一个组件生成一个"include"文件,然后您想要包含在另一个组件中,则这是必要的.大多数组件不需要设置此变量.
COMPONENT_ADD_LINKER_DEPS:相对组件路径的文件的可选列表,如果它们发生更改,应触发 ELF 文件的重新链接。 通常用于链接描述文件和二进制库。大多数组件不需要设置此变量。
以下变量仅适用于属于 esp-idf 本身的组件:

COMPONENT_SUBMODULES:组件使用的 git 子模块路径(相对于 COMPONENT_PATH)的可选列表.这些将由构建过程检查(并在必要时初始化).如果组件位于 IDF_PATH 目录之外,则忽略此变量.
可选特定的组件变量
可以在component.mk中设置以下变量来控制该组件的构建:

COMPONENT_PRIV_INCLUDEDIRS:目录路径,必须相对于组件目录,该组件目录将仅添加到此组件源文件的"include"搜索路径中.
COMPONENT_EXTRA_INCLUDES:编译组件源文件时使用的任何额外包含路径.这些将以’-I’为前缀,并按原样传递给编译器.与COMPONENT_PRIV_INCLUDEDIRS变量类似,但这些路径不会相对于组件目录进行扩展.
COMPONENT_SRCDIRS:目录路径,必须相对于组件目录,将用以搜索源文件( .cpp, .c,* .S).默认为’.’,即组件目录本身.覆盖它以指定包含源文件的不同目录列表.
COMPONENT_OBJS:要编译的对象文件.默认值是COMPONENT_SRCDIRS中找到的每个源文件的 a.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 += 进行组件特定的添加.也可以(尽管不推荐)完全覆盖该组件的变量.


结构参考

CMake示例项目

- 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/

GNU Make示例项目

- 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/

个人工程分析

目录结构如下

.
├── CMakeLists.txt
├── components
│   ├── mj_http
│   │   ├── CMakeLists.txt
│   │   ├── component.mk
│   │   ├── include
│   │   │   └── mj_http.h
│   │   └── mj_http.c
│   ├── mj_nvs
│   │   ├── CMakeLists.txt
│   │   ├── component.mk
│   │   ├── include
│   │   │   └── mj_nvs.h
│   │   └── mj_nvs.c
│   └── mj_wifi
│   ├── CMakeLists.txt
│   ├── component.mk
│   ├── include
│   │   └── mj_wifi.h
│   └── mj_wifi.c
├── main
│   ├── CMakeLists.txt
│   ├── component.mk
│   ├── howsmyssl_com_root_cert.pem
│   └── mj_main.c
├── Makefile
├── README.md
└── sdkconfig

CMake 部分

CMakeLists.txt

# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(daobanmojie)

main/CMakeLists.txt

# Embed the server root certificate into the final binary
#
# (If this was a component, we would set COMPONENT_EMBED_TXTFILES here.)
idf_component_register(SRCS "mj_main.c"
                    INCLUDE_DIRS "."
                    EMBED_TXTFILES howsmyssl_com_root_cert.pem)

components/mj_http/CMakeLists.txt

idf_component_register(SRCS "mj_http.c"
                    INCLUDE_DIRS "include"
                    REQUIRES esp_http_client json esp-tls   )

GNU Make 部分

Makefile

#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#

PROJECT_NAME := daobanmojie
include $(IDF_PATH)/make/project.mk

main/component.mk

#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
#

components/mj_http/component.mk

#
# Component Makefile
#

# 头文件的包含路径
COMPONENT_ADD_INCLUDEDIRS := include

# 源文件的包含路径
#COMPONENT_SRCDIRS := src
COMPONENT_SRCDIRS := .
最后修改:2021 年 04 月 23 日
男宾一位~ 欢迎下次再来!