diff --git a/.github/workflows/ci_cargo.yml b/.github/workflows/ci_cargo.yml index 5617d6b..59c75b1 100644 --- a/.github/workflows/ci_cargo.yml +++ b/.github/workflows/ci_cargo.yml @@ -2,6 +2,8 @@ name: Cargo CI on: push: + branches: + - master paths: - "cargo/**" - ".github/workflows/ci_cargo.yml" @@ -18,95 +20,20 @@ env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: - cargo-checks-v5: - name: "${{ matrix.target }} | ${{ matrix.esp-idf.version }} | std(hal):${{ matrix.std-config.std }}(${{ matrix.std-config.hal }})" + checks: + name: "${{ matrix.target }} | ${{ matrix.esp-idf.version }} | std=${{ matrix.std-config.std }}" runs-on: ubuntu-latest strategy: fail-fast: false matrix: - target: ["esp32", "esp32c3", esp32c2, esp32c6, esp32h2, "esp32s2", "esp32s3"] + target: ["esp32", "esp32c3", "esp32c2", "esp32c6", "esp32h2", "esp32s2", "esp32s3"] std-config: - - std: false - - std: true - hal: "No" - std: true - hal: "Yes (default features)" - - std: true - hal: "Yes (all features)" - esp-idf: - - version: v5.1 - # - version: master - # name: master - steps: - - name: Setup | Rust (RISC-V) - if: matrix.target != 'esp32' && matrix.target != 'esp32s2' && matrix.target != 'esp32s3' - uses: dtolnay/rust-toolchain@v1 - with: - toolchain: nightly - components: clippy, rustfmt, rust-src - - name: Setup | Rust (Xtensa) - if: matrix.target == 'esp32' || matrix.target == 'esp32s2' || matrix.target == 'esp32s3' - uses: esp-rs/xtensa-toolchain@v1.5.1 - with: - default: true - buildtargets: ${{ matrix.target }} - ldproxy: false - version: "1.69.0" - - uses: Swatinem/rust-cache@v2 - - name: Setup | cargo-generate (binary) - id: cargo-generate-binary - continue-on-error: true - run: | - sudo curl -L "https://github.com/cargo-generate/cargo-generate/releases/latest/download/cargo-generate-$(git ls-remote --refs --sort="version:refname" --tags "https://github.com/cargo-generate/cargo-generate" | cut -d/ -f3- | tail -n1)-x86_64-unknown-linux-gnu.tar.gz" -o "/home/runner/.cargo/bin/cargo-generate.tar.gz" - tar xf "/home/runner/.cargo/bin/cargo-generate.tar.gz" -C /home/runner/.cargo/bin - chmod u+x /home/runner/.cargo/bin/cargo-generate - - name: Setup | cargo-generate (cargo) - if: steps.cargo-generate-binary.outcome != 'success' - run: cargo install cargo-generate - - name: Setup | ldproxy (binary) - id: ldproxy-binary - continue-on-error: true - run: | - sudo curl -L "https://github.com/esp-rs/embuild/releases/latest/download/ldproxy-x86_64-unknown-linux-gnu.zip" -o "/home/runner/.cargo/bin/ldproxy.zip" - unzip "/home/runner/.cargo/bin/ldproxy.zip" -d "/home/runner/.cargo/bin/" - chmod u+x /home/runner/.cargo/bin/ldproxy - - name: Setup | ldproxy (cargo) - if: steps.ldproxy-binary.outcome != 'success' - run: cargo install ldproxy - - uses: actions/checkout@v4 - with: - path: /home/runner/work/esp-idf-template/esp-idf-template/github-esp-idf-template - - name: Generate (STD) - if: matrix.std-config.std == true - run: cargo generate --path /home/runner/work/esp-idf-template/esp-idf-template/github-esp-idf-template cargo --name test --vcs none --silent -d mcu=${{ matrix.target }} -d advanced=true -d espidfver=${{ matrix.esp-idf.version }} -d std=${{ matrix.std-config.std }} -d hal="${{ matrix.std-config.hal }}" -d devcontainer=false -d wokwi=false -d ci=false - - name: Generate (No STD) - if: matrix.std-config.std == false - run: cargo generate --path /home/runner/work/esp-idf-template/esp-idf-template/github-esp-idf-template cargo --name test --vcs none --silent -d mcu=${{ matrix.target }} -d advanced=true -d espidfver=${{ matrix.esp-idf.version }} -d std=${{ matrix.std-config.std }} -d devcontainer=false -d wokwi=false -d ci=false - - name: Build | Fmt Check (RISC-V) - if: matrix.target != 'esp32' && matrix.target != 'esp32s2' && matrix.target != 'esp32s3' - run: cd test; cargo fmt -- --check - - name: Build | Clippy (RISC-V) - if: matrix.target != 'esp32' && matrix.target != 'esp32s2' && matrix.target != 'esp32s3' - run: cd test; cargo clippy --no-deps -- -Dwarnings - - name: Build | Compile - run: cd test; cargo build - cargo-checks-v4: - name: "${{ matrix.target }} | ${{ matrix.esp-idf.version }} | std(hal):${{ matrix.std-config.std }}(${{ matrix.std-config.hal }})" - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - target: ["esp32", "esp32c3", "esp32s2", "esp32s3"] - std-config: - std: false - - std: true - hal: "No" - - std: true - hal: "Yes (default features)" - - std: true - hal: "Yes (all features)" esp-idf: + - version: v5.1 - version: v4.4 + # - version: master steps: - name: Setup | Rust (RISC-V) if: matrix.target != 'esp32' && matrix.target != 'esp32s2' && matrix.target != 'esp32s3' @@ -121,6 +48,7 @@ jobs: default: true buildtargets: ${{ matrix.target }} ldproxy: false + version: "1.71.0" - uses: Swatinem/rust-cache@v2 - name: Setup | cargo-generate (binary) id: cargo-generate-binary @@ -145,21 +73,19 @@ jobs: - uses: actions/checkout@v4 with: path: /home/runner/work/esp-idf-template/esp-idf-template/github-esp-idf-template - - name: Generate (STD) - if: matrix.std-config.std == true - run: cargo generate --path /home/runner/work/esp-idf-template/esp-idf-template/github-esp-idf-template cargo --name test --vcs none --silent -d mcu=${{ matrix.target }} -d advanced=true -d espidfver=${{ matrix.esp-idf.version }} -d std=${{ matrix.std-config.std }} -d hal="${{ matrix.std-config.hal }}" -d devcontainer=false -d wokwi=false -d ci=false - - name: Generate (No STD) - if: matrix.std-config.std == false + - name: Generate + if: matrix.esp-idf.version != 'v4.4' || matrix.target == 'esp32' || matrix.target == 'esp32s2' || matrix.target == 'esp32s3' || matrix.target == 'esp32c3' run: cargo generate --path /home/runner/work/esp-idf-template/esp-idf-template/github-esp-idf-template cargo --name test --vcs none --silent -d mcu=${{ matrix.target }} -d advanced=true -d espidfver=${{ matrix.esp-idf.version }} -d std=${{ matrix.std-config.std }} -d devcontainer=false -d wokwi=false -d ci=false - - name: Build | Fmt Check (RISC-V) - if: matrix.target != 'esp32' && matrix.target != 'esp32s2' && matrix.target != 'esp32s3' + - name: Build | Fmt Check + if: matrix.esp-idf.version == 'v4.4' && matrix.target == 'esp32c3' run: cd test; cargo fmt -- --check - - name: Build | Clippy (RISC-V) - if: matrix.target != 'esp32' && matrix.target != 'esp32s2' && matrix.target != 'esp32s3' + - name: Build | Clippy + if: matrix.esp-idf.version != 'v4.4' || matrix.target == 'esp32' || matrix.target == 'esp32s2' || matrix.target == 'esp32s3' || matrix.target == 'esp32c3' run: cd test; cargo clippy --no-deps -- -Dwarnings - name: Build | Compile + if: matrix.esp-idf.version != 'v4.4' || matrix.target == 'esp32' || matrix.target == 'esp32s2' || matrix.target == 'esp32s3' || matrix.target == 'esp32c3' run: cd test; cargo build - container-check: + container-checks: name: "Container Check: ${{ matrix.target }}" runs-on: ubuntu-latest strategy: @@ -183,8 +109,8 @@ jobs: - uses: actions/checkout@v4 with: path: /home/runner/work/esp-idf-template/esp-idf-template/github-esp-idf-template - - name: Generate Project - run: cargo generate --path /home/runner/work/esp-idf-template/esp-idf-template/github-esp-idf-template cargo --name test-${{ matrix.target }} --vcs none --silent -d mcu=${{ matrix.target }} -d espidfver=v4.4 -d advanced=true -d std=true -d hal="Yes (default features)" -d devcontainer=true -d wokwi=false -d ci=false + - name: Generate + run: cargo generate --path /home/runner/work/esp-idf-template/esp-idf-template/github-esp-idf-template cargo --name test-${{ matrix.target }} --vcs none --silent -d mcu=${{ matrix.target }} -d advanced=true -d espidfver=v5.1 -d std=true -d devcontainer=true -d wokwi=false -d ci=false - name: Update ownership run: | sudo chown 1000:1000 -R test-${{ matrix.target }} diff --git a/.github/workflows/ci_cmake.yml b/.github/workflows/ci_cmake.yml index f8b1869..29e005f 100644 --- a/.github/workflows/ci_cmake.yml +++ b/.github/workflows/ci_cmake.yml @@ -2,6 +2,8 @@ name: CMake CI on: push: + branches: + - master paths: - "cmake/**" - ".github/workflows/ci_cmake.yml" @@ -19,47 +21,52 @@ env: jobs: cmake-checks: - name: "${{ matrix.target.board }} | ${{ matrix.esp-idf.version }} | std(hal):${{ matrix.std-config.std }}(${{ matrix.std-config.hal }}))" + name: "${{ matrix.target.mcu }} | ${{ matrix.esp-idf.version }} | hal=${{ matrix.std-config.hal }} | std=${{ matrix.std-config.std }}" runs-on: ubuntu-latest strategy: fail-fast: false matrix: target: - - board: "esp32" + - mcu: "esp32" toolchain: "esp" - - board: "esp32s2" + - mcu: "esp32s2" toolchain: "esp" - - board: "esp32s3" + - mcu: "esp32s3" toolchain: "esp" - - board: "esp32c3" + - mcu: "esp32c3" + toolchain: "nightly" + - mcu: "esp32c2" + toolchain: "nightly" + - mcu: "esp32c6" + toolchain: "nightly" + - mcu: "esp32h2" toolchain: "nightly" std-config: - - std: false - - std: true - hal: "No" - - std: true - hal: "Yes (default features)" - - std: true - hal: "Yes (all features)" + - hal: true + std: true + - hal: true + std: false + - hal: false + std: false esp-idf: - version: v5.1 # - version: master # name: master steps: - name: Setup | Rust (RISC-V) - if: matrix.target.board != 'esp32' && matrix.target.board != 'esp32s2' && matrix.target.board != 'esp32s3' + if: matrix.target.mcu != 'esp32' && matrix.target.mcu != 'esp32s2' && matrix.target.mcu != 'esp32s3' uses: dtolnay/rust-toolchain@v1 with: toolchain: nightly components: clippy, rustfmt, rust-src - name: Setup | Rust (Xtensa) - if: matrix.target.board == 'esp32' || matrix.target.board == 'esp32s2' || matrix.target.board == 'esp32s3' + if: matrix.target.mcu == 'esp32' || matrix.target.mcu == 'esp32s2' || matrix.target.mcu == 'esp32s3' uses: esp-rs/xtensa-toolchain@v1.5.1 with: default: true - buildtargets: ${{ matrix.target.board }} + buildtargets: ${{ matrix.target.mcu }} ldproxy: false - version: "1.69.0" + version: "1.71.0" - uses: Swatinem/rust-cache@v2 - name: Setup | cargo-generate (binary) id: cargo-generate-binary @@ -84,18 +91,13 @@ jobs: - uses: actions/checkout@v4 with: path: /home/runner/work/esp-idf-template/esp-idf-template/github-esp-idf-template - - name: Generate (STD) - if: matrix.std-config.std == true - run: cargo generate --path /home/runner/work/esp-idf-template/esp-idf-template/github-esp-idf-template cmake --name test --vcs none --silent -d toolchain=${{ matrix.target.toolchain }} -d std=${{ matrix.std-config.std }} -d espidfver=${{ matrix.esp-idf.version }} -d hal="${{ matrix.std-config.hal }}" - - name: Generate (No STD) - if: matrix.std-config.std == false - run: cargo generate --path /home/runner/work/esp-idf-template/esp-idf-template/github-esp-idf-template cmake --name test --vcs none --silent -d toolchain=${{ matrix.target.toolchain }} -d std=${{ matrix.std-config.std }} -d espidfver=${{ matrix.esp-idf.version }} + - name: Generate + run: cargo generate --path /home/runner/work/esp-idf-template/esp-idf-template/github-esp-idf-template cmake --name test --vcs none --silent -d toolchain=${{ matrix.target.toolchain }} -d advanced=true -d hal=${{ matrix.std-config.hal }} -d std=${{ matrix.std-config.std }} -d espidfver=${{ matrix.esp-idf.version }} - name: ESP-IDF | Checkout run: git clone https://github.com/espressif/esp-idf; git -C esp-idf checkout ${{ matrix.esp-idf.version }} - name: ESP-IDF | Install Tooling run: esp-idf/install.sh - name: Build | Set Target - run: . esp-idf/export.sh; cd test; idf.py set-target ${{ matrix.target.board }} + run: . esp-idf/export.sh; cd test; idf.py set-target ${{ matrix.target.mcu }} - name: Build | Compile run: . esp-idf/export.sh; cd test; idf.py build - diff --git a/README-cmake-details.md b/README-cmake-details.md new file mode 100644 index 0000000..f488002 --- /dev/null +++ b/README-cmake-details.md @@ -0,0 +1,224 @@ +# Integrating a Rust Component into an ESP-IDF Project + +ESP-IDF, the official development framework for the ESP32 Series SoCs, supports integration of components written in C/C++ and Rust which is gaining traction for embedded development due to its safety features. This article outlines the project layout generated by using the `esp-idf-template` utility and its support for using Rust in CMake-based ESP-IDF projects. + +Note that this article assumes prior knolwedge in the ESP-IDF build system (CMake). + +## Prerequisites + +Follow all the steps in the [CMake tutorial](../README-cmake.md). With the steps outlined there, generate a project named `test`. I.e. + +```sh +cargo generate --vcs none --git https://github.com/esp-rs/esp-idf-template cmake --name test +``` + +## Generated Project Structure + +Here's how your project directory might look after following the guide: + +``` +test/ +|-- CMakeLists.txt +|-- main/ +| |-- CMakeLists.txt +| |-- main.c +|-- sdkconfig +|-- components/ +| |-- rust-test/ +| |-- CMakeLists.txt +| |-- placeholder.c +| |-- build.rs +| |-- Cargo.toml +| |-- rust-toolchain.toml +| |-- src/ +| |-- lib.rs +``` + +### Key Elements + +- `test` contains the main C code like any other ESP-IDF application. +- An ESP-IDF component, with name `rust-test` is stored in a subdirectory named `components`. +- The Rust code is stored directly as part of the `rust-test` ESP-IDF component, i.e. the `rust-test` ESP-IDF component is simultaneously an ESP-IDF component as well as a Rust library crate. + +The component can be uploaded later to [Component Manager](https://components.espressif.com/). + +### How the Build Process Works + +The CMake build for your `rust-test` component would proceed as usual and will compile all C code in the component (which is not much - just an empty `placeholder.c` file which needs to be there for the approach to work). + +More importantly, `components/rust-test/CMakeLists.txt` is setup by the generator in such a way, so as to **also** call the Rust standard build utility - [`cargo`](https://doc.rust-lang.org/cargo/index.html) - which would take care of compiling the Rust source code in your ESP-IDF component. + +The Rust source code (or as it is named in the Rust world - the Rust *crate*) is setup - in `components/rust-test/Cargo.toml` - to have the shape of a *static* library - just like any other ESP-IDF component. When the CMake build calls `cargo` to compile your `rust-test` Rust crate, you'll essentially end up with a static library named `librust_test.a`, which is then linked to the rest of the ESP-IDF project by CMake. + +## Project Structure Details + +We would be skipping over the project root (the `test/` top-level directory), as it is pretty standard and there is nothing Rust-specific in it. All the interesting stuff happens in the `components/rust-test` sub-folder. Before going into the details of each file, let's do the following separation: + +``` +|-- components/ +| |-- rust-test/ +| |-- CMakeLists.txt +| |-- placeholder.c +| ^^^ These two files are standard ESP-IDF CMake component build files, with CMakeLists.txt being slightly more complicated than usual. +| | +| |-- build.rs +| |-- Cargo.toml +| |-- rust-toolchain.toml +| |-- src/ +| |-- lib.rs +| ^^^ All these other files are what typically constitutes the layout of a Rust library crate and they are NOT specifc to ESP-IDF. +| You can google any Rust source on the internet as to their purpose and meaning. +``` + +### ESP-IDF Component Portion + +#### `components/rust-test/CMakeLists.txt`: + +This file builds the `/components/rust-test` ESP-IDF component. It is however more complex than usual, as it needs to setup quite a few things so that calling into Rust `cargo` is running smoothly. The content of this file is the main boilerplate that the `esp-idf-template` automates. + +You can of course modify this file post-generation. The various variables and overall behavior of the file is documented with inline comments. + +#### `components/rust-test/placeholder.c`: + +As mentioned previously, this is an empty C file which is only there so as the CMake->Cargo integration magic to work. + +### Rust Library Crate Portion + +#### `components/rust-test/build.rs`: + +This file is a standard Rust [build scriptlet](https://doc.rust-lang.org/cargo/reference/build-scripts.html). It would be empty if you have not opted into the "HAL" option (note that by default "HAL" is enabled). + +If you have selected the "HAL" option, the `build.rs` file will contain a call into the `embuild` library that nakes sure that your component can properly link against - and in general - can "talk" to the [Safe Rust bindings for ESP-IDF](https://github.com/esp-rs/esp-idf-svc) (the "HAL"). + +#### `components/rust-test/Cargo.toml`: + +This is a pretty standard and mostly empty `cargo` [build manifest file](https://doc.rust-lang.org/cargo/reference/manifest.html), which sets up your Rust crate to be built as a Rust static libray. + +If you have selected the "HAL" option when generating the project, the manifest would also list: +* a dependency to `esp-idf-svc` (i.e. [the safe Rust wrappers for ESP-IDF](https://github.com/esp-rs/esp-idf-svc)); +* a built-time dependency to [`embuild`](https://github.com/esp-rs/embuild) which helps the build integration between ESP-IDF and Rust to run smoothly); +* the popular Rust [`log`](https://github.com/rust-lang/log) crate. + +Post-generation, you can add/remove additional dependencies on Rust crates here as well as modify how Rust compiles your project (as in optimization level and others). + +#### `components/rust-test/rust-toolchain.toml`: + +This file instructs `cargo` [what Rust compiler toolchain to use](https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file) when building your component. It would list either the `esp` toolchain (a Rust toolchain provided by Espressif that has extra support for the esp32 xtensa MCUs), or the standard Rust `nightly` toolchain. The latter can only be used with Espressif RiscV MCUs. + +#### `components/rust-test/src/lib.rs`: + +The actual source code of your Rust library. You can extend here at will. + +## Troubleshooting + +- If you encounter linker errors, you may need to update your Rust flags. For example, you might need to add the `-Zbuild-std-features=compiler-builtins-weak-intrinsics` flag to `CARGO_BUILD_FLAGS` in your `CMakeLists.txt`. + +## Simulation + +### Simulation with Wokwi in VS Code + +[Wokwi for Visual Studio Code](https://docs.wokwi.com/vscode/getting-started) provides a simulation solution for embedded and IoT system engineers. The extension integrates with your existing development environment, allowing you to simulate your projects directly from your code editor. + +- [Install VS Code](https://code.visualstudio.com/download) +- [Install the Wokwi plugin](https://docs.wokwi.com/vscode/getting-started#installation) +- Activate Wokwi plugin - command palette, search for `Wokwi: Start Simulator`, select and activate the plugin using web browser + +### Add files for Wokwi simulator + +Create [`wokwi.toml`](./wokwi.toml) in the root of the project. The file contains references to BIN and ELF previously built by `idf.py`. + +```toml +[wokwi] +version = 1 +elf = "build/test.elf" +firmware = "build/test.bin" +``` + +Create [`diagram.json`](./diagram.json). The file contains board selected for the simulation. [Here](https://github.com/wokwi/wokwi-boards/tree/main/boards)'s the list of available boards. + +```json +{ + "version": 1, + "author": "Espressif Systems", + "editor": "wokwi", + "parts": [ { "type": "wokwi-esp32-devkit-v1", "id": "esp", "top": 0, "left": 0, "attrs": {} } ], + "connections": [ [ "esp:TX0", "$serialMonitor:RX", "", [] ], [ "esp:RX0", "$serialMonitor:TX", "", [] ] ], + "dependencies": {} +} +``` + +Open VS Code, open command palette (CMD/Ctrl+Shift+P), search for `Wokwi: Start Simulator`, select the option to start simulation. + +Use Pause button to display state of pins. + +The plugin auto-reload application if the binary was updated. + +## Adding GitHub Action Tests + +If we want to add CI/CD to our project, we can leverage the [Wokwi CI](https://docs.wokwi.com/wokwi-ci/getting-started). +For example, the following YAML file will build and check that the generated project runs properly: + +```yaml +name: CI +on: + push: + pull_request: + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ESP_TARGET: esp32 + ESP_IDF_VERSION: v5.1 + + +jobs: + build-check: + name: Checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup | Rust (RISC-V) + if: env.ESP_TARGET != 'esp32' && env.ESP_TARGET != 'esp32s2' && env.ESP_TARGET != 'esp32s3' + uses: dtolnay/rust-toolchain@v1 + with: + target: riscv32imc-unknown-none-elf + toolchain: nightly + components: rust-src + - name: Setup | Rust (Xtensa) + if: env.ESP_TARGET == 'esp32' && env.ESP_TARGET == 'esp32s2' && env.ESP_TARGET == 'esp32s3' + uses: esp-rs/xtensa-toolchain@v1.5 + with: + default: true + buildtargets: {{ env.ESP_TARGET }} + ldproxy: false + - uses: Swatinem/rust-cache@v2 + - name: Setup | ESP-IDF + shell: bash + run: | + git clone -b {{ env.ESP_IDF_VERSION }} --shallow-submodules --single-branch --recursive https://github.com/espressif/esp-idf.git /home/runner/work/esp-idf + /home/runner/work/esp-idf/install.sh {{ env.ESP_TARGET }} + - name: Build project + shell: bash + run: | + . /home/runner/work/esp-idf/export.sh + idf.py set-target {{ env.ESP_TARGET }} + idf.py build + - name: Wokwi CI check + uses: wokwi/wokwi-ci-action@v1 + with: + token: ${{ secrets.WOKWI_CLI_TOKEN }} + timeout: 10000 + expect_text: 'Hello ESP-RS. https://github.com/esp-rs' + fail_text: 'Error' +``` + +Its important to note that we need to set the `WOKWI_CLI_TOKEN` secret: +* [Create a Wokwi CI token](https://wokwi.com/dashboard/ci); +* [Add it as a secret in your GitHub repository](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions). + +Also, the CI file needs to be modified if used for other targets. + +## Related Material + +[This repository](https://github.com/georgik/esp32-idf-no-std-rust-component) has a step-by-step guide, where you can create all project artefacts manually, without using `cargo generate`. It does emphasise specifically the creation of a Rust `no_std` and "no dependencies" ESP-IDF component, which is supported by the `esp-idf-template` generator as well, by selecting "advanced" options and then opting out of the "HAL" feature. diff --git a/README-cmake.md b/README-cmake.md index 069f015..f2bb04f 100644 --- a/README-cmake.md +++ b/README-cmake.md @@ -2,6 +2,8 @@ A "Hello, world!" template of a mixed Rust/C ESP-IDF project driven by `idf.py` and CMake. +If you would like to to understand how it works under the hood, read [this article](README-cmake-details.md). + ## Generate the project **Please make sure you have installed all [prerequisites](#prerequisites) first!** @@ -12,8 +14,9 @@ cargo generate --vcs none --git https://github.com/esp-rs/esp-idf-template cmake The command will display a few prompts: - `Project Name`: Name of the crate. - - `Rust toolchain`: Selects the `channel` in the [toolchain file](https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file) to use. Select `nightly` for ESP32-C3 and `esp` for the other targets. - - `STD support`: When `true`, adds support for [Rust Standard Library](https://doc.rust-lang.org/std/). Otherwise, we will use [Rust Core Library](https://doc.rust-lang.org/core/index.html). + - `Rust toolchain`: Selects the `channel` in the [toolchain file](https://rust-lang.github.io/rustup/overrides.html#the-toolchain-file) to use. Select `esp` (the default). You can also select `nightly` if you are building for a RiscV ESP32 MCU + - (Advanced setup only) `HAL support`: When `true`, adds support for [calling into ESP-IDF and its components](https://github.com/esp-rs/esp-idf-svc) + - (Advanced setup only) `STD support`: When `true`, adds support for the [Rust Standard Library](https://doc.rust-lang.org/std/). Otherwise, we will use [Rust Core Library](https://doc.rust-lang.org/core/index.html). ## Enable ESP IDF components that you would like to use from Rust @@ -23,7 +26,7 @@ Contrary to the [cargo-first](https://github.com/esp-rs/esp-idf-template/blob/ma ```sh cd -idf.py set-target [esp32|esp32s2|esp32s3|esp32c3] +idf.py set-target [esp32|esp32s2|esp32s3|esp32c2|esp32c3|esp32c6|esp32h2] idf.py build ``` @@ -129,12 +132,12 @@ You need a Python 3.7 or later installed on your machine. Install it from the pa ### Install ESP-IDF SDK & Tooling -When using `idf.py` and CMake driven ESP-IDF projects, you need to [install the ESP-IDF SDK and its tooling manually](https://docs.espressif.com/projects/esp-idf/en/v4.3.1/esp32/get-started/index.html). +When using `idf.py` and CMake driven ESP-IDF projects, you need to [install the ESP-IDF SDK and its tooling manually](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/). Simple installation for Linux & MacOS: ```sh git clone https://github.com/espressif/esp-idf -git -C esp-idf checkout release/v4.4 +git -C esp-idf checkout release/v5.1 esp-idf/install.sh . esp-idf/export.sh ``` @@ -142,7 +145,7 @@ esp-idf/install.sh Simple installation for Windows: ```sh git clone https://github.com/espressif/esp-idf -git -C esp-idf checkout release/v4.4 +git -C esp-idf checkout release/v5.1 esp-idf\install esp-idf\export ``` diff --git a/cargo/.cargo/config.toml b/cargo/.cargo/config.toml index e0f3b1f..3348be2 100644 --- a/cargo/.cargo/config.toml +++ b/cargo/.cargo/config.toml @@ -21,7 +21,8 @@ build-std = ["core", "alloc", "panic_abort"] {%- endif %} [env] -# Note: these variables are not used when using pio builder (`cargo build --features pio`) +MCU="{{ mcu }}" +# Note: this variable is not used by the pio builder (`cargo build --features pio`) {%- if espidfver == "v4.4" %} ESP_IDF_VERSION = "v4.4.6" {% elsif espidfver == "v5.1" %} diff --git a/cargo/Cargo.toml b/cargo/Cargo.toml index e598d5c..37e9ba5 100644 --- a/cargo/Cargo.toml +++ b/cargo/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["{{authors}}"] edition = "2021" resolver = "2" -rust-version = "1.66" +rust-version = "1.71" [profile.release] opt-level = "s" @@ -14,31 +14,22 @@ debug = true # Symbols are nice and they don't increase the size on Flash opt-level = "z" [features] -{% if std and hal == "Yes (default features)" %} -default = ["std", "hal", "esp-idf-sys/native"] -{% elsif std and hal == "Yes (all features)" %} -default = ["all", "hal", "esp-idf-sys/native"] -{% elsif std %} -default = ["std", "esp-idf-sys/native"] -{% else %} -default = ["esp-idf-sys/native", "esp-idf-sys/panic_handler", "esp-idf-sys/alloc_handler", "esp-idf-sys/libstart"] -{% endif %} +{%- if std %} +default = ["std", "embassy", "esp-idf-svc/native"] +{%- else %} +default = ["alloc", "embassy", "esp-idf-svc/native", "esp-idf-svc/panic_handler", "esp-idf-svc/alloc_handler", "esp-idf-svc/libstart"] +{%- endif %} -pio = ["esp-idf-sys/pio"] -all = ["std", "nightly", "experimental", "embassy"] -hal = ["esp-idf-hal", "embedded-svc", "esp-idf-svc"] -std = ["alloc", "esp-idf-sys/std", "esp-idf-sys/binstart", "embedded-svc?/std", "esp-idf-hal?/std", "esp-idf-svc?/std"] -alloc = ["embedded-svc?/alloc", "esp-idf-hal?/alloc", "esp-idf-svc?/alloc"] -nightly = ["embedded-svc?/nightly", "esp-idf-svc?/nightly"] # Future: "esp-idf-hal?/nightly" -experimental = ["embedded-svc?/experimental", "esp-idf-svc?/experimental"] -embassy = ["esp-idf-hal?/embassy-sync", "esp-idf-hal?/critical-section", "esp-idf-hal?/edge-executor", "esp-idf-svc?/embassy-time-driver", "esp-idf-svc?/embassy-time-isr-queue"] +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.17", default-features = false } -esp-idf-sys = { version = "0.33", default-features = false } -esp-idf-hal = { version = "0.41", optional = true, default-features = false } -esp-idf-svc = { version = "0.46", optional = true, default-features = false } -embedded-svc = { version = "0.25", optional = true, default-features = false } +log = { version = "0.4", default-features = false } +esp-idf-svc = { version = "0.47.1", default-features = false } [build-dependencies] -embuild = "0.31.2" +embuild = "0.31.3" diff --git a/cargo/build.rs b/cargo/build.rs index ccb6e75..112ec3f 100644 --- a/cargo/build.rs +++ b/cargo/build.rs @@ -1,6 +1,3 @@ -// Necessary because of this issue: https://github.com/rust-lang/cargo/issues/9641 -fn main() -> Result<(), Box> { - embuild::build::CfgArgs::output_propagated("ESP_IDF")?; - embuild::build::LinkArgs::output_propagated("ESP_IDF")?; - Ok(()) +fn main() { + embuild::espidf::sysenv::output(); } diff --git a/cargo/cargo-generate.toml b/cargo/cargo-generate.toml index cb4c67a..e5b94de 100644 --- a/cargo/cargo-generate.toml +++ b/cargo/cargo-generate.toml @@ -31,7 +31,7 @@ default = "v5.1" type = "string" prompt = "ESP-IDF version (master = UNSTABLE)" choices = ["v4.4", "v5.1", "master"] -default = "v4.4" +default = "v5.1" [conditional.'advanced'.placeholders.devcontainer] type = "bool" @@ -43,12 +43,6 @@ type = "bool" prompt = "Configure project to support Wokwi simulation with Wokwi VS Code extension?" default = false -[conditional.'std'.placeholders.hal] -type = "string" -prompt = "Include esp-idf-hal/esp-idf-svc?" -choices = ["No", "Yes (default features)", "Yes (all features)"] -default = "Yes (default features)" - [conditional.'advanced'.placeholders.ci] type = "bool" prompt = "Add CI files for GitHub Action?" diff --git a/cargo/pre-script.rhai b/cargo/pre-script.rhai index 9aecd95..af1c05d 100644 --- a/cargo/pre-script.rhai +++ b/cargo/pre-script.rhai @@ -52,13 +52,7 @@ for key in target_properties.keys() { let advanced = variable::get("advanced"); if !advanced { variable::set("std", true); - if target == "esp32" || target == "esp32s2" || target == "esp32s3" || target == "esp32c3" { - variable::set("espidfver", "v4.4"); - } - else { - variable::set("espidfver", "v5.1"); - } + variable::set("espidfver", "v5.1"); variable::set("devcontainer", false); variable::set("wokwi", false); - variable::set("hal", "Yes (default features)"); } diff --git a/cargo/sdkconfig.defaults b/cargo/sdkconfig.defaults index 3ca3b5d..9ea5d73 100644 --- a/cargo/sdkconfig.defaults +++ b/cargo/sdkconfig.defaults @@ -1,5 +1,5 @@ # 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=7000 +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). diff --git a/cargo/src/main.rs b/cargo/src/main.rs index 055cfe8..8e30889 100644 --- a/cargo/src/main.rs +++ b/cargo/src/main.rs @@ -1,25 +1,18 @@ {% unless std -%} #![no_std] #![no_main] -{% endunless -%} -use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported -{%- if std and hal != "No" %} -use log::*; -{% endif %} -{%- unless std %} +{% endunless -%} +{% unless std -%} #[no_mangle] -{%- endunless %} +{% endunless -%} 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_sys::link_patches(); -{%- if std and hal == "No" %} - println!("Hello, world!"); -{%- elsif std and hal != "No" %} + esp_idf_svc::sys::link_patches(); + // Bind the log crate to the ESP Logging facilities esp_idf_svc::log::EspLogger::initialize_default(); - info!("Hello, world!"); -{%- endif %} + log::info!("Hello, world!"); } diff --git a/cmake/cargo-generate.toml b/cmake/cargo-generate.toml index 973e615..f579cc8 100644 --- a/cmake/cargo-generate.toml +++ b/cmake/cargo-generate.toml @@ -1,6 +1,23 @@ [template] -cargo_generate_version = ">=0.10.0" +cargo_generate_version = ">=0.17.4" -[placeholders] -toolchain = { type = "string", prompt = "Rust toolchain (beware: nightly works only for esp32c3!)", choices = ["esp", "nightly"], default = "esp" } -std = { type = "bool", prompt = "STD support", default = true } +[placeholders.advanced] +type = "bool" +prompt = "Configure advanced template options?" +default = false + +[conditional.'advanced'.placeholders.toolchain] +type = "string" +prompt = "Rust toolchain (beware: nightly works only for riscv MCUs!)" +choices = ["esp", "nightly"] +default = "esp" + +[conditional.'advanced'.placeholders.hal] +type = "bool" +prompt = "Enable HAL support?" +default = true + +[conditional.'advanced'.placeholders.std] +type = "bool" +prompt = "Enable STD support?" +default = true diff --git a/cmake/components/rust-{{project-name}}/CMakeLists.txt b/cmake/components/rust-{{project-name}}/CMakeLists.txt index 30acfc7..9c03913 100644 --- a/cmake/components/rust-{{project-name}}/CMakeLists.txt +++ b/cmake/components/rust-{{project-name}}/CMakeLists.txt @@ -11,7 +11,7 @@ idf_component_register( ) if(CONFIG_IDF_TARGET_ARCH_RISCV) - if (CONFIG_IDF_TARGET_ESP32C6) + if (CONFIG_IDF_TARGET_ESP32C6 OR CONFIG_IDF_TARGET_ESP32C5 OR CONFIG_IDF_TARGET_ESP32H2) set(RUST_TARGET "riscv32imac-esp-espidf") else () set(RUST_TARGET "riscv32imc-esp-espidf") @@ -34,10 +34,12 @@ else() set(CARGO_BUILD_ARG "--release") endif() -{% if std %} -set(CARGO_BUILD_STD_ARG -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort) +{% if hal and std %} +set(CARGO_BUILD_STD_ARG -Zbuild-std=std,panic_abort) +{% elsif hal %} +set(CARGO_BUILD_STD_ARG -Zbuild-std=core,alloc,panic_abort) {% else %} -set(CARGO_BUILD_STD_ARG -Zbuild-std=core,alloc,panic_abort -Zbuild-std-features=panic_immediate_abort) +set(CARGO_BUILD_STD_ARG -Zbuild-std=core,panic_abort -Zbuild-std-features=panic_immediate_abort) {% endif %} if(IDF_VERSION_MAJOR GREATER "4") @@ -72,6 +74,7 @@ ExternalProject_Add( CARGO_CMAKE_BUILD_ESP_IDF=${idf_path} CARGO_CMAKE_BUILD_COMPILER=${CMAKE_C_COMPILER} RUSTFLAGS=${ESP_RUSTFLAGS} + MCU=${CONFIG_IDF_TARGET} cargo build --target ${RUST_TARGET} --target-dir ${CARGO_TARGET_DIR} ${CARGO_BUILD_ARG} ${CARGO_BUILD_STD_ARG} INSTALL_COMMAND "" BUILD_ALWAYS TRUE diff --git a/cmake/components/rust-{{project-name}}/Cargo.toml b/cmake/components/rust-{{project-name}}/Cargo.toml index ef179b8..b92a4e8 100644 --- a/cmake/components/rust-{{project-name}}/Cargo.toml +++ b/cmake/components/rust-{{project-name}}/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["{{authors}}"] edition = "2021" resolver = "2" -rust-version = "1.66" +rust-version = "1.71" [lib] crate-type = ["staticlib"] @@ -16,32 +16,25 @@ opt-level = "s" debug = true # Symbols are nice and they don't increase the size on Flash opt-level = "z" +{%- if hal %} [features] -{% if std and hal == "Yes (default features)" %} -default = ["std", "hal", "esp-idf-sys/native"] -{% elsif std and hal == "Yes (all features)" %} -default = ["all", "hal", "esp-idf-sys/native"] -{% elsif std %} -default = ["std", "esp-idf-sys/native"] -{% else %} -default = ["esp-idf-sys/native", "esp-idf-sys/panic_handler", "esp-idf-sys/alloc_handler"] -{% endif %} +{%- if std %} +default = ["std", "embassy", "esp-idf-svc/native"] +{%- else %} +default = ["alloc", "embassy", "esp-idf-svc/native", "esp-idf-svc/panic_handler", "esp-idf-svc/alloc_handler"] +{%- endif %} -pio = ["esp-idf-sys/pio"] -all = ["std", "nightly", "experimental", "embassy"] -hal = ["esp-idf-hal", "embedded-svc", "esp-idf-svc"] -std = ["alloc", "esp-idf-sys/std", "embedded-svc?/std", "esp-idf-hal?/std", "esp-idf-svc?/std"] -alloc = ["embedded-svc?/alloc", "esp-idf-hal?/alloc", "esp-idf-svc?/alloc"] -nightly = ["embedded-svc?/nightly", "esp-idf-svc?/nightly"] # Future: "esp-idf-hal?/nightly" -experimental = ["embedded-svc?/experimental", "esp-idf-svc?/experimental"] -embassy = ["esp-idf-hal?/embassy-sync", "esp-idf-hal?/critical-section", "esp-idf-hal?/edge-executor", "esp-idf-svc?/embassy-time-driver", "esp-idf-svc?/embassy-time-isr-queue"] +pio = ["esp-idf-svc/pio"] +std = ["alloc", "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.17", default-features = false } -esp-idf-sys = { version = "0.33", default-features = false } -esp-idf-hal = { version = "0.41", optional = true, default-features = false } -esp-idf-svc = { version = "0.46", optional = true, default-features = false } -embedded-svc = { version = "0.25", optional = true, default-features = false } +log = { version = "0.4", default-features = false } +esp-idf-svc = { version = "0.47.1", default-features = false } [build-dependencies] -embuild = "0.31.2" +embuild = "0.31.3" +{%- endif %} diff --git a/cmake/components/rust-{{project-name}}/build.rs b/cmake/components/rust-{{project-name}}/build.rs index bc16914..028bb4d 100644 --- a/cmake/components/rust-{{project-name}}/build.rs +++ b/cmake/components/rust-{{project-name}}/build.rs @@ -1,5 +1,5 @@ -// Necessary because of this issue: https://github.com/rust-lang/cargo/issues/9641 -fn main() -> Result<(), Box> { - embuild::build::CfgArgs::output_propagated("ESP_IDF")?; - Ok(()) +fn main() { +{%- if hal %} + embuild::espidf::sysenv::output(); +{%- endif %} } diff --git a/cmake/components/rust-{{project-name}}/rust-toolchain.toml b/cmake/components/rust-{{project-name}}/rust-toolchain.toml index f35bc08..99b0a46 100644 --- a/cmake/components/rust-{{project-name}}/rust-toolchain.toml +++ b/cmake/components/rust-{{project-name}}/rust-toolchain.toml @@ -1,7 +1,7 @@ [toolchain] -{% if toolchain == "esp" %} -channel = "esp" -{% endif %} -{% if toolchain == "nightly" %} +{% if toolchain == "nightly" -%} channel = "nightly" -{% endif %} +components = ["rust-src"] +{% else -%} +channel = "esp" +{% endif %} \ No newline at end of file diff --git a/cmake/components/rust-{{project-name}}/src/lib.rs b/cmake/components/rust-{{project-name}}/src/lib.rs index 3a7b640..5102fab 100644 --- a/cmake/components/rust-{{project-name}}/src/lib.rs +++ b/cmake/components/rust-{{project-name}}/src/lib.rs @@ -1,24 +1,28 @@ {% unless std -%} #![no_std] {% endunless -%} -use esp_idf_sys as _; // If using the `libstart` feature of `esp-idf-sys`, always keep this module imported -{%- if std and hal != "No" %} -use log::*; -{% endif %} #[no_mangle] extern "C" fn rust_main() -> i32 { - // Temporary. Will disappear once ESP-IDF 4.4 is released, but for now it is necessary to call this function once, - // or else some patches to the runtime implemented by esp-idf-sys might not link properly. - esp_idf_sys::link_patches(); -{%- if std and hal == "No" %} - println!("Hello world from Rust!"); -{%- elsif std and hal != "No" %} +{%- if hal %} + // 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(); - info!("Hello world from Rust!"); + log::info!("Hello, world!"); +{%- elsif std %} + println!("Hello, world from Rust!"); {%- endif %} - 42 } + +{% unless hal -%} +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop { + } +} +{% endunless -%} diff --git a/cmake/sdkconfig.defaults b/cmake/sdkconfig.defaults index 6f6cfdd..f19c694 100644 --- a/cmake/sdkconfig.defaults +++ b/cmake/sdkconfig.defaults @@ -1,5 +1,5 @@ # 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=7000 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=8000 # Workaround for https://github.com/espressif/esp-idf/issues/7631 #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n