跳过正文

开发 Rust std 应用

·5204 字
Rust Esp32 Rust Esp32
目录
rust-esp32 - 这篇文章属于一个选集。
§ 5: 本文

对于 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- 开头):

  1. esp-idf-sys crate:esp-idf 的 unsafe binding,Gives raw (unsafe) access to drivers, Wi-Fi and more.
  2. esp-idf-svc crate:esp-idf 的 safe binding,抽象层次更高,实现了 embedded-svc trait。
  3. 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();
}

构建项目:

  1. 每次构建前都需要先 source ~/esp/export-esp.sh 脚本。# 不能启用 python env,不能使用 socks 代理,需要设置环境变量;
  2. 构建过程中默认下载和安装 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

构建失败的解决办法:

  1. cargo clen 清理 target 目录;
  2. 手动清理 .embuild 目录;
  3. 不能启用 socks 代理;
  4. 不能开启 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

参考:

  1. https://docs.esp-rs.org/std-training/01_intro.html
  2. https://github.com/esp-rs/std-training
  3. 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 环境变量:

添加 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 两种方式(环境变量的优先级更高):

  1. 在 .cargo/config.toml 中配置 env 环境变量、rusts flags;
  2. 在 Cargo.toml 中为 [package.metadata.esp-idf-sys] section 添加配置参数;

如果项目 crate package 位于 workspace 中,则:

  1. 上面 .cargo/config.toml 为 workspace 根目录下的目录和配置;
  2. [package.metadata.esp-idf-sys] 位于项目 crate package 的 Cargo.toml 文件中;
  3. 如果 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 配置: 环境变量列表

  1. esp_idf_sdkconfig_defaults, $ESP_IDF_SDKCONFIG_DEFAULTS: 默认为 sdkconfig.defaults;
  2. esp_idf_sdkconfig, $ESP_IDF_SDKCONFIG: 默认为 sdkconfig
  3. 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;
  4. 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.
  5. esp_idf_version, $ESP_IDF_VERSION (native builder only) : The version used for the esp-idf, can be one of the following
  6. mcu, $MCU: The MCU name (i.e. esp32, esp32s2, esp32s3 esp32c3, esp32c2, esp32h2, esp32c5, esp32c6, esp32p4).
  7. esp_idf_components, $ESP_IDF_COMPONENTS (native builder only) : Defaults to all components being built.

为了加快 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

参考:

  1. esp-rs/std-trainning: Embedded Rust Trainings for Espressif
  2. https://github.com/ivmarkov/rust-esp32-std-demo%EF%BC%9A Rust on ESP32 STD demo app
  3. https://apollolabsblog.hashnode.dev/series/esp32-std-embedded-rust%EF%BC%9A 强烈推荐。
  4. https://github.com/apollolabsdev/ESP32C3
rust-esp32 - 这篇文章属于一个选集。
§ 5: 本文

相关文章

Rust 驱动 Audio - 播放和录音
·6323 字
Rust Esp32 Rust Esp32
Rust 驱动 Camera - 采集和播放
·7304 字
Rust Esp32 Rust Esp32
Rust 驱动 LCD - 显示中英文
·818 字
Rust Esp32 Rust Esp32
Rust 驱动 LCD - 显示图片
·4232 字
Rust Esp32 Rust Esp32