目录结构
创建一个简单的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.txt
:CMake 配置文件
Makefile
:make 配置文件
sdkconfig
:【自动生成】通过 menuconfig
生成的配置文件
主目录下有CMakeLists.txt
和Makefile
文件,其实这分别对应着两种编译指令。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_INCLUDEDIRSCOMPONENT_ADD_LDFLAGS
:为 LDFLAGS 添加链接器参数以用于应用程序可执行文件.默认为 -l$(COMPONENT_NAME).如果将预编译库添加到此目录,请将它们添加为绝对路径 -e$(COMPONENT_PATH)/libwhatever.aCOMPONENT_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 := .