ESP32
一、硬件配置
ESP32-CAM:
-
核心貌似是ESP-32S(ESP32-WROOM-32)模组
-
两个低功耗 Xtensa® 32-bit LX6 MCU,主频高达 240MHz, 运算能力高达 600 DMIPS,小端序
- 字节地址 0x0、 0x1、 0x2、 0x3 访问的字节分别是 0x0 访问的 32-bit字中的最低、次低、次高、最高字节
-
RAM:片内520 KB SRAM,片外8MB PSRAM
- RTC中8 KB SRAM(RTC慢速存储器),Deep-sleep模式下被协处理器访问
- RTC中8 KB SRAM(RTC快速存储器),Deep-sleep模式下RTC启动时用于数据存储以及被主CPU访问
- 1kbit eFuse,其中256bit为系统专用(MAC 地址和芯片设置)其余 768bit保留给用户应用,包括Flash加密和芯片ID
-
ROM:片内448 KB ROM(用于程序启动和内核功能调用),片外32Mbit SPI Flash(4MB)
-
支持UART、 SPI、 I2C、 PWM
-
TF卡最大支持4G
-
供电:4.75-5.25V
-
引脚定义:
Goouuu-ESP32:
- ESP32-WROOM-32模组(ESP32-D0WDQ6(revision 1))
- 两个低功耗 Xtensa® 32-bit LX6 MCU,主频高达 240MHz, 运算能力高达 600 DMIPS,小端序
- 字节地址 0x0、 0x1、 0x2、 0x3 访问的字节分别是 0x0 访问的 32-bit字中的最低、次低、次高、最高字节
- 购买链接
ESP32-Audio-kit(安信可官方SDK):
-
音频芯片:AC101
-
IIC:SCL(32)、SDA(33)
-
I2S:SCLK(bck 27)、LCLK(ws 26)、DSIN(data_out 25)、DOUT(data_in 35)
-
LED:GREEN(22)
-
SD:INTR_GPIO(GPIO_NUM_34)、INTR_SEL(GPIO_SEL_34)
-
PA:EN(GPIO_NUM_21)、SEL_PA_EN(GPIO_SEL_21)
-
按键:REC(GPIO_NUM_36)、MODE(GPIO_NUM_13)、SET(GPIO_NUM_19)、PLAY(GPIO_NUM_23)、VOLUP(GPIO_NUM_18)、VOLDOWN(GPIO_NUM_5)
-
模块内部具体引脚:
二、软件配置
- [IDF(IoT Development Framework)编程指南]((IoT Development Framework))
- ESP-CAM:ESP32_CAMERA_QR项目,该项目wiki文档
三、搭建开发环境(Windows10)
3.1 准备工作
目前,ESP-IDF 仅适用于 Python 2.7
附件:
mconf-v4.6.0.0-idf-20190628-win32.zip
:mconf-idfninja-win.zip
:ninjacmake-3.17.2-win64-x64.msi
:cmake安装包xtensa-esp32-elf-gcc8_2_0-esp-2019r2-win32.zip
:编译工具链pyelftools-0.26-py2.py3-none-any.whl
:pyelftools包
步骤:
-
下载安装Cmake(安装时注意选上
Add CMake to the system PATH for all users
) -
下载安装ninja(手动加到系统环境变量,只支持64位windows,如需其他版本需自己编译)
-
下载安装
python2.7
并通过pip install --user pyserial
安装pyserial
包(可以通过Anaconda新建环境安装) -
从 这里下载配置工具
mconf-idf
(需要手动加到系统环境变量) -
下载交叉编译工具链,解压之后需要添加到系统环境变量,如
C:\Program Files\xtensa-esp32-elf\bin
-
下载安装Git
-
下载ESP-IDF(3.3.2为长期支持版,最新稳定版为4.0)
-
设置环境变量:
IDF_PATH
应设置为 ESP-IDF 根目录的路径PATH
应包括同一IDF_PATH
目录下的tools
目录路径(为了使用idf.py
工具,%IDF_PATH%\tools
即可)
-
根据
IDF_PATH/requirements.txt
文件安装Python软件包:1
2
3# 注意查询所使用的Python版本(运行命令python --version),并根据查询结果将python替换为python2或python2.7等
python -m pip install --user -r $IDF_PATH/requirements.txt # 使用pip安装
# 也可以使用anaconda安装安装内容:
1
2
3
4
5
6click>=5.0
pyserial>=3.0
future>=0.15.2
cryptography>=2.1.4
pyparsing>=2.0.3,<2.4.0
pyelftools>=0.22- 其中
pyelftools
软件包在anaconda中没有,需要手动从这里下载.whl
文件,在Anaconda Prompt
中通过命令pip install E:\xxx\xxx.whl
安装- 也可以在conda官网https://anaconda.org/中搜索
pyelftools
软件包,并通过软件包对应命令进行安装(推荐)
- 也可以在conda官网https://anaconda.org/中搜索
- 其中
3.2 创建工程
ESP-IDF 的examples
目录下有一系列示例工程,可以复制到其他地方进行运行(推荐),也可以直接编译示例,无需进行复制。
注意:ESP-IDF 编译系统不支持带有空格的路径
四、搭建开发环境(ubuntu18.04)
4.1 IDF框架
-
安装必备软件:
1
2sudo apt-get install git wget libncurses-dev flex bison gperf python python-pip python-setuptools cmake ninja-build ccache libffi-dev libssl-dev
sudo apt-get install python3 python3-pip python3-setuptools -
获取ESP-IDF
1 | #通过git |
-
设置工具(编译器、调试器、Python 包等)
1
./install.sh
- 默认安装在用户根文件夹中,Linux中为
$HOME/.espressif
- 默认安装在用户根文件夹中,Linux中为
-
设置环境变量
1 | . export.sh |
- 增加至
.profile
或.bash_profile
脚本即可在任何命令窗口使用 ESP-IDF 工具 - 每次都需要配置,除非添加到
.profile
或.bash_profile
脚本中
-
创建工程
1
cp -r $IDF_PATH/examples/get-started/hello_world .
-
连接设备
1
ls /dev/ttyUSB*
-
需要具有访问串口权限
-
方法一:添加串口设备访问规则
1
2sudo vim /etc/udev/rules.d/70-ttyusb.rules
KERNEL=="ttyUSB[0-9]*",MODE="0666" -
方法二:将目标用户添加至dialout用户组
1
2
3sudo usermod -a -G dialout $USER
# 检查用户组
groups $USER
-
-
-
配置工程
1
2cd ~/esp/hello_world
idf.py menuconfig -
编译
1
idf.py build
- 编译应用程序和所有 ESP-IDF 组件,接着生成 bootloader、分区表和应用程序二进制文件
-
烧录
1
idf.py -p PORT [-b BAUD] flash
-p
:串口对应的端口-b
:烧录波特率,默认460800flash
:自动编译并烧录(无需再次执行idf.py build)
-
监视器
1
idf.py -p PORT monitor
-p
:串口对应的端口- 快捷键
Ctrl+]
:退出 IDF 监视器 - 更多用法详见官方文档
如遇到特殊情况需要擦除flash:
1 | idf.py erase_flash |
4.2 ADF框架
详见官方安装准备
-
安装好IDF框架
-
获取ADF
1
2cd ~/esp # ADF的安装目录,不支持空格
git clone --recursive https://github.com/espressif/esp-adf.git -
设置ADF路径
ADF_PATH
1
2export ADF_PATH=~/esp/esp-adf # 设置
printenv ADF_PATH # 检查- 每次打开终端都需要重新设置
-
设置环境变量
1
. export.sh # 设置IDF的环境变量(需要进入IDF目录)
-
创建工程
1
2cd ~/esp
cp -r $ADF_PATH/examples/get-started/play_mp3 . # 拷贝demo -
连接设备
-
配置项目
1
2
3cd ~/esp/play_mp3 # 进入对应项目
#idf.py set-target esp32 # IDF4.1及之后版本需要选择目标芯片
idf.py menuconfig # 配置 -
构建工程
1
idf.py build # 编译所有IDF、ADF组件,并生成BootLoader、分区表和二进制应用文件
-
烧写到设备上
1
idf.py -p PORT [-b BAUD] flash monitor
- PORT:目标板卡串口号
- BAUD:烧写波特率,默认460800
- flash:会自动构建工程并烧写整个项目,此时可以无需
idf.py build
命令 - 烧写时,板卡应该进入上传模式(upload mode):按下boot按钮->按下reset按钮->松开boot按钮
-
升级ADF(需要能够正常访问github)
1
2
3cd ~/esp/esp-adf # 进入adf安装目录
git pull # 获取合并和更改
git submodule update --init --recursive # 更新现有子模块或获取新的子模块的副本
4.3 监视器使用
具体详见官方文档
快捷键 | 操作 | 描述 |
---|---|---|
Ctrl+] | 退出监视器程序 | |
Ctrl+T | 菜单退出键 | 按下如下给出的任意键,并按指示操作。 |
Ctrl+T | 将菜单字符发送至远程 | |
Ctrl+] | 将 exit 字符发送至远程 | |
Ctrl+P | 重置目标设备,进入 Bootloader,通过 RTS 线暂停应用程序 | 重置目标设备,通过 RTS 线(如已连接)进入 Bootloader,此时开发板不运行任何程序。等待其他设备启动时可以使用此操作。 |
Ctrl+R | 通过 RTS 线重置目标设备 | 重置设备,并通过 RTS 线(如已连接)重新启动应用程序。 |
Ctrl+F | 编译并烧录此项目 | 暂停 idf_monitor,运行 idf.py flash 目标,然后恢复 idf_monitor。任何改动的源文件都会被重新编译,然后重新烧录。 |
Ctrl+A (A) | 仅编译及烧录应用程序 | 暂停 idf_monitor,运行 app-flash 目标,然后恢复 idf_monitor。 这与 flash 类似,但只有主应用程序被编译并被重新烧录。 |
Ctrl+Y | 停止/恢复日志输出在屏幕上打印 | 激活时,会丢弃所有传入的串行数据。允许在不退出监视器的情况下快速暂停和检查日志输出。 |
Ctrl+L | 停止/恢复向文件写入日志输出 | 在工程目录下创建一个文件,用于写入日志输出。可使用快捷键停止/恢复该功能(退出 IDF 监视器也会终止该功能) |
Ctrl+H (H) | 显示所有快捷键 |
- 除了
Ctrl-]
和Ctrl-T
,其他快捷键信号会通过串口发送到目标设备。
五、一般注意事项
启动日志:
1 | ets Jun 8 2016 00:22:57 |
其中默认分区表(默认位置为Flash上0x8000):
1 | ******************************************************************************* |
- data:存储 NVS 库专用分区,flash 的
0x9000
处,24K大小- 存储每台设备的 PHY 校准数据
- 存储 Wi-Fi 数据
- 其他应用程序数据
- data:PHY 初始化数据,flash 的
0xf000
处,4k大小 - app:应用程序,flash 的
0x10000
(64k)处,1M大小(Bootloader 将默认加载这个应用程序,app区始终会加密)
1 | load:0x3fff0018,len:4 |
5.1 应用程序启动流程
宏观上,该启动流程可以分为如下 3 个步骤:
- 一级引导程序被固化在了 ESP32 内部的 ROM 中,它会从 Flash 的
0x1000
偏移地址处加载二级引导程序至 RAM(IRAM & DRAM) 中 - 二级引导程序从 Flash 中加载分区表和主程序镜像至内存中,主程序中包含了 RAM 段和通过 Flash 高速缓存映射的只读段
- 主程序运行,这时第二个 CPU 和 RTOS 的调度器可以开始运行
具体如下:
5.2 应用程序内存布局方案
ESP32的地址映射结构如下所示:
- 小端序:字节地址 0x0、 0x1、 0x2、 0x3 访问的字节分别是 0x0 访问的 32-bit字中的最低、次低、次高、最高字节
- CPU通过指令总线访问数据必须字对齐,数据总线无要求
总线类型 | 低位地址 | 高位地址 | 容量 | 目标 |
---|---|---|---|---|
0x0000_ 0000 | 0x3F3F_FFFF | 保留 | ||
数据 | 0x3F40_0000 | 0x3F7F_FFFF | 4 MB | 片外存储器 |
数据 | 0x3F80_0000 | 0x3FBF_FFFF | 4 MB | 片外存储器 |
0x3FC0_0000 | 0x3FEF_FFFF | 3 MB | 保留 | |
数据 | 0x3FF0_0000 | 0x3FF7_FFFF | 512 KB | 外设 |
数据 | 0x3FF8_0000 | Ox3FFF_FFFF | 512 KB | 片上存储器 |
指令 | 0x4000_0000 | 0x400C_1FFF | 776 KB | 片上存储器 |
指令 | 0x400C_2000 | 0x40BF_FFFF | 11512 KB | 片外存储器 |
0x40C0_0000 | 0x4FFF_FFFF | 244 MB | 保留 | |
数据/指令 | 0x5000_0000 | 0x5000_1FFF | 8 KB | 片上存储器 |
0x5000_2000 | 0xFFFF_FFFF | 保留 |
对于外部Flash而言(采用默认分区表):
ESP-IDF 应用程序的代码可以放在:
-
IRAM(指令RAM)
ESP-IDF 将内部 SRAM0 区域(在技术参考手册中有定义)的一部分分配为指令RAM。除了开始的 64kB 用作 PRO CPU 和 APP CPU 的高速缓存外,剩余内存区域(从
0x40080000
至0x400A0000
)被用来存储应用程序中部分需要在RAM中运行的代码一些 ESP-IDF 的组件和 WiFi 协议栈的部分代码通过链接脚本文件被存放到了这块内存区域
如果一些应用程序的代码需要放在 IRAM 中运行,可以使用
IRAM_ATTR
宏定义进行声明 -
IROM(代码从Flash中运行)
如果一个函数没有被显式地声明放在 IRAM 或者 RTC 内存中,则将其置于 Flash 中
-
RTC快速内存
从深度睡眠模式唤醒后必须要运行的代码要放在 RTC 内存中,更多信息请查阅官方文档 深度睡眠
-
DRAM(数据RAM)
链接器将非常量静态数据和零初始化数据放入
0x3FFB0000 — 0x3FFF0000
这 256kB 的区域。注意,如果使用蓝牙堆栈,此区域会减少 64kB(通过将起始地址移至0x3FFC0000
)。如果使用了内存跟踪的功能,该区域的长度还要减少 16kB 或者 32kB。放置静态数据后,留在此区域中的剩余空间都用作运行时堆。常量数据也可以放在 DRAM 中,需要使用
DRAM_ATTR
宏来声明。 -
DROM(数据存储在Flash中)
默认情况下,链接器将常量数据放入一个 4MB 区域 (
0x3F400000 — 0x3F800000
) ,该区域用于通过 Flash MMU 和高速缓存来访问外部 Flash。一种特例情况是,字面量会被编译器嵌入到应用程序代码中。 -
RTC慢速内存
从 RTC 内存运行的代码(例如深度睡眠模块的代码)使用的全局和静态变量必须要放在 RTC 慢速内存中。更多详细说明请查看官方文档 深度睡眠 。
宏
RTC_NOINIT_ATTR
用来声明将数据放入 RTC 慢速内存中,该数据在深度睡眠唤醒后将保持不变。
DMA能力要求
大多数的 DMA 控制器(比如 SPI,SDMMC 等)都要求发送/接收缓冲区放在 DRAM 中,并且按字对齐。建议将 DMA 缓冲区放在静态变量中而不是堆栈中。使用 DMA_ATTR
宏可以声明该全局/本地的静态变量具备 DMA 能力(推荐全局)
六、构建系统(CMake)
6.1 基本概念
项目
特指一个目录,其中包含了构建可执行应用程序所需的全部文件和配置,以及其他支持型文件,例如分区表、数据/文件系统分区和引导程序。项目配置
保存在项目根目录下名为sdkconfig
的文件中,可以通过idf.py menuconfig
进行修改,且一个项目只能包含一个项目配置。应用程序
是由 ESP-IDF 构建得到的可执行文件。一个项目通常会构建两个应用程序:项目应用程序(可执行的主文件,即用户自定义的固件)和引导程序(启动并初始化项目应用程序)。组件
是模块化且独立的代码,会被编译成静态库(.a 文件)并链接到应用程序。部分组件由 ESP-IDF 官方提供,其他组件则来源于其它开源项目。目标
特指运行构建后应用程序的硬件设备。ESP-IDF 当前仅支持ESP32
这一个硬件目标。
6.2 idf.py使用
idf.py
命令行工具提供了一个前端,可以帮助您轻松管理项目的构建过程,它管理了以下工具:
- CMake,配置待构建的系统
- 命令行构建工具(Ninja 或 GNU Make)
- esptool.py,烧录 ESP32
idf.py
应运行在 ESP-IDF 的 项目
目录下,即包含 CMakeLists.txt
文件的目录
常用的命令:
-
idf.py menuconfig
:运行menuconfig
配置项目 -
idf.py build
:构建在当前目录下的项目,包括:- 根据需要创建
build
构建目录,用于保存输出文件,可以使用-B
选项修改默认的构建目录 - 根据需要运行 CMake 配置命令,为主构建工具生成构建文件
- 运行主构建工具(Ninja 或 GNU Make)。默认情况下,构建工具会被自动检测,可以使用
-G
选项显式地指定构建工具
如果自上次构建以来源文件或项目配置没有发生改变,则不会执行任何操作
- 根据需要创建
-
idf.py clean
:把输出文件从构建目录中删除,从而清理整个项目。下次构建时会强制“重新完整构建”这个项目。清理时,不会删除 CMake 配置输出及其他文件 -
idf.py fullclean
会将整个build
目录下的内容全部删除,包括所有 CMake 的配置输出文件。下次构建项目时,CMake 会从头开始配置项目。请注意,该命令会递归删除构建目录下的 所有文件,请谨慎使用。项目配置文件不会被删除 -
idf.py flash
会在必要时自动构建项目,并将生成的二进制程序烧录进ESP32中。-p
和-b
选项可分别设置串口的设备名和烧录时的波特率 -
idf.py monitor
用于显示 ESP32 设备的串口输出。-p
选项可用于设置主机端串口的设备名,按下Ctrl-]
可退出监视器。更多有关监视器的详情,请参阅官方 IDF 监视器
多个 idf.py
命令可合并成一个
环境变量
ESPPORT
和ESPBAUD
可分别用作-p
和-b
选项的默认值。在命令行中,重新为这两个选项赋值,会覆盖其默认值
高级命令:
idf.py app
,idf.py bootloader
,idf.py partition_table
:从项目中构建应用程序、引导程序或分区表idf.py app-flash
等匹配命令:将特定部分烧录至 ESP32idf.py -p PORT erase_flash
:使用 esptool.py 擦除整个 Flashidf.py size
:打印应用程序相关的大小信息,idf.py size-components
和idf.py size-files
这两个命令分别用于打印每个组件或源文件的详细信息idf.py reconfigure
:重新运行CMake(即便无需重新运行)。正常使用时,并不需要运行此命令,但当源码树中添加/删除文件后或更改 CMake cache 变量时,此命令会非常有用,例如,idf.py -DNAME='VALUE' reconfigure
会将 CMake cache 中的变量NAME
的值设置为VALUE
同时调用多个 idf.py
命令时,命令的输入顺序并不重要,它们会按照正确的顺序依次执行,并保证每一条命令都生效(即先构建后烧录,先擦除后烧录等)
6.3 CMakeLists文件
6.3.1 项目CMakeLists
每个项目都有一个顶层 CMakeLists.txt
文件,包含整个项目的构建设置。默认情况下,项目 CMakeLists 文件会非常小:
1 | cmake_minimum_required(VERSION 3.5) |
cmake_minimum_required(VERSION 3.5)
必须放在 CMakeLists.txt 文件的第一行,告诉 CMake 构建该项目所需要的最小版本号include($ENV{IDF_PATH}/tools/cmake/project.cmake)
导入 CMake 的其余功能来完成配置项目、检索组件等任务project(myProject)
创建项目本身,并指定项目名称。该名称会作为最终输出的二进制文件的名字,即myProject.elf
和myProject.bin
。每个 CMakeLists 文件只能定义一个项目
默认变量(用户可以覆盖这些变量值以自定义构建行为(更多实现细节,请参阅 /tools/cmake/project.cmake 文件)):
COMPONENT_DIRS
:组件的搜索目录,默认为${IDF_PATH}/components
、${PROJECT_PATH}/components
和EXTRA_COMPONENT_DIRS
EXTRA_COMPONENT_DIRS
:搜索组件的其它可选目录列表COMPONENTS
:要构建进项目中的组件名称列表,默认为COMPONENT_DIRS
目录下检索到的所有组件。使用此变量可以“精简”项目以缩短构建时间。请注意,如果一个组件通过COMPONENT_REQUIRES
指定了它依赖的另一个组件,则会自动将其添加到COMPONENTS
中,所以COMPONENTS
列表可能会非常短。COMPONENT_REQUIRES_COMMON
:每个组件都需要的通用组件列表,这些通用组件会自动添加到每个组件的COMPONENT_PRIV_REQUIRES
列表中以及项目的COMPONENTS
列表中。默认情况下,此变量设置为 ESP-IDF 项目所需的最小核心“系统”组件集。通常您无需在项目中更改此变量
以上变量中的路径可以是绝对路径,或者是相对于项目目录的相对路径
请使用 cmake 中的 set 命令 来设置这些变量,即 set(VARIABLE "VALUE")
。请注意,set()
命令需放在 include(...)
之前,cmake_minimum(...)
之后
6.3.2 组件CMakeLists
每个组件目录都包含一个 CMakeLists.txt
文件,里面会定义一些变量以控制该组件的构建过程,以及其与整个项目的集成
每个组件还可以包含一个 Kconfig
文件,它用于定义 menuconfig
时展示的 组件配置 选项。某些组件可能还会包含 Kconfig.projbuild
和 project_include.cmake
特殊文件,它们用于 覆盖项目的部分设置
组件是 COMPONENT_DIRS
列表中包含 CMakeLists.txt
文件的任何目录
ESP-IDF 搜索待构建组件顺序(由COMPONENT_DIRS
指定):
-
${IDF_PATH}/components
-
${PROJECT_PATH}/components
-
EXTRA_COMPONENT_DIRS
如果包含同名组件,则使用最后一个位置的组件
最小组件 CMakeLists.txt
文件:
1 | set(COMPONENT_SRCS "foo.c") |
COMPONENT_SRCS
:用空格分隔的源文件列表(*.c
,*.cpp
,*.cc
,*.S
),里面所有的源文件都将会编译进组件库中COMPONENT_ADD_INCLUDEDIRS
:用空格分隔的目录列表,里面的路径会被添加到所有需要该组件的组件(包括 main 组件)全局 include 搜索路径中。register_component()
:使用上述设置的变量将组件添加到构建系统中,构建生成与组件同名的库,并最终被链接到应用程序中。如果因为使用了 CMake 中的 if 命令 或类似命令而跳过了这一步,那么该组件将不会被添加到构建系统中
有关更完整的 CMakeLists.txt
示例,请参阅组件 CMakeLists 示例
章节
预设变量
COMPONENT_PATH
:组件目录,即包含CMakeLists.txt
文件的绝对路径,它与CMAKE_CURRENT_SOURCE_DIR
变量一样,路径中不能包含空格COMPONENT_NAME
:组件名,与组件目录名相同COMPONENT_TARGET
:库目标名,它由构建系统在内部为组件创建
可以在组件 CMakeLists 中使用的项目 CMakeLists 预设变量:
PROJECT_NAME
:项目名,在项目 CMakeLists.txt 文件中设置PROJECT_PATH
:项目目录(包含项目 CMakeLists 文件)的绝对路径,与CMAKE_SOURCE_DIR
变量相同COMPONENTS
:此次构建中包含的所有组件的名称,具体格式为用分号隔开的 CMake 列表CONFIG_*
:项目配置中的每个值在 cmake 中都对应一个以CONFIG_
开头的变量。更多详细信息请参阅 KconfigIDF_TARGET
:项目的硬件目标名称
如果修改以上变量,并不会影响其他组件的构建,但可能会使该组件变得难以构建或调试
-
COMPONENT_ADD_INCLUDEDIRS
:相对于组件目录的相对路径,为被添加到所有需要该组件的其他组件的全局 include 搜索路径中。如果某个 include 路径仅仅在编译当前组件时需要,请将其添加到COMPONENT_PRIV_INCLUDEDIRS
中。 -
COMPONENT_REQUIRES
是一个用空格分隔的组件列表,列出了当前组件依赖的其他组件。如果当前组件有一个头文件位于COMPONENT_ADD_INCLUDEDIRS
目录下,且该头文件包含了另一个组件的头文件,那么这个被依赖的组件需要在COMPONENT_REQUIRES
中指出。这种依赖关系可以是递归的。COMPONENT_REQUIRES
可以为空,因为所有的组件都需要一些常用的组件(如 newlib 组件提供的 libc 库、freertos 组件提供的 RTOS 功能),这些通用组件已经在项目级变量COMPONENT_REQUIRES_COMMON
中被设置。如果一个组件仅需要额外组件的头文件来编译其源文件(而不是全局引入它们的头文件),则这些被依赖的组件需要在
COMPONENT_PRIV_REQUIRES
中指出。
可选变量
COMPONENT_PRIV_INCLUDEDIRS
:相对于组件目录的相对路径,仅会被添加到该组件的 include 搜索路径中COMPONENT_PRIV_REQUIRES
:以空格分隔的组件列表,用于编译或链接当前组件的源文件。这些组件的头文件路径不会传递给其余需要它的组件,仅用于编译当前组件的源代码。更多详细信息请参阅 组件依赖COMPONENT_SRCS
:要编译进当前组件的源文件的路径,推荐使用此方法向构建系统中添加源文件COMPONENT_SRCDIRS
:相对于组件目录的源文件目录路径,用于搜索源文件(*.cpp
,*.c
,*.S
)。匹配成功的源文件会替代COMPONENT_SRCS
中指定的源文件,进而被编译进组件。即设置COMPONENT_SRCDIRS
会导致COMPONENT_SRCS
会被忽略。此方法可以很容易地将源文件整体导入到组件中,但并不推荐使用(使用该方法会导致增量更新编译变得更加麻烦)COMPONENT_SRCEXCLUDE
:需要从组件中剔除的源文件路径。当某个目录中有大量的源文件需要被导入组件中,但同时又有个别文件不需要导入时,可以配合COMPONENT_SRCDIRS
变量一起设置。路径可以是相对于组件目录的相对路径,也可以是绝对路径。COMPONENT_ADD_LDFRAGMENTS
:组件使用的链接片段文件的路径,用于自动生成链接器脚本文件。详细信息请参阅 链接脚本生成机制
如果没有设置
COMPONENT_SRCDIRS
或COMPONENT_SRCS
,组件不会被编译成库文件,但仍可以被添加到 include 路径中,以便在编译其他组件时使用
编译控制
在编译特定组件的源文件时,可以使用 target_compile_options
命令来传递编译器选项:
1 | target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-variable) |
- 实际上为 CMake 的 target_compile_options 命令
如果给单个源文件指定编译器标志,可以使用 CMake 的 set_source_files_properties 命令:
1 | set_source_files_properties(mysrc.c |
**注意:**上述两条命令只能在组件 CMakeLists 文件的 register_component()
命令之后调用
6.4 组件依赖
编译各个组件时,每个组件的源文件都会使用以下路径中的头文件进行编译:
- 当前组件的
COMPONENT_ADD_INCLUDEDIRS
和COMPONENT_PRIV_INCLUDEDIRS
- 当前组件的
COMPONENT_REQUIRES
和COMPONENT_PRIV_REQUIRES
变量指定的其他组件(即当前组件的所有公共和私有依赖项)所设置的COMPONENT_ADD_INCLUDEDIRS
- 所有组件的
COMPONENT_REQUIRES
做递归操作,即该组件递归运算后的所有公共依赖项
6.4.1 编写组件
COMPONENT_REQUIRES
:包含所有被当前组件的公共头文件 #include 的头文件所在的组件。COMPONENT_PRIV_REQUIRES
:包含被当前组件的源文件 #include 的头文件所在的组件(除非已经被设置在了COMPONENT_PRIV_REQUIRES
中),或者是当前组件正常工作必须要链接的组件COMPONENT_REQUIRES
、COMPONENT_PRIV_REQUIRES
需要在调用register_component()
之前设置COMPONENT_REQUIRES
和COMPONENT_PRIV_REQUIRES
的值不能依赖于任何配置选项(CONFIG_xxx
),这是因为在配置加载之前,依赖关系就已经被展开。其它组件变量(比如COMPONENT_SRCS
和COMPONENT_ADD_INCLUDEDIRS
)可以依赖配置选择- 如果当前组件除了
COMPONENT_REQUIRES_COMMON
中设置的通用组件(比如 RTOS、libc 等)外,并不依赖其它组件,那么上述两个REQUIRES
变量可以为空
如果组件仅支持某些硬件目标(即依赖于特定的 IDF_TARGET
),则可以调用 require_idf_targets(NAMES...)
来声明这个需求
6.4.2 创建项目
- 默认情况下,每个组件都会包含在构建系统中
- 如果将
COMPONENTS
变量设置为项目直接使用的最小组件列表,那么构建系统会导入:COMPONENTS
中明确提及的组件- 这些组件的依赖项(以及递归运算后的组件)
- 每个组件都依赖的通用组件
- 将
COMPONENTS
设置为所需组件的最小列表,可以显著减少项目的构建时间
6.4.3 其他细节
- 在 CMake 配置进程的早期阶段会运行
expand_requirements.cmake
脚本。该脚本会对所有组件的 CMakeLists.txt 文件进行局部的运算,得到一张组件依赖关系图(此图可能会有闭环)。此图用于在构建目录中生成component_depends.cmake
文件 - CMake 主进程会导入该文件,并以此来确定要包含到构建系统中的组件列表(内部使用的
BUILD_COMPONENTS
变量)。BUILD_COMPONENTS
变量已排好序,依赖组件会排在前面。由于组件依赖关系图中可能存在闭环,因此不能保证每个组件都满足该排序规则。如果给定相同的组件集和依赖关系,那么最终的排序结果应该是确定的 - CMake 会将
BUILD_COMPONENTS
的值以 “Component names:” 的形式打印出来 - 然后执行构建系统中包含的每个组件的配置
- 每个组件都被正常包含在构建系统中,然后再次执行 CMakeLists.txt 文件,将组件库加入构建系统
BUILD_COMPONENTS
变量中组件的顺序决定了构建过程中的其它顺序:
- 项目导入 project_include.cmake 文件的顺序
- 生成用于编译(通过
-I
参数)的头文件路径列表的顺序。请注意,对于给定组件的源文件,仅需将该组件的依赖组件的头文件路径告知编译器
6.5 组件CmakeLists示例
因为构建环境试图设置大多数情况都能工作的合理默认值,所以组件 CMakeLists.txt
文件可能非常小,甚至是空的,请参考组件CMakeLists
章节。但有些功能往往需要覆盖预设变量
才能实现
6.5.1 条件配置
添加
Kconfig
:
1 | config FOO_ENABLE_BAR |
CMakeLists.txt
:
1 | set(COMPONENT_SRCS "foo.c" "more_foo.c") |
上述示例使用了 CMake 的 if 函数和 list APPEND 函数。
选择或删除
Kconfig
:
1 | config ENABLE_LCD_OUTPUT |
CMakeLists.txt
:
1 | if(CONFIG_ENABLE_LCD_OUTPUT) |
由硬件目标决定
CMake 文件可以使用 IDF_TARGET
变量来获取当前的硬件目标。
此外,如果当前的硬件目标是 xyz
(即 IDF_TARGET=xyz
),那么 Kconfig 变量 CONFIG_IDF_TARGET_XYZ
同样也会被设置。
请注意,组件可以依赖 IDF_TARGET
变量,但不能依赖这个 Kconfig 变量。同样也不可在 CMake 文件的 include
语句中使用 Kconfig 变量,在这种上下文中可以使用 IDF_TARGET
。