Rust bindings for ESP-IDF

(Espressif's IoT Development Framework)

CI crates.io Documentation Matrix

The ESP-IDF API in Rust, with support for each ESP chip (ESP32, ESP32S2, ESP32S3, ESP32C3, etc.) based on the Rust target.

For more information, check out: * The Rust on ESP Book * The esp-idf-template project * The esp-idf-svc project * The esp-idf-hal project * The Rust for Xtensa toolchain * The Rust-with-STD demo project

Table of contents - Build - Features - sdkconfig - Build configuration - Extra esp-idf components - Conditional compilation - ESP32-C6 Preliminary Support - More info

Build

Features

sdkconfig

The esp-idf makes use of an sdkconfig file for its compile-time component configuration (see the esp-idf docs for more information). This config is separate from the build configuration.

(native builder only) Using cargo-idf to interactively modify ESP-IDF's sdkconfig file

TBD: Upcoming

(pio builder only) Using cargo-pio to interactively modify ESP-IDF's sdkconfig file

To enable Bluetooth, or do other configurations to the ESP-IDF sdkconfig you might take advantage of the cargo-pio Cargo subcommand: * To install it, issue cargo install cargo-pio --git https://github.com/ivmarkov/cargo-pio * To open the ESP-IDF interactive menuconfig system, issue cargo pio espidf menuconfig in the root of your binary crate project * To use the generated/updated sdkconfig file, follow the steps described in the "Bluetooth Support" section

Build configuration

There are two ways to configure how the ESP-IDF framework is compiled: 1. Environment variables, denoted by $VARIABLE;

The environment variables can be passed on the command line, or put into the [env] section of a .cargo/config.toml file (see cargo reference).

  1. The [package.metadata.esp-idf-sys] section of the Cargo.toml, denoted by field.

    Note
    Configuration can only come from the root crate's Cargo.toml. The root crate is the package in the workspace directory. If there is not root crate in case of a virtual workspace, its name can be specified with the ESP_IDF_SYS_ROOT_CRATE environment variable.

    ⚠️ Environment variables always take precedence over Cargo.toml metadata.

Note: workspace directory
The workspace directory mentioned here is always the directory containing the Cargo.lock file and the target directory (where the build artifacts are stored). It can be overridden with the CARGO_WORKSPACE_DIR environment variable, should this not be the right directory.
(See embuild::cargo::workspace_dir for more information).

There is no need to explicitly add a [workspace] section to the Cargo.toml of the workspace directory.

The following configuration options are available:

Example

An example of the `[package.metadata.esp-idf-sys]` section of the `Cargo.toml`. ```toml [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"] ```

Extra esp-idf components

It is possible to let *esp-idf-sys* compile extra [esp-idf components](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html#concepts) and generate bindings for them. This is possible by adding an object to the `package.metadata.esp-idf-sys.extra_components` array of the `Cargo.toml`. *esp-idf-sys* will honor all such extra components in the *root crate*'s and all **direct** dependencies' `Cargo.toml`.

Note
By only specifying the bindings_header field, one can extend the set of esp-idf bindings that were generated from src/include/esp-idf/bindings.h. To do this you need to create a *.h header file in your project source, and reference that in the bindings_header variable. You can then include extra esp-idf header files from there.

An extra component can be specified like this:

```toml [[package.metadata.esp-idf-sys.extra_components]]

A single path or a list of paths to a component directory or directory

containing components.

Each path can be absolute or relative. Relative paths will be relative to the

folder containing the defining Cargo.toml.

This field is optional. No component will be built if this field is absent, though

the bindings of the [Self::bindings_header] will still be generated.

component_dirs = ["dir1", "dir2"] # or "dir"

The path to the C header to generate the bindings with. If this option is absent,

no bindings will be generated.

#

The path can be absolute or relative. A relative path will be relative to the

folder containing the defining Cargo.toml.

#

This field is optional.

bindings_header = "bindings.h"

If this field is present, the component bindings will be generated separately from

the esp-idf bindings and put into their own module inside the esp-idf-sys crate.

Otherwise, if absent, the component bindings will be added to the existing

esp-idf bindings (which are available in the crate root).

#

To put the bindings into its own module, a separate bindgen instance will generate

the bindings. Note that this will result in duplicate esp-idf bindings if the

same esp-idf headers that were already processed for the esp-idf bindings are

included by the component(s).

#

This field is optional.

bindings_module = "name" ```

and is equivalent to toml [package.metadata.esp-idf-sys] extra_components = [ { component_dirs = [ "dir1", "dir2" ], bindings_header = "bindings.h", bindings_module = "name" } ]

Conditional compilation

The esp-idf-sys build script will set rustc cfgs available for its sources.

⚠️ If an upstream crate also wants to have access to the cfgs it must: - have esp-idf-sys as a dependency, and - propagate the cfgs in its build script with

rust embuild::build::CfgArgs::output_propagated("ESP_IDF").expect("no esp-idf-sys cfgs"); using the embuild crate.

The list of available cfgs: - esp_idf_comp_{component}_enabled for each component - esp_idf_version="{major}.{minor}" - esp_idf_version_full="{major}.{minor}.{patch}" - esp_idf_version_major="{major}" - esp_idf_version_minor="{minor}" - esp_idf_version_patch="{patch}" - esp_idf_{sdkconfig_option}

Each sdkconfig setting where {sdkconfig_option} corresponds to the option set in the sdkconfig lowercased and without the CONFIG_ prefix. Only options set to y will get a cfg.

More info

If you are interested in how it all works under the hood, check the build.rs build script of this crate.

ESP32-C6 Preliminary Support

ESP32-C6, as well as the upcoming ESP32-P4 are the first Espressif chips that support the "A" (atomic) extension of the RISCV specification. As such, the do not work with the existing riscv32imc-esp-espidf target, and instead need a new one (to be upstreamed to Rust) called riscv32imac-esp-espidf.

Until the target is upstreamed, you can use the following custom target:

json { "arch": "riscv32", "cpu": "generic-rv32", "data-layout": "e-m:e-p:32:32-i64:64-n32-S128", "eh-frame-header": false, "emit-debug-gdb-scripts": false, "env": "newlib", "features": "+m,+a,+c", "is-builtin": false, "linker": "riscv32-esp-elf-gcc", "llvm-target": "riscv32", "max-atomic-width": 64, "os": "espidf", "panic-strategy": "abort", "relocation-model": "static", "target-family": [ "unix" ], "target-pointer-width": "32", "vendor": "espressif" }

Save the target somewhere (perhaps in the root of your binary crate) under the name riscv32imac-esp-espidf.json and when building with cargo, pass it with --target riscv32imac-esp-espidf.json.