对于 std 应用, 核心是使用 esp-idf-sys 和它绑定链接到的 C/C++ 库 esp-idf。
- 构建的 target 是
xtensa-esp32s3-espidf
, 而 non_std 应用的 target 是xtensa-esp32s3-none-elf
esp-idf 是 C/C++ 开发的,运行 FreeRTOS 操作系统,为 Rust std 提供了 newlib enviroment 实现(~/.rustup/toolchains/esp 目录),这样 std 应用可以使用 Rust 标准库的各种类型和特性,如 Vec/HashMap/Box,net,heap 内存分配、thread/Mutex 等;
Rust std 应用与 esp-idf 之间的 3 种互操作方式(这些 std 库惯例是 esp-idf- 开头):
esp-idf-sys
crate:esp-idf 的 unsafe binding,Givesraw (unsafe) access
to drivers, Wi-Fi and more.esp-idf-svc
crate:esp-idf 的 safe binding,抽象层次更高,实现了 embedded-svc trait。esp-idf-hal
crate:实现了embedded-hal
trait,支持 async,底层也是基于 esp-idf;
它们之间的层次关系: esp-idf-svc -> esp-idf-hal -> esp-idf-sys(esp-idf 的 Rust binding).
embedded-hal 和 embedded-svc 是 Rust embedded workgroup 定义的厂商中立的嵌入式规范.
编译 esp-idf-sys 时, build.rs 会会自动下载/安装/配置/编译和链接 esp-idf 库,安装到 $ESP_IDF_TOOLS_INSTALL_DIR 位置,默认为 by 项目的 .embuild/espressif 目录。
esp-idf-sys 默认启用 esp-idf 所有的 Component(静态库的形式)
,这样可以后续直接链接他们(后续也可以通过 esp_idf_components, $ESP_IDF_COMPONENTS 来配置)。
esp-idf-sys 也支持添加本地和远程的 C/C++ Component,在构建 esp-idf-sys 时自动使用 bindgen 将它封装为Rust 接口,后续自动链接到可执行程序中。(参考后文)。
如果 esp-idf-hal 不满足需求(如缺少一些 esp32 的寄存器的操作),可以使用 esp-rs/esp-pacs
下的esp32s3
create, 它是使用 svd2rust 工具来基于芯片的 svd 自动生成的库。
https://apollolabsblog.hashnode.dev/the-embedded-rust-esp-development-ecosystem
使用 esp-rs/esp-idf-template 模板来创建 std 应用:
- 在编译 esp-idf-sys 时自动下载和安装 esp-idf 框架到项目的 .embuild/espressif/esp-idf 目录下;
- 可以配置项目的 .cargo/config.toml 文件, 添加 env ESP_IDF_TOOLS_INSTALL_DIR = “global” 来使用全局
~/.espressif/
下的 esp-idf 工具链(建议). - sdkconfig.defaults 文件为 esp-idf 的缺省参数提供 override 参数如 stack size, log level;
zj@a:~/codes/esp32/$ cargo generate esp-rs/esp-idf-template cargo
⚠️ Favorite `esp-rs/esp-idf-template` not found in config, using it as a git repository: https://github.com/esp-rs/esp-idf-template.git
🔧 project-name: myesp ...
🔧 Generating template ...
✔ 🤷 Which MCU to target? · esp32s3
✔ 🤷 Configure advanced template options? · true
✔ 🤷 Enable STD support? · true
✔ 🤷 Configure project to use Dev Containers (VS Code and GitHub Codespaces)? · false
✔ 🤷 Configure project to support Wokwi simulation with Wokwi VS Code extension? · false
✔ 🤷 Add CI files for GitHub Action? · false
✔ 🤷 ESP-IDF version (master = UNSTABLE) · v5.1
🔧 Moving generated files into: `/Users/zhangjun/codes/esp32/esp-demo2/myesp`...
🔧 Initializing a fresh Git repository
✨ Done! New project created /Users/zhangjun/codes/esp32/esp-demo2/myesp
zj@a:~/codes/esp32/$ cd myesp
zj@a:~/code/esp32/std/myespv4$ cat src/main.rs
fn main() {
// It is necessary to call this function once. Otherwise some patches to the runtime
// implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
esp_idf_svc::sys::link_patches();
// Bind the log crate to the ESP Logging facilities
esp_idf_svc::log::EspLogger::initialize_default();
log::info!("Hello, world!");
}
按需修改 Cargo.toml:
zj@a:~/code/esp32/std/myesp$ cat Cargo.toml
[package]
name = "myesp"
version = "0.1.0"
authors = ["alizj"]
edition = "2021"
resolver = "2"
rust-version = "1.71"
[profile.release]
opt-level = "s"
[profile.dev]
debug = true # Symbols are nice and they don't increase the size on Flash
opt-level = "z"
[features]
# 缺省 features:重点是包含 std 和 esp-idf-svc/native
# esp-idf-svc/native 指的是 native 平台类型,除此之外还有 pio 平台类型。
default = ["std", "embassy", "esp-idf-svc/native"]
# std 包含 alloc 和 esp-idf-svc/std
std = ["alloc", "esp-idf-svc/binstart", "esp-idf-svc/std"]
# alloc 依赖 esp-idf-svc/alloc
alloc = ["esp-idf-svc/alloc"]
# embassy 也仅依赖 esp-idf-svc
embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-svc/embassy-time-driver"]
# 总结:cargo gen 通过模板创建的 std 应用仅依赖 std 和 esp-idf-svc
pio = ["esp-idf-svc/pio"]
nightly = ["esp-idf-svc/nightly"]
experimental = ["esp-idf-svc/experimental"]
[dependencies]
# 通过 log 打印日志,esp-idf-svc 提供了 log 的具体实现
log = { version = "0.4", default-features = false }
# std 类型的 crate
esp-idf-svc = { version = "0.48", default-features = false }
[build-dependencies] # build.rs 编译脚本的依赖
embuild = "0.31.3" # build.rs 依赖 embuild
修改 .cargo/config.toml 文件中的 ESP_IDF_VERSION 为最新版本,内容如下:
zj@a:~/codes/esp32/myesp$ cat .cargo/config.toml
[build]
target = "xtensa-esp32s3-espidf" # 要构建的 target,这里使用链接 esp-idf 的 target
[target.xtensa-esp32s3-espidf] # target 对应的配置
linker = "ldproxy" # 位于 ~/.cargo/bin/
# runner = "espflash --monitor" # Select this runner for espflash v1.x.x
runner = "espflash flash --monitor" # Select this runner for espflash v2.x.x
rustflags = [ "--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
[unstable]
build-std = ["std", "panic_abort"] # 构建和使用 std(对于 no_std 是 core)
[env] # 被 embuild 使用的环境变量
MCU="esp32s3"
# Note: this variable is not used by the pio builder (`cargo build --features pio`)
ESP_IDF_VERSION = "v5.2.1"
# 使用全局 ~/.espressif/ 工具链,默认是 by 项目 workspace 的。
ESP_IDF_TOOLS_INSTALL_DIR = "global"
按需修改 esp-idf 的配置参数文件:sdkconfig.defaults
zj@a:~/docs$ cat ~/code/esp32/std/myespv2/sdkconfig.defaults
# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K)
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8000
# Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default).
# This allows to use 1 ms granuality for thread sleeps (10 ms by default).
#CONFIG_FREERTOS_HZ=1000
# Workaround for https://github.com/espressif/esp-idf/issues/7631
#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n
#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n
按需修改 rust-toolchain.toml :
# esp32 rust 项目通过 rust-toolchain.toml 来选择 channel 和 target
zj@a:~/code/esp32/myespv2$ cat rust-toolchain.toml
[toolchain]
channel = "esp" # ~/.rustup/toolchains/ 下的目录名称,这里使用 esp toolchain
# 使用 embuild crate 来安装和构建 esp-idf framework
# 对于 non_std 应用,不依赖 esp-idf, 故不需要 build.rs .
zj@a:~/code/esp32/myespv2$ cat build.rs
fn main() {
embuild::espidf::sysenv::output();
}
构建项目:
- 每次构建前都需要先 source ~/esp/export-esp.sh 脚本。# 不能启用 python env,不能使用 socks 代理,需要设置环境变量;
- 构建过程中默认下载和安装 esp-idf 到 workspace 的 .embuild/espressif/ 目录;
# 构建 std 应用时,不能 source source ~/esp/esp-idf/v5.2.1/export.sh 文件,否则会构建失败。
zj@a:~/code/esp32/myesp$ source ~/esp/export-esp.sh
zj@a:~/code/esp32/myesp$ cargo build
# by workspace 安装的 esp-idf 到 .embuild/espressif/ 目录
zj@a:~/code/esp32/myespv2$ ls -l .embuild/espressif/
total 4.0K
drwxr-xr-x 6 alizj 192 5 5 14:54 dist/
drwxr-xr-x 3 alizj 96 5 5 14:49 esp-idf/
-rw-r--r-- 1 alizj 2.8K 5 5 14:51 espidf.constraints.v5.2.txt
drwxr-xr-x 3 alizj 96 5 5 14:51 python_env/
drwxr-xr-x 6 alizj 192 5 5 14:54 tools/
# dist 下载的内容被解压到 .embuild/espressif/tools/ 目录
zj@a:~/code/esp32/myespv2$ ls -l .embuild/espressif/dist/
total 193M
-rw-r--r-- 1 alizj 70M 5 5 14:52 cmake-3.24.0-macos-universal.tar.gz
-rw-r--r-- 1 alizj 15M 5 5 14:54 esp32ulp-elf-2.35_20220830-macos-arm64.tar.gz
-rw-r--r-- 1 alizj 271K 5 5 14:52 ninja-mac-v1.11.1.zip
-rw-r--r-- 1 alizj 96M 5 5 14:51 xtensa-esp-elf-13.2.0_20230928-aarch64-apple-darwin.tar.xz # 交叉编译工具链
zj@a:~/code/esp32/myespv2$ ls -l .embuild/espressif/tools/
total 0
drwxr-xr-x 3 alizj 96 5 5 14:52 cmake/
drwxr-xr-x 3 alizj 96 5 5 14:54 esp32ulp-elf/ # ULP (Ultra-Low-Powered)
drwxr-xr-x 3 alizj 96 5 5 14:52 ninja/
drwxr-xr-x 3 alizj 96 5 5 14:51 xtensa-esp-elf/
# esp-idf framework,被安装到 python_env/ 目录
zj@a:~/code/esp32/myespv2$ ls -l .embuild/espressif/esp-idf/v5.2.1/
total 172K
-rw-r--r-- 1 alizj 12K 5 5 14:49 CMakeLists.txt
-rw-r--r-- 1 alizj 4.2K 5 5 14:49 COMPATIBILITY.md
-rw-r--r-- 1 alizj 4.3K 5 5 14:49 COMPATIBILITY_CN.md
-rw-r--r-- 1 alizj 314 5 5 14:49 CONTRIBUTING.md
-rw-r--r-- 1 alizj 25K 5 5 14:49 Kconfig
-rw-r--r-- 1 alizj 12K 5 5 14:49 LICENSE
-rw-r--r-- 1 alizj 8.9K 5 5 14:49 README.md
-rw-r--r-- 1 alizj 8.8K 5 5 14:49 README_CN.md
-rw-r--r-- 1 alizj 532 5 5 14:49 SECURITY.md
-rw-r--r-- 1 alizj 3.7K 5 5 14:49 SUPPORT_POLICY.md
-rw-r--r-- 1 alizj 3.4K 5 5 14:49 SUPPORT_POLICY_CN.md
-rw-r--r-- 1 alizj 721 5 5 14:49 add_path.sh
drwxr-xr-x 81 alizj 2.6K 5 5 14:49 components/
-rw-r--r-- 1 alizj 12K 5 5 14:49 conftest.py
drwxr-xr-x 15 alizj 480 5 5 14:49 docs/
drwxr-xr-x 22 alizj 704 5 5 14:49 examples/
-rw-r--r-- 1 alizj 3.9K 5 5 14:49 export.bat
-rw-r--r-- 1 alizj 3.7K 5 5 14:49 export.fish
-rw-r--r-- 1 alizj 3.5K 5 5 14:49 export.ps1
-rw-r--r-- 1 alizj 8.0K 5 5 14:49 export.sh
-rw-r--r-- 1 alizj 1.8K 5 5 14:49 install.bat
-rwxr-xr-x 1 alizj 971 5 5 14:49 install.fish*
-rw-r--r-- 1 alizj 982 5 5 14:49 install.ps1
-rwxr-xr-x 1 alizj 1004 5 5 14:49 install.sh*
-rw-r--r-- 1 alizj 889 5 5 14:49 pytest.ini
-rw-r--r-- 1 alizj 2.0K 5 5 14:49 sdkconfig.rename
-rw-r--r-- 1 alizj 530 5 5 14:49 sonar-project.properties
drwxr-xr-x 47 alizj 1.5K 5 5 14:51 tools/
zj@a:~/code/esp32/myesp$ ls target/
CACHEDIR.TAG debug/ xtensa-esp32s3-espidf/
构建结果位于 target/xtensa-esp32s3-espidf 目录下:
zj@a:~/code/esp32/myesp$ ls -l target/xtensa-esp32s3-espidf/debug/
total 11M
-rw-r--r-- 1 alizj 21K 5 5 14:45 bootloader.bin # bootloader
drwxr-xr-x 18 alizj 576 5 5 14:39 build/
drwxr-xr-x 178 alizj 5.6K 5 5 14:45 deps/
drwxr-xr-x 2 alizj 64 5 5 14:39 examples/
drwxr-xr-x 3 alizj 96 5 5 14:45 incremental/
-rwxr-xr-x 1 alizj 11M 5 5 14:45 myesp* # 二进制程序
-rw-r--r-- 1 alizj 153 5 5 14:45 myesp.d
-rw-r--r-- 1 alizj 3.0K 5 5 14:45 partition-table.bin # 分区表
zj@a:~/code/esp32/myesp$ file target/xtensa-esp32s3-espidf/debug/myesp
target/xtensa-esp32s3-espidf/debug/myesp: ELF 32-bit LSB executable, Tensilica Xtensa, version 1 (SYSV), statically linked, with debug_info, not stripped
构建失败的解决办法:
- cargo clen 清理 target 目录;
- 手动清理 .embuild 目录;
- 不能启用 socks 代理;
- 不能开启 python venv;
zj@a:~/code/esp32/myesp$ cargo clean
zj@a:~/code/esp32/myesp$ rm -rf .embuild/
zj@a:~/code/esp32/myesp$ ls
Cargo.lock Cargo.toml build.rs rust-toolchain.toml sdkconfig.defaults src/
zj@a:~/code/esp32/myesp$ cargo build
# cargo build 会安装 esp-idf,期间会安装 python venv 和按照 python 包。所以,不能使用 python 不支
# 持的 socks 代理,也不能启用 python env。
zj@a:~/code/esp32/$ enable_http_proxy
zj@a:~/code/esp32/$ export DIR_TO_REMOVE=/Users/alizj/.venv/bin
zj@a:~/code/esp32/$ export PATH=$(echo $PATH | sed -e "s;:$DIR_TO_REMOVE;;" -e "s;$DIR_TO_REMOVE:;;" -e "s;$DIR_TO_REMOVE;;")
# 如果是 Mac M1 笔记本,需要给 cargo build 添加环境变量 CRATE_CC_NO_DEFAULTS=1,否则会构建失败,报错:
# xtensa-esp-elf-gcc: error: unrecognized command-line option '--target=xtensa-esp32s3-espidf'
# 参考:https://github.com/rust-lang/cc-rs/issues/1005
zj@a:~/code/esp32/$ export CRATE_CC_NO_DEFAULTS=1
参考:
- https://docs.esp-rs.org/std-training/01_intro.html
- https://github.com/esp-rs/std-training
- https://github.com/danclive/esp-examples/tree/main
1 为 Rust std 应用添加组件 component #
使用 cargo build 构建基于 eps-idf-sys 的 std 应用时, build.rs 构建脚本执行的 embuild crate 会下载 esp-idf 库,使用 bindgen 来生成 Rust 接口,将编译后的 component 静态库链接到 Rust 可执行程序。
- Rust 习惯使用 xx-sys 来命名其他语言 xx 项目的 Rust wrapper,所以 esp-idf-sys 是 C 项目 esp-idf 的 Rust wrapper。
esp-idf 内置了大量 component,同时社区和 component registry 上还有大量其他 component 可供使用。
内置 component 的 bindgen 由 esp-idf-sys 的 bindings.h 头文件定义。
esp-idf-sys 默认启用了 所有内置 component
,并链接到 Rust 可执行程序。可以通过配置
esp_idf_components, $ESP_IDF_COMPONENTS 来指定要链接的 esp-idf 内置 component 列表。
[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/esp_common/libesp_common.a
[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/esp_timer/libesp_timer.a
[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/app_trace/libapp_trace.a
[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/esp_event/libesp_event.a
[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/nvs_flash/libnvs_flash.a
[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/esp_phy/libesp_phy.a
[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/vfs/libvfs.a
// ...
[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=/Users/alizj/code/esp32/std/.embuild/espressif/esp-idf/v5.2.1/components/esp_wifi/lib/esp32s3/libcore.a
[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=/Users/alizj/code/esp32/std/.embuild/espressif/esp-idf/v5.2.1/components/esp_wifi/lib/esp32s3/libespnow.a
// ...
[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/esp_coex/libesp_coex.a
[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/esp_wifi/libesp_wifi.a
// ...
[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=esp-idf/mbedtls/mbedtls/3rdparty/p256-m/libp256m.a
[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=/Users/alizj/code/esp32/std/.embuild/espressif/esp-idf/v5.2.1/components/esp_wifi/lib/esp32s3/libcore.a
[esp32-camera-binding 0.1.0]
// ...
[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=-Wl,--wrap=_Unwind_Backtrace
[esp32-camera-binding 0.1.0] cargo:rustc-link-arg=-Wl,--wrap=__cxa_call_unexpected
为 esp-idf-sys 添加 外部额外的 component
,让 esp-idf-sys bindgen 它的头文件,并进行编译和链接。
- bindgen 为 C 生成的 Rust 接口会放到 esp-idf-sys module 中供 Rust 程序使用。
esp-idf-sys crate 提供了一些影响 esp-idf bindgen、编译构建的配置参数和 .cargo/config.tom 环境变量:
- https://github.com/esp-rs/esp-idf-sys/blob/master/BUILD-OPTIONS.md
- https://docs.esp-rs.org/esp-idf-svc/esp_idf_sys/index.html
添加 extral component 示例:
# 在 workspace 的 members 列表里添加待创建的 crate package 目录名称
zj@a:~/code/esp32/std$ cat Cargo.toml
[workspace]
resolver = "2"
members = [
"myesp",
"myespv2",
"myespv3",
"myespv4",
"esp32-camera-binding" # 待创建的 create package 目录名称
]
#...
# 由于 workspace 的 Cargo.toml 中没有配置 [package], 即该 workspace 没有默认的 root crate, 它是一
# 个virtual workspace.
#
# 对于 virtual workspace, 必须在 workspace 的 .cargo/config.toml 文件中, 通过环境变量
# ESP_IDF_SYS_ROOT_CRATE="esp32-camera-binding" 来指定 root crate, 如 esp32-camera-binding.
zj@a:~/code/esp32/std$ cat .cargo/config.toml
[env]
ESP_IDF_SYS_ROOT_CRATE="esp32-camera-binding" # 关键! 否则编译 esp32-camera-binding 时不会进行 bindgen 转换.
# 创建上面定义的 esp32-camera-binding std 项目
zj@a:~/code/esp32/std$ cargo generate esp-rs/esp-idf-template cargo
zj@a:~/code/esp32/std$ cd esp32-camera-binding/
# clone 要 bindgen 的 esp-idf component 项目到本地
zj@a:~/code/esp32/std/esp32-camera-binding$ git clone [email protected]:espressif/esp32-camera.git
zj@a:~/code/esp32/std/esp32-camera-binding$ ls
Cargo.toml bindings.h build.rs esp32-camera/ src/
# 创建一个 bindings.h 文件,内容为 bindgen 要转换的 component 头文件列表,
# 可以查看项目目录中头文件名称。
zj@a:~/code/esp32/std/esp32-camera-binding$ cat bindings.h
#include "esp_camera.h"
# 配置刚创建的 package 的 Cargo.toml 文件,添加 [[package.metadata.esp-idf-sys.extra_components]]
# 这样在编译 esp-idf-sys 时,会自动使用 bindgen 将 bindings.h 中的头文件生成到 esp-idf-sys 的 module 中,
# 同时也会编译该 component 为静态库,后续可以链接到 Rust 可执行程序中。
zj@a:~/code/esp32/std/esp32-camera-binding$ cat Cargo.toml
[package]
name = "esp32-camera-binding"
version = "0.1.0"
authors = ["alizj"]
edition = "2021"
resolver = "2"
rust-version = "1.71"
[profile.release]
opt-level = "s"
[profile.dev]
debug = true
opt-level = "z"
[features]
default = ["std", "embassy", "esp-idf-svc/native", "esp-idf-sys/native"]
pio = ["esp-idf-svc/pio"]
std = ["alloc", "esp-idf-svc/binstart", "esp-idf-svc/std"]
alloc = ["esp-idf-svc/alloc"]
nightly = ["esp-idf-svc/nightly"]
experimental = ["esp-idf-svc/experimental"]
embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-svc/embassy-time-driver"]
[dependencies]
log = { version = "0.4", default-features = false }
esp-idf-svc = { version = "0.48", default-features = false }
esp-idf-sys = { version = "0.34.1"} # 添加 esp-idf-sys 依赖
esp-idf-hal = { version = "0.43.1"}
[build-dependencies]
embuild = "0.31.3"
# esp-idf-sys 的构建脚本 embuild 在编译 esp-idf C 时使用的配置参数.
#
# 通过在 crate package 的 Cargo.toml 文件, 而非 workspace 级别的 .cargo/config.toml 的环境变量来配置,
# 可以更灵活和个性化.
#
# 下面的 package.metadata.esp-idf-sys 只能在 root crate 中配置才能生效, 而 root crate 的配置方式有两种:
# 1. 在 workspace 的 Cargo.toml 中配置的 [package] 称为 root crate;
# 2. 否则, 需要在 workspace 的 .cargo/config.toml 中用环境变量配置 ESP_IDF_SYS_ROOT_CRATE 配置 root crate 如 "esp32-camera-binding".
[package.metadata.esp-idf-sys]
esp_idf_tools_install_dir = "workspace"
esp_idf_sdkconfig = "sdkconfig" # 相对于 workspace 目录
esp_idf_sdkconfig_defaults = ["sdkconfig.defaults", "sdkconfig.defaults.esp32s3"] # 相对于 workspace 目录
#esp_idf_components = ["pthread"]
# native builder only
esp_idf_version = "v5.2.1"
[[package.metadata.esp-idf-sys.extra_components]] # 为 esp-idf-sys 添加额外的 C component
component_dirs = [ "esp32-camera" ] # 本地 component 目录列表
bindings_header = "bindings.h" # bindgen 头文件,内容为 component 的头文件
bindings_module = "camera" # bindgen 转换后的 Rust 代码所属的 esp-idf-sys module
zj@a:~/code/esp32/std/esp32-camera-binding$
上面是手动将 component 下载到本地,还可以添加 ESP-IDF component registry 中的 remote component
,这时
esp-idf 会自动下载到本地。
[package.metadata.esp-idf-sys.extra_components.0.remote_component]
# The name of the remote component. Corresponds to a key in the dependencies of
# `idf_component.yml`.
name = "component_name"
# The version of the remote component. Corresponds to the `version` field of the
# `idf_component.yml`.
version = "1.2"
# A git url that contains this remote component. Corresponds to the `git`
# field of the `idf_component.yml`.
#
# This field is optional.
git = "https://github.com/espressif/esp32-camera.git"
# A path to the component.
# Corresponds to the `path` field of the `idf_component.yml`.
#
# Note: This should not be used for local components, use
# `component_dirs` of extra components instead.
#
# This field is optional.
path = "path/to/component"
# A url to a custom component registry. Corresponds to the `service_url`
# field of the `idf_component.yml`.
#
# This field is optional.
service_url = "https://componentregistry.company.com"
remote component 示例:
zj@a:~/code/esp32/std/esp32-camera-binding$ cat Cargo.toml
[package]
name = "esp32-camera-binding"
version = "0.1.0"
authors = ["alizj"]
edition = "2021"
resolver = "2"
rust-version = "1.71"
[profile.release]
opt-level = "s"
[profile.dev]
debug = true # Symbols are nice and they don't increase the size on Flash
opt-level = "z"
[features]
default = ["std", "embassy", "esp-idf-svc/native", "esp-idf-sys/native"]
pio = ["esp-idf-svc/pio"]
std = ["alloc", "esp-idf-svc/binstart", "esp-idf-svc/std"]
alloc = ["esp-idf-svc/alloc"]
nightly = ["esp-idf-svc/nightly"]
experimental = ["esp-idf-svc/experimental"]
embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-svc/embassy-time-driver"]
[dependencies]
log = { version = "0.4", default-features = false }
esp-idf-svc = { version = "0.48" }
esp-idf-sys = { version = "0.34.1"}
esp-idf-hal = { version = "0.43.1"}
embedded-svc = "0.21"
anyhow = "1"
base64 = "0.13.0"
[build-dependencies]
embuild = "0.31.3"
# esp-idf-sys 的构建脚本 embuild 在编译 esp-idf C 时使用的配置参数.
#
# 通过在 crate package 的 Cargo.toml 文件, 而非 workspace 级别的 .cargo/config.toml 的环境变量来配
# 置,可以更灵活和个性化.
#
# 下面的 package.metadata.esp-idf-sys 只能在 root crate 中配置才能生效, 而 root crate 的配置方式有两种:
# 1. 在 workspace 的 Cargo.toml 中配置的 [package] 称为 root crate;
# 2. 否则, 需要在 workspace 的 .cargo/config.toml 中用环境变量配置 ESP_IDF_SYS_ROOT_CRATE 配置 root crate 如 "esp32-camera-binding".
[package.metadata.esp-idf-sys]
esp_idf_tools_install_dir = "workspace"
esp_idf_sdkconfig = "sdkconfig" # 相对于 workspace 目录
esp_idf_sdkconfig_defaults = ["sdkconfig.defaults", "sdkconfig.defaults.esp32s3"] # 相对于 workspace 目录
#esp_idf_components = ["pthread"]
# native builder only
esp_idf_version = "v5.2.1"
# 对于 virtual workspace 类型, 必须在 .cargo/config.toml 中配置 ESP_IDF_SYS_ROOT_CRATE 来指定 root crate package.
#esp_idf_sys_root_crate="esp32-camera-binding"
[[package.metadata.esp-idf-sys.extra_components]]
component_dirs = "esp32-camera" # 本地 component
bindings_header = "bindings.h"
bindings_module = "camera"
[[package.metadata.esp-idf-sys.extra_components]]
remote_component = { name = "espressif/button", version = "3.2.0"} # 远程 ESP-IDF component registry
bindings_header = "bindings.h"
bindings_module = "button"
zj@a:~/code/esp32/std/esp32-camera-binding$
zj@a:~/code/esp32/std/esp32-camera-binding$ cat bindings.h # 两个 component 的头文件
#include "esp_camera.h"
#include "iot_button.h"
cargo build 会自动将 component 下载到本地,然后进行 bindgen 和链接:
zj@a:~/code/esp32/std/esp32-camera-binding$ ls -l ../target/xtensa-esp32s3-espidf/debug/build/esp-idf-sys-7b038a52fa416f70/out
total 5.3M
-rw-r--r-- 1 alizj 1.4K 7 24 2006 CMakeLists.txt
-rw-r--r-- 1 alizj 5.2M 5 24 12:36 bindings.rs # eps-idf-sys 所有 component Rust 接口绑定
drwxr-xr-x 36 alizj 1.2K 5 24 12:36 build/
-rw-r--r-- 1 alizj 2.6K 5 24 12:36 esp-idf-build.json
-rw-r--r-- 1 alizj 147 5 24 12:36 gen-sdkconfig.defaults
drwxr-xr-x 5 alizj 160 5 24 12:35 main/
drwxr-xr-x 4 alizj 128 5 24 12:35 managed_components/ # 自动下载到本地的 remote component 目录
-rw-r--r-- 1 alizj 62K 5 24 12:36 sdkconfig
zj@a:~/code/esp32/std/esp32-camera-binding$ ls -l ../target/xtensa-esp32s3-espidf/debug/build/esp-idf-sys-7b038a52fa416f70/out/managed_components/
total 0
drwxr-xr-x 16 alizj 512 5 24 12:35 espressif__button/ # button
drwxr-xr-x 19 alizj 608 5 24 12:35 espressif__cmake_utilities/
zj@a:~/code/esp32/std/esp32-camera-binding$ ls -l ../target/xtensa-esp32s3-espidf/debug/build/esp-idf-sys-7b038a52fa416f70/out/managed_components/espressif__button/
total 84K
-rw-r--r-- 1 alizj 2.4K 1 9 16:34 CHANGELOG.md
-rw-r--r-- 1 alizj 487 1 9 16:34 CMakeLists.txt
-rw-r--r-- 1 alizj 1.8K 1 9 16:34 Kconfig
-rw-r--r-- 1 alizj 1.7K 1 9 16:34 README.md
-rw-r--r-- 1 alizj 12K 1 9 16:34 button_adc.c
-rw-r--r-- 1 alizj 2.6K 1 9 16:34 button_gpio.c
-rw-r--r-- 1 alizj 2.0K 1 9 16:34 button_matrix.c
drwxr-xr-x 3 alizj 96 1 9 16:34 examples/
-rw-r--r-- 1 alizj 440 1 9 16:34 idf_component.yml
drwxr-xr-x 6 alizj 192 1 9 16:34 include/
-rw-r--r-- 1 alizj 30K 1 9 16:34 iot_button.c
-rw-r--r-- 1 alizj 12K 1 9 16:34 license.txt
drwxr-xr-x 8 alizj 256 1 9 16:34 test_apps/
zj@a:~/code/esp32/std/esp32-camera-binding$ grep -A 3 'pub mod camera' ../target/xtensa-esp32s3-espidf/debug/build/esp-idf-sys-7b038a52fa416f70/out/bindings.rs
pub mod camera {
/* automatically generated by rust-bindgen 0.63.0 */
#[repr(C)]
zj@a:~/code/esp32/std/esp32-camera-binding$ grep -A 3 'pub mod button' ../target/xtensa-esp32s3-espidf/debug/build/esp-idf-sys-7b038a52fa416f70/out/bindings.rs
pub mod button {
/* automatically generated by rust-bindgen 0.63.0 */
#[repr(C)]
2 配置 esp-rs 项目的 esp-idf-sys 参数 #
各 esp-idf-sys 配置参数有环境变量和 metadata 两种方式(环境变量的优先级更高):
- 在 .cargo/config.toml 中配置 env 环境变量、rusts flags;
- 在 Cargo.toml 中为 [package.metadata.esp-idf-sys] section 添加配置参数;
如果项目 crate package 位于 workspace 中,则:
- 上面 .cargo/config.toml 为 workspace 根目录下的目录和配置;
- [package.metadata.esp-idf-sys] 位于项目 crate package 的 Cargo.toml 文件中;
- 如果 workspace 没有 root package,则称为 virtual workspace,这时需要在 workspace 的 .cargo/config.toml 中通过环境变量 ESP_IDF_SYS_ROOT_CRATE 来指定项目 crate package 名称,否则编译 esp-idf-sys 时不会编译和 bindgen 额外的 component。
通过项目 crate package 的 Cargo.toml [package.metadata.esp-idf-sys] 配置:
- 必须在先在 workspace 的 .cargo/config.toml 中配置环境变量 ESP_IDF_SYS_ROOT_CRATE 指向本 crate,下面的配置才生效。
[package.metadata.esp-idf-sys]
esp_idf_tools_install_dir = "global"
esp_idf_sdkconfig = "sdkconfig"
esp_idf_sdkconfig_defaults = ["sdkconfig.defaults", "sdkconfig.defaults.ble"]
# native builder only
esp_idf_version = "branch:release/v4.4"
esp_idf_components = ["pthread"]
通过 cargo/config.toml 中的 env 配置: 环境变量列表
- esp_idf_sdkconfig_defaults, $ESP_IDF_SDKCONFIG_DEFAULTS: 默认为 sdkconfig.defaults;
- esp_idf_sdkconfig, $ESP_IDF_SDKCONFIG: 默认为 sdkconfig
- esp_idf_tools_install_dir, $ESP_IDF_TOOLS_INSTALL_DIR, 可选值为:
- workspace(缺省),默认为 <crate-workspace-dir>/.embuild/espressif;
- out - the tooling will be installed or used inside esp-idf-sys’s build output directory, and
will be deleted when
cargo clean
is invoked; global(建议)
- the tooling will be installed or used in its standard directory (~/.platformio for PlatformIO, and~/.espressif
for the native ESP-IDF toolset);- custom:<dir> - the tooling will be installed or used in the directory specified by <dir>. If this directory is a relative location, it is assumed to be relative to the workspace directory;
- idf_path, $IDF_PATH (native builder only): A path to a user-provided local clone of the esp-idf, that will be used instead of the one downloaded by the build script.
- esp_idf_version, $ESP_IDF_VERSION (native builder only) : The version used for the esp-idf, can be one of the following
- mcu, $MCU: The MCU name (i.e. esp32, esp32s2, esp32s3 esp32c3, esp32c2, esp32h2, esp32c5, esp32c6, esp32p4).
- esp_idf_components, $ESP_IDF_COMPONENTS (native builder only) : Defaults to
all components
being built.- esp-idf 的 component 列表参考 esp-idf components 目录 ;
为了加快 cargo build 速率,避免每次都重新编译构建 esp-idf,建议使用的 .cargo/config.toml
示例配置(在项目执行 cargo build 命令来进行验证):
[build]
target = "xtensa-esp32s3-espidf"
# 使用 sccache 来调用 rustc 编译器, 有利用缓存加快构建速度.
# 先安装 sccache: cargo install sccache --locked
rustc-wrapper = "/opt/homebrew/bin/sccache"
[target.xtensa-esp32s3-espidf]
linker = "ldproxy"
# runner = "espflash --monitor" # Select this runner for espflash v1.x.x
runner = "espflash flash --monitor" # Select this runner for espflash v2.x.x
# 以下是使用 rustc 构建 esp-rs/esp-idf-sys 时传递的参数
# https://github.com/esp-rs/esp-idf-sys/blob/master/BUILD-OPTIONS.md This is a flag for the libc
# crate that uses 64-bits (instead of 32-bits) for time_t. This must be set for ESP-IDF 5.0 and
# above and must be unset for lesser versions.
rustflags = [ "--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110
# https://github.com/esp-rs/esp-idf-sys/blob/master/BUILD-OPTIONS.md
# 等效于: -Zbuild-std=std,panic_abort
# Required for std support. Rust does not provide std libraries for ESP32 targets since they are tier-2/-3.
[unstable]
build-std = ["std", "panic_abort"]
# cargo 调用命令时使用的环境变量
# 参考:https://github.com/esp-rs/esp-idf-sys/blob/master/BUILD-OPTIONS.md#esp-idf-configuration
[env]
MCU="esp32s3"
# Note: this variable is not used by the pio builder (`cargo build --features pio`)
# 最新 esp-idf 版本:https://github.com/espressif/esp-idf/releases
ESP_IDF_VERSION = "v5.2.q"
# 使用全局 ~/.espressif/ 工具链,默认是 by 项目 workspace 的。
ESP_IDF_TOOLS_INSTALL_DIR = "global"
sccache 统计信息:
zj@a:~/code/esp32/myespv3$ sccache --show-stats
Compile requests 0
Compile requests executed 0
Cache hits 0
Cache misses 0
Cache timeouts 0
Cache read errors 0
Forced recaches 0
Cache write errors 0
Compilation failures 0
Cache errors 0
Non-cacheable compilations 0
Non-cacheable calls 0
Non-compilation calls 0
Unsupported compiler calls 0
Average cache write 0.000 s
Average compiler 0.000 s
Average cache read hit 0.000 s
Failed distributed compilations 0
Cache location Local disk: "/Users/alizj/Library/Caches/Mozilla.sccache"
Use direct/preprocessor mode? yes
Version (client) 0.7.7
Max cache size 10 GiB
参考:
- esp-rs/std-trainning: Embedded Rust Trainings for Espressif
- https://github.com/ivmarkov/rust-esp32-std-demo%EF%BC%9A Rust on ESP32 STD demo app
- https://apollolabsblog.hashnode.dev/series/esp32-std-embedded-rust%EF%BC%9A 强烈推荐。
- https://github.com/apollolabsdev/ESP32C3