diff --git a/.cargo/config.toml b/.cargo/config.toml index f6ec1ef65fc..7cb1f991b51 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,22 +1,30 @@ +[build] +target = "riscv32imc-esp-espidf" +#target = "xtensa-esp32-espidf" + [target.xtensa-esp32-espidf] linker = "ldproxy" +rustflags = ["--cfg", "espidf_time64"] [target.xtensa-esp32s2-espidf] linker = "ldproxy" +rustflags = ["--cfg", "espidf_time64"] [target.xtensa-esp32s3-espidf] linker = "ldproxy" +rustflags = ["--cfg", "espidf_time64"] [target.riscv32imc-esp-espidf] linker = "ldproxy" +rustflags = ["--cfg", "espidf_time64"] -# Future - necessary for the experimental "native build" of esp-idf-sys with ESP32C3 -# See also https://github.com/ivmarkov/embuild/issues/16 -rustflags = ["-C", "default-linker-libraries"] +[target.riscv32imac-esp-espidf] +linker = "ldproxy" +rustflags = ["--cfg", "espidf_time64"] [env] ESP_IDF_SDKCONFIG_DEFAULTS = ".github/configs/sdkconfig.defaults" +ESP_IDF_VERSION = "v5.3.2" [unstable] build-std = ["std", "panic_abort"] -build-std-features = ["panic_immediate_abort"] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000000..d6cacbf6527 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,35 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: ["bug", "status:needs-attention"] +assignees: '' + +--- + +## Bug description + + + +- Would you like to work on a fix? [y/n] + +## To Reproduce + + +1. ... +2. ... + + + + + +## Expected behavior + + + +## Environment + +- Crate (`esp-idf-hal`) version: [e.g. 0.44.1; type master if you use the crate from the GIT master branch] +- ESP-IDF branch or tag: [e.g. release/v5.2, v5.2.1, etc.] +- Target device (MCU): [e.g. esp32s3] +- OS: [e.g. Ubuntu 20.04] diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000..897d5178b86 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: Ask questions in Matrix channel + url: https://matrix.to/#/#esp-rs:matrix.org + about: Ask any questions directly in our Matrix channel. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000000..6e38ae0e046 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,26 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: ["enhancement", "status:needs-attention"] +assignees: '' + +--- + +## Motivations + + + +- Would you like to implement this feature? [y/n] + +## Solution + + + +## Alternatives + + + +## Additional context + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..c471bd53074 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,18 @@ +## Thank you for your contribution! + +We appreciate the time and effort you've put into this pull request. +To help us review it efficiently, please ensure you've gone through the following checklist: + +### Submission Checklist 📝 +- [ ] I have updated existing examples or added new ones (if applicable). +- [ ] I have used `cargo fmt` command to ensure that all changed code is formatted correctly. +- [ ] I have used `cargo clippy` command to ensure that all changed code passes latest Clippy nightly lints. +- [ ] My changes were added to the [`CHANGELOG.md`](https://github.com/esp-rs/esp-idf-hal/blob/main/esp-idf-hal/CHANGELOG.md) in the **_proper_** section. + +### Pull Request Details 📖 + +#### Description +Please provide a clear and concise description of your changes, including the motivation behind these changes. The context is crucial for the reviewers. + +#### Testing +Describe how you tested your changes. \ No newline at end of file diff --git a/.github/configs/sdkconfig.defaults b/.github/configs/sdkconfig.defaults index 459b53dac86..79313e62310 100644 --- a/.github/configs/sdkconfig.defaults +++ b/.github/configs/sdkconfig.defaults @@ -2,6 +2,9 @@ #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n -# Some examples (ledc-simple) require a larger than the default stack size for +# Some examples (ledc_simple, spi_loopback_async) require a larger than the default stack size for # the main thread. -CONFIG_ESP_MAIN_TASK_STACK_SIZE=7000 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=8000 + +# Async SPI only enabled when this config is disabled (it is enabled by default) +CONFIG_SPI_MASTER_ISR_IN_IRAM=n diff --git a/.github/workflows/ci-esp-idf-master.yml b/.github/workflows/ci-esp-idf-master.yml deleted file mode 100644 index 10be21c4c27..00000000000 --- a/.github/workflows/ci-esp-idf-master.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: CIEspIdfMaster - -on: - workflow_dispatch: - schedule: - - cron: '50 5 * * *' - -env: - rust_toolchain: nightly - -jobs: - compile: - name: Compile - runs-on: ubuntu-latest - steps: - - name: Setup | Checkout - uses: actions/checkout@v2 - - name: Setup | libncurses5 - run: sudo apt-get install libncurses5 - - name: Setup | Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ env.rust_toolchain }} - components: rustfmt, clippy - - name: Setup | Std - run: rustup component add rust-src --toolchain ${{ env.rust_toolchain }}-x86_64-unknown-linux-gnu - - name: Setup | Default to nightly - run: rustup default ${{ env.rust_toolchain }} - - name: Build | Fmt Check - run: cargo fmt -- --check - - name: Build | Compile Native ESP-IDF master no_std - run: export RUSTFLAGS="--cfg espidf_time64"; export ESP_IDF_VERSION=master; export ESP_IDF_SDKCONFIG_DEFAULTS=$(pwd)/.github/configs/sdkconfig.defaults; cargo build --features esp-idf-sys/native --no-default-features --target riscv32imc-esp-espidf -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort diff --git a/.github/workflows/ci-esp-idf-next.yml b/.github/workflows/ci-esp-idf-next.yml new file mode 100644 index 00000000000..dfdf1b3753a --- /dev/null +++ b/.github/workflows/ci-esp-idf-next.yml @@ -0,0 +1,98 @@ +name: CIEspIdfNext + +on: + push: + branches: + - master + workflow_dispatch: + schedule: + - cron: '50 5 * * *' + +env: + rust_toolchain: nightly + +jobs: + compile: + name: Compile + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: + - riscv32imc-esp-espidf + - riscv32imac-esp-espidf + - xtensa-esp32-espidf + - xtensa-esp32s2-espidf + - xtensa-esp32s3-espidf + idf-version: + - release/v5.3 + - release/v5.4 + - master + steps: + - name: Setup | Checkout + uses: actions/checkout@v3 + + - name: Setup | Rust + if: matrix.target == 'riscv32imc-esp-espidf' + uses: dtolnay/rust-toolchain@v1 + with: + toolchain: ${{ env.rust_toolchain }} + components: rustfmt, clippy, rust-src + + - name: Install Rust for Xtensa + if: matrix.target != 'riscv32imc-esp-espidf' + uses: esp-rs/xtensa-toolchain@v1.5.1 + with: + default: true + + - name: Build | Fmt Check + run: cargo fmt -- --check + + - name: Build | Clippy + env: + ESP_IDF_VERSION: ${{ matrix.idf-version }} + ESP_IDF_SDKCONFIG_DEFAULTS: "${{ github.workspace }}/.github/configs/sdkconfig.defaults" + RUSTFLAGS: "${{ matrix.idf-version != 'release/v4.4' && '--cfg espidf_time64' || matrix.idf-version == 'release/v4.4' && '--cfg espidf_time32' }}" + run: cargo clippy --features experimental --no-deps --target ${{ matrix.target }} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort -- -Dwarnings + + - name: Build | Compile, all + env: + ESP_IDF_VERSION: ${{ matrix.idf-version }} + ESP_IDF_SDKCONFIG_DEFAULTS: "${{ github.workspace }}/.github/configs/sdkconfig.defaults" + RUSTFLAGS: "${{ matrix.idf-version != 'release/v4.4' && '--cfg espidf_time64' || matrix.idf-version == 'release/v4.4' && '--cfg espidf_time32' }}" + run: cargo build --target ${{ matrix.target }} --features nightly,experimental,critical-section,embassy-sync -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort + + - name: Build | Compile, no_std + env: + ESP_IDF_VERSION: ${{ matrix.idf-version }} + ESP_IDF_SDKCONFIG_DEFAULTS: "${{ github.workspace }}/.github/configs/sdkconfig.defaults" + RUSTFLAGS: "${{ matrix.idf-version != 'release/v4.4' && '--cfg espidf_time64' || matrix.idf-version == 'release/v4.4' && '--cfg espidf_time32' }}" + run: cargo build --no-default-features --features experimental --target ${{ matrix.target }} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort + + - name: Build | Compile, no_std, alloc + env: + ESP_IDF_VERSION: ${{ matrix.idf-version }} + ESP_IDF_SDKCONFIG_DEFAULTS: "${{ github.workspace }}/.github/configs/sdkconfig.defaults" + RUSTFLAGS: "${{ matrix.idf-version != 'release/v4.4' && '--cfg espidf_time64' || matrix.idf-version == 'release/v4.4' && '--cfg espidf_time32' }}" + run: cargo build --features alloc --no-default-features --features experimental --target ${{ matrix.target }} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort + + - name: Setup | ldproxy + if: matrix.target == 'riscv32imc-esp-espidf' + run: | + curl -L https://github.com/esp-rs/embuild/releases/latest/download/ldproxy-x86_64-unknown-linux-gnu.zip -o $HOME/.cargo/bin/ldproxy.zip + unzip "$HOME/.cargo/bin/ldproxy.zip" -d "$HOME/.cargo/bin/" + chmod a+x $HOME/.cargo/bin/ldproxy + + - name: Clippy | Examples + env: + ESP_IDF_VERSION: ${{ matrix.idf-version }} + ESP_IDF_SDKCONFIG_DEFAULTS: "${{ github.workspace }}/.github/configs/sdkconfig.defaults" + RUSTFLAGS: "${{ matrix.idf-version != 'release/v4.4' && '--cfg espidf_time64' || matrix.idf-version == 'release/v4.4' && '--cfg espidf_time32' }}" + run: cargo clippy --examples --target ${{ matrix.target }} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort -- -Dwarnings + + - name: Build | Examples + env: + ESP_IDF_VERSION: ${{ matrix.idf-version }} + ESP_IDF_SDKCONFIG_DEFAULTS: "${{ github.workspace }}/.github/configs/sdkconfig.defaults" + RUSTFLAGS: "${{ matrix.idf-version != 'release/v4.4' && '--cfg espidf_time64' || matrix.idf-version == 'release/v4.4' && '--cfg espidf_time32' }}" + run: cargo build --examples --target ${{ matrix.target }} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dcc2da6d429..23668311cbf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,99 +6,101 @@ on: - master pull_request: schedule: - - cron: '50 4 * * *' + - cron: "50 4 * * *" + workflow_dispatch: env: rust_toolchain: nightly + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: compile: name: Compile runs-on: ubuntu-latest strategy: + fail-fast: false matrix: target: - riscv32imc-esp-espidf + - riscv32imac-esp-espidf - xtensa-esp32-espidf - xtensa-esp32s2-espidf - xtensa-esp32s3-espidf idf-version: - release/v4.4 - - release/v5.0 + - release/v5.2 + - v5.3.2 steps: - name: Setup | Checkout - uses: actions/checkout@v2 - - - name: Setup | libncurses5 - run: sudo apt-get install libncurses5 + uses: actions/checkout@v3 - name: Setup | Rust - uses: actions-rs/toolchain@v1 + if: matrix.target == 'riscv32imc-esp-espidf' + uses: dtolnay/rust-toolchain@v1 with: toolchain: ${{ env.rust_toolchain }} - components: rustfmt, clippy - default: true - if: matrix.target == 'riscv32imc-esp-espidf' - - - name: Setup | Std - run: rustup component add rust-src --toolchain ${{ env.rust_toolchain }}-x86_64-unknown-linux-gnu - if: matrix.target == 'riscv32imc-esp-espidf' + components: rustfmt, clippy, rust-src - name: Install Rust for Xtensa - uses: esp-rs/xtensa-toolchain@v1.4.0 + if: matrix.target != 'riscv32imc-esp-espidf' + uses: esp-rs/xtensa-toolchain@v1.5.1 with: default: true - if: matrix.target != 'riscv32imc-esp-espidf' - - - name: Temp fix for clang error - run: sudo apt-get install libncurses5 - + - name: Build | Fmt Check run: cargo fmt -- --check - name: Build | Clippy + if: matrix.target != 'riscv32imac-esp-espidf' || matrix.idf-version != 'release/v4.4' env: ESP_IDF_VERSION: ${{ matrix.idf-version }} - ESP_IDF_SDKCONFIG_DEFAULTS: $(pwd)/.github/configs/sdkconfig.defaults - RUSTFLAGS: "${{ matrix.idf-version == 'release/v5.0' && '--cfg espidf_time64' || ''}}" - run: cargo clippy --no-deps --target ${{ matrix.target }} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort -- -Dwarnings - - - name: Build | Compile - env: - ESP_IDF_VERSION: ${{ matrix.idf-version }} - ESP_IDF_SDKCONFIG_DEFAULTS: $(pwd)/.github/configs/sdkconfig.defaults - RUSTFLAGS: "${{ matrix.idf-version == 'release/v5.0' && '--cfg espidf_time64' || ''}}" - run: cargo build --target ${{ matrix.target }} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort + ESP_IDF_SDKCONFIG_DEFAULTS: "${{ github.workspace }}/.github/configs/sdkconfig.defaults" + RUSTFLAGS: "${{ matrix.idf-version != 'release/v4.4' && '--cfg espidf_time64' || matrix.idf-version == 'release/v4.4' && '--cfg espidf_time32' }}" + run: cargo clippy --features experimental --no-deps --target ${{ matrix.target }} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort -- -Dwarnings - - name: Build | RISCV-ULP-HAL feature + - name: Build | Compile, all + if: matrix.target != 'riscv32imac-esp-espidf' || matrix.idf-version != 'release/v4.4' env: ESP_IDF_VERSION: ${{ matrix.idf-version }} - run: cargo build --features riscv-ulp-hal --no-default-features --target riscv32imc-unknown-none-elf -Zbuild-std=core,panic_abort -Zbuild-std-features=panic_immediate_abort + ESP_IDF_SDKCONFIG_DEFAULTS: "${{ github.workspace }}/.github/configs/sdkconfig.defaults" + RUSTFLAGS: "${{ matrix.idf-version != 'release/v4.4' && '--cfg espidf_time64' || matrix.idf-version == 'release/v4.4' && '--cfg espidf_time32' }}" + run: cargo build --target ${{ matrix.target }} --features nightly,experimental,critical-section,embassy-sync -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort - name: Build | Compile, no_std + if: matrix.target != 'riscv32imac-esp-espidf' || matrix.idf-version != 'release/v4.4' env: ESP_IDF_VERSION: ${{ matrix.idf-version }} - ESP_IDF_SDKCONFIG_DEFAULTS: $(pwd)/.github/configs/sdkconfig.defaults - RUSTFLAGS: "${{ matrix.idf-version == 'release/v5.0' && '--cfg espidf_time64' || ''}}" - run: cargo build --features esp-idf-sys --no-default-features --target ${{ matrix.target }} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort + ESP_IDF_SDKCONFIG_DEFAULTS: "${{ github.workspace }}/.github/configs/sdkconfig.defaults" + RUSTFLAGS: "${{ matrix.idf-version != 'release/v4.4' && '--cfg espidf_time64' || matrix.idf-version == 'release/v4.4' && '--cfg espidf_time32' }}" + run: cargo build --no-default-features --features experimental --target ${{ matrix.target }} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort - name: Build | Compile, no_std, alloc + if: matrix.target != 'riscv32imac-esp-espidf' || matrix.idf-version != 'release/v4.4' env: ESP_IDF_VERSION: ${{ matrix.idf-version }} - ESP_IDF_SDKCONFIG_DEFAULTS: $(pwd)/.github/configs/sdkconfig.defaults - RUSTFLAGS: "${{ matrix.idf-version == 'release/v5.0' && '--cfg espidf_time64' || ''}}" - run: cargo build --features alloc,esp-idf-sys --no-default-features --target ${{ matrix.target }} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort + ESP_IDF_SDKCONFIG_DEFAULTS: "${{ github.workspace }}/.github/configs/sdkconfig.defaults" + RUSTFLAGS: "${{ matrix.idf-version != 'release/v4.4' && '--cfg espidf_time64' || matrix.idf-version == 'release/v4.4' && '--cfg espidf_time32' }}" + run: cargo build --features alloc --no-default-features --features experimental --target ${{ matrix.target }} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort - name: Setup | ldproxy - uses: actions-rs/install@v0.1 - with: - crate: ldproxy - version: latest if: matrix.target == 'riscv32imc-esp-espidf' + run: | + curl -L https://github.com/esp-rs/embuild/releases/latest/download/ldproxy-x86_64-unknown-linux-gnu.zip -o $HOME/.cargo/bin/ldproxy.zip + unzip "$HOME/.cargo/bin/ldproxy.zip" -d "$HOME/.cargo/bin/" + chmod a+x $HOME/.cargo/bin/ldproxy + + - name: Clippy | Examples + if: matrix.target != 'riscv32imac-esp-espidf' || matrix.idf-version != 'release/v4.4' + env: + ESP_IDF_VERSION: ${{ matrix.idf-version }} + ESP_IDF_SDKCONFIG_DEFAULTS: "${{ github.workspace }}/.github/configs/sdkconfig.defaults" + RUSTFLAGS: "${{ matrix.idf-version != 'release/v4.4' && '--cfg espidf_time64' || matrix.idf-version == 'release/v4.4' && '--cfg espidf_time32' }}" + run: cargo clippy --examples --target ${{ matrix.target }} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort -- -Dwarnings - name: Build | Examples + if: matrix.target != 'riscv32imac-esp-espidf' || matrix.idf-version != 'release/v4.4' env: ESP_IDF_VERSION: ${{ matrix.idf-version }} - ESP_IDF_SDKCONFIG_DEFAULTS: $(pwd)/.github/configs/sdkconfig.defaults - RUSTFLAGS: "${{ matrix.idf-version == 'release/v5.0' && '--cfg espidf_time64' || ''}} ${{ matrix.target == 'riscv32imc-esp-espidf' && '-C default-linker-libraries' || ''}}" + ESP_IDF_SDKCONFIG_DEFAULTS: "${{ github.workspace }}/.github/configs/sdkconfig.defaults" + RUSTFLAGS: "${{ matrix.idf-version != 'release/v4.4' && '--cfg espidf_time64' || matrix.idf-version == 'release/v4.4' && '--cfg espidf_time32' }}" run: cargo build --examples --target ${{ matrix.target }} -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort diff --git a/.github/workflows/issue_handler.yml b/.github/workflows/issue_handler.yml new file mode 100644 index 00000000000..978e80428ce --- /dev/null +++ b/.github/workflows/issue_handler.yml @@ -0,0 +1,16 @@ +name: Add new issues to project + +on: + issues: + types: + - opened + +jobs: + add-to-project: + name: Add issue to project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@v0.5.0 + with: + project-url: https://github.com/orgs/esp-rs/projects/2 + github-token: ${{ secrets.PAT }} diff --git a/.github/workflows/publish-dry-run.yml b/.github/workflows/publish-dry-run.yml index 919e511dec0..7e7ca5d4d68 100644 --- a/.github/workflows/publish-dry-run.yml +++ b/.github/workflows/publish-dry-run.yml @@ -12,14 +12,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup | Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 + - name: Setup | Rust - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@v1 with: toolchain: ${{ env.rust_toolchain }} - - name: Setup | Std - run: rustup component add rust-src --toolchain ${{ env.rust_toolchain }}-x86_64-unknown-linux-gnu - - name: Setup | Default to nightly - run: rustup default ${{ env.rust_toolchain }} + components: rust-src + - name: Build | Publish Dry Run run: export ESP_IDF_TOOLS_INSTALL_DIR=out; export ESP_IDF_SDKCONFIG_DEFAULTS=$(pwd)/.github/configs/sdkconfig.defaults; cargo publish --dry-run --target riscv32imc-esp-espidf -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 68055fc7207..3e7396fb32e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,21 +13,23 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup | Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 + - name: Setup | Rust - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@v1 with: toolchain: ${{ env.rust_toolchain }} - - name: Setup | Std - run: rustup component add rust-src --toolchain ${{ env.rust_toolchain }}-x86_64-unknown-linux-gnu - - name: Setup | Default to nightly - run: rustup default ${{ env.rust_toolchain }} + components: rust-src + - name: Login run: cargo login ${{ secrets.crates_io_token }} + - name: Build | Publish run: export ESP_IDF_TOOLS_INSTALL_DIR=out; export ESP_IDF_SDKCONFIG_DEFAULTS=$(pwd)/.github/configs/sdkconfig.defaults; cargo publish --target riscv32imc-esp-espidf -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort + - name: Build Documentation run: cargo doc --features esp-idf-sys/native --target riscv32imc-esp-espidf -Zbuild-std=std,panic_abort -Zbuild-std-features=panic_immediate_abort; echo "" > target/riscv32imc-esp-espidf/doc/index.html; mv target/riscv32imc-esp-espidf/doc ./docs + - name: Deploy Documentation if: ${{ github.ref == 'refs/heads/master' }} uses: peaceiris/actions-gh-pages@v3 @@ -35,11 +37,13 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} force_orphan: true publish_dir: ./docs + - name: Get the crate version from cargo run: | version=$(cargo metadata --format-version=1 --no-deps | jq -r ".packages[] | select(.name == \"${{env.crate_name}}\") | .version") echo "crate_version=$version" >> $GITHUB_ENV echo "${{env.crate_name}} version: $version" + - name: Tag the new release uses: rickstaa/action-create-tag@v1 with: diff --git a/.gitignore b/.gitignore index abd34f10a7a..ea892980fae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /.vscode +/.idea /.espressif /.embuild /target @@ -6,3 +7,5 @@ **/*.rs.bk ulp/ulp_start.o install-rust-toolchain.sh +/.devcontainer +components_esp32.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index cae03b22f50..afb297f98ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,200 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Deprecated +- `DB_11` ADC attenuation in favor of `DB_12` for ESP-IDF V5.0+ + +### Added +- `Send` for `AsyncCanDriver` +- `DB_12` ADC attenuation +- `fade_with_time`, `fade_with_step` for `LedcDriver` + +### Fixed +- Fix the SDMMC driver for ESP-IDF V5.5+ +- Replace Arc with Rc in ledc_threads example (#514) +- Fix outdated task docs + +## [0.45.2] - 2025-01-15 + +### Fixed +- Fix ledc example (#511) +- Fix missing sdspi_host_get_dma_info for sdcard driver bug (#509) + +## [0.45.1] - 2025-01-10 + +### Fixed +- Fix RGB to u32 conversion order in rmt_neopixel example (#505) +- Fix missing sdmmc_host_get_dma_info for sdcard driver bug (#507) + +## [0.45.0] - 2025-01-02 + +### Deprecated + +### Breaking +- Make the UART config to ESP-IDF C config reusable outside the UART driver (for OpenThread) +- Re-use the SPI config struct of the SPI driver (for OpenThread) +- Add a `usb_serial` peripheral to the `Peripherals` struct for MCUs that do support the USB-serial-jtag peripheral (#503) +- UART default baud rate changed from 19'200 to 115'200 + +### Added +- Compatibility with ESP-IDF v5.3.X +- Docs: Add some docstrings for ADC module. (#455) +- Update uart.rs, add rs485 half duplex (#456) +- Update spi.rs, fix half3wire read (#459) +- SPI: Mark DMA as supported in the docu +- SD Card driver; SD Card host drivers (SPI and SDMMC) (#457) +- RMT Onewire Peripheral (#454) +- Add an example of using interrupt directly without async (#474) +- Add support for ESP internal temperature sensor (#337) +- Support the thread modem peripheral on the c6 MCU +- Add dual and quad SPI support (#479) +- Initial USB-serial-jtag driver (#504) + +### Fixed +- Fix: UartDriver as embedded_io::Read blocks until buffer is full (#475) +- Allow modem splits even when only HW coex is used +- Fix esp_reset_reason_t constant names (#483) +- Fix drop for AdcDriver in deregister event callbacks (#487) +- Typo fix in task docs (#489) +- Update spi.rs for correct label in gpio_cs capture (#492) +- Fix a crash when dropping `SpiDriver` + +## [0.44.1] - 2024-07-09 +### Fixed +* The crate now does build with ESP-IDF V4.4.x + esp32c2/esp32c6/esp32h2, yet these MCUs should only be used with ESP-IDF V5+ + as they are not officially supported with ESP-IDF V4.4.x (#450) +* Enum `ResetReason` not dealing with all reset reasons (panics on unknown reset reason) (#443, #444) + +## [0.44.0] - 2024-06-23 +### Deprecated +**ESP-IDF v4.4** Please start upgrading to ESP-IDF v5. +### Breaking +* **removed**: ESP-IDF v4.3 support, including mostly conditional compilations. (#420) +* GPIO20 is now included on esp32. Notice that not every esp32 has a physical GPIO20. (#370) +* ledc: now it can also be used with high_speed channels on esp32. (#424) +* dependency: updated embassy-sync to 0.6. (#437) +* adc: legacy adc driver is now behind a feature flag `adc-oneshot-legacy` starting with >= ESP-IDF v5.0. For justification, look at https://github.com/espressif/esp-idf/issues/13938. (#433) +* spi: cs_pre/post_trans_delay() config option included. (#266) +### Added +* rmt: `Symbol` now derives `Clone` and `Copy` (#386) +* reset: restart() function (#383) +* pcnt: Can now be used with esp32c6. (#407) +* can: Frame flags enum (#411) +* task: added MallocCap enum - Flag to indicate the capabilities of a Memory Region. (#419) +* i2s: new I2sDriver implementation allowing for splited bidirectional i2s. (#435) +* spi: added new_without_sclk SpiDriver constructor (#440) +### Fixed +* esp32h2 builds: added missing hys_ctrl_mode field (#387) +* rmt: FixedLengthSignal was broken, which led to failures in the neopixel/smartled examples, among other things. (#402) +* e-hal 0.2: implements DelayUs and DelayMs for u8 to be consistent with the other implementations (#414) +* i2c: wrong timeout calculations. (#415) +* task: Compatibility with ESP-iDF v5.3 (pre-release) - adding stack_alloc_caps to `ThreadSpawnConfiguration`. (#419) +* i2s: Compatibility with ESP-IDF v5.3 (pre-release) - small internal adjustments to i2s (#419) +* ledc: max_duty() method miscalulated in certain conditions. (#431) +* timer: fix clock usage in timer driver used with ESP-IDF v5.x (#441) +* i2c: use correct xtal for esp32c2 on timeout calculations. (#438) + +## [0.43.1] - 2024-02-21 +* Fix - PinDriver state changes and the drop call invoked pull-ups to be enabled. New default behavior on init / state transition / drop is to not enable pull-ups. (#344). If users want to reduce power usage on unused pins, they now need to manually enable pull-ups on a pin. For example, call `core::mem::forget` on the PinDriver instance after setting the pull-ups. +* #354 - breaking change - `rmt` driver now does not directly expose `rmt_item32_t` but rather - wraps it with a `Symbol` newtype +* #374 - Improve delay/timer errors and rounding calculations +* #379 - Remove 4096 limit for SPI dma transfer size +* Fix clippy duplicate imports warnings with latest 1.78 nightly + +## [0.43.0] - 2024-01-26 +* Breaking change: feature `riscv-ulp-hal` (and consequently, feature `esp-idf-sys`) is now removed. Use the [esp-ulp-riscv-hal](https://github.com/esp-rs/esp-hal/tree/main/esp-ulp-riscv-hal) crate instead +* MSRV 1.75; remove the nightly feature flag from all async trait implementations +* Update public dependencies `e-hal` to 1.0.0 and `embassy-sync` to 0.5 and private dependency `heapless` to 0.8 +* Allow `cargo check --all-features` to work correctly (there used to be a build error when both the `esp-idf-sys` and the `riscv-ulp-hal` features were enabled) +* #365 - Async Uart driver is not `Send` +* #362 - Async Uart write hangs forever +* #353 - Use the `PLL_F80M` clock source with ESP IDF 5.1 and esp32c6 +* #351 - Remove the `OutputPin` requirement from SPI SDI pin +* #350 - Not checking for ESP_FAIL in AsyncCanDriver::transmit + +## [0.42.5] - 2023-11-12 +* BREAKING CHANGE IN A PATCH RELEASE DUE TO DISCOVERED UB: The `subscribe` methods in drivers `PinDriver`, `PcntDriver` and `TimerDriver` +no longer accept non-static callbacks, as these lead to UB / crash when the driver is forgotten with e.g. `core::mem::forget`. Since local borrows +are a very useful feature however, these are still allowed via the newly-introduced and even more unsafe `subscribe_nonstatic` method. + +## [0.42.4] - 2023-11-02 +* Remove dependency on `AtomicU64` which is no longer supported by the upstream `*-espidf` targets +* Fix some Clippy warnings in the `spi` driver + +## [0.42.3] - 2023-10-29 +* Fix Timer array index bug #331 - prevented the use of TIMER10 on devices that support only 2 Timers +* Fix wrong TIMER11 index definition that declared TIMER11 as TIMER10 #331 + +## [0.42.2] - 2023-10-28 +* Support for latest ESP IDF 5.2 dev (master) + +## [0.42.1] - 2023-10-18 +* Fix ambiguous name error #325 - a compilation issue when the NimBLE component is enabled in `esp-idf-sys` +* Fix compilation issues of the I2S driver for esp32h2 and esp32c2 +* Fix compilation issues of the ADC drivers when the ESP IDF `esp_adc` component is not enabled +* Fix compilation issues of the GPIO driver for esp32c6 + +## [0.42.0] - 2023-10-17 +* MSRV raised to 1.71 +* New driver: I2S +* New driver: continuous ADC +* New driver: snapshot ADC, implementing the new ESP-IDF 5.0+ snapshot ADC API +* Async support in the following drivers: Timer, SPI, I2S, continuous ADC, CAN (via an `AsyncCanDriver` wrapper), UART (via an `AsyncUartDriver` wrapper) +* All async drivers can now work out of the box with any executor (`edge-executor` no longer a necessity) via a new ISR-to-task bridge (`esp_idf_hal::interrupt::asynch::IsrReactor`) +* CAN driver: support for pulling alerts in blocking and async mode +* UART driver: support for pulling UART events +* GPIO driver: scoped callback; `subscribe` callback now needs to live only as long as the `PinDriver` instance +* Timer driver: scoped callback; `subscribe` callback now needs to live only as long as the `TimerDriver` instance +* `task` and `interrupt` modules: new submodule in each - `asynch` - featuring a signal/notification-like synchronization primitive +* `task` module: `block_on` method capable of executing a future on the current thread +* `task` module: new sub-module - `queue` for the FreeRTOS `queue` synchonization primitive +* `task` module: new sub-module - `notification` - a more ergonomic API around the FreeRTOS task notification API + which was already exposed via the `task::notify` and `task::wait_notification` APIs +* Upgraded to `embedded-hal` 1.0.0-rc.1 and `embedded-hal-async` 1.0.0-rc.1 +* Dependency `esp-idf-sys` now re-exported as `esp_idf_hal::sys` +* Breaking change: `Peripherals::take` now returns an error when the peripherals are already taken +* Breaking change: `delay::Delay` struct extended with configurable threshold +* Breaking change: `task::wait_any_notification` removed; `task::notify` renamed to `task::notify_and_yield`; new function - `task::notify` - that notifies a task without automatically yielding to the notified task if it is a higher priority than the currently interrupted one +* Breaking change: `task::notify*` and `task::wait` now take/return `NonZeroU32` instead of `u32` +* Breaking change: GPIO - interrupts are now disabled automatically each time an ISR is triggered so as to avoid the IWDT triggering on level interrupts; use has to re-enable - in non-ISR code - via `PinDriver::enable_interrupt()` +* Breaking change: ESP IDF support for `edge-executor` moved to the `edge-executor` crate +* Deprecated: Using ESP-IDF 4.3 is now deprecated and all special cfg flags will be removed in the next release + +## [0.41.2] - 2023-06-21 + +* Do not set the single shot flag in the CAN frame (#263) +* Add safe abstractions to CRC functions in ESP ROM (#261) +* Use same error for all e-hal 0.2 trait impls (#260) +* Add support for WakeupReason from Ext0 events (#259) +* Compilation failed when ESP_IDF_VERSION = "release/v5.1" (#258) +* Fix UART docs (#252) + +## [0.41.1] - 2023-05-21 + +* UART driver was broken - see #250 for more details +* Removed a forgotten `[patch.crates-io]` section that was using `esp-idf-sys` from `master` (low impact, only relevant when building the examples) +* Clippy fixes + +## [0.41.0] - 2023-05-13 (Yanked due to #250, see above) + +* MSRV 1.66 (but MSRV 1.70 necessary if `nightly` is enabled) +* Support for new chips: esp32c2, esp32h2, esp32c6 and future proofed for esp32c5 and esp32p4 +* Support for ESP IDF 5.0, 5.1 and 5.2 (master) +* Support for `embedded-hal` 1.0-alpha.10 and `embedded-hal-async` 0.2.0-alpha.2 (API breakage in i2c and spi) +* Async GPIO native API with support for `embedded-hal-async` +* PCNT driver +* Alerts and Mode support in the TWAI driver +* Task Watchdog API +* Configurable interrupt levels in all drivers (minor API breakage in `SpiDriver`) +* `EspError::from_infallible` +* Auto-reload support in the timer driver + +## [0.40.1] - 2022-12-13 + +Fix the build when the ULP peripheral is enabled. + ## [0.40] - 2022-12-09 Rebase on top of `esp-idf-sys` 0.32: diff --git a/Cargo.toml b/Cargo.toml index 0ce5171d30f..6c217bbf26a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "esp-idf-hal" -version = "0.40.0" +version = "0.45.2" authors = ["sapir ", "Ivan Markov "] -edition = "2018" +edition = "2021" resolver = "2" categories = ["embedded", "hardware-support"] keywords = ["hal", "idf", "esp-idf", "esp32"] @@ -10,34 +10,63 @@ description = "A Hardware abstraction layer for Espressif's ESP family of microc repository = "https://github.com/esp-rs/esp-idf-hal" license = "MIT OR Apache-2.0" readme = "README.md" -documentation = "https://esp-rs.github.io/esp-idf-hal/" -rust-version = "1.61" +links = "esp_idf_hal" +build = "build.rs" +documentation = "https://docs.esp-rs.org/esp-idf-hal/" +rust-version = "1.75" + +[lib] +harness = false [features] -default = ["std", "alloc", "esp-idf-sys"] -std = ["alloc", "esp-idf-sys/std", "edge-executor?/std"] +default = ["std", "binstart"] +std = ["alloc", "esp-idf-sys/std"] alloc = [] -riscv-ulp-hal = [] +nightly = [] +experimental = [] +wake-from-isr = [] # Only enable if you plan to use the `edge-executor` crate +embassy-sync = [] # For now, the dependecy on the `embassy-sync` crate is non-optional, but this might change in future +# Temporary, until (https://github.com/espressif/esp-idf/issues/13938) is addressed +# - When enabled, the code for the legacy ADC oneshot driver will be compiled; +# - When not enabled (default) the code for the new ADC oneshot driver will be compiled; +# - Since we don't wrap the legacy _continuous_ ADC driver, the new _continuous_ ADC driver is always compiled. +adc-oneshot-legacy = [] +# Similar to adc-oneshot-legacy +# - When enabled (default), the code for the legacy RMT TX/RX driver will be compiled. +# - When disabled the code for the new onewire RMT driver will be compiled. +rmt-legacy = [] +# Propagated esp-idf-sys features +native = ["esp-idf-sys/native"] +pio = ["esp-idf-sys/pio"] +alloc_handler = ["esp-idf-sys/alloc_handler"] +panic_handler = ["esp-idf-sys/panic_handler"] +binstart = ["esp-idf-sys/binstart"] +libstart = ["esp-idf-sys/libstart"] [dependencies] -nb = "1.0.0" +nb = "1" embedded-can = "0.4.1" -embedded-hal = "=1.0.0-alpha.9" +embedded-hal = "1" embedded-hal-0-2 = { package = "embedded-hal", version = "0.2.7", features = ["unproven"] } -embedded-hal-nb = "=1.0.0-alpha.1" -esp-idf-sys = { version = "0.32", optional = true, default-features = false, features = ["native"] } -critical-section = { version = "1.1", optional = true } -heapless = "0.7" -embassy-sync = { version = "0.1", optional = true } -edge-executor = { version = "0.3", optional = true, default-features = false } +embedded-hal-nb = "1" +embedded-hal-async = "1" +embedded-io = "0.6" +embedded-io-async = "0.6" +esp-idf-sys = { version = "0.36", default-features = false } +critical-section = { version = "1.1.1", optional = true, features = ["restore-state-none"] } +heapless = "0.8" +num_enum = { version = "0.7", default-features = false } +enumset = { version = "1.1.4", default-features = false } +log = { version = "0.4", default-features = false } +atomic-waker = { version = "1.1.1", default-features = false } +embassy-sync = { version = "0.6" } [build-dependencies] -embuild = "0.31" -anyhow = "1" +embuild = "0.33" [dev-dependencies] anyhow = "1" -esp-idf-sys = { version = "0.32", features = ["native", "binstart"] } +esp-idf-sys = { version = "0.36", features = ["binstart"] } mipidsi = "0.5.0" display-interface-spi = "0.4.1" embedded-graphics = "0.7.1" diff --git a/README.md b/README.md index 3df92b023a2..bf7ba186a23 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,63 @@ -# An [embedded-hal](https://github.com/rust-embedded/embedded-hal) implementation for ESP32[-XX] + ESP-IDF +# Safe Rust wrappers for the drivers in the [ESP IDF SDK](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/) [![CI](https://github.com/esp-rs/esp-idf-hal/actions/workflows/ci.yml/badge.svg)](https://github.com/esp-rs/esp-idf-hal/actions/workflows/ci.yml) -![crates.io](https://img.shields.io/crates/v/esp-idf-hal.svg) +[![crates.io](https://img.shields.io/crates/v/esp-idf-hal.svg)](https://crates.io/crates/esp-idf-hal) [![Documentation](https://img.shields.io/badge/docs-esp--rs-brightgreen)](https://esp-rs.github.io/esp-idf-hal/esp_idf_hal/index.html) +[![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&color=BEC5C9&logo=matrix)](https://matrix.to/#/#esp-rs:matrix.org) +[![Wokwi](https://img.shields.io/endpoint?url=https%3A%2F%2Fwokwi.com%2Fbadge%2Fclick-to-simulate.json)](https://wokwi.com/projects/332188235906155092) -* This crate is intended for usage in ESP32[-XX] embedded projects that utilize and link with the **ESP-IDF SDK**. -* For embedded projects that don't need Rust STD support, WiFi or BLE (and thus don't link with the ESP-IDF SDK), please check [esp-hal](https://github.com/esp-rs/esp-hal). +## Highlights + +* Implements the traits of [embedded-hal](https://github.com/rust-embedded/embedded-hal) `V0.2` as well as those of `V1.0` - both blocking and async +* Supports almost all ESP IDF drivers: GPIO, SPI, I2C, TIMER, PWM, I2S, UART, etc. +* Blocking and `async` mode for each driver (`async` support in progress) +* Re-exports `esp-idf-sys` as `esp_idf_hal::sys` + +**You might want to also check out the ESP IDF [Services](https://github.com/esp-rs/esp-idf-svc) wrappers, and the raw bindings to ESP IDF in the [`esp-idf-sys`](https://github.com/esp-rs/esp-idf-sys) crate!** + +(For baremetal Rust ESP projects please check [`esp-hal`](https://github.com/esp-rs/esp-hal).) + +## Build Prerequisites + +Follow the [Prerequisites](https://github.com/esp-rs/esp-idf-template#prerequisites) section in the `esp-idf-template` crate. + +## Examples + +The examples could be built and flashed conveniently with [`cargo-espflash`](https://github.com/esp-rs/espflash/). To run e.g. the `ledc_simple` on an e.g. ESP32-C3: +(Swap the Rust target and example name with the target corresponding for your ESP32 MCU and with the example you would like to build) + +with `cargo-espflash`: +```sh +$ MCU=esp32c3 cargo espflash flash --target riscv32imc-esp-espidf --example ledc_simple --monitor +``` + +| MCU | "--target" | +| --- | ------ | +| esp32c2 | riscv32imc-esp-espidf | +| esp32c3| riscv32imc-esp-espidf | +| esp32c6| riscv32imac-esp-espidf | +| esp32h2 | riscv32imac-esp-espidf | +| esp32p4 | riscv32imafc-esp-espidf | +| esp32 | xtensa-esp32-espidf | +| esp32s2 | xtensa-esp32s2-espidf | +| esp32s3 | xtensa-esp32s3-espidf | + +In order to run the examples on other chips you will most likely need to adapt at least the used pins. + +## Setting up a "Hello, world!" binary crate with ESP IDF + +Use the [`esp-idf-template`](https://github.com/esp-rs/esp-idf-template) project. Everything would be arranged and built for you automatically - no need to manually clone the ESP IDF repository. + +## More information For more information, check out: * The [Rust on ESP Book](https://esp-rs.github.io/book/) -* The [embedded-hal](https://github.com/rust-embedded/embedded-hal) project -* The [esp-hal](https://github.com/esp-rs/esp-hal) project -* The [esp-idf-template](https://github.com/esp-rs/esp-idf-template) project -* The [esp-idf-sys](https://github.com/esp-rs/esp-idf-sys) project -* The [embedded-svc](https://github.com/esp-rs/embedded-svc) project -* The [esp-idf-svc](https://github.com/esp-rs/esp-idf-svc) project +* The [ESP Embedded Training](https://github.com/esp-rs/std-training) +* The [`esp-idf-template`](https://github.com/esp-rs/esp-idf-template) project +* The [`embedded-hal`](https://github.com/rust-embedded/embedded-hal) project +* The [`esp-idf-svc`](https://github.com/esp-rs/esp-idf-svc) project +* The [`embedded-svc`](https://github.com/esp-rs/embedded-svc) project +* The [`esp-idf-sys`](https://github.com/esp-rs/esp-idf-sys) project * The [Rust for Xtensa toolchain](https://github.com/esp-rs/rust-build) * The [Rust-with-STD demo](https://github.com/ivmarkov/rust-esp32-std-demo) project @@ -27,17 +70,11 @@ Please refer to the table below to determine the pins which are not recommended | Chip | GPIOs | | ------------ | :----------------: | | **ESP32** | 6 - 11, 16 - 17 | +| **ESP32-C2** | 12 - 17 | | **ESP32-C3** | 12 - 17 | +| **ESP32-C6** | 24 - 30 | +| **ESP32-H2** | 15 - 21 | | **ESP32-S2** | 26 - 32 | | **ESP32-S3** | 26 - 32, 33 - 37\* | _\* When using Octal Flash and/or Octal PSRAM_ - -## Examples - -The examples could be built and flashed conveniently with [`cargo-espflash`](https://github.com/esp-rs/espflash/). To run `ledc-simple` on an ESP32-C3: -``` -$ cargo espflash --release --target riscv32imc-esp-espidf --example ledc-simple --monitor /dev/ttyUSB0 -``` - -In order to run the examples on other chips you will most likely need to adapt at least the used pins. diff --git a/build.rs b/build.rs index f13628e59e2..f0f0a39dd52 100644 --- a/build.rs +++ b/build.rs @@ -1,33 +1,4 @@ -#[cfg(not(feature = "riscv-ulp-hal"))] -fn main() -> anyhow::Result<()> { - embuild::build::CfgArgs::output_propagated("ESP_IDF")?; - - // Will not be available when built with a CMake-first or a PIO-first build - // We need to output these only when building the examples' binaries anyway - if let Ok(args) = embuild::build::LinkArgs::try_from_env("ESP_IDF") { - args.output(); - } - - Ok(()) -} - -#[cfg(feature = "riscv-ulp-hal")] fn main() { - println!("cargo:rustc-cfg=esp32s2"); - - let riscv_ulp_dir = std::env::current_dir().unwrap().join("riscv-ulp"); - println!("cargo:rustc-link-search={}", riscv_ulp_dir.display()); - - println!( - "cargo:rerun-if-changed={}", - riscv_ulp_dir.join("libriscv_ulp_start.a").display() - ); - println!( - "cargo:rerun-if-changed={}", - riscv_ulp_dir.join("riscv_ulp_link_base.x").display() - ); - println!( - "cargo:rerun-if-changed={}", - riscv_ulp_dir.join("riscv_ulp_link_default.x").display() - ); + embuild::espidf::sysenv::relay(); + embuild::espidf::sysenv::output(); // Only necessary for building the examples } diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000000..493dbc02a83 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +future-size-threshold = 900 diff --git a/espflash.toml b/espflash.toml new file mode 100644 index 00000000000..d22b92e7795 --- /dev/null +++ b/espflash.toml @@ -0,0 +1 @@ +partition_table = "partitions.csv" diff --git a/examples/adc.rs b/examples/adc.rs index 3e1c0e5b3c8..bdd2ec21e9b 100644 --- a/examples/adc.rs +++ b/examples/adc.rs @@ -1,24 +1,22 @@ //! ADC example, reading a value form a pin and printing it on the terminal //! -use esp_idf_sys::{self as _}; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported +#![allow(unknown_lints)] +#![allow(unexpected_cfgs)] + +use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported use std::thread; use std::time::Duration; -use esp_idf_hal::adc::config::Config; -use esp_idf_hal::adc::Atten11dB; -use esp_idf_hal::adc::*; -use esp_idf_hal::peripherals::Peripherals; - -#[cfg(not(esp32))] -use esp_idf_hal::gpio::Gpio4; - -#[cfg(esp32)] -use esp_idf_hal::gpio::Gpio12; - +#[cfg(any(feature = "adc-oneshot-legacy", esp_idf_version_major = "4"))] fn main() -> anyhow::Result<()> { - let peripherals = Peripherals::take().unwrap(); + use esp_idf_hal::adc::config::Config; + use esp_idf_hal::adc::*; + use esp_idf_hal::peripherals::Peripherals; + use esp_idf_hal::sys::adc_atten_t; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported + + let peripherals = Peripherals::take()?; #[cfg(not(esp32))] let mut adc = AdcDriver::new(peripherals.adc1, &Config::new().calibration(true))?; @@ -26,19 +24,30 @@ fn main() -> anyhow::Result<()> { #[cfg(esp32)] let mut adc = AdcDriver::new(peripherals.adc2, &Config::new().calibration(true))?; + const ATTENUATION: adc_atten_t = attenuation::DB_12; + // configuring pin to analog read, you can regulate the adc input voltage range depending on your need // for this example we use the attenuation of 11db which sets the input voltage range to around 0-3.6V #[cfg(not(esp32))] - let mut adc_pin: esp_idf_hal::adc::AdcChannelDriver<'_, Gpio4, Atten11dB<_>> = + let mut adc_pin: esp_idf_hal::adc::AdcChannelDriver<{ ATTENUATION }, _> = AdcChannelDriver::new(peripherals.pins.gpio4)?; #[cfg(esp32)] - let mut adc_pin: esp_idf_hal::adc::AdcChannelDriver<'_, Gpio12, Atten11dB<_>> = + let mut adc_pin: esp_idf_hal::adc::AdcChannelDriver<{ ATTENUATION }, _> = AdcChannelDriver::new(peripherals.pins.gpio12)?; loop { // you can change the sleep duration depending on how often you want to sample thread::sleep(Duration::from_millis(10)); - println!("ADC value: {}", adc.read(&mut adc_pin).unwrap()); + println!("ADC value: {}", adc.read(&mut adc_pin)?); + } +} + +#[cfg(not(any(feature = "adc-oneshot-legacy", esp_idf_version_major = "4")))] +fn main() -> anyhow::Result<()> { + println!("This example requires feature `adc-oneshot-legacy` enabled or using ESP-IDF v4.4.X"); + + loop { + thread::sleep(Duration::from_millis(1000)); } } diff --git a/examples/adc_oneshot.rs b/examples/adc_oneshot.rs new file mode 100644 index 00000000000..7181ee00510 --- /dev/null +++ b/examples/adc_oneshot.rs @@ -0,0 +1,54 @@ +//! ADC oneshot example, reading a value form a pin and printing it on the terminal +//! requires ESP-IDF v5.0 or newer + +#![allow(unknown_lints)] +#![allow(unexpected_cfgs)] + +use std::thread; +use std::time::Duration; + +#[cfg(not(any(feature = "adc-oneshot-legacy", esp_idf_version_major = "4")))] +fn main() -> anyhow::Result<()> { + use esp_idf_hal::adc::attenuation::DB_12; + use esp_idf_hal::adc::oneshot::config::AdcChannelConfig; + use esp_idf_hal::adc::oneshot::*; + use esp_idf_hal::peripherals::Peripherals; + + let peripherals = Peripherals::take()?; + + #[cfg(not(esp32))] + let adc = AdcDriver::new(peripherals.adc1)?; + + #[cfg(esp32)] + let adc = AdcDriver::new(peripherals.adc2)?; + + // configuring pin to analog read, you can regulate the adc input voltage range depending on your need + // for this example we use the attenuation of 11db which sets the input voltage range to around 0-3.6V + let config = AdcChannelConfig { + attenuation: DB_12, + ..Default::default() + }; + + #[cfg(not(esp32))] + let mut adc_pin = AdcChannelDriver::new(&adc, peripherals.pins.gpio2, &config)?; + + #[cfg(esp32)] + let mut adc_pin = AdcChannelDriver::new(&adc, peripherals.pins.gpio12, &config)?; + + loop { + // you can change the sleep duration depending on how often you want to sample + thread::sleep(Duration::from_millis(100)); + println!("ADC value: {}", adc.read(&mut adc_pin)?); + } +} + +#[cfg(any(feature = "adc-oneshot-legacy", esp_idf_version_major = "4"))] +fn main() -> anyhow::Result<()> { + println!( + "This example requires ESP-IDF v5.X or newer and feature `adc-oneshot-legacy` disabled" + ); + + loop { + thread::sleep(Duration::from_millis(1000)); + } +} diff --git a/examples/blinky.rs b/examples/blinky.rs index 31422f16df7..5d5f329fbb4 100644 --- a/examples/blinky.rs +++ b/examples/blinky.rs @@ -10,9 +10,9 @@ use esp_idf_hal::gpio::*; use esp_idf_hal::peripherals::Peripherals; fn main() -> anyhow::Result<()> { - esp_idf_sys::link_patches(); + esp_idf_hal::sys::link_patches(); - let peripherals = Peripherals::take().unwrap(); + let peripherals = Peripherals::take()?; let mut led = PinDriver::output(peripherals.pins.gpio4)?; loop { diff --git a/examples/blinky_async.rs b/examples/blinky_async.rs new file mode 100644 index 00000000000..343ad951e68 --- /dev/null +++ b/examples/blinky_async.rs @@ -0,0 +1,32 @@ +//! Blinks an LED +//! +//! This assumes that a LED is connected to GPIO4. +//! Depending on your target and the board you are using you should change the pin. +//! If your board doesn't have on-board LEDs don't forget to add an appropriate resistor. +//! + +use esp_idf_hal::gpio::*; +use esp_idf_hal::peripherals::Peripherals; +use esp_idf_hal::task::*; +use esp_idf_hal::timer::*; + +fn main() -> anyhow::Result<()> { + esp_idf_hal::sys::link_patches(); + + let peripherals = Peripherals::take()?; + + let mut led = PinDriver::output(peripherals.pins.gpio4)?; + let mut timer = TimerDriver::new(peripherals.timer00, &TimerConfig::new())?; + + block_on(async { + loop { + led.set_high()?; + + timer.delay(timer.tick_hz()).await?; + + led.set_low()?; + + timer.delay(timer.tick_hz()).await?; + } + }) +} diff --git a/examples/button.rs b/examples/button.rs index c1326592fe2..cc74d800bfb 100644 --- a/examples/button.rs +++ b/examples/button.rs @@ -12,9 +12,9 @@ use esp_idf_hal::gpio::*; use esp_idf_hal::peripherals::Peripherals; fn main() -> anyhow::Result<()> { - esp_idf_sys::link_patches(); + esp_idf_hal::sys::link_patches(); - let peripherals = Peripherals::take().unwrap(); + let peripherals = Peripherals::take()?; let mut led = PinDriver::output(peripherals.pins.gpio4)?; let mut button = PinDriver::input(peripherals.pins.gpio9)?; diff --git a/examples/button_async.rs b/examples/button_async.rs new file mode 100644 index 00000000000..b3f0caab06b --- /dev/null +++ b/examples/button_async.rs @@ -0,0 +1,35 @@ +//! Turn an LED on/off depending on the state of a button +//! +//! This assumes that a LED is connected to GPIO4. +//! Additionally this assumes a button connected to GPIO9. +//! On an ESP32C3 development board this is the BOOT button. +//! +//! Depending on your target and the board you are using you should change the pins. +//! If your board doesn't have on-board LEDs don't forget to add an appropriate resistor. + +use esp_idf_hal::gpio::*; +use esp_idf_hal::peripherals::Peripherals; +use esp_idf_hal::task::*; + +fn main() -> anyhow::Result<()> { + esp_idf_hal::sys::link_patches(); + + let peripherals = Peripherals::take()?; + + let mut led = PinDriver::output(peripherals.pins.gpio4)?; + let mut button = PinDriver::input(peripherals.pins.gpio9)?; + + button.set_pull(Pull::Down)?; + + block_on(async { + loop { + button.wait_for_high().await?; + + led.set_high()?; + + button.wait_for_low().await?; + + led.set_low()?; + } + }) +} diff --git a/examples/button_interrupt.rs b/examples/button_interrupt.rs new file mode 100644 index 00000000000..97c7bf0ed67 --- /dev/null +++ b/examples/button_interrupt.rs @@ -0,0 +1,60 @@ +//! Toggle an LED on/off with a button +//! +//! This assumes that a LED is connected to GPIO4. +//! Additionally this assumes a button connected to GPIO9. +//! On an ESP32C3 development board this is the BOOT button. +//! +//! Depending on your target and the board you are using you should change the pins. +//! If your board doesn't have on-board LEDs don't forget to add an appropriate resistor. + +use core::num::NonZero; +use esp_idf_hal::delay::FreeRtos; +use esp_idf_hal::gpio::{InterruptType, PinDriver, Pull}; +use esp_idf_hal::peripherals::Peripherals; +use esp_idf_hal::task::notification::Notification; + +fn main() -> anyhow::Result<()> { + esp_idf_hal::sys::link_patches(); + + let peripherals = Peripherals::take()?; + let mut led = PinDriver::output(peripherals.pins.gpio4)?; + let mut button = PinDriver::input(peripherals.pins.gpio9)?; + + button.set_pull(Pull::Down)?; + button.set_interrupt_type(InterruptType::PosEdge)?; + + let mut led_state = false; + led.set_low()?; + + loop { + // prepare communication channel + let notification = Notification::new(); + let waker = notification.notifier(); + + // register interrupt callback, here it's a closure on stack + unsafe { + button + .subscribe_nonstatic(move || { + waker.notify(NonZero::new(1).unwrap()); + }) + .unwrap(); + } + + // enable interrupt, will be automatically disabled after being triggered + button.enable_interrupt()?; + // block until notified + notification.wait_any(); + + // toggle the LED + if led_state { + led.set_low()?; + led_state = false; + } else { + led.set_high()?; + led_state = true; + } + + // debounce + FreeRtos::delay_ms(200); + } +} diff --git a/examples/i2c_master_slave.rs b/examples/i2c_master_slave.rs new file mode 100644 index 00000000000..2759b3e606d --- /dev/null +++ b/examples/i2c_master_slave.rs @@ -0,0 +1,179 @@ +//! Test communication between a master and a slave I2C on the same device +//! +//! Wiring required, but can be changed in main(): +//! - GPIO21 to GPIO18 +//! - GPIO22 to GPIO19 +//! +//! ESP32-C2/C3 does not have two I2C peripherals, so this example will not work. +//! +//! Description: +//! Consists of three parts: +//! 1. Simple master write, master writes 8 bytes and print out what slave receives +//! 2. Simple master read, master read 8 bytes and print out. +//! 3. Read/write register, write a value to a register addr and read it back. +//! +#![allow(unused)] +#![allow(unknown_lints)] +#![allow(unexpected_cfgs)] + +use esp_idf_hal::delay::BLOCK; +use esp_idf_hal::gpio::{AnyIOPin, InputPin, OutputPin}; +use esp_idf_hal::i2c::{I2c, I2cConfig, I2cDriver, I2cSlaveConfig, I2cSlaveDriver}; +use esp_idf_hal::peripheral::Peripheral; +use esp_idf_hal::peripherals::Peripherals; +use esp_idf_hal::prelude::*; +use esp_idf_hal::units::Hertz; + +const SLAVE_ADDR: u8 = 0x22; +const SLAVE_BUFFER_SIZE: usize = 128; + +fn i2c_master_init<'d>( + i2c: impl Peripheral

+ 'd, + sda: AnyIOPin, + scl: AnyIOPin, + baudrate: Hertz, +) -> anyhow::Result> { + let config = I2cConfig::new().baudrate(baudrate); + let driver = I2cDriver::new(i2c, sda, scl, &config)?; + Ok(driver) +} + +fn i2c_slave_init<'d>( + i2c: impl Peripheral

+ 'd, + sda: AnyIOPin, + scl: AnyIOPin, + buflen: usize, + slave_addr: u8, +) -> anyhow::Result> { + let config = I2cSlaveConfig::new() + .rx_buffer_length(buflen) + .tx_buffer_length(buflen); + let driver = I2cSlaveDriver::new(i2c, sda, scl, slave_addr, &config)?; + Ok(driver) +} + +#[cfg(not(esp32))] +fn main() -> anyhow::Result<()> { + println!("Test only configured for ESP32"); + Ok(()) +} + +#[cfg(esp32)] +fn main() -> anyhow::Result<()> { + esp_idf_hal::sys::link_patches(); + + println!("Starting I2C self test"); + + let peripherals = Peripherals::take()?; + + let mut i2c_master = i2c_master_init( + peripherals.i2c0, + peripherals.pins.gpio21.into(), + peripherals.pins.gpio22.into(), + 100.kHz().into(), + )?; + + let mut i2c_slave = i2c_slave_init( + peripherals.i2c1, + peripherals.pins.gpio18.into(), + peripherals.pins.gpio19.into(), + SLAVE_BUFFER_SIZE, + SLAVE_ADDR, + )?; + + let tx_buf: [u8; 8] = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]; + + println!("-------- TESTING SIMPLE MASTER WRITE --------"); + i2c_master.write(SLAVE_ADDR, &tx_buf, BLOCK)?; + + let mut rx_buf: [u8; 8] = [0; 8]; + match i2c_slave.read(&mut rx_buf, BLOCK) { + Ok(_) => println!("Master send {:?} Slave receives {:?}", tx_buf, rx_buf), + Err(e) => println!("Error: {:?}", e), + } + + println!("-------- TESTING SIMPLE MASTER READ --------"); + i2c_slave.write(&tx_buf, BLOCK)?; // fill slave buffer before master tries to read + + let mut rx_buf: [u8; 8] = [0; 8]; + match i2c_master.read(SLAVE_ADDR, &mut rx_buf, BLOCK) { + Ok(_) => println!("Slave send {:?} Master receives {:?}", tx_buf, rx_buf), + Err(e) => println!("Error: {:?}", e), + } + + println!("-------- TESTING READ/WRITE REGISTER --------"); + + let thread0 = std::thread::Builder::new() + .stack_size(7000) + .spawn(move || { + let mut data: [u8; 256] = [0; 256]; + loop { + let mut reg_addr: [u8; 1] = [0]; + let res = i2c_slave.read(&mut reg_addr, BLOCK); + if res.is_err() { + println!( + "SLAVE: failed to read register address from master: Error: {:?}", + res + ); + continue; + } + let mut rx_data: [u8; 1] = [0]; + match i2c_slave.read(&mut rx_data, 0) { + Ok(_) => { + println!( + "SLAVE: write operation {:#04x} to reg addr {:#04x}", + rx_data[0], reg_addr[0] + ); + data[reg_addr[0] as usize] = rx_data[0]; + } + Err(_) => { + let d = data[reg_addr[0] as usize]; + println!( + "SLAVE: read operation {:#04x} from reg addr {:#04x}", + d, reg_addr[0] + ); + i2c_slave.write(&[d], BLOCK).unwrap(); + } + } + } + })?; + + // allow thread to run + std::thread::yield_now(); + + let reg_addr: u8 = 0x05; + let new_value: u8 = 0x42; + + println!("MASTER: read reg addr {:#04x}", reg_addr); + let mut rx_buf: [u8; 1] = [0; 1]; + // TODO: make write_read work + i2c_master.write(SLAVE_ADDR, &[reg_addr], BLOCK)?; + i2c_master.read(SLAVE_ADDR, &mut rx_buf, BLOCK)?; + println!( + "MASTER: value of reg addr {:#04x} is {:#04x}", + reg_addr, rx_buf[0] + ); + + println!("---------------------"); + + println!( + "MASTER: write {:#04x} to reg addr {:#04x}", + new_value, reg_addr + ); + i2c_master.write(SLAVE_ADDR, &[reg_addr, new_value], BLOCK)?; + + println!("---------------------"); + + println!("MASTER: read reg addr {:#04x}", reg_addr); + let mut rx_buf: [u8; 1] = [0; 1]; + // TODO: make write_read work + i2c_master.write(SLAVE_ADDR, &[reg_addr], BLOCK)?; + i2c_master.read(SLAVE_ADDR, &mut rx_buf, BLOCK)?; + println!( + "MASTER: value of reg addr {:#04x} is {:#04x}", + reg_addr, rx_buf[0] + ); + + thread0.join().unwrap(); + Ok(()) +} diff --git a/examples/i2c_ssd1306.rs b/examples/i2c_ssd1306.rs index 0de4e0e079d..87df5b498ab 100644 --- a/examples/i2c_ssd1306.rs +++ b/examples/i2c_ssd1306.rs @@ -17,9 +17,9 @@ use esp_idf_hal::prelude::*; const SSD1306_ADDRESS: u8 = 0x3c; fn main() -> anyhow::Result<()> { - esp_idf_sys::link_patches(); + esp_idf_hal::sys::link_patches(); - let peripherals = Peripherals::take().unwrap(); + let peripherals = Peripherals::take()?; let i2c = peripherals.i2c0; let sda = peripherals.pins.gpio5; let scl = peripherals.pins.gpio6; diff --git a/examples/ledc_fade.rs b/examples/ledc_fade.rs new file mode 100644 index 00000000000..5a68ed24679 --- /dev/null +++ b/examples/ledc_fade.rs @@ -0,0 +1,39 @@ +use esp_idf_hal::delay::FreeRtos; +use esp_idf_hal::ledc::{config::TimerConfig, LedcDriver, LedcTimerDriver}; +use esp_idf_hal::peripherals::Peripherals; +use esp_idf_hal::prelude::*; +use std::time::Duration; + +fn main() -> anyhow::Result<()> { + esp_idf_hal::sys::link_patches(); + + let peripherals = Peripherals::take()?; + + let timer_driver = LedcTimerDriver::new( + peripherals.ledc.timer0, + &TimerConfig::default().frequency(25.kHz().into()), + )?; + + let mut ledc_driver = LedcDriver::new( + peripherals.ledc.channel0, + timer_driver, + peripherals.pins.gpio7, + )?; + + for _ in 0..2 { + // Fade up over 2 seconds + ledc_driver.fade_with_time( + ledc_driver.get_max_duty(), + Duration::from_secs(2).as_millis() as i32, + true, + )?; + + // Fade down over 2 seconds + ledc_driver.fade_with_time(0, Duration::from_secs(2).as_millis() as i32, true)?; + } + + ledc_driver.set_duty(ledc_driver.get_max_duty() / 10)?; + FreeRtos::delay_ms(10000); + + Ok(()) +} diff --git a/examples/ledc-simple.rs b/examples/ledc_simple.rs similarity index 73% rename from examples/ledc-simple.rs rename to examples/ledc_simple.rs index fd4c5917070..fb7da2b4262 100644 --- a/examples/ledc-simple.rs +++ b/examples/ledc_simple.rs @@ -4,17 +4,18 @@ use esp_idf_hal::peripherals::Peripherals; use esp_idf_hal::prelude::*; fn main() -> anyhow::Result<()> { - esp_idf_sys::link_patches(); + esp_idf_hal::sys::link_patches(); println!("Configuring output channel"); - let peripherals = Peripherals::take().unwrap(); - let config = config::TimerConfig::new().frequency(25.kHz().into()); + let peripherals = Peripherals::take()?; let mut channel = LedcDriver::new( peripherals.ledc.channel0, - LedcTimerDriver::new(peripherals.ledc.timer0, &config)?, + LedcTimerDriver::new( + peripherals.ledc.timer0, + &config::TimerConfig::new().frequency(25.kHz().into()), + )?, peripherals.pins.gpio4, - &config, )?; println!("Starting duty-cycle loop"); diff --git a/examples/ledc-threads.rs b/examples/ledc_threads.rs similarity index 81% rename from examples/ledc-threads.rs rename to examples/ledc_threads.rs index 91edbc9429e..d0946a030fa 100644 --- a/examples/ledc-threads.rs +++ b/examples/ledc_threads.rs @@ -1,4 +1,5 @@ -use std::{sync::Arc, time::Duration}; +use std::rc::Rc; +use std::time::Duration; use embedded_hal_0_2::PwmPin; @@ -9,25 +10,19 @@ use esp_idf_hal::prelude::*; const CYCLES: usize = 3; fn main() -> anyhow::Result<()> { - esp_idf_sys::link_patches(); + esp_idf_hal::sys::link_patches(); println!("Setting up PWM output channels"); - let peripherals = Peripherals::take().unwrap(); + let peripherals = Peripherals::take()?; let config = config::TimerConfig::new().frequency(25.kHz().into()); - let timer = Arc::new(LedcTimerDriver::new(peripherals.ledc.timer0, &config)?); + let timer = Rc::new(LedcTimerDriver::new(peripherals.ledc.timer0, &config)?); let channel0 = LedcDriver::new( peripherals.ledc.channel0, timer.clone(), peripherals.pins.gpio4, - &config, - )?; - let channel1 = LedcDriver::new( - peripherals.ledc.channel1, - timer, - peripherals.pins.gpio5, - &config, )?; + let channel1 = LedcDriver::new(peripherals.ledc.channel1, timer, peripherals.pins.gpio5)?; println!("Spawning PWM threads"); diff --git a/examples/mcpwm-simple.rs b/examples/mcpwm-simple.rs index 5921d82d59d..ccdd3014a1f 100644 --- a/examples/mcpwm-simple.rs +++ b/examples/mcpwm-simple.rs @@ -58,8 +58,6 @@ #[cfg(all(any(esp32, esp32s3), esp_idf_version_major = "5"))] fn main() -> anyhow::Result<()> { - use embedded_hal::delay::DelayUs; - use esp_idf_hal::delay::FreeRtos; use esp_idf_hal::mcpwm::{OperatorConfig, TimerConfig, TimerDriver}; use esp_idf_hal::prelude::Peripherals; @@ -98,7 +96,7 @@ fn main() -> anyhow::Result<()> { operator.set_compare_value_x(duty)?; // In this configuration this controls the duty on pin4 operator.set_compare_value_y(period_ticks - duty)?; // and this controls pin5 - FreeRtos.delay_ms(10)?; + FreeRtos::delay_ms(10); } unreachable!() diff --git a/examples/pcnt_rotary_encoder.rs b/examples/pcnt_rotary_encoder.rs new file mode 100644 index 00000000000..f7398553ad8 --- /dev/null +++ b/examples/pcnt_rotary_encoder.rs @@ -0,0 +1,146 @@ +//! PCNT decoding a rotary encoder +//! +//! To try this out, connect a rotary encoder to pins 5 and 6, the common should be grounded +//! +//! Note that PCNT only track a singed 16bit value. We use interrupts to detect a LOW and HIGH +//! threshold and track how much that accounts for and provide an i32 value result +//! + +#![allow(unknown_lints)] +#![allow(unexpected_cfgs)] + +#[cfg(any(esp32, esp32s2, esp32s3))] +fn main() -> anyhow::Result<()> { + use anyhow::Context; + use encoder::Encoder; + use esp_idf_hal::delay::FreeRtos; + use esp_idf_hal::prelude::*; + + // 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_hal::sys::link_patches(); + + println!("setup pins"); + let peripherals = Peripherals::take().context("failed to take Peripherals")?; + let mut pin_a = peripherals.pins.gpio5; + let mut pin_b = peripherals.pins.gpio6; + println!("setup encoder"); + let encoder = Encoder::new(peripherals.pcnt0, &mut pin_a, &mut pin_b)?; + + let mut last_value = 0i32; + loop { + let value = encoder.get_value()?; + if value != last_value { + println!("value: {value}"); + last_value = value; + } + FreeRtos::delay_ms(100u32); + } +} + +#[cfg(not(any(esp32, esp32s2, esp32s3)))] +fn main() { + use esp_idf_hal::delay::FreeRtos; + println!("pcnt peripheral not supported on this device!"); + loop { + FreeRtos::delay_ms(100u32); + } +} + +#[cfg(any(esp32, esp32s2, esp32s3))] +// esp-idf encoder implementation using v4 pcnt api +mod encoder { + use std::cmp::min; + use std::sync::atomic::AtomicI32; + use std::sync::atomic::Ordering; + use std::sync::Arc; + + use esp_idf_hal::gpio::AnyInputPin; + use esp_idf_hal::gpio::InputPin; + use esp_idf_hal::pcnt::*; + use esp_idf_hal::peripheral::Peripheral; + use esp_idf_sys::EspError; + + const LOW_LIMIT: i16 = -100; + const HIGH_LIMIT: i16 = 100; + + pub struct Encoder<'d> { + unit: PcntDriver<'d>, + approx_value: Arc, + } + + impl<'d> Encoder<'d> { + pub fn new( + pcnt: impl Peripheral

+ 'd, + pin_a: impl Peripheral

+ 'd, + pin_b: impl Peripheral

+ 'd, + ) -> Result { + let mut unit = PcntDriver::new( + pcnt, + Some(pin_a), + Some(pin_b), + Option::::None, + Option::::None, + )?; + unit.channel_config( + PcntChannel::Channel0, + PinIndex::Pin0, + PinIndex::Pin1, + &PcntChannelConfig { + lctrl_mode: PcntControlMode::Reverse, + hctrl_mode: PcntControlMode::Keep, + pos_mode: PcntCountMode::Decrement, + neg_mode: PcntCountMode::Increment, + counter_h_lim: HIGH_LIMIT, + counter_l_lim: LOW_LIMIT, + }, + )?; + unit.channel_config( + PcntChannel::Channel1, + PinIndex::Pin1, + PinIndex::Pin0, + &PcntChannelConfig { + lctrl_mode: PcntControlMode::Reverse, + hctrl_mode: PcntControlMode::Keep, + pos_mode: PcntCountMode::Increment, + neg_mode: PcntCountMode::Decrement, + counter_h_lim: HIGH_LIMIT, + counter_l_lim: LOW_LIMIT, + }, + )?; + + unit.set_filter_value(min(10 * 80, 1023))?; + unit.filter_enable()?; + + let approx_value = Arc::new(AtomicI32::new(0)); + // unsafe interrupt code to catch the upper and lower limits from the encoder + // and track the overflow in `value: Arc` - I plan to use this for + // a wheeled robot's odomerty + unsafe { + let approx_value = approx_value.clone(); + unit.subscribe(move |status| { + let status = PcntEventType::from_repr_truncated(status); + if status.contains(PcntEvent::HighLimit) { + approx_value.fetch_add(HIGH_LIMIT as i32, Ordering::SeqCst); + } + if status.contains(PcntEvent::LowLimit) { + approx_value.fetch_add(LOW_LIMIT as i32, Ordering::SeqCst); + } + })?; + } + unit.event_enable(PcntEvent::HighLimit)?; + unit.event_enable(PcntEvent::LowLimit)?; + unit.counter_pause()?; + unit.counter_clear()?; + unit.counter_resume()?; + + Ok(Self { unit, approx_value }) + } + + pub fn get_value(&self) -> Result { + let value = + self.approx_value.load(Ordering::Relaxed) + self.unit.get_counter_value()? as i32; + Ok(value) + } + } +} diff --git a/examples/reset_reason.rs b/examples/reset_reason.rs new file mode 100644 index 00000000000..20c2c0bb58c --- /dev/null +++ b/examples/reset_reason.rs @@ -0,0 +1,25 @@ +//! This example demonstrates a how to ask for the reset reason and the wakeup reason. +use std::{thread, time}; + +use esp_idf_sys::{self as _}; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported + +fn main() -> anyhow::Result<()> { + esp_idf_sys::link_patches(); + + let wakeup_reason = esp_idf_hal::reset::WakeupReason::get(); + println!("Wakeup reason: {:?}", wakeup_reason); + + let reset_reason = esp_idf_hal::reset::ResetReason::get(); + println!("Reset reason: {:?}", reset_reason); + + thread::sleep(time::Duration::from_millis(1000)); + + let sleep_micros = 2_000_000; + unsafe { + esp_idf_sys::esp_sleep_enable_timer_wakeup(sleep_micros); + + println!("Going to deep sleep {} seconds", sleep_micros / 1_000_000); + esp_idf_sys::esp_deep_sleep_start(); + // Software reset! + } +} diff --git a/examples/rmt_morse_code.rs b/examples/rmt_morse_code.rs index 95ea01ddda2..3837fa6f75b 100644 --- a/examples/rmt_morse_code.rs +++ b/examples/rmt_morse_code.rs @@ -14,150 +14,177 @@ //! * Background sending. //! * Taking a [`Pin`] and [`Channel`] by ref mut, so that they can be used again later. //! -use esp_idf_hal::delay::Ets; -use esp_idf_hal::gpio::*; -use esp_idf_hal::peripheral::*; -use esp_idf_hal::peripherals::Peripherals; -use esp_idf_hal::rmt::config::{CarrierConfig, DutyPercent, Loop, TransmitConfig}; -use esp_idf_hal::rmt::*; -use esp_idf_hal::units::FromValueType; -fn main() -> anyhow::Result<()> { - esp_idf_sys::link_patches(); - - let peripherals = Peripherals::take().unwrap(); - let mut channel = peripherals.rmt.channel0; - let mut led = peripherals.pins.gpio17; - let stop = peripherals.pins.gpio16; - - let carrier = CarrierConfig::new() - .duty_percent(DutyPercent::new(50)?) - .frequency(611.Hz()); - let mut config = TransmitConfig::new() - .carrier(Some(carrier)) - .looping(Loop::Endless) - .clock_divider(255); - - let tx = send_morse_code(&mut channel, &mut led, &config, "HELLO ")?; +#![allow(unknown_lints)] +#![allow(unexpected_cfgs)] - let stop = PinDriver::input(stop)?; +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +fn main() -> anyhow::Result<()> { + example::main() +} - println!("Keep sending until pin {} is set low.", stop.pin()); +#[cfg(not(any(feature = "rmt-legacy", esp_idf_version_major = "4")))] +fn main() -> anyhow::Result<()> { + println!("This example requires feature `rmt-legacy` enabled or using ESP-IDF v4.4.X"); - while stop.is_high() { - Ets::delay_ms(100); + loop { + std::thread::sleep(std::time::Duration::from_millis(1000)); } +} - println!("Pin {} set to low. Stopped.", stop.pin()); +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +mod example { + use esp_idf_hal::units::FromValueType; + use esp_idf_hal::{ + delay::Ets, + gpio::{OutputPin, PinDriver}, + peripheral::Peripheral, + prelude::Peripherals, + rmt::{ + config::{CarrierConfig, DutyPercent, Loop, TransmitConfig}, + PinState, Pulse, PulseTicks, RmtChannel, TxRmtDriver, VariableLengthSignal, + }, + }; + + pub fn main() -> anyhow::Result<()> { + esp_idf_hal::sys::link_patches(); + + let peripherals = Peripherals::take()?; + let mut channel = peripherals.rmt.channel0; + let mut led = peripherals.pins.gpio17; + let stop = peripherals.pins.gpio16; + + let carrier = CarrierConfig::new() + .duty_percent(DutyPercent::new(50)?) + .frequency(611.Hz()); + let mut config = TransmitConfig::new() + .carrier(Some(carrier)) + .looping(Loop::Endless) + .clock_divider(255); + + let tx = send_morse_code(&mut channel, &mut led, &config, "HELLO ")?; + + let stop = PinDriver::input(stop)?; + + println!("Keep sending until pin {} is set low.", stop.pin()); + + while stop.is_high() { + Ets::delay_ms(100); + } - // Release pin and channel so we can use them again. - drop(tx); + println!("Pin {} set to low. Stopped.", stop.pin()); - // Wait so the messages don't get garbled. - Ets::delay_ms(3000); + // Release pin and channel so we can use them again. + drop(tx); - // Now send a single message and stop. - println!("Saying GOODBYE!"); - config.looping = Loop::None; - send_morse_code(channel, led, &config, "GOODBYE")?; + // Wait so the messages don't get garbled. + Ets::delay_ms(3000); - Ok(()) -} + // Now send a single message and stop. + println!("Saying GOODBYE!"); + config.looping = Loop::None; + send_morse_code(channel, led, &config, "GOODBYE")?; -fn send_morse_code<'d>( - channel: impl Peripheral

+ 'd, - led: impl Peripheral

+ 'd, - config: &TransmitConfig, - message: &str, -) -> anyhow::Result> { - println!("Sending morse message '{message}'."); - - let mut signal = VariableLengthSignal::new(); - let pulses = str_pulses(message); - // We've been collecting `Pulse` but `VariableLengthSignal` needs `&Pulse`: - let pulses: Vec<&Pulse> = pulses.iter().collect(); - signal.push(pulses)?; - - let mut tx = TxRmtDriver::new(channel, led, config)?; - tx.start(signal)?; - - // Return `tx` so we can release the pin and channel later. - Ok(tx) -} - -fn high() -> Pulse { - Pulse::new(PinState::High, PulseTicks::max()) -} + Ok(()) + } -fn low() -> Pulse { - Pulse::new(PinState::Low, PulseTicks::max()) -} + fn send_morse_code<'d>( + channel: impl Peripheral

+ 'd, + led: impl Peripheral

+ 'd, + config: &TransmitConfig, + message: &str, + ) -> anyhow::Result> { + println!("Sending morse message '{message}'."); + + let mut signal = VariableLengthSignal::new(); + let pulses = str_pulses(message); + // We've been collecting `Pulse` but `VariableLengthSignal` needs `&Pulse`: + let pulses: Vec<&Pulse> = pulses.iter().collect(); + signal.push(pulses)?; + + let mut tx = TxRmtDriver::new(channel, led, config)?; + tx.start(signal)?; + + // Return `tx` so we can release the pin and channel later. + Ok(tx) + } -enum Code { - Dot, - Dash, - WordGap, -} + enum Code { + Dot, + Dash, + WordGap, + } -impl Code { - pub fn push_pulse(&self, pulses: &mut Vec) { - match &self { - Code::Dot => pulses.extend_from_slice(&[high(), low()]), - Code::Dash => pulses.extend_from_slice(&[high(), high(), high(), low()]), - Code::WordGap => pulses.extend_from_slice(&[low(), low(), low(), low(), low(), low()]), + impl Code { + pub fn push_pulse(&self, pulses: &mut Vec) { + match &self { + Code::Dot => pulses.extend_from_slice(&[high(), low()]), + Code::Dash => pulses.extend_from_slice(&[high(), high(), high(), low()]), + Code::WordGap => { + pulses.extend_from_slice(&[low(), low(), low(), low(), low(), low()]) + } + } } } -} -fn find_codes(c: &char) -> &'static [Code] { - for (found, codes) in CODES.iter() { - if found == c { - return codes; - } + fn high() -> Pulse { + Pulse::new(PinState::High, PulseTicks::max()) } - &[] -} -fn str_pulses(s: &str) -> Vec { - let mut pulses = vec![]; - for c in s.chars() { - for code in find_codes(&c) { - code.push_pulse(&mut pulses); + fn low() -> Pulse { + Pulse::new(PinState::Low, PulseTicks::max()) + } + + fn find_codes(c: &char) -> &'static [Code] { + for (found, codes) in CODES.iter() { + if found == c { + return codes; + } } + &[] + } + + fn str_pulses(s: &str) -> Vec { + let mut pulses = vec![]; + for c in s.chars() { + for code in find_codes(&c) { + code.push_pulse(&mut pulses); + } - // Create a gap after each symbol. - pulses.push(low()); - pulses.push(low()); + // Create a gap after each symbol. + pulses.push(low()); + pulses.push(low()); + } + pulses } - pulses -} -const CODES: &[(char, &[Code])] = &[ - (' ', &[Code::WordGap]), - ('A', &[Code::Dot, Code::Dash]), - ('B', &[Code::Dash, Code::Dot, Code::Dot, Code::Dot]), - ('C', &[Code::Dash, Code::Dot, Code::Dash, Code::Dot]), - ('D', &[Code::Dash, Code::Dot, Code::Dot]), - ('E', &[Code::Dot]), - ('F', &[Code::Dot, Code::Dot, Code::Dash, Code::Dot]), - ('G', &[Code::Dash, Code::Dash, Code::Dot]), - ('H', &[Code::Dot, Code::Dot, Code::Dot, Code::Dot]), - ('I', &[Code::Dot, Code::Dot]), - ('J', &[Code::Dot, Code::Dash, Code::Dash, Code::Dash]), - ('K', &[Code::Dash, Code::Dot, Code::Dash]), - ('L', &[Code::Dot, Code::Dash, Code::Dot, Code::Dot]), - ('M', &[Code::Dash, Code::Dash]), - ('N', &[Code::Dash, Code::Dot]), - ('O', &[Code::Dash, Code::Dash, Code::Dash]), - ('P', &[Code::Dot, Code::Dash, Code::Dash, Code::Dot]), - ('Q', &[Code::Dash, Code::Dash, Code::Dot, Code::Dash]), - ('R', &[Code::Dot, Code::Dash, Code::Dot]), - ('S', &[Code::Dot, Code::Dot, Code::Dot]), - ('T', &[Code::Dash]), - ('U', &[Code::Dot, Code::Dot, Code::Dash]), - ('V', &[Code::Dot, Code::Dot, Code::Dot, Code::Dash]), - ('W', &[Code::Dot, Code::Dash, Code::Dash]), - ('X', &[Code::Dash, Code::Dot, Code::Dot, Code::Dash]), - ('Y', &[Code::Dash, Code::Dot, Code::Dash, Code::Dash]), - ('Z', &[Code::Dash, Code::Dash, Code::Dot, Code::Dot]), -]; + const CODES: &[(char, &[Code])] = &[ + (' ', &[Code::WordGap]), + ('A', &[Code::Dot, Code::Dash]), + ('B', &[Code::Dash, Code::Dot, Code::Dot, Code::Dot]), + ('C', &[Code::Dash, Code::Dot, Code::Dash, Code::Dot]), + ('D', &[Code::Dash, Code::Dot, Code::Dot]), + ('E', &[Code::Dot]), + ('F', &[Code::Dot, Code::Dot, Code::Dash, Code::Dot]), + ('G', &[Code::Dash, Code::Dash, Code::Dot]), + ('H', &[Code::Dot, Code::Dot, Code::Dot, Code::Dot]), + ('I', &[Code::Dot, Code::Dot]), + ('J', &[Code::Dot, Code::Dash, Code::Dash, Code::Dash]), + ('K', &[Code::Dash, Code::Dot, Code::Dash]), + ('L', &[Code::Dot, Code::Dash, Code::Dot, Code::Dot]), + ('M', &[Code::Dash, Code::Dash]), + ('N', &[Code::Dash, Code::Dot]), + ('O', &[Code::Dash, Code::Dash, Code::Dash]), + ('P', &[Code::Dot, Code::Dash, Code::Dash, Code::Dot]), + ('Q', &[Code::Dash, Code::Dash, Code::Dot, Code::Dash]), + ('R', &[Code::Dot, Code::Dash, Code::Dot]), + ('S', &[Code::Dot, Code::Dot, Code::Dot]), + ('T', &[Code::Dash]), + ('U', &[Code::Dot, Code::Dot, Code::Dash]), + ('V', &[Code::Dot, Code::Dot, Code::Dot, Code::Dash]), + ('W', &[Code::Dot, Code::Dash, Code::Dash]), + ('X', &[Code::Dash, Code::Dot, Code::Dot, Code::Dash]), + ('Y', &[Code::Dash, Code::Dot, Code::Dash, Code::Dash]), + ('Z', &[Code::Dash, Code::Dash, Code::Dot, Code::Dot]), + ]; +} diff --git a/examples/rmt_musical_buzzer.rs b/examples/rmt_musical_buzzer.rs index dee24a392e8..75ae4f43d80 100644 --- a/examples/rmt_musical_buzzer.rs +++ b/examples/rmt_musical_buzzer.rs @@ -4,166 +4,230 @@ //! //! Based off the ESP-IDF rmt musical buzzer example: //! https://github.com/espressif/esp-idf/blob/b092fa073047c957545a0ae9504f04972a8c6d74/examples/peripherals/rmt/musical_buzzer/main/musical_buzzer_example_main.c -use core::time::Duration; -use esp_idf_hal::delay::Ets; -use esp_idf_hal::peripherals::Peripherals; -use esp_idf_hal::rmt::config::{Loop, TransmitConfig}; -use esp_idf_hal::rmt::*; - -use notes::*; +#![allow(unknown_lints)] +#![allow(unexpected_cfgs)] +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] fn main() -> anyhow::Result<()> { - esp_idf_sys::link_patches(); + example::main() +} - let peripherals = Peripherals::take().unwrap(); - let led = peripherals.pins.gpio17; - let channel = peripherals.rmt.channel0; - let config = TransmitConfig::new().looping(Loop::Endless); - let mut tx: TxRmtDriver<'static> = TxRmtDriver::new(channel, led, &config)?; +#[cfg(not(any(feature = "rmt-legacy", esp_idf_version_major = "4")))] +fn main() -> anyhow::Result<()> { + println!("This example requires feature `rmt-legacy` enabled or using ESP-IDF v4.4.X"); loop { - play_song(&mut tx, ODE_TO_JOY)?; - Ets::delay_ms(3000); + std::thread::sleep(std::time::Duration::from_millis(1000)); } } -pub fn play_song(tx: &mut TxRmtDriver<'static>, song: &[NoteValue]) -> anyhow::Result<()> { - for note_value in song { - play_note(tx, note_value.note.0, note_value.duration)?; +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +mod example { + use std::time::Duration; + + use esp_idf_hal::delay::Ets; + use esp_idf_hal::{ + prelude::Peripherals, + rmt::{self, config::TransmitConfig, TxRmtDriver}, + units::Hertz, + }; + use notes::*; + + pub fn main() -> anyhow::Result<()> { + esp_idf_hal::sys::link_patches(); + + let peripherals = Peripherals::take()?; + let led = peripherals.pins.gpio17; + let channel = peripherals.rmt.channel0; + let config = TransmitConfig::new(); + let mut tx: TxRmtDriver<'static> = TxRmtDriver::new(channel, led, &config)?; + + loop { + play_song(&mut tx, ODE_TO_JOY)?; + Ets::delay_ms(3000); + } } - Ok(()) -} -pub fn play_note( - tx: &mut TxRmtDriver<'static>, - pitch: u16, - duration: Duration, -) -> anyhow::Result<()> { - // Calculate the frequency for a piezo buzzer. - let ticks_hz = tx.counter_clock()?; - let tick_count = (ticks_hz.0 as u128 / pitch as u128 / 2_u128) as u16; - let ticks = PulseTicks::new(tick_count)?; - - // Add high and low pulses for the tick duration. - let on = Pulse::new(PinState::High, ticks); - let off = Pulse::new(PinState::Low, ticks); - let mut signal = FixedLengthSignal::<1>::new(); - signal.set(0, &(on, off))?; - - // Play the note for the 80% of the duration. - tx.start(signal)?; - Ets::delay_ms((80 * duration.as_millis() / 100) as u32); - - // Small pause between notes, 20% of the specified duration. - tx.stop()?; - Ets::delay_ms((20 * duration.as_millis() / 100) as u32); - - Ok(()) -} + pub fn play_song(tx: &mut TxRmtDriver<'static>, song: &[NoteValue]) -> anyhow::Result<()> { + for note_value in song { + note_value.play(tx)?; + } + Ok(()) + } -#[derive(Debug)] -pub struct Note(u16); + pub struct NoteValueIter { + ticks: rmt::PulseTicks, + tone_cycles: u32, + pause_cycles: u32, + } -pub mod notes { - use crate::Note; + impl NoteValueIter { + fn new(ticks_per_sec: Hertz, note: &NoteValue) -> Self { + // Calculate the frequency for a piezo buzzer. + let dur_ms = note.duration.as_millis(); + let cycles_per_sec = note.note.0; // pitch + let ticks_per_cycle = ticks_per_sec.0 as u128 / cycles_per_sec as u128; + let ticks_per_half = (ticks_per_cycle / 2_u128) as u16; + let ticks = rmt::PulseTicks::new(ticks_per_half).unwrap(); + + let total_cycles = (cycles_per_sec as u128 * dur_ms / 1000_u128) as u32; + // Pause for the last 40ms of every note + let pause_cycles = (cycles_per_sec as u128 * 40_u128 / 1000_u128) as u32; + let tone_cycles = total_cycles - pause_cycles; + + Self { + ticks, + tone_cycles, + pause_cycles, + } + } + } + + impl std::iter::Iterator for NoteValueIter { + type Item = rmt::Symbol; + + // runs in ISR + fn next(&mut self) -> Option { + if self.tone_cycles + self.pause_cycles > 0 { + let high_state = if self.tone_cycles > 0 { + self.tone_cycles -= 1; + rmt::PinState::High + } else { + self.pause_cycles -= 1; + rmt::PinState::Low + }; + let level0 = rmt::Pulse::new(high_state, self.ticks); + let level1 = rmt::Pulse::new(rmt::PinState::Low, self.ticks); + Some(rmt::Symbol::new(level0, level1)) + } else { + None + } + } + } + + #[derive(Debug)] + pub struct Note(u16); #[allow(dead_code)] - pub const A4: Note = Note(440); - pub const AS4: Note = Note(466); - pub const B4: Note = Note(494); - pub const C5: Note = Note(523); - pub const CS5: Note = Note(554); - pub const D5: Note = Note(587); - pub const DS5: Note = Note(622); - pub const E5: Note = Note(659); - pub const F5: Note = Note(698); - pub const FS5: Note = Note(740); - pub const G5: Note = Note(784); - pub const GS5: Note = Note(831); - pub const A5: Note = Note(880); -} + pub mod notes { + use super::Note; + + pub const A4: Note = Note(440); + pub const AS4: Note = Note(466); + pub const B4: Note = Note(494); + pub const C5: Note = Note(523); + pub const CS5: Note = Note(554); + pub const D5: Note = Note(587); + pub const DS5: Note = Note(622); + pub const E5: Note = Note(659); + pub const F5: Note = Note(698); + pub const FS5: Note = Note(740); + pub const G5: Note = Note(784); + pub const GS5: Note = Note(831); + pub const A5: Note = Note(880); + } -#[derive(Debug)] -pub struct NoteValue { - note: Note, - duration: Duration, -} + #[derive(Debug)] + pub struct NoteValue { + note: Note, + duration: Duration, + } -macro_rules! n { - ($n: expr, $duration: expr) => { - NoteValue { - note: $n, - duration: Duration::from_millis($duration), + impl NoteValue { + pub fn play(&self, tx: &mut TxRmtDriver<'static>) -> anyhow::Result<()> { + let ticks_hz = tx.counter_clock()?; + tx.start_iter_blocking(self.iter(ticks_hz))?; + Ok(()) } - }; + + pub fn iter(&self, ticks_hz: Hertz) -> NoteValueIter { + NoteValueIter::new(ticks_hz, self) + } + } + + macro_rules! n { + ($n: expr, $duration: expr) => { + NoteValue { + note: $n, + duration: Duration::from_millis($duration), + } + }; + } + + const ODE_TO_JOY: &[NoteValue] = &[ + n!(FS5, 400), + n!(FS5, 600), + n!(G5, 400), + n!(A5, 400), + n!(A5, 400), + n!(G5, 400), + n!(FS5, 400), + n!(E5, 400), + n!(D5, 400), + n!(D5, 400), + n!(E5, 400), + n!(FS5, 400), + n!(FS5, 400), + n!(FS5, 200), + n!(E5, 200), + n!(E5, 800), + n!(FS5, 400), + n!(FS5, 600), + n!(G5, 400), + n!(A5, 400), + n!(A5, 400), + n!(G5, 400), + n!(FS5, 400), + n!(E5, 400), + n!(D5, 400), + n!(D5, 400), + n!(E5, 400), + n!(FS5, 400), + n!(E5, 400), + n!(E5, 200), + n!(D5, 200), + n!(D5, 800), + n!(E5, 400), + n!(E5, 400), + n!(FS5, 400), + n!(D5, 400), + n!(E5, 400), + n!(FS5, 200), + n!(G5, 200), + n!(FS5, 400), + n!(D5, 400), + n!(E5, 400), + n!(FS5, 200), + n!(G5, 200), + n!(FS5, 400), + n!(E5, 400), + n!(D5, 400), + n!(E5, 400), + n!(A4, 400), + n!(A4, 400), + n!(FS5, 400), + n!(FS5, 600), + n!(G5, 400), + n!(A5, 400), + n!(A5, 400), + n!(G5, 400), + n!(FS5, 400), + n!(E5, 400), + n!(D5, 400), + n!(D5, 400), + n!(E5, 400), + n!(FS5, 400), + n!(E5, 400), + n!(E5, 200), + n!(D5, 200), + n!(D5, 800), + ]; } -const ODE_TO_JOY: &[NoteValue] = &[ - n!(FS5, 400), - n!(FS5, 600), - n!(G5, 400), - n!(A5, 400), - n!(A5, 400), - n!(G5, 400), - n!(FS5, 400), - n!(E5, 400), - n!(D5, 400), - n!(D5, 400), - n!(E5, 400), - n!(FS5, 400), - n!(FS5, 400), - n!(FS5, 200), - n!(E5, 200), - n!(E5, 800), - n!(FS5, 400), - n!(FS5, 600), - n!(G5, 400), - n!(A5, 400), - n!(A5, 400), - n!(G5, 400), - n!(FS5, 400), - n!(E5, 400), - n!(D5, 400), - n!(D5, 400), - n!(E5, 400), - n!(FS5, 400), - n!(E5, 400), - n!(E5, 200), - n!(D5, 200), - n!(D5, 800), - n!(E5, 400), - n!(E5, 400), - n!(FS5, 400), - n!(D5, 400), - n!(E5, 400), - n!(FS5, 200), - n!(G5, 200), - n!(FS5, 400), - n!(D5, 400), - n!(E5, 400), - n!(FS5, 200), - n!(G5, 200), - n!(FS5, 400), - n!(E5, 400), - n!(D5, 400), - n!(E5, 400), - n!(A4, 400), - n!(A4, 400), - n!(FS5, 400), - n!(FS5, 600), - n!(G5, 400), - n!(A5, 400), - n!(A5, 400), - n!(G5, 400), - n!(FS5, 400), - n!(E5, 400), - n!(D5, 400), - n!(D5, 400), - n!(E5, 400), - n!(FS5, 400), - n!(E5, 400), - n!(E5, 200), - n!(D5, 200), - n!(D5, 800), -]; +// #[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +// #[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +// #[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +// #[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +// #[allow(dead_code)] +// #[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] diff --git a/examples/rmt_neopixel.rs b/examples/rmt_neopixel.rs index 1de75a8a23c..70e8171716e 100644 --- a/examples/rmt_neopixel.rs +++ b/examples/rmt_neopixel.rs @@ -1,7 +1,5 @@ //! A simple example to change colours of a WS2812/NeoPixel compatible LED. //! -//! It is set to pin 18 which some dev boards have connected to a compatible LED. -//! //! This example demonstrates the use of [`FixedLengthSignal`][crate::rmt::FixedLengthSignal] which //! lives on the stack and requires a known length before creating it. //! @@ -11,43 +9,122 @@ //! Datasheet (PDF) for a WS2812, which explains how the pulses are to be sent: //! https://cdn-shop.adafruit.com/datasheets/WS2812.pdf -use core::time::Duration; - -use esp_idf_hal::delay::Ets; -use esp_idf_hal::peripherals::Peripherals; -use esp_idf_hal::rmt::config::TransmitConfig; -use esp_idf_hal::rmt::*; +#![allow(unknown_lints)] +#![allow(unexpected_cfgs)] +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] fn main() -> anyhow::Result<()> { - esp_idf_sys::link_patches(); + example::main() +} - let peripherals = Peripherals::take().unwrap(); - let led = peripherals.pins.gpio18; - let channel = peripherals.rmt.channel0; - let config = TransmitConfig::new().clock_divider(1); - let mut tx = TxRmtDriver::new(channel, led, &config)?; +#[cfg(not(any(feature = "rmt-legacy", esp_idf_version_major = "4")))] +fn main() -> anyhow::Result<()> { + println!("This example requires feature `rmt-legacy` enabled or using ESP-IDF v4.4.X"); - let rgbs = [0xff0000, 0xffff00, 0x00ffff, 0x00ff00, 0xa000ff]; loop { - for rgb in rgbs { - let ticks_hz = tx.counter_clock()?; - let t0h = Pulse::new_with_duration(ticks_hz, PinState::High, &ns(350))?; - let t0l = Pulse::new_with_duration(ticks_hz, PinState::Low, &ns(800))?; - let t1h = Pulse::new_with_duration(ticks_hz, PinState::High, &ns(700))?; - let t1l = Pulse::new_with_duration(ticks_hz, PinState::Low, &ns(600))?; - - let mut signal = FixedLengthSignal::<24>::new(); - for i in 0..24 { - let bit = 2_u32.pow(i) & rgb != 0; - let (high_pulse, low_pulse) = if bit { (t1h, t1l) } else { (t0h, t0l) }; - signal.set(i as usize, &(high_pulse, low_pulse))?; + std::thread::sleep(std::time::Duration::from_millis(1000)); + } +} + +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +mod example { + use std::time::Duration; + + use anyhow::{bail, Result}; + use esp_idf_hal::{ + delay::FreeRtos, + prelude::Peripherals, + rmt::{config::TransmitConfig, FixedLengthSignal, PinState, Pulse, TxRmtDriver}, + }; + + pub fn main() -> Result<()> { + esp_idf_hal::sys::link_patches(); + + let peripherals = Peripherals::take()?; + // Onboard RGB LED pin + // ESP32-C3-DevKitC-02 gpio8, ESP32-C3-DevKit-RUST-1 gpio2 + let led = peripherals.pins.gpio2; + let channel = peripherals.rmt.channel0; + let config = TransmitConfig::new().clock_divider(1); + let mut tx = TxRmtDriver::new(channel, led, &config)?; + + // 3 seconds white at 10% brightness + neopixel(Rgb::new(25, 25, 25), &mut tx)?; + FreeRtos::delay_ms(3000); + + // infinite rainbow loop at 20% brightness + (0..360).cycle().try_for_each(|hue| { + FreeRtos::delay_ms(10); + let rgb = Rgb::from_hsv(hue, 100, 20)?; + neopixel(rgb, &mut tx) + }) + } + + fn neopixel(rgb: Rgb, tx: &mut TxRmtDriver) -> Result<()> { + let color: u32 = rgb.into(); + let ticks_hz = tx.counter_clock()?; + let (t0h, t0l, t1h, t1l) = ( + Pulse::new_with_duration(ticks_hz, PinState::High, &Duration::from_nanos(350))?, + Pulse::new_with_duration(ticks_hz, PinState::Low, &Duration::from_nanos(800))?, + Pulse::new_with_duration(ticks_hz, PinState::High, &Duration::from_nanos(700))?, + Pulse::new_with_duration(ticks_hz, PinState::Low, &Duration::from_nanos(600))?, + ); + let mut signal = FixedLengthSignal::<24>::new(); + for i in (0..24).rev() { + let p = 2_u32.pow(i); + let bit: bool = p & color != 0; + let (high_pulse, low_pulse) = if bit { (t1h, t1l) } else { (t0h, t0l) }; + signal.set(23 - i as usize, &(high_pulse, low_pulse))?; + } + tx.start_blocking(&signal)?; + Ok(()) + } + + struct Rgb { + r: u8, + g: u8, + b: u8, + } + + impl Rgb { + pub fn new(r: u8, g: u8, b: u8) -> Self { + Self { r, g, b } + } + /// Converts hue, saturation, value to RGB + pub fn from_hsv(h: u32, s: u32, v: u32) -> Result { + if h > 360 || s > 100 || v > 100 { + bail!("The given HSV values are not in valid range"); } - tx.start_blocking(&signal)?; - Ets::delay_ms(1000); + let s = s as f64 / 100.0; + let v = v as f64 / 100.0; + let c = s * v; + let x = c * (1.0 - (((h as f64 / 60.0) % 2.0) - 1.0).abs()); + let m = v - c; + let (r, g, b) = match h { + 0..=59 => (c, x, 0.0), + 60..=119 => (x, c, 0.0), + 120..=179 => (0.0, c, x), + 180..=239 => (0.0, x, c), + 240..=299 => (x, 0.0, c), + _ => (c, 0.0, x), + }; + Ok(Self { + r: ((r + m) * 255.0) as u8, + g: ((g + m) * 255.0) as u8, + b: ((b + m) * 255.0) as u8, + }) } } -} -fn ns(nanos: u64) -> Duration { - Duration::from_nanos(nanos) + impl From for u32 { + /// Convert RGB to u32 color value + /// + /// e.g. rgb: (1,2,4) + /// G R B + /// 7 0 7 0 7 0 + /// 00000010 00000001 00000100 + fn from(rgb: Rgb) -> Self { + ((rgb.g as u32) << 16) | ((rgb.r as u32) << 8) | rgb.b as u32 + } + } } diff --git a/examples/rmt_onewire_temperature.rs b/examples/rmt_onewire_temperature.rs new file mode 100644 index 00000000000..594d0d5ce57 --- /dev/null +++ b/examples/rmt_onewire_temperature.rs @@ -0,0 +1,175 @@ +//! RMT Onewire Example +//! +//! Example demonstrating the use of the onewire component to measure temperature from the ds18b20 temperature probe. +//! +//! In order to use this example, an overidden `Cargo.toml` must be defined with the following definitions: +//! ``` +//! [[package.metadata.esp-idf-sys.extra_components]] +//! remote_component = { name = "onewire_bus", version = "^1.0.2" } +//! +//! +//! [patch.crates-io] +//! esp-idf-sys = { git = "https://github.com/esp-rs/esp-idf-sys", rev = "2728b85" } +//! +//! ``` +//! +//! The example can then be run with +//! `MCU= cargo run --example rmt_onewire --manifest-path /path/to/other/Cargo.toml` +//! +//! Below is a connection sketch, the signal pin must be externally pulled-up +//! with a 4.7kOhm resistor. +//! This example uses gpio 16, but any pin capable of +//! input AND output is suitable. +//! +//! If the example is successful, it should print the address of each +//! onewire device attached to the bus. +//! +//! ┌──────────────────────────┐ +//! │ 3.3V├───────┬─────────────┬──────────────────────┐ +//! │ │ ┌┴┐ │VDD │VDD +//! │ ESP Board │ 4.7k│ │ ┌──────┴──────┐ ┌──────┴──────┐ +//! │ │ └┬┘ DQ│ │ DQ│ │ +//! │ ONEWIRE_GPIO_PIN├───────┴──┬───┤ DS18B20 │ ┌───┤ DS18B20 │ ...... +//! │ │ └───│-------------│────┴───│-------------│── +//! │ │ └──────┬──────┘ └──────┬──────┘ +//! │ │ │GND │GND +//! │ GND├─────────────────────┴──────────────────────┘ +//! └──────────────────────────┘ +//! +//! +//! This example demonstrates: +//! * A RMT device in both TX and RX mode. +//! * Usage of the onewire bus driver interface. +//! * How to iterate through a device search to discover devices on the bus. + +#![allow(unknown_lints)] +#![allow(unexpected_cfgs)] + +#[cfg(all( + esp_idf_soc_rmt_supported, + not(feature = "rmt-legacy"), + esp_idf_comp_espressif__onewire_bus_enabled, +))] +use esp_idf_hal::onewire::{OWAddress, OWCommand, OWDriver}; + +#[cfg(all( + esp_idf_soc_rmt_supported, + not(esp_idf_version_major = "4"), + esp_idf_comp_espressif__onewire_bus_enabled, +))] +fn main() -> anyhow::Result<()> { + use esp_idf_hal::delay::FreeRtos; + use esp_idf_hal::peripherals::Peripherals; + use esp_idf_hal::sys::EspError; + use std::time::Duration; + + println!("Starting APP!"); + + let peripherals = Peripherals::take()?; + + let channel = peripherals.rmt.channel0; + let onewire_gpio_pin = peripherals.pins.gpio16; + + let mut onewire_bus: OWDriver = OWDriver::new(onewire_gpio_pin, channel)?; + let device = { + let mut search = onewire_bus.search()?; + search.next() + }; + + if device.is_none() { + println!("No device found"); + return Ok(()); + } + + let device = device.unwrap(); + if let Err(err) = device { + println!("An error occured searching for the device, err = {}", err); + return Err(err.into()); + } + let device = device.unwrap(); + println!( + "Found Device: {:?}, family code = {}", + device, + device.family_code() + ); + + loop { + ds18b20_trigger_temp_conversion(&device, &onewire_bus)?; + let temp = ds18b20_get_temperature(&device, &onewire_bus)?; + println!("Temperature: {}", temp); + FreeRtos::delay_ms(3000); + } +} + +#[cfg(any( + feature = "rmt-legacy", + esp_idf_version_major = "4", + not(esp_idf_comp_espressif__onewire_bus_enabled), + not(esp_idf_soc_rmt_supported), +))] +fn main() -> anyhow::Result<()> { + println!("This example requires feature `rmt-legacy` disabled, using ESP-IDF > v4.4.X, the component included in `Cargo.toml`, or is not supported on this MCU"); + + loop { + std::thread::sleep(std::time::Duration::from_millis(1000)); + } +} + +#[cfg(all( + esp_idf_soc_rmt_supported, + not(esp_idf_version_major = "4"), + esp_idf_comp_espressif__onewire_bus_enabled, +))] +fn ds18b20_send_command<'a>(addr: &OWAddress, bus: &OWDriver, cmd: u8) -> Result<(), EspError> { + let mut buf = [0; 10]; + buf[0] = OWCommand::MatchRom as _; + let addr = addr.address().to_le_bytes(); + buf[1..9].copy_from_slice(&addr); + buf[9] = cmd; + + bus.write(&buf) +} + +#[allow(dead_code)] +#[repr(u8)] +enum Ds18b20Command { + ConvertTemp = 0x44, + WriteScratch = 0x4E, + ReadScratch = 0xBE, +} +#[cfg(all( + esp_idf_soc_rmt_supported, + not(esp_idf_version_major = "4"), + esp_idf_comp_espressif__onewire_bus_enabled, +))] +fn ds18b20_trigger_temp_conversion<'a>(addr: &OWAddress, bus: &OWDriver) -> Result<(), EspError> { + // reset bus and check if the ds18b20 is present + bus.reset()?; + + ds18b20_send_command(addr, bus, Ds18b20Command::ConvertTemp as u8)?; + + // delay proper time for temp conversion, + // assume max resolution (12-bits) + std::thread::sleep(Duration::from_millis(800)); + + Ok(()) +} +#[cfg(all( + esp_idf_soc_rmt_supported, + not(esp_idf_version_major = "4"), + esp_idf_comp_espressif__onewire_bus_enabled, +))] +fn ds18b20_get_temperature<'a>(addr: &OWAddress, bus: &OWDriver) -> Result { + bus.reset()?; + + ds18b20_send_command(addr, bus, Ds18b20Command::ReadScratch as u8)?; + + let mut buf = [0u8; 9]; + bus.read(&mut buf)?; + let lsb = buf[0]; + let msb = buf[1]; + + let temp_raw: u16 = (u16::from(msb) << 8) | u16::from(lsb); + + Ok(f32::from(temp_raw) / 16.0) +} diff --git a/examples/rmt_transceiver.rs b/examples/rmt_transceiver.rs index 396a4bdd27b..b9b6fa87a8d 100644 --- a/examples/rmt_transceiver.rs +++ b/examples/rmt_transceiver.rs @@ -18,92 +18,112 @@ //! level0 = High dur0 = PulseTicks(210) level1 = Low dur1 = PulseTicks(0) //! Tx Loop -use esp_idf_sys::{self as _}; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported - -use esp_idf_hal::delay::FreeRtos; -use esp_idf_hal::peripherals::Peripherals; -use esp_idf_hal::rmt::{ - FixedLengthSignal, PinState, Pulse, PulseTicks, Receive, RmtReceiveConfig, RmtTransmitConfig, - RxRmtDriver, TxRmtDriver, -}; +#![allow(unknown_lints)] +#![allow(unexpected_cfgs)] +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] fn main() -> anyhow::Result<()> { - println!("Starting APP!"); - - let peripherals = Peripherals::take().unwrap(); - - /* - *********************** SET UP RMT RECEIVER ****************************** - */ - let mut rx = RxRmtDriver::new( - peripherals.rmt.channel2, - peripherals.pins.gpio2, - &RmtReceiveConfig::new().idle_threshold(700u16), - 250, - )?; - - rx.start().unwrap(); - - let _ = std::thread::Builder::new() - .stack_size(10000) - .spawn(move || loop { - println!("Rx Loop"); - - let mut pulses = [(Pulse::zero(), Pulse::zero()); 250]; + example::main() +} - // See sdkconfig.defaults to determine the tick time value ( default is one tick = 10 milliseconds) - // Set ticks_to_wait to 0 for non-blocking - let receive = rx.receive(&mut pulses, 0).unwrap(); +#[cfg(not(any(feature = "rmt-legacy", esp_idf_version_major = "4")))] +fn main() -> anyhow::Result<()> { + println!("This example requires feature `rmt-legacy` enabled or using ESP-IDF v4.4.X"); - if let Receive::Read(length) = receive { - let pulses = &pulses[..length]; + loop { + std::thread::sleep(std::time::Duration::from_millis(1000)); + } +} - for (pulse0, pulse1) in pulses { - println!("0={pulse0:?}, 1={pulse1:?}"); +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +mod example { + use esp_idf_hal::{ + delay::FreeRtos, + prelude::Peripherals, + rmt::{ + FixedLengthSignal, PinState, Pulse, PulseTicks, Receive, RmtReceiveConfig, + RmtTransmitConfig, RxRmtDriver, TxRmtDriver, + }, + }; + + pub fn main() -> anyhow::Result<()> { + println!("Starting APP!"); + + let peripherals = Peripherals::take()?; + + /* + *********************** SET UP RMT RECEIVER ****************************** + */ + let mut rx = RxRmtDriver::new( + peripherals.rmt.channel2, + peripherals.pins.gpio2, + &RmtReceiveConfig::new().idle_threshold(700u16), + 250, + )?; + + rx.start()?; + + let _ = std::thread::Builder::new() + .stack_size(10000) + .spawn(move || loop { + println!("Rx Loop"); + + let mut pulses = [(Pulse::zero(), Pulse::zero()); 250]; + + // See sdkconfig.defaults to determine the tick time value ( default is one tick = 10 milliseconds) + // Set ticks_to_wait to 0 for non-blocking + let receive = rx.receive(&mut pulses, 0).unwrap(); + + if let Receive::Read(length) = receive { + let pulses = &pulses[..length]; + + for (pulse0, pulse1) in pulses { + println!("0={pulse0:?}, 1={pulse1:?}"); + } } - } - FreeRtos::delay_ms(500); + FreeRtos::delay_ms(500); + }); + + /* + *********************** SET UP RMT TRANSMITTER ****************************** + */ + + // Prepare the tx_config + // The default uses one memory block or 64 signals and clock divider set to 80 (1us tick) + let mut tx = TxRmtDriver::new( + peripherals.rmt.channel0, + peripherals.pins.gpio4, + &RmtTransmitConfig::new(), + )?; + + // Prepare signal pulse signal to be sent. + let one_low = Pulse::new(PinState::Low, PulseTicks::new(410)?); + let one_high = Pulse::new(PinState::High, PulseTicks::new(210)?); + let zero_low = Pulse::new(PinState::Low, PulseTicks::new(210)?); + let zero_high = Pulse::new(PinState::High, PulseTicks::new(410)?); + let sync_low = Pulse::new(PinState::Low, PulseTicks::new(620)?); + let sync_high = Pulse::new(PinState::High, PulseTicks::new(620)?); + + let _ = std::thread::spawn(move || loop { + println!("Tx Loop"); + + // Create a sequence + let mut signal = FixedLengthSignal::<5>::new(); + signal.set(0, &(sync_high, sync_low)).unwrap(); + signal.set(1, &(sync_high, sync_low)).unwrap(); + signal.set(2, &(one_high, one_low)).unwrap(); + signal.set(3, &(zero_high, zero_low)).unwrap(); + signal.set(4, &(one_high, one_low)).unwrap(); + + // Transmit the signal (send sequence) + tx.start(signal).unwrap(); + + FreeRtos::delay_ms(1000); }); - /* - *********************** SET UP RMT TRANSMITTER ****************************** - */ - - // Prepare the tx_config - // The default uses one memory block or 64 signals and clock divider set to 80 (1us tick) - let mut tx = TxRmtDriver::new( - peripherals.rmt.channel0, - peripherals.pins.gpio4, - &RmtTransmitConfig::new(), - )?; - - // Prepare signal pulse signal to be sent. - let one_low = Pulse::new(PinState::Low, PulseTicks::new(410)?); - let one_high = Pulse::new(PinState::High, PulseTicks::new(210)?); - let zero_low = Pulse::new(PinState::Low, PulseTicks::new(210)?); - let zero_high = Pulse::new(PinState::High, PulseTicks::new(410)?); - let sync_low = Pulse::new(PinState::Low, PulseTicks::new(620)?); - let sync_high = Pulse::new(PinState::High, PulseTicks::new(620)?); - - let _ = std::thread::spawn(move || loop { - println!("Tx Loop"); - - // Create a sequence - let mut signal = FixedLengthSignal::<5>::new(); - signal.set(0, &(sync_high, sync_low)).unwrap(); - signal.set(1, &(sync_high, sync_low)).unwrap(); - signal.set(2, &(one_high, one_low)).unwrap(); - signal.set(3, &(zero_high, zero_low)).unwrap(); - signal.set(4, &(one_high, one_low)).unwrap(); - - // Transmit the signal (send sequence) - tx.start(signal).unwrap(); - - FreeRtos::delay_ms(1000); - }); - - loop { - FreeRtos::delay_ms(3000); + loop { + FreeRtos::delay_ms(3000); + } } } diff --git a/examples/spi_loopback.rs b/examples/spi_loopback.rs index 75c98efdf87..c58c85716ff 100644 --- a/examples/spi_loopback.rs +++ b/examples/spi_loopback.rs @@ -18,20 +18,26 @@ use esp_idf_hal::prelude::*; use esp_idf_hal::spi::*; fn main() -> anyhow::Result<()> { - esp_idf_sys::link_patches(); + esp_idf_hal::sys::link_patches(); - let peripherals = Peripherals::take().unwrap(); + let peripherals = Peripherals::take()?; let spi = peripherals.spi2; - let sclk = peripherals.pins.gpio6; - let serial_in = peripherals.pins.gpio2; // SDI - let serial_out = peripherals.pins.gpio7; // SDO - let cs_1 = peripherals.pins.gpio10; - let cs_2 = peripherals.pins.gpio3; + let sclk = peripherals.pins.gpio15; + let serial_in = peripherals.pins.gpio16; // SDI + let serial_out = peripherals.pins.gpio17; // SDO + let cs_1 = peripherals.pins.gpio18; + let cs_2 = peripherals.pins.gpio19; println!("Starting SPI loopback test"); - let driver = SpiDriver::new::(spi, sclk, serial_out, Some(serial_in), Dma::Disabled)?; + let driver = SpiDriver::new::( + spi, + sclk, + serial_out, + Some(serial_in), + &SpiDriverConfig::new(), + )?; let config_1 = config::Config::new().baudrate(26.MHz().into()); let mut device_1 = SpiDeviceDriver::new(&driver, Some(cs_1), &config_1)?; @@ -42,16 +48,23 @@ fn main() -> anyhow::Result<()> { let mut read = [0u8; 4]; let write = [0xde, 0xad, 0xbe, 0xef]; - let mut in_place_buf = [0xde, 0xad, 0xbe, 0xef]; - loop { // we are using thread::sleep here to make sure the watchdog isn't triggered FreeRtos::delay_ms(500); device_1.transfer(&mut read, &write)?; println!("Device 1: Wrote {write:x?}, read {read:x?}"); - println!("Device 2: To write {in_place_buf:x?} ... "); - device_2.transaction(|bus| bus.transfer_in_place(&mut in_place_buf))?; - println!("... read {in_place_buf:x?}"); + let write_buf = [0xde, 0xad, 0xbe, 0xef]; + let mut write_in_place_buf = [0xde, 0xad, 0xbe, 0xef]; + let mut read_buf = [0; 8]; + + println!("Device 2: To write {write_in_place_buf:x?} ... "); + // cascade multiple operations with different buffer length into one transaction + device_2.transaction(&mut [ + Operation::Write(&write_buf), + Operation::TransferInPlace(&mut write_in_place_buf), + Operation::Read(&mut read_buf), + ])?; + println!("... read {write_in_place_buf:x?}"); } } diff --git a/examples/spi_loopback_async.rs b/examples/spi_loopback_async.rs new file mode 100644 index 00000000000..f19d94846e5 --- /dev/null +++ b/examples/spi_loopback_async.rs @@ -0,0 +1,72 @@ +//! SPI loopback test +//! +//! Folowing pins are used: +//! SCLK GPIO6 +//! SDI GPIO2 +//! SDO GPIO7 +//! CS_1 GPIO10 +//! CS_2 GPIO3 +//! +//! Depending on your target and the board you are using you have to change the pins. +//! +//! This example transfers data via SPI. +//! Connect SDI and SDO pins to see the outgoing data is read as incoming data. + +use esp_idf_hal::peripherals::Peripherals; +use esp_idf_hal::prelude::*; +use esp_idf_hal::spi::*; +use esp_idf_hal::task::*; + +fn main() -> anyhow::Result<()> { + esp_idf_hal::sys::link_patches(); + + let peripherals = Peripherals::take()?; + let spi = peripherals.spi2; + + let sclk = peripherals.pins.gpio15; + let serial_in = peripherals.pins.gpio16; // SDI + let serial_out = peripherals.pins.gpio17; // SDO + let cs_1 = peripherals.pins.gpio18; + let cs_2 = peripherals.pins.gpio19; + + println!("Starting SPI loopback test"); + + let driver = SpiDriver::new::( + spi, + sclk, + serial_out, + Some(serial_in), + &SpiDriverConfig::new(), + )?; + + let config_1 = config::Config::new().baudrate(26.MHz().into()); + let mut device_1 = SpiDeviceDriver::new(&driver, Some(cs_1), &config_1)?; + + let config_2 = config::Config::new().baudrate(13.MHz().into()); + let mut device_2 = SpiDeviceDriver::new(&driver, Some(cs_2), &config_2)?; + + let write = [0xde, 0xad, 0xbe, 0xef]; + let write_buf = [0xde, 0xad, 0xbe, 0xef]; + + let mut read = [0u8; 4]; + let mut write_in_place_buf = [0xde, 0xad, 0xbe, 0xef]; + let mut read_buf = [0; 8]; + + block_on(async { + loop { + device_1.transfer_async(&mut read, &write).await?; + println!("Device 1: Wrote {write:x?}, read {read:x?}"); + + println!("Device 2: To write {write_in_place_buf:x?} ... "); + // cascade multiple operations with different buffer length into one transaction + device_2 + .transaction_async(&mut [ + Operation::Write(&write_buf), + Operation::TransferInPlace(&mut write_in_place_buf), + Operation::Read(&mut read_buf), + ]) + .await?; + println!("... read {write_in_place_buf:x?}"); + } + }) +} diff --git a/examples/spi_st7789.rs b/examples/spi_st7789.rs index 861d7edbfee..9d3db52d08c 100644 --- a/examples/spi_st7789.rs +++ b/examples/spi_st7789.rs @@ -12,8 +12,6 @@ //! For this example you need to hook up an ST7789 SPI display. //! The display will display an image on ferris the crab on a black background. -use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported - use std::thread; use std::time::Duration; @@ -34,7 +32,7 @@ use embedded_graphics::prelude::*; use mipidsi::{Builder, Orientation}; fn main() -> anyhow::Result<()> { - let peripherals = Peripherals::take().unwrap(); + let peripherals = Peripherals::take()?; let spi = peripherals.spi2; let rst = PinDriver::output(peripherals.pins.gpio3)?; @@ -52,8 +50,15 @@ fn main() -> anyhow::Result<()> { .baudrate(26.MHz().into()) .data_mode(MODE_3); - let device = - SpiDeviceDriver::new_single(spi, sclk, sda, Some(sdi), Dma::Disabled, Some(cs), &config)?; + let device = SpiDeviceDriver::new_single( + spi, + sclk, + sda, + Some(sdi), + Some(cs), + &SpiDriverConfig::new(), + &config, + )?; // display interface abstraction from SPI and DC let di = SPIInterfaceNoCS::new(device, dc); diff --git a/examples/temperature_sensor.rs b/examples/temperature_sensor.rs new file mode 100644 index 00000000000..7572a586ebd --- /dev/null +++ b/examples/temperature_sensor.rs @@ -0,0 +1,31 @@ +#![allow(unknown_lints)] +#![allow(unexpected_cfgs)] + +#[cfg(all(esp_idf_soc_temp_sensor_supported, esp_idf_version_major = "5"))] +fn main() -> anyhow::Result<()> { + use esp_idf_hal::delay::FreeRtos; + use esp_idf_hal::peripherals::Peripherals; + use esp_idf_hal::temp_sensor::*; + + esp_idf_hal::sys::link_patches(); + + let peripherals = Peripherals::take()?; + let cfg = TempSensorConfig::default(); + let mut temp = TempSensorDriver::new(&cfg, peripherals.temp_sensor)?; + temp.enable()?; + + loop { + let t = temp.get_celsius()?; + println!("Temperature {t}C"); + FreeRtos::delay_ms(1000); + } +} + +#[cfg(not(all(esp_idf_soc_temp_sensor_supported, esp_idf_version_major = "5")))] +fn main() -> anyhow::Result<()> { + println!("This example requires feature `esp_idf_soc_temp_sensor_supported` enabled"); + + loop { + std::thread::sleep(std::time::Duration::from_millis(1000)); + } +} diff --git a/examples/timer_async.rs b/examples/timer_async.rs new file mode 100644 index 00000000000..cc8b16d36d7 --- /dev/null +++ b/examples/timer_async.rs @@ -0,0 +1,22 @@ +use esp_idf_hal::peripherals::*; +use esp_idf_hal::sys::EspError; +use esp_idf_hal::task::*; +use esp_idf_hal::timer::*; + +fn main() -> Result<(), EspError> { + // 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_hal::sys::link_patches(); + + let per = Peripherals::take()?; + + let mut timer = TimerDriver::new(per.timer00, &TimerConfig::new())?; + + block_on(async { + loop { + timer.delay(timer.tick_hz()).await?; // Every second + + println!("Tick"); + } + }) +} diff --git a/examples/timer_notify.rs b/examples/timer_notify.rs new file mode 100644 index 00000000000..595b154a5d1 --- /dev/null +++ b/examples/timer_notify.rs @@ -0,0 +1,52 @@ +use std::num::NonZeroU32; + +use esp_idf_hal::peripherals::*; +use esp_idf_hal::sys::EspError; +use esp_idf_hal::task::notification::Notification; +use esp_idf_hal::timer::*; + +fn main() -> Result<(), EspError> { + // 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_hal::sys::link_patches(); + + let per = Peripherals::take()?; + + // A safer abstraction over FreeRTOS/ESP-IDF task notifications. + let notification = Notification::new(); + + // BaseClock for the Timer is the APB_CLK that is running on 80MHz at default + // The default clock-divider is -> 80 + // default APB clk is available with the APB_CLK_FREQ constant + let timer_conf = config::Config::new().auto_reload(true); + let mut timer = TimerDriver::new(per.timer00, &timer_conf)?; + + // Every half a second + timer.set_alarm(timer.tick_hz() / 2)?; + + let notifier = notification.notifier(); + + // Saftey: make sure the `Notification` object is not dropped while the subscription is active + unsafe { + timer.subscribe(move || { + let bitset = 0b10001010101; + notifier.notify_and_yield(NonZeroU32::new(bitset).unwrap()); + })?; + } + + timer.enable_interrupt()?; + timer.enable_alarm(true)?; + timer.enable(true)?; + + loop { + // Notify approach + // The benefit with this approach over checking a global static variable is + // that the scheduler can block the task, and quickly resume it when notified + // so no spinlock is needed / the CPU does not waste cycles. + let bitset = notification.wait(esp_idf_hal::delay::BLOCK); + + if let Some(bitset) = bitset { + println!("got event with bits {bitset:#b} from ISR"); + } + } +} diff --git a/examples/uart_loopback.rs b/examples/uart_loopback.rs index c2d18bc8caf..d90f1b694d8 100644 --- a/examples/uart_loopback.rs +++ b/examples/uart_loopback.rs @@ -1,8 +1,8 @@ //! UART loopback test //! //! Folowing pins are used: -//! TX GPIO5 -//! RX GPIO6 +//! TX GPIO12 +//! RX GPIO13 //! //! Depending on your target and the board you are using you have to change the pins. //! @@ -16,11 +16,11 @@ use esp_idf_hal::prelude::*; use esp_idf_hal::uart::*; fn main() -> anyhow::Result<()> { - esp_idf_sys::link_patches(); + esp_idf_hal::sys::link_patches(); - let peripherals = Peripherals::take().unwrap(); - let tx = peripherals.pins.gpio5; - let rx = peripherals.pins.gpio6; + let peripherals = Peripherals::take()?; + let tx = peripherals.pins.gpio12; + let rx = peripherals.pins.gpio13; println!("Starting UART loopback test"); let config = config::Config::new().baudrate(Hertz(115_200)); @@ -31,8 +31,7 @@ fn main() -> anyhow::Result<()> { Option::::None, Option::::None, &config, - ) - .unwrap(); + )?; loop { uart.write(&[0xaa])?; diff --git a/examples/uart_loopback_async.rs b/examples/uart_loopback_async.rs new file mode 100644 index 00000000000..a012370da03 --- /dev/null +++ b/examples/uart_loopback_async.rs @@ -0,0 +1,46 @@ +//! UART loopback test +//! +//! Folowing pins are used: +//! TX GPIO12 +//! RX GPIO13 +//! +//! Depending on your target and the board you are using you have to change the pins. +//! +//! This example transfers data via UART. +//! Connect TX and RX pins to see the outgoing data is read as incoming data. + +use esp_idf_hal::gpio; +use esp_idf_hal::peripherals::Peripherals; +use esp_idf_hal::prelude::*; +use esp_idf_hal::task::*; +use esp_idf_hal::uart::*; + +fn main() -> anyhow::Result<()> { + esp_idf_hal::sys::link_patches(); + + let peripherals = Peripherals::take()?; + let tx = peripherals.pins.gpio12; + let rx = peripherals.pins.gpio13; + + println!("Starting UART loopback test"); + let config = config::Config::new().baudrate(Hertz(115_200)); + let uart = AsyncUartDriver::new( + peripherals.uart1, + tx, + rx, + Option::::None, + Option::::None, + &config, + )?; + + block_on(async { + loop { + uart.write(&[0xaa]).await?; + + let mut buf = [0_u8; 1]; + uart.read(&mut buf).await?; + + println!("Written 0xaa, read 0x{:02x}", buf[0]); + } + }) +} diff --git a/partitions.csv b/partitions.csv new file mode 100644 index 00000000000..924878bc2d5 --- /dev/null +++ b/partitions.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, , 0x6000, +phy_init, data, phy, , 0x1000, +factory, app, factory, , 3M, \ No newline at end of file diff --git a/riscv-ulp/libriscv_ulp_start.a b/riscv-ulp/libriscv_ulp_start.a deleted file mode 100644 index d3f858baf1d..00000000000 Binary files a/riscv-ulp/libriscv_ulp_start.a and /dev/null differ diff --git a/riscv-ulp/riscv_ulp_link_base.x b/riscv-ulp/riscv_ulp_link_base.x deleted file mode 100644 index 769ba832519..00000000000 --- a/riscv-ulp/riscv_ulp_link_base.x +++ /dev/null @@ -1,86 +0,0 @@ -PROVIDE(UserSoft = DefaultHandler); -PROVIDE(SupervisorSoft = DefaultHandler); -PROVIDE(MachineSoft = DefaultHandler); -PROVIDE(UserTimer = DefaultHandler); -PROVIDE(SupervisorTimer = DefaultHandler); -PROVIDE(MachineTimer = DefaultHandler); -PROVIDE(UserExternal = DefaultHandler); -PROVIDE(SupervisorExternal = DefaultHandler); -PROVIDE(MachineExternal = DefaultHandler); - -PROVIDE(DefaultHandler = DefaultInterruptHandler); -PROVIDE(ExceptionHandler = DefaultExceptionHandler); - -ENTRY(reset_vector) - -MEMORY -{ - ram(RW) : ORIGIN = 0, LENGTH = _ram_size -} - -PROVIDE(_stext = ORIGIN(ram)); - -SECTIONS -{ - .text.dummy (NOLOAD) : - { - /* This section is intended to make _stext address work */ - . = ABSOLUTE(_stext); - } > ram - - .text _stext : - { - KEEP(*(.init)); // Default reset vector must link to offset 0x0 - KEEP(*(.start.rust)); - KEEP(*(.trap.rust)); - - *(.text .text.*); - } > ram - - .rodata ALIGN(4): - { - *(.srodata .srodata.*); - *(.rodata .rodata.*); - - /* 4-byte align the end (VMA) of this section. - This is required by LLD to ensure the LMA of the following .data - section will have the correct alignment. */ - . = ALIGN(4); - } > ram - - .data ALIGN(4): - { - /* Must be called __global_pointer$ for linker relaxations to work. */ - PROVIDE(__global_pointer$ = . + 0x800); - *(.sdata .sdata.* .sdata2 .sdata2.*); - *(.data .data.*); - . = ALIGN(4); - } > ram - - .bss ALIGN(4) : - { - *(.sbss .sbss.* .bss .bss.*); - . = ALIGN(4); - } > ram - - /* fake output .got section */ - /* Dynamic relocations are unsupported. This section is only used to detect - relocatable code in the input files and raise an error if relocatable code - is found */ - .got (INFO) : - { - KEEP(*(.got .got.*)); - } - - .eh_frame (INFO) : - { - KEEP(*(.eh_frame)) - } - - .eh_frame_hdr (INFO) : - { - *(.eh_frame_hdr) - } - - _stack_top = ORIGIN(ram) + LENGTH(ram); -} \ No newline at end of file diff --git a/riscv-ulp/riscv_ulp_link_default.x b/riscv-ulp/riscv_ulp_link_default.x deleted file mode 100644 index fbf70875072..00000000000 --- a/riscv-ulp/riscv_ulp_link_default.x +++ /dev/null @@ -1,2 +0,0 @@ -PROVIDE(_ram_size = 4K); -INCLUDE riscv_ulp_link_base.x; diff --git a/riscv-ulp/riscv_ulp_start.S b/riscv-ulp/riscv_ulp_start.S deleted file mode 100644 index cc33001eae0..00000000000 --- a/riscv-ulp/riscv_ulp_start.S +++ /dev/null @@ -1,117 +0,0 @@ -// NOTE: Adapted from riscv-rt/asm.S -#define REGBYTES (1 << 2) - -.section .init, "ax" - .global reset_vector - .global irq_vector - -// The reset vector, jumps to startup code -reset_vector: - j _start - -// Interrupt handler -.option push -.option norelax // To prevent an unsupported R_RISCV_ALIGN relocation from being generated -.balign 16 -irq_vector: - addi sp, sp, -16*REGBYTES - - sw ra, 0*REGBYTES(sp) - sw t0, 1*REGBYTES(sp) - sw t1, 2*REGBYTES(sp) - sw t2, 3*REGBYTES(sp) - sw t3, 4*REGBYTES(sp) - sw t4, 5*REGBYTES(sp) - sw t5, 6*REGBYTES(sp) - sw t6, 7*REGBYTES(sp) - sw a0, 8*REGBYTES(sp) - sw a1, 9*REGBYTES(sp) - sw a2, 10*REGBYTES(sp) - sw a3, 11*REGBYTES(sp) - sw a4, 12*REGBYTES(sp) - sw a5, 13*REGBYTES(sp) - sw a6, 14*REGBYTES(sp) - sw a7, 15*REGBYTES(sp) - - add a0, sp, zero - jal ra, _start_trap_rust - - lw ra, 0*REGBYTES(sp) - lw t0, 1*REGBYTES(sp) - lw t1, 2*REGBYTES(sp) - lw t2, 3*REGBYTES(sp) - lw t3, 4*REGBYTES(sp) - lw t4, 5*REGBYTES(sp) - lw t5, 6*REGBYTES(sp) - lw t6, 7*REGBYTES(sp) - lw a0, 8*REGBYTES(sp) - lw a1, 9*REGBYTES(sp) - lw a2, 10*REGBYTES(sp) - lw a3, 11*REGBYTES(sp) - lw a4, 12*REGBYTES(sp) - lw a5, 13*REGBYTES(sp) - lw a6, 14*REGBYTES(sp) - lw a7, 15*REGBYTES(sp) - - addi sp, sp, 16*REGBYTES - ret -.option pop - -_start: - .cfi_startproc - .cfi_undefined ra - - li x1, 0 - li x2, 0 - li x3, 0 - li x4, 0 - li x5, 0 - li x6, 0 - li x7, 0 - li x8, 0 - li x9, 0 - li x10,0 - li x11,0 - li x12,0 - li x13,0 - li x14,0 - li x15,0 - li x16,0 - li x17,0 - li x18,0 - li x19,0 - li x20,0 - li x21,0 - li x22,0 - li x23,0 - li x24,0 - li x25,0 - li x26,0 - li x27,0 - li x28,0 - li x29,0 - li x30,0 - li x31,0 - - .option push - .option norelax // To prevent an unsupported R_RISCV_ALIGN relocation from being generated - la gp, __global_pointer$ - .option pop - - // Allocate stack - la sp, _stack_top - - // Set frame pointer - add s0, sp, zero - - jal zero, _start_rust - - .cfi_endproc - -loop: - j loop - -// Make sure there is an abort when linking -.globl abort -abort: - j abort diff --git a/riscv-ulp/riscv_ulp_start_assemble.ps1 b/riscv-ulp/riscv_ulp_start_assemble.ps1 deleted file mode 100644 index 226bd384a6c..00000000000 --- a/riscv-ulp/riscv_ulp_start_assemble.ps1 +++ /dev/null @@ -1,7 +0,0 @@ -# remove existing blob because otherwise this will append object file to the old blob -Remove-Item -Force riscv_ulp_start.a - -riscv32-esp-elf-gcc -Desp_ulp -ggdb3 -fdebug-prefix-map=$(pwd)=/riscv_ulp_start -c -mabi=ilp32 -march=rv32imc riscv_ulp_start.S -o riscv_ulp_start.o -riscv32-esp-elf-ar crs libriscv_ulp_start.a riscv_ulp_start.o - -Remove-Item riscv_ulp_start.o diff --git a/riscv-ulp/riscv_ulp_start_assemble.sh b/riscv-ulp/riscv_ulp_start_assemble.sh deleted file mode 100755 index f366acb8620..00000000000 --- a/riscv-ulp/riscv_ulp_start_assemble.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -set -euxo pipefail - -# remove existing blob because otherwise this will append object file to the old blob -rm -f riscv_ulp_start.a - -riscv32-esp-elf-gcc -Desp_ulp -ggdb3 -fdebug-prefix-map=$(pwd)=/riscv_ulp_start -c -mabi=ilp32 -march=rv32imc riscv_ulp_start.S -o riscv_ulp_start.o -riscv32-esp-elf-ar crs libriscv_ulp_start.a riscv_ulp_start.o - -rm riscv_ulp_start.o diff --git a/src/adc.rs b/src/adc.rs index a05ebe3d97f..1ebffe0b9ed 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -1,306 +1,452 @@ -use core::marker::PhantomData; +//! Analog to Digital Converter peripheral control. -#[cfg(not(feature = "riscv-ulp-hal"))] use esp_idf_sys::*; -#[cfg(feature = "riscv-ulp-hal")] -use crate::riscv_ulp_hal::sys::*; - -#[cfg(not(feature = "riscv-ulp-hal"))] -use crate::gpio::ADCPin; - -#[cfg(not(feature = "riscv-ulp-hal"))] -use crate::peripheral::{Peripheral, PeripheralRef}; - -#[cfg(not(feature = "riscv-ulp-hal"))] -pub type AdcConfig = config::Config; +#[cfg(all( + not(esp_idf_version_major = "4"), + not(esp32c2), + esp_idf_comp_esp_adc_enabled +))] +#[allow(deprecated)] +pub use continuous::{ + config as cont_config, config::Config as AdcContConfig, AdcChannels, AdcChannelsArray, + AdcDriver as AdcContDriver, AdcMeasurement, Atten11dB, Atten12dB, Atten2p5dB, Atten6dB, + AttenNone, Attenuated, ChainedAdcChannels, EmptyAdcChannels, +}; + +#[cfg(any(feature = "adc-oneshot-legacy", esp_idf_version_major = "4"))] +pub use oneshot_legacy::*; pub trait Adc: Send { fn unit() -> adc_unit_t; } -pub trait Attenuation: Send { - fn attenuation() -> adc_atten_t; +// NOTE: Will be changed to an enum once C-style enums are usable as const generics +pub mod attenuation { + pub use esp_idf_sys::{ + adc_atten_t, adc_atten_t_ADC_ATTEN_DB_0, adc_atten_t_ADC_ATTEN_DB_11, + adc_atten_t_ADC_ATTEN_DB_11 as adc_atten_t_ADC_ATTEN_DB_12, adc_atten_t_ADC_ATTEN_DB_2_5, + adc_atten_t_ADC_ATTEN_DB_6, + }; + + pub const NONE: adc_atten_t = adc_atten_t_ADC_ATTEN_DB_0; + pub const DB_2_5: adc_atten_t = adc_atten_t_ADC_ATTEN_DB_2_5; + pub const DB_6: adc_atten_t = adc_atten_t_ADC_ATTEN_DB_6; + #[deprecated(since = "0.45.3", note = "Use `DB_12` instead")] + pub const DB_11: adc_atten_t = adc_atten_t_ADC_ATTEN_DB_11; + pub const DB_12: adc_atten_t = adc_atten_t_ADC_ATTEN_DB_12; } -pub struct Atten0dB(PhantomData); -pub struct Atten2p5dB(PhantomData); -pub struct Atten6dB(PhantomData); -pub struct Atten11dB(PhantomData); +/// The sampling/readout resolution of the ADC +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Resolution { + #[cfg(esp32)] + Resolution9Bit, + #[cfg(esp32)] + Resolution10Bit, + #[cfg(esp32)] + Resolution11Bit, + #[cfg(any(esp32, esp32c3, esp32s3, esp32c2, esp32h2, esp32c5, esp32c6, esp32p4))] + Resolution12Bit, + #[cfg(esp32s2)] + Resolution13Bit, +} -impl Attenuation for Atten0dB { - fn attenuation() -> adc_atten_t { - adc_atten_t_ADC_ATTEN_DB_0 +impl Resolution { + #[cfg(not(esp32s2))] + pub const fn new() -> Self { + Self::Resolution12Bit } -} -impl Attenuation for Atten2p5dB { - fn attenuation() -> adc_atten_t { - adc_atten_t_ADC_ATTEN_DB_2_5 + #[cfg(esp32s2)] + pub const fn new() -> Self { + Self::Resolution13Bit } } -impl Attenuation for Atten6dB { - fn attenuation() -> adc_atten_t { - adc_atten_t_ADC_ATTEN_DB_6 +impl Default for Resolution { + fn default() -> Self { + Self::new() } } -impl Attenuation for Atten11dB { - fn attenuation() -> adc_atten_t { - adc_atten_t_ADC_ATTEN_DB_11 +impl From for adc_bits_width_t { + fn from(resolution: Resolution) -> Self { + match resolution { + #[cfg(esp32)] + Resolution::Resolution9Bit => adc_bits_width_t_ADC_WIDTH_BIT_9, + #[cfg(esp32)] + Resolution::Resolution10Bit => adc_bits_width_t_ADC_WIDTH_BIT_10, + #[cfg(esp32)] + Resolution::Resolution11Bit => adc_bits_width_t_ADC_WIDTH_BIT_11, + #[cfg(any(esp32, esp32s3, esp32c3, esp32c2, esp32h2, esp32c5, esp32c6, esp32p4))] + Resolution::Resolution12Bit => adc_bits_width_t_ADC_WIDTH_BIT_12, + #[cfg(esp32s2)] + Resolution::Resolution13Bit => adc_bits_width_t_ADC_WIDTH_BIT_13, + } } } -/// ADC configuration -#[cfg(not(feature = "riscv-ulp-hal"))] -pub mod config { +#[cfg(any(feature = "adc-oneshot-legacy", esp_idf_version_major = "4"))] +mod oneshot_legacy { use esp_idf_sys::*; - /// The sampling/readout resolution of the ADC - #[derive(Debug, PartialEq, Eq, Clone, Copy)] - pub enum Resolution { - #[cfg(esp32)] - Resolution9Bit, - #[cfg(esp32)] - Resolution10Bit, - #[cfg(esp32)] - Resolution11Bit, - #[cfg(any(esp32, esp32c3, esp32s3))] - Resolution12Bit, - #[cfg(esp32s2)] - Resolution13Bit, - } + use crate::gpio::ADCPin; - impl Default for Resolution { - #[cfg(any(esp32, esp32c3, esp32s3))] - fn default() -> Self { - Self::Resolution12Bit + use crate::peripheral::{Peripheral, PeripheralRef}; + + use super::{to_nb_err, Adc, DirectConverter}; + + pub type AdcConfig = config::Config; + + /// ADC configuration + pub mod config { + pub use crate::adc::Resolution; + + #[derive(Debug, Copy, Clone, Default)] + pub struct Config { + pub resolution: Resolution, + #[cfg(any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled))] + pub calibration: bool, } - #[cfg(esp32s2)] - fn default() -> Self { - Self::Resolution13Bit - } - } - - impl From for adc_bits_width_t { - fn from(resolution: Resolution) -> Self { - match resolution { - #[cfg(esp32)] - Resolution::Resolution9Bit => adc_bits_width_t_ADC_WIDTH_BIT_9, - #[cfg(esp32)] - Resolution::Resolution10Bit => adc_bits_width_t_ADC_WIDTH_BIT_10, - #[cfg(esp32)] - Resolution::Resolution11Bit => adc_bits_width_t_ADC_WIDTH_BIT_11, - #[cfg(any(esp32, esp32s3, esp32c3))] - Resolution::Resolution12Bit => adc_bits_width_t_ADC_WIDTH_BIT_12, - #[cfg(esp32s2)] - Resolution::Resolution13Bit => adc_bits_width_t_ADC_WIDTH_BIT_13, + impl Config { + pub fn new() -> Self { + Default::default() + } + + #[must_use] + pub fn resolution(mut self, resolution: Resolution) -> Self { + self.resolution = resolution; + self + } + + #[cfg(any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled))] + #[must_use] + pub fn calibration(mut self, calibration: bool) -> Self { + self.calibration = calibration; + self } } } - #[derive(Debug, Copy, Clone, Default)] - pub struct Config { - pub resolution: Resolution, - #[cfg(any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled))] - pub calibration: bool, + pub struct AdcChannelDriver<'d, const A: adc_atten_t, T: ADCPin> { + pin: PeripheralRef<'d, T>, } - impl Config { - pub fn new() -> Self { - Default::default() - } - - #[must_use] - pub fn resolution(mut self, resolution: Resolution) -> Self { - self.resolution = resolution; - self - } + impl<'d, const A: adc_atten_t, T: ADCPin> AdcChannelDriver<'d, A, T> { + pub fn new(pin: impl Peripheral

+ 'd) -> Result { + crate::into_ref!(pin); - #[cfg(any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled))] - #[must_use] - pub fn calibration(mut self, calibration: bool) -> Self { - self.calibration = calibration; - self - } - } -} + unsafe { + crate::gpio::rtc_reset_pin(pin.pin())?; + } -#[cfg(not(feature = "riscv-ulp-hal"))] -pub struct AdcChannelDriver<'d, T: ADCPin, ATTEN> { - pin: PeripheralRef<'d, T>, - _atten: PhantomData, -} + if T::Adc::unit() == adc_unit_t_ADC_UNIT_1 { + esp!(unsafe { adc1_config_channel_atten(pin.adc_channel(), A) })?; + } else { + #[cfg(not(any(esp32c2, esp32h2, esp32c5, esp32c6, esp32p4)))] + esp!(unsafe { adc2_config_channel_atten(pin.adc_channel(), A) })?; -#[cfg(not(feature = "riscv-ulp-hal"))] -impl<'d, T: ADCPin, ATTEN> AdcChannelDriver<'d, T, ATTEN> -where - ATTEN: Attenuation, -{ - #[inline] - pub fn new( - pin: impl Peripheral

+ 'd, - ) -> Result, EspError> { - crate::into_ref!(pin); + #[cfg(any(esp32c2, esp32h2, esp32c5, esp32c6, esp32p4))] + unreachable!(); + } - unsafe { - crate::gpio::rtc_reset_pin(pin.pin())?; + Ok(Self { pin }) } - if T::Adc::unit() == adc_unit_t_ADC_UNIT_1 { - esp!(unsafe { adc1_config_channel_atten(pin.adc_channel(), ATTEN::attenuation()) })?; - } else { - esp!(unsafe { adc2_config_channel_atten(pin.adc_channel(), ATTEN::attenuation()) })?; + fn pin(&mut self) -> &mut PeripheralRef<'d, T> { + &mut self.pin } + } + + impl embedded_hal_0_2::adc::Channel + for AdcChannelDriver<'_, A, T> + { + type ID = (adc_channel_t, adc_atten_t); - Ok(Self { - pin, - _atten: PhantomData, - }) + fn channel() -> Self::ID { + (T::CHANNEL, A) + } } - fn pin(&mut self) -> &mut PeripheralRef<'d, T> { - &mut self.pin + pub struct AdcDriver<'d, ADC: Adc> { + _adc: PeripheralRef<'d, ADC>, + #[allow(dead_code)] + resolution: config::Resolution, + #[cfg(all( + any(esp32, esp32s2, esp32s3, esp32c3), + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled) + ))] + cal_characteristics: Option< + [Option; adc_atten_t_ADC_ATTEN_DB_11 as usize + 1], + >, } -} -#[cfg(not(feature = "riscv-ulp-hal"))] -impl<'d, T: ADCPin, ATTEN> embedded_hal_0_2::adc::Channel - for AdcChannelDriver<'d, T, ATTEN> -{ - type ID = u8; + unsafe impl Send for AdcDriver<'_, ADC> {} + + impl<'d, ADC: Adc> AdcDriver<'d, ADC> { + #[cfg(all( + esp32, + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled) + ))] + const CALIBRATION_SCHEME: esp_adc_cal_value_t = + esp_adc_cal_value_t_ESP_ADC_CAL_VAL_EFUSE_VREF; + + #[cfg(all( + any(esp32c3, esp32s2), + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled) + ))] + const CALIBRATION_SCHEME: esp_adc_cal_value_t = + esp_adc_cal_value_t_ESP_ADC_CAL_VAL_EFUSE_TP; + + #[cfg(all( + esp32s3, + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled) + ))] + const CALIBRATION_SCHEME: esp_adc_cal_value_t = + esp_adc_cal_value_t_ESP_ADC_CAL_VAL_EFUSE_TP_FIT; + + pub fn new( + adc: impl Peripheral

+ 'd, + config: &config::Config, + ) -> Result { + crate::into_ref!(adc); + + #[cfg(all( + any(esp32, esp32s2, esp32s3, esp32c3), + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled) + ))] + if config.calibration { + esp!(unsafe { esp_adc_cal_check_efuse(Self::CALIBRATION_SCHEME) })?; + } - fn channel() -> Self::ID { - T::CHANNEL as _ - } -} + if ADC::unit() == adc_unit_t_ADC_UNIT_1 { + esp!(unsafe { adc1_config_width(config.resolution.into()) })?; + } -#[cfg(not(feature = "riscv-ulp-hal"))] -pub struct AdcDriver<'d, ADC: Adc> { - _adc: PeripheralRef<'d, ADC>, - resolution: config::Resolution, - #[cfg(any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled))] - cal_characteristics: - Option<[Option; adc_atten_t_ADC_ATTEN_DB_11 as usize + 1]>, -} + Ok(Self { + _adc: adc, + resolution: config.resolution, + #[cfg(all( + any(esp32, esp32s2, esp32s3, esp32c3), + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled) + ))] + cal_characteristics: if config.calibration { + Some(Default::default()) + } else { + None + }, + }) + } -#[cfg(not(feature = "riscv-ulp-hal"))] -unsafe impl<'d, ADC: Adc> Send for AdcDriver<'d, ADC> {} - -#[cfg(not(feature = "riscv-ulp-hal"))] -impl<'d, ADC: Adc> AdcDriver<'d, ADC> { - #[cfg(all( - esp32, - any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled) - ))] - const CALIBRATION_SCHEME: esp_adc_cal_value_t = esp_adc_cal_value_t_ESP_ADC_CAL_VAL_EFUSE_VREF; - - #[cfg(all( - any(esp32c3, esp32s2), - any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled) - ))] - const CALIBRATION_SCHEME: esp_adc_cal_value_t = esp_adc_cal_value_t_ESP_ADC_CAL_VAL_EFUSE_TP; - - #[cfg(all( - esp32s3, - any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled) - ))] - const CALIBRATION_SCHEME: esp_adc_cal_value_t = - esp_adc_cal_value_t_ESP_ADC_CAL_VAL_EFUSE_TP_FIT; + #[inline(always)] + pub fn read( + &mut self, + pin: &mut AdcChannelDriver<'_, A, T>, + ) -> Result + where + T: ADCPin, + { + self.read_internal(ADC::unit(), pin.pin().adc_channel(), A) + } - #[cfg(not(esp32s2))] - const MAX_READING: u32 = 4095; + #[inline(always)] + pub fn read_raw( + &mut self, + pin: &mut AdcChannelDriver<'_, A, T>, + ) -> Result + where + T: ADCPin, + { + self.read_internal_raw(ADC::unit(), pin.pin().adc_channel()) + } - #[cfg(esp32s2)] - const MAX_READING: u32 = 8191; + #[inline(always)] + #[cfg(all(esp32, esp_idf_version_major = "4"))] + pub fn read_hall( + &mut self, + hall_sensor: &mut crate::hall::HallSensor, + ) -> Result { + let measurement = self.read_hall_raw(hall_sensor); - pub fn new( - adc: impl Peripheral

+ 'd, - config: &config::Config, - ) -> Result { - crate::into_ref!(adc); + self.raw_to_voltage(measurement, adc_atten_t_ADC_ATTEN_DB_0) + } - #[cfg(any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled))] - if config.calibration { - esp!(unsafe { esp_adc_cal_check_efuse(Self::CALIBRATION_SCHEME) })?; + #[inline(always)] + #[cfg(all(esp32, esp_idf_version_major = "4"))] + pub fn read_hall_raw(&mut self, _hall_sensor: &mut crate::hall::HallSensor) -> u16 { + unsafe { hall_sensor_read() as u16 } } - if ADC::unit() == adc_unit_t_ADC_UNIT_1 { - esp!(unsafe { adc1_config_width(config.resolution.into()) })?; + #[inline(always)] + fn read_internal( + &mut self, + unit: adc_unit_t, + channel: adc_channel_t, + atten: adc_atten_t, + ) -> Result { + let measurement = self.read_internal_raw(unit, channel)?; + self.raw_to_voltage(measurement, atten) } - Ok(Self { - _adc: adc, - resolution: config.resolution, - #[cfg(any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled))] - cal_characteristics: if config.calibration { - Some(Default::default()) + #[inline(always)] + fn read_internal_raw( + &mut self, + unit: adc_unit_t, + channel: adc_channel_t, + ) -> Result { + if unit == adc_unit_t_ADC_UNIT_1 { + Ok(unsafe { adc1_get_raw(channel) } as _) } else { - None - }, - }) + #[cfg(not(any(esp32c2, esp32h2, esp32c5, esp32c6, esp32p4)))] + { + let mut measurement = 0; + esp!(unsafe { + adc2_get_raw(channel, self.resolution.into(), &mut measurement) + })?; + + Ok(measurement as _) + } + + #[cfg(any(esp32c2, esp32h2, esp32c5, esp32c6, esp32p4))] + unreachable!(); + } + } + + #[inline(always)] + fn raw_to_voltage( + &mut self, + measurement: u16, + attenuation: adc_atten_t, + ) -> Result { + #[cfg(all( + any(esp32, esp32s2, esp32s3, esp32c3), + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled) + ))] + let mv = if let Some(cal) = self.get_cal_characteristics(attenuation)? { + unsafe { esp_adc_cal_raw_to_voltage(measurement as u32, &cal) as u16 } + } else { + DirectConverter(attenuation).raw_to_mv(measurement) + }; + + #[cfg(not(all( + any(esp32, esp32s2, esp32s3, esp32c3), + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled) + )))] + let mv = DirectConverter(attenuation).raw_to_mv(measurement); + + Ok(mv) + } + + #[cfg(all( + any(esp32, esp32s2, esp32s3, esp32c3), + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled) + ))] + fn get_cal_characteristics( + &mut self, + attenuation: adc_atten_t, + ) -> Result, EspError> { + if let Some(characteristics) = &mut self.cal_characteristics { + if let Some(cal) = characteristics[attenuation as usize] { + Ok(Some(cal)) + } else { + esp!(unsafe { esp_adc_cal_check_efuse(Self::CALIBRATION_SCHEME) })?; + + let mut cal: esp_adc_cal_characteristics_t = Default::default(); + unsafe { + esp_adc_cal_characterize( + ADC::unit(), + attenuation, + self.resolution.into(), + 0, + &mut cal, + ) + }; + + characteristics[attenuation as usize] = Some(cal); + + Ok(Some(cal)) + } + } else { + Ok(None) + } + } } - pub fn read( - &mut self, - pin: &mut AdcChannelDriver<'_, T, ATTEN>, - ) -> Result + impl<'c, const A: adc_atten_t, T> + embedded_hal_0_2::adc::OneShot> + for AdcDriver<'_, T::Adc> where T: ADCPin, - ATTEN: Attenuation, { - self.read_internal(ADC::unit(), pin.pin().adc_channel(), ATTEN::attenuation()) + type Error = EspError; + + fn read(&mut self, pin: &mut AdcChannelDriver<'c, A, T>) -> nb::Result { + self.read_internal(T::Adc::unit(), pin.pin.adc_channel(), A) + .map_err(to_nb_err) + } } #[cfg(all(esp32, esp_idf_version_major = "4"))] - pub fn read_hall( - &mut self, - _hall_sensor: &mut crate::hall::HallSensor, - ) -> Result { - let measurement = unsafe { hall_sensor_read() }; - - self.raw_to_voltage(measurement, adc_atten_t_ADC_ATTEN_DB_0) - } - - fn read_internal( - &mut self, - unit: adc_unit_t, - channel: adc_channel_t, - atten: adc_atten_t, - ) -> Result { - let mut measurement = 0_i32; - - if unit == adc_unit_t_ADC_UNIT_1 { - measurement = unsafe { adc1_get_raw(channel) }; - } else { - esp!(unsafe { adc2_get_raw(channel, self.resolution.into(), &mut measurement) })?; - }; + impl embedded_hal_0_2::adc::OneShot + for AdcDriver<'_, super::ADC1> + { + type Error = EspError; - self.raw_to_voltage(measurement, atten) + fn read( + &mut self, + hall_sensor: &mut crate::hall::HallSensor, + ) -> nb::Result { + AdcDriver::read_hall(self, hall_sensor).map_err(to_nb_err) + } } +} - fn raw_to_voltage( - &mut self, - measurement: core::ffi::c_int, - attenuation: adc_atten_t, - ) -> Result { - #[cfg(any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled))] - let mv = if let Some(cal) = self.get_cal_characteristics(attenuation)? { - unsafe { esp_adc_cal_raw_to_voltage(measurement as u32, &cal) as u16 } - } else { - (measurement as u32 * Self::get_max_mv(attenuation) / Self::MAX_READING) as u16 - }; +fn to_nb_err(err: EspError) -> nb::Error { + if err.code() == ESP_ERR_INVALID_STATE { + nb::Error::WouldBlock + } else { + nb::Error::Other(err) + } +} - #[cfg(not(any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled)))] - let mv = (measurement as u32 * Self::get_max_mv(attenuation) / Self::MAX_READING) as u16; +macro_rules! impl_adc { + ($adc:ident: $unit:expr) => { + crate::impl_peripheral!($adc); - Ok(mv) + impl Adc for $adc { + #[inline(always)] + fn unit() -> adc_unit_t { + $unit + } + } + }; +} + +impl_adc!(ADC1: adc_unit_t_ADC_UNIT_1); +#[cfg(not(any(esp32c2, esp32h2, esp32c5, esp32c6, esp32p4)))] // TODO: Check for esp32c5 and esp32p4 +impl_adc!(ADC2: adc_unit_t_ADC_UNIT_2); + +/// Converts a raw reading to mV without using calibration +struct DirectConverter(adc_atten_t); + +impl DirectConverter { + #[cfg(not(esp32s2))] + const MAX_READING: u32 = 4095; + + #[cfg(esp32s2)] + const MAX_READING: u32 = 8191; + + fn raw_to_mv(&self, raw: u16) -> u16 { + (raw as u32 * self.get_max_mv() as u32 / Self::MAX_READING) as u16 } + #[inline(always)] #[allow(non_upper_case_globals)] - fn get_max_mv(attenuation: adc_atten_t) -> u32 { + fn get_max_mv(&self) -> u16 { + let attenuation = self.0; + #[cfg(esp32)] let mv = match attenuation { adc_atten_t_ADC_ATTEN_DB_0 => 950, @@ -310,7 +456,7 @@ impl<'d, ADC: Adc> AdcDriver<'d, ADC> { other => panic!("Unknown attenuation: {}", other), }; - #[cfg(any(esp32c3, esp32s2))] + #[cfg(any(esp32c3, esp32s2, esp32c2, esp32h2, esp32c5, esp32c6, esp32p4))] let mv = match attenuation { adc_atten_t_ADC_ATTEN_DB_0 => 750, adc_atten_t_ADC_ATTEN_DB_2_5 => 1050, @@ -330,90 +476,1109 @@ impl<'d, ADC: Adc> AdcDriver<'d, ADC> { mv } +} - #[cfg(any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled))] - fn get_cal_characteristics( - &mut self, - attenuation: adc_atten_t, - ) -> Result, EspError> { - if let Some(characteristics) = &mut self.cal_characteristics { - if let Some(cal) = characteristics[attenuation as usize] { - Ok(Some(cal)) - } else { - esp!(unsafe { esp_adc_cal_check_efuse(Self::CALIBRATION_SCHEME) })?; +/// One-shot ADC module +/// Example: reading a value form a pin and printing it on the terminal +/// ``` +/// use std::thread; +/// use std::time::Duration; +/// +/// fn main() -> anyhow::Result<()> { +/// use esp_idf_hal::adc::attenuation::DB_12; +/// use esp_idf_hal::adc::oneshot::config::AdcChannelConfig; +/// use esp_idf_hal::adc::oneshot::*; +/// use esp_idf_hal::peripherals::Peripherals; +/// +/// let peripherals = Peripherals::take()?; +/// let adc = AdcDriver::new(peripherals.adc1)?; +/// +/// /// configuring pin to analog read, you can regulate the adc input voltage range depending on your need +/// /// for this example we use the attenuation of 11db which sets the input voltage range to around 0-3.6V +/// let config = AdcChannelConfig { +/// attenuation: DB_12, +/// ..Default::default() +/// }; +/// let mut adc_pin = AdcChannelDriver::new(&adc, peripherals.pins.gpio2, &config)?; +/// +/// loop { +/// /// you can change the sleep duration depending on how often you want to sample +/// thread::sleep(Duration::from_millis(100)); +/// println!("ADC value: {}", adc.read(&mut adc_pin)?); +/// } +/// } +/// ``` +#[cfg(all( + not(feature = "adc-oneshot-legacy"), + not(esp_idf_version_major = "4"), + esp_idf_comp_esp_adc_enabled +))] +pub mod oneshot { + use core::borrow::Borrow; + + use esp_idf_sys::*; + + use crate::gpio::ADCPin; + use crate::peripheral::Peripheral; + use crate::peripheral::PeripheralRef; + + use super::attenuation::adc_atten_t; + use super::to_nb_err; + use super::Adc; + use super::DirectConverter; + + pub mod config { + use super::adc_atten_t; + + pub use crate::adc::Resolution; + + #[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash)] + pub enum Calibration { + #[default] + None, + #[cfg(all( + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled), + any( + esp32c3, + all( + esp32c6, + not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")), + not(esp_idf_version_full = "5.1.0") + ), + esp32s3, + ) + ))] + Curve, + #[cfg(all( + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled), + any(esp32, esp32c2, esp32s2) + ))] + Line, + } + + #[derive(Debug, Copy, Clone, Default)] + pub struct AdcChannelConfig { + pub attenuation: adc_atten_t, + pub resolution: Resolution, + pub calibration: Calibration, + } - let mut cal: esp_adc_cal_characteristics_t = Default::default(); - unsafe { - esp_adc_cal_characterize( - ADC::unit(), - attenuation, - self.resolution.into(), - 0, - &mut cal, + impl AdcChannelConfig { + pub const fn new() -> Self { + Self { + attenuation: crate::adc::attenuation::NONE, + resolution: Resolution::new(), + calibration: Calibration::None, + } + } + } + } + + /// Converts a raw reading to mV with or without calibration + enum Converter { + NoCalibration(adc_atten_t), + #[cfg(all( + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled), + any( + esp32c3, + all( + esp32c6, + not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")), + not(esp_idf_version_full = "5.1.0") + ), + esp32s3, + ) + ))] + CurveFittingCalibration(adc_cali_handle_t), + #[cfg(all( + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled), + any(esp32, esp32c2, esp32s2) + ))] + LineFittingCalibration(adc_cali_handle_t), + } + + impl Converter { + #[allow(unused_variables)] + fn create( + unit_id: u8, + chan: adc_channel_t, + atten: adc_atten_t, + bitwidth: adc_bits_width_t, + calibration: config::Calibration, + ) -> Result { + match calibration { + config::Calibration::None => Ok(Self::NoCalibration(atten)), + #[cfg(all( + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled), + any( + esp32c3, + all( + esp32c6, + not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")), + not(esp_idf_version_full = "5.1.0") + ), + esp32s3, ) - }; + ))] + config::Calibration::Curve => { + // it would be nice if esp-idf-sys could export some cfg values to replicate these two defines + // from esp-idf: + // ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + // ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + // then we wouuld not need the ugliness for the esp32c6 + let cal_config = adc_cali_curve_fitting_config_t { + unit_id: unit_id as u32, + #[cfg(all( + esp_idf_version_major = "5", + not(esp_idf_version_minor = "0"), + not(all(esp_idf_version_minor = "1", esp_idf_version_patch = "0")) + ))] + chan, + atten, + bitwidth, + }; + let mut cal_handle: adc_cali_handle_t = core::ptr::null_mut(); + esp!(unsafe { + esp_idf_sys::adc_cali_create_scheme_curve_fitting( + &cal_config, + &mut cal_handle, + ) + })?; + + Ok(Self::CurveFittingCalibration(cal_handle)) + } + #[cfg(all( + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled), + any(esp32, esp32c2, esp32s2) + ))] + config::Calibration::Line => { + // esp32 has an additional field that the exanple defaults + // to using fuse values for vref. Maybe we should expose + // this as a config option? + #[allow(clippy::needless_update)] + let cal_config = adc_cali_line_fitting_config_t { + unit_id: unit_id as u32, + atten, + bitwidth, + ..Default::default() + }; + let mut cal_handle: adc_cali_handle_t = core::ptr::null_mut(); + esp!(unsafe { + esp_idf_sys::adc_cali_create_scheme_line_fitting( + &cal_config, + &mut cal_handle, + ) + })?; + + Ok(Self::LineFittingCalibration(cal_handle)) + } + } + } - characteristics[attenuation as usize] = Some(cal); + fn raw_to_mv(&self, raw: u16) -> Result { + match self { + Self::NoCalibration(atten) => Ok(DirectConverter(*atten).raw_to_mv(raw)), + #[cfg(all( + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled), + any( + esp32c3, + all( + esp32c6, + not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")), + not(esp_idf_version_full = "5.1.0") + ), + esp32s3, + ) + ))] + Self::CurveFittingCalibration(handle) => { + let mut mv = 0i32; + esp!(unsafe { adc_cali_raw_to_voltage(*handle, raw as i32, &mut mv) })?; + + Ok(mv as u16) + } + #[cfg(all( + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled), + any(esp32, esp32c2, esp32s2) + ))] + Self::LineFittingCalibration(handle) => { + let mut mv = 0i32; + esp!(unsafe { adc_cali_raw_to_voltage(*handle, raw as i32, &mut mv) })?; + + Ok(mv as u16) + } + } + } + } - Ok(Some(cal)) + impl Drop for Converter { + fn drop(&mut self) { + match self { + Self::NoCalibration(_) => (), + #[cfg(all( + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled), + any( + esp32c3, + all( + esp32c6, + not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")), + not(esp_idf_version_full = "5.1.0") + ), + esp32s3, + ) + ))] + Self::CurveFittingCalibration(handle) => { + esp!(unsafe { esp_idf_sys::adc_cali_delete_scheme_curve_fitting(*handle) }) + .unwrap() + } + #[cfg(all( + any(esp_idf_comp_esp_adc_cal_enabled, esp_idf_comp_esp_adc_enabled), + any(esp32, esp32c2, esp32s2) + ))] + Self::LineFittingCalibration(handle) => { + esp!(unsafe { esp_idf_sys::adc_cali_delete_scheme_line_fitting(*handle) }) + .unwrap() + } } - } else { - Ok(None) } } -} -#[cfg(not(feature = "riscv-ulp-hal"))] -impl<'d, ADC, ATTEN, PIN> embedded_hal_0_2::adc::OneShot for AdcDriver<'d, ADC> -where - ADC: Adc, - ATTEN: Attenuation, - PIN: embedded_hal_0_2::adc::Channel, -{ - type Error = EspError; - - fn read(&mut self, _pin: &mut PIN) -> nb::Result { - self.read_internal( - ADC::unit(), - PIN::channel() as adc_channel_t, - ATTEN::attenuation(), - ) - .map_err(to_nb_err) + pub struct AdcChannelDriver<'d, T, M> + where + T: ADCPin, + M: Borrow>, + { + adc: M, + _pin: PeripheralRef<'d, T>, + converter: Converter, } -} -#[cfg(all(esp32, esp_idf_version_major = "4", not(feature = "riscv-ulp-hal")))] -impl<'d> embedded_hal_0_2::adc::OneShot - for AdcDriver<'d, ADC1> -{ - type Error = EspError; + impl<'d, T, M> AdcChannelDriver<'d, T, M> + where + T: ADCPin, + M: Borrow>, + { + pub fn new( + adc: M, + pin: impl Peripheral

+ 'd, + config: &config::AdcChannelConfig, + ) -> Result { + crate::into_ref!(pin); + + unsafe { + crate::gpio::rtc_reset_pin(pin.pin())?; + } + + let chan_config = adc_oneshot_chan_cfg_t { + atten: config.attenuation, + bitwidth: config.resolution.into(), + }; + + let converter = Converter::create( + T::Adc::unit() as u8, + pin.adc_channel(), + config.attenuation, + config.resolution.into(), + config.calibration, + )?; + + unsafe { + esp!(adc_oneshot_config_channel( + adc.borrow().handle, + pin.adc_channel(), + &chan_config + ))? + }; + + Ok(Self { + adc, + _pin: pin, + converter, + }) + } + + #[inline(always)] + pub fn read(&mut self) -> Result { + let raw = self.read_raw()?; + self.raw_to_mv(raw) + } - fn read(&mut self, hall_sensor: &mut crate::hall::HallSensor) -> nb::Result { - AdcDriver::read_hall(self, hall_sensor).map_err(to_nb_err) + #[inline(always)] + pub fn read_raw(&mut self) -> Result { + let channel = T::CHANNEL; + self.adc.borrow().read_raw_internal(channel) + } + + #[inline(always)] + pub fn raw_to_mv(&self, raw: u16) -> Result { + self.converter.raw_to_mv(raw) + } } -} -#[cfg(not(feature = "riscv-ulp-hal"))] -fn to_nb_err(err: EspError) -> nb::Error { - if err.code() == ESP_ERR_INVALID_STATE { - nb::Error::WouldBlock - } else { - nb::Error::Other(err) + impl<'d, T, M> embedded_hal_0_2::adc::Channel for AdcChannelDriver<'d, T, M> + where + T: ADCPin, + M: Borrow>, + { + type ID = adc_channel_t; + + fn channel() -> Self::ID { + T::CHANNEL + } + } + + unsafe impl<'d, T, M> Send for AdcChannelDriver<'d, T, M> + where + T: ADCPin, + M: Borrow>, + { + } + + pub struct AdcDriver<'d, ADC: Adc> { + handle: adc_oneshot_unit_handle_t, + _adc: PeripheralRef<'d, ADC>, + } + + impl<'d, ADC: Adc> AdcDriver<'d, ADC> { + pub fn new(adc: impl Peripheral

+ 'd) -> Result { + crate::into_ref!(adc); + let config = adc_oneshot_unit_init_cfg_t { + unit_id: ADC::unit(), + ..Default::default() + }; + let mut handle: adc_oneshot_unit_handle_t = core::ptr::null_mut(); + unsafe { esp!(adc_oneshot_new_unit(&config, &mut handle))? }; + Ok(Self { handle, _adc: adc }) + } + + #[inline(always)] + pub fn read(&self, channel: &mut AdcChannelDriver<'d, T, M>) -> Result + where + T: ADCPin, + M: Borrow>, + { + let raw = self.read_raw(channel)?; + self.raw_to_mv(channel, raw) + } + + #[inline(always)] + pub fn read_raw( + &self, + _channel: &mut AdcChannelDriver<'d, T, M>, + ) -> Result + where + T: ADCPin, + M: Borrow>, + { + self.read_raw_internal(T::CHANNEL) + } + + #[inline(always)] + fn read_raw_internal(&self, channel: adc_channel_t) -> Result { + let mut measurement = 0; + unsafe { esp!(adc_oneshot_read(self.handle, channel, &mut measurement)) }?; + Ok(measurement as u16) + } + + #[inline(always)] + pub fn raw_to_mv( + &self, + channel: &AdcChannelDriver<'d, T, M>, + raw: u16, + ) -> Result + where + T: ADCPin, + M: Borrow>, + { + channel.converter.raw_to_mv(raw) + } } + + impl Drop for AdcDriver<'_, ADC> { + fn drop(&mut self) { + unsafe { esp!(adc_oneshot_del_unit(self.handle)) }.unwrap(); + } + } + + impl<'d, T, M> embedded_hal_0_2::adc::OneShot> + for AdcDriver<'d, T::Adc> + where + T: ADCPin, + M: Borrow>, + { + type Error = EspError; + + fn read(&mut self, pin: &mut AdcChannelDriver<'d, T, M>) -> nb::Result { + AdcDriver::read(self, pin).map_err(to_nb_err) + } + } + + unsafe impl Send for AdcDriver<'_, ADC> {} + unsafe impl Sync for AdcDriver<'_, ADC> {} } -macro_rules! impl_adc { - ($adc:ident: $unit:expr) => { - crate::impl_peripheral!($adc); +/// Continuous ADC module +/// Example: continuously reading value from a pin +/// ``` +/// use ::log::{info, debug}; +/// +/// fn main() -> anyhow::Result<()> { +/// use esp_idf_svc::hal::adc::{AdcContConfig, AdcContDriver, AdcMeasurement, Attenuated}; +/// use esp_idf_svc::hal::modem::Modem; +/// use esp_idf_svc::hal::peripherals::Peripherals; +/// use esp_idf_svc::sys::EspError; +/// +/// esp_idf_svc::sys::link_patches(); +/// esp_idf_svc::log::EspLogger::initialize_default(); +/// +/// let peripherals = Peripherals::take()?; +/// let config = AdcContConfig::default(); +/// +/// let adc_1_channel_0 = Attenuated::db12(peripherals.pins.gpio0); +/// let mut adc = AdcContDriver::new(peripherals.adc1, &config, adc_1_channel_0)?; +/// +/// adc.start()?; +/// +/// /// Default to just read 100 measurements per each read +/// let mut samples = [AdcMeasurement::default(); 100]; +/// +/// loop { +/// if let Ok(num_read) = adc.read(&mut samples, 10) { +/// debug!("Read {} measurement.", num_read); +/// for index in 0..num_read { +/// debug!("{}", samples[index].data()); +/// } +/// } +/// } +/// } +/// ``` +#[cfg(all( + not(esp_idf_version_major = "4"), + not(esp32c2), + esp_idf_comp_esp_adc_enabled +))] +pub mod continuous { + use core::ffi::c_void; + use core::fmt::{self, Debug, Display}; + use core::marker::PhantomData; - impl Adc for $adc { - #[inline(always)] - fn unit() -> adc_unit_t { - $unit + use esp_idf_sys::*; + + use crate::delay::{self, TickType}; + use crate::gpio::{sealed::ADCPin as _, ADCPin}; + use crate::interrupt::asynch::HalIsrNotification; + use crate::io::EspIOError; + use crate::peripheral::Peripheral; + + use super::{attenuation, Adc}; + + /// Set ADC attenuation level + /// Example: let pin = Attenuated::db12(peripherals.pins.gpio0); + pub struct Attenuated(T); + + impl Attenuated<{ attenuation::NONE }, T> { + pub const fn none(t: T) -> Self { + Self(t) + } + } + + impl Attenuated<{ attenuation::DB_2_5 }, T> { + pub const fn db2_5(t: T) -> Self { + Self(t) + } + } + + impl Attenuated<{ attenuation::DB_6 }, T> { + pub const fn db6(t: T) -> Self { + Self(t) + } + } + + #[allow(deprecated)] + impl Attenuated<{ attenuation::DB_11 }, T> { + #[cfg_attr( + not(esp_idf_version_major = "4"), + deprecated(since = "0.45.3", note = "Use `Attenuated::db12` instead") + )] + pub const fn db11(t: T) -> Self { + Self(t) + } + } + + #[cfg(not(esp_idf_version_major = "4"))] + impl Attenuated<{ attenuation::DB_12 }, T> { + pub const fn db12(t: T) -> Self { + Self(t) + } + } + + impl Attenuated { + pub fn atten(channel: (adc_channel_t, adc_atten_t)) -> (adc_channel_t, adc_atten_t) { + (channel.0, A) + } + } + + pub type AttenNone = Attenuated<{ attenuation::NONE }, T>; + pub type Atten2p5dB = Attenuated<{ attenuation::DB_2_5 }, T>; + pub type Atten6dB = Attenuated<{ attenuation::DB_6 }, T>; + #[deprecated(since = "0.45.3", note = "Use `Atten12dB` instead")] + #[allow(deprecated)] + pub type Atten11dB = Attenuated<{ attenuation::DB_11 }, T>; + pub type Atten12dB = Attenuated<{ attenuation::DB_12 }, T>; + + pub trait AdcChannels { + type Adc: Adc; + type Iterator<'a>: Iterator + where + Self: 'a; + + fn iter(&self) -> Self::Iterator<'_>; + } + + impl

AdcChannels for P + where + P: Peripheral, + P::P: ADCPin, + { + type Adc = <

::P as ADCPin>::Adc; + + type Iterator<'a> + = core::iter::Once<(adc_channel_t, adc_atten_t)> + where + Self: 'a; + + fn iter(&self) -> Self::Iterator<'_> { + core::iter::once((P::P::CHANNEL, attenuation::NONE)) + } + } + + impl AdcChannels for Attenuated + where + C: AdcChannels, + { + type Adc = C::Adc; + + type Iterator<'a> + = core::iter::Map< + C::Iterator<'a>, + fn((adc_channel_t, adc_atten_t)) -> (adc_channel_t, adc_atten_t), + > + where + Self: 'a; + + fn iter(&self) -> Self::Iterator<'_> { + self.0.iter().map(Attenuated::::atten) + } + } + + pub struct AdcChannelsArray(pub [C; N]); + + impl AdcChannels for AdcChannelsArray + where + C: AdcChannels, + { + type Adc = C::Adc; + + type Iterator<'a> + = core::iter::FlatMap< + core::slice::Iter<'a, C>, + ::Iterator<'a>, + fn(&'a C) -> C::Iterator<'a>, + > + where + Self: 'a; + + fn iter(&self) -> Self::Iterator<'_> { + self.0.iter().flat_map(AdcChannels::iter) + } + } + + pub struct EmptyAdcChannels(PhantomData); + + impl EmptyAdcChannels { + pub fn chain(other: O) -> ChainedAdcChannels + where + A: Adc, + O: AdcChannels, + { + ChainedAdcChannels { + first: Self(PhantomData), + second: other, } } - }; -} + } -impl_adc!(ADC1: adc_unit_t_ADC_UNIT_1); -impl_adc!(ADC2: adc_unit_t_ADC_UNIT_2); + impl AdcChannels for EmptyAdcChannels + where + A: Adc, + { + type Adc = A; + + type Iterator<'a> + = core::iter::Empty<(adc_channel_t, adc_atten_t)> + where + Self: 'a; + + fn iter(&self) -> Self::Iterator<'_> { + core::iter::empty() + } + } + + pub struct ChainedAdcChannels { + first: F, + second: S, + } + + impl ChainedAdcChannels { + pub fn chain(self, other: O) -> ChainedAdcChannels + where + F: AdcChannels, + S: AdcChannels, + O: AdcChannels, + { + ChainedAdcChannels { + first: self, + second: other, + } + } + } + + impl AdcChannels for ChainedAdcChannels + where + F: AdcChannels, + S: AdcChannels, + { + type Adc = F::Adc; + + type Iterator<'a> + = core::iter::Chain, S::Iterator<'a>> + where + Self: 'a; + + fn iter(&self) -> Self::Iterator<'_> { + self.first.iter().chain(self.second.iter()) + } + } + + #[derive(Copy, Clone)] + #[repr(transparent)] + pub struct AdcMeasurement(adc_digi_output_data_t); + + impl Default for AdcMeasurement { + fn default() -> Self { + Self::new() + } + } + + impl AdcMeasurement { + pub const INIT: Self = AdcMeasurement(unsafe { + core::mem::transmute::< + [u8; core::mem::size_of::()], + adc_digi_output_data_t, + >([0u8; core::mem::size_of::()]) + }); + + pub const fn new() -> Self { + Self::INIT + } + + #[cfg(any(esp32, esp32s2))] + pub fn data(&self) -> u16 { + unsafe { self.0.__bindgen_anon_1.type1.data() as _ } + } + + #[cfg(any(esp32, esp32s2))] + pub fn channel(&self) -> adc_channel_t { + unsafe { self.0.__bindgen_anon_1.type1.channel() as _ } + } + + #[cfg(not(any(esp32, esp32s2)))] + pub fn data(&self) -> u16 { + unsafe { self.0.__bindgen_anon_1.type2.data() as _ } + } + + #[cfg(not(any(esp32, esp32s2)))] + pub fn channel(&self) -> adc_channel_t { + unsafe { self.0.__bindgen_anon_1.type2.channel() as _ } + } + + #[cfg(not(any(esp32, esp32s2, esp32h2, esp32c6)))] + pub fn unit(&self) -> adc_unit_t { + unsafe { self.0.__bindgen_anon_1.type2.unit() as _ } + } + + #[cfg(any(esp32, esp32s2))] + pub fn nullify(&mut self) { + self.0.__bindgen_anon_1.val = self.data(); + } + + #[cfg(not(any(esp32, esp32s2)))] + pub fn nullify(&mut self) { + self.0.__bindgen_anon_1.val = self.data() as _; + } + + #[cfg(any(esp32, esp32s2))] + pub fn as_pcm16(data: &mut [AdcMeasurement]) -> &mut [u16] { + for measurement in data.iter_mut() { + measurement.nullify(); + } + + unsafe { core::slice::from_raw_parts_mut(data.as_mut_ptr() as *mut _, data.len()) } + } + + #[cfg(not(any(esp32, esp32s2)))] + pub fn as_pcm32(data: &mut [AdcMeasurement]) -> &mut [u32] { + for measurement in data.iter_mut() { + measurement.nullify(); + } + + unsafe { core::slice::from_raw_parts_mut(data.as_mut_ptr() as *mut _, data.len()) } + } + } + + impl Display for AdcMeasurement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "ADC Reading {{channel={}, data={}}}", + self.channel(), + self.data() + ) + } + } + + impl Debug for AdcMeasurement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "AdcMeasurement {{channel: {}, data: {}}}", + self.channel(), + self.data() + ) + } + } + + pub mod config { + use crate::units::*; + + /// ADC continuous mode driver configurations. + /// The default configuration is: + /// * [`sample_freq`][Config::sample_freq]: 2000 Hz + /// * [`frame_measurements`][Config::frame_measurements]: 100 + /// * [`frames_count`][Config::frames_count]: 10 + #[derive(Debug, Copy, Clone)] + pub struct Config { + pub sample_freq: Hertz, + pub frame_measurements: usize, + pub frames_count: usize, + } + + impl Config { + pub const fn new() -> Self { + Self { + sample_freq: Hertz(20000), + frame_measurements: 100, + frames_count: 10, + } + } + + #[must_use] + pub fn sample_freq(mut self, sample_freq: Hertz) -> Self { + self.sample_freq = sample_freq; + self + } + + #[must_use] + pub fn frame_measurements(mut self, frame_measurements: usize) -> Self { + self.frame_measurements = frame_measurements; + self + } + + #[must_use] + pub fn frames_count(mut self, frames_count: usize) -> Self { + self.frames_count = frames_count; + self + } + } + + impl Default for Config { + fn default() -> Self { + Self::new() + } + } + } + + pub struct AdcDriver<'d> { + handle: adc_continuous_handle_t, + adc: u8, + _ref: PhantomData<&'d ()>, + } + + impl<'d> AdcDriver<'d> { + /// Initialize ADC continuous driver with configuration and channels. + #[cfg(esp32)] + pub fn new( + adc: impl Peripheral

+ 'd, + _i2s: impl Peripheral

+ 'd, + config: &config::Config, + channels: impl AdcChannels + 'd, + ) -> Result { + Self::internal_new(adc, config, channels) + } + + /// Initialize ADC continuous driver with configuration and channels + #[cfg(esp32s2)] + pub fn new( + adc: impl Peripheral

+ 'd, + _spi: impl Peripheral

+ 'd, + config: &config::Config, + channels: impl AdcChannels + 'd, + ) -> Result { + Self::internal_new(adc, config, channels) + } + + /// Initialize ADC continuous driver with configuration and channels. + #[cfg(not(any(esp32, esp32s2)))] + pub fn new( + adc: impl Peripheral

+ 'd, + config: &config::Config, + channels: impl AdcChannels + 'd, + ) -> Result { + Self::internal_new(adc, config, channels) + } + + fn internal_new( + _adc: impl Peripheral

+ 'd, + config: &config::Config, + channels: impl AdcChannels + 'd, + ) -> Result { + let mut patterns = [adc_digi_pattern_config_t::default(); 32]; + + for (index, (channel, atten)) in channels.iter().enumerate() { + if index >= patterns.len() { + return Err(EspError::from_infallible::()); + } + + patterns[index].atten = atten as _; + patterns[index].channel = channel as _; + patterns[index].unit = A::unit() as _; + patterns[index].bit_width = 12; // For now only 12 bits is supported + } + + let mut handle: adc_continuous_handle_t = core::ptr::null_mut(); + + #[allow(clippy::needless_update)] + esp!(unsafe { + adc_continuous_new_handle( + &adc_continuous_handle_cfg_t { + max_store_buf_size: SOC_ADC_DIGI_DATA_BYTES_PER_CONV + * (config.frame_measurements as u32) + * (config.frames_count as u32), + conv_frame_size: SOC_ADC_DIGI_DATA_BYTES_PER_CONV + * (config.frame_measurements as u32), + ..Default::default() + }, + &mut handle, + ) + })?; + + let conv_mode = if A::unit() == 0 { + adc_digi_convert_mode_t_ADC_CONV_SINGLE_UNIT_1 + } else { + adc_digi_convert_mode_t_ADC_CONV_SINGLE_UNIT_2 + }; + + #[cfg(any(esp32, esp32s2))] + let format = adc_digi_output_format_t_ADC_DIGI_OUTPUT_FORMAT_TYPE1; + + #[cfg(not(any(esp32, esp32s2)))] + let format = adc_digi_output_format_t_ADC_DIGI_OUTPUT_FORMAT_TYPE2; + + esp!(unsafe { + adc_continuous_config( + handle, + &adc_continuous_config_t { + pattern_num: channels.iter().count() as _, + adc_pattern: &patterns as *const _ as *mut _, + sample_freq_hz: config.sample_freq.into(), + conv_mode, + format, + }, + ) + })?; + + #[cfg(not(esp_idf_adc_continuous_isr_iram_safe))] + { + esp!(unsafe { + adc_continuous_register_event_callbacks( + handle, + &adc_continuous_evt_cbs_t { + on_conv_done: Some(Self::handle_isr), + on_pool_ovf: Some(Self::handle_isr), + }, + &NOTIFIER[A::unit() as usize] as *const _ as *mut _, + ) + })?; + } + + Ok(Self { + handle, + adc: A::unit() as _, + _ref: PhantomData, + }) + } + + /// Get the ADC driver handle. + pub fn handle(&self) -> adc_continuous_handle_t { + self.handle + } + + // Get the ADC unit. ADC1 or ADC2. + pub fn unit(&self) -> adc_unit_t { + self.adc as _ + } + + /// Start the ADC under continuous mode and generate results. + pub fn start(&mut self) -> Result<(), EspError> { + esp!(unsafe { adc_continuous_start(self.handle) }) + } + + /// Stop the ADC. + pub fn stop(&mut self) -> Result<(), EspError> { + esp!(unsafe { adc_continuous_stop(self.handle) }) + } + + /// Read `AdcMeasurements` in continuous mode. + pub fn read( + &mut self, + buf: &mut [AdcMeasurement], + timeout: TickType_t, + ) -> Result { + let mut read: u32 = 0; + + esp!(unsafe { + adc_continuous_read( + self.handle, + buf.as_mut_ptr() as *mut _, + core::mem::size_of_val(buf) as _, + &mut read, + TickType(timeout).as_millis_u32(), + ) + })?; + + Ok(read as usize / core::mem::size_of::()) + } + + /// Read bytes in continuous mode. + pub fn read_bytes( + &mut self, + buf: &mut [u8], + timeout: TickType_t, + ) -> Result { + let mut read: u32 = 0; + + esp!(unsafe { + adc_continuous_read( + self.handle, + buf.as_mut_ptr() as *mut _, + core::mem::size_of_val(buf) as _, + &mut read, + TickType(timeout).as_millis_u32(), + ) + })?; + + Ok(read as usize) + } + + #[cfg(not(esp_idf_adc_continuous_isr_iram_safe))] + pub async fn read_async(&mut self, buf: &mut [AdcMeasurement]) -> Result { + loop { + match self.read(buf, delay::NON_BLOCK) { + Ok(len) if len > 0 => return Ok(len), + Err(e) if e.code() != ESP_ERR_TIMEOUT => return Err(e), + _ => { + NOTIFIER[self.adc as usize].wait().await; + } + } + } + } + + #[cfg(not(esp_idf_adc_continuous_isr_iram_safe))] + pub async fn read_bytes_async(&mut self, buf: &mut [u8]) -> Result { + loop { + match self.read_bytes(buf, delay::NON_BLOCK) { + Ok(len) if len > 0 => return Ok(len), + Err(e) if e.code() != ESP_ERR_TIMEOUT => return Err(e), + _ => { + NOTIFIER[self.adc as usize].wait().await; + } + } + } + } + + #[cfg(not(esp_idf_adc_continuous_isr_iram_safe))] + extern "C" fn handle_isr( + _handle: adc_continuous_handle_t, + _data: *const adc_continuous_evt_data_t, + user_data: *mut c_void, + ) -> bool { + let notifier: &HalIsrNotification = + unsafe { (user_data as *const HalIsrNotification).as_ref() }.unwrap(); + + notifier.notify_lsb() + } + } + + impl Drop for AdcDriver<'_> { + fn drop(&mut self) { + let _ = self.stop(); + + #[cfg(not(esp_idf_adc_continuous_isr_iram_safe))] + { + esp!(unsafe { + adc_continuous_register_event_callbacks( + self.handle, + &adc_continuous_evt_cbs_t { + on_conv_done: None, + on_pool_ovf: None, + }, + core::ptr::null_mut(), + ) + }) + .unwrap(); + } + + esp!(unsafe { adc_continuous_deinit(self.handle) }).unwrap(); + + #[cfg(not(esp_idf_adc_continuous_isr_iram_safe))] + NOTIFIER[self.adc as usize].reset(); + } + } + + unsafe impl Send for AdcDriver<'_> {} + + impl embedded_io::ErrorType for AdcDriver<'_> { + type Error = EspIOError; + } + + impl embedded_io::Read for AdcDriver<'_> { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.read_bytes(buf, delay::BLOCK).map_err(EspIOError) + } + } + + #[cfg(not(esp_idf_adc_continuous_isr_iram_safe))] + impl embedded_io_async::Read for AdcDriver<'_> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read_bytes_async(buf).await.map_err(EspIOError) + } + } + + #[cfg(not(esp_idf_adc_continuous_isr_iram_safe))] + #[cfg(any(esp32c2, esp32h2, esp32c5, esp32c6, esp32p4))] // TODO: Check for esp32c5 and esp32p4 + static NOTIFIER: [HalIsrNotification; 1] = [HalIsrNotification::new()]; + + #[cfg(not(esp_idf_adc_continuous_isr_iram_safe))] + #[cfg(not(any(esp32c2, esp32h2, esp32c5, esp32c6, esp32p4)))] // TODO: Check for esp32c5 and esp32p4 + static NOTIFIER: [HalIsrNotification; 2] = + [HalIsrNotification::new(), HalIsrNotification::new()]; +} diff --git a/src/can.rs b/src/can.rs index 7c0ff37e14b..c03d54e0dba 100644 --- a/src/can.rs +++ b/src/can.rs @@ -32,11 +32,23 @@ //! } //! ``` +use core::borrow::BorrowMut; +use core::ffi::CStr; +use core::marker::PhantomData; +use core::num::NonZeroU32; + +use enumset::{EnumSet, EnumSetType}; + use esp_idf_sys::*; -use crate::delay::{BLOCK, NON_BLOCK}; -use crate::gpio::*; +use num_enum::TryFromPrimitive; + +use crate::cpu::Core; +use crate::delay::{self, BLOCK, NON_BLOCK}; +use crate::interrupt::InterruptType; use crate::peripheral::{Peripheral, PeripheralRef}; +use crate::task::asynch::Notification; +use crate::{gpio::*, task}; crate::embedded_hal_error!(CanError, embedded_can::Error, embedded_can::ErrorKind); @@ -49,8 +61,13 @@ crate::embedded_hal_error!( pub type CanConfig = config::Config; pub mod config { + use enumset::EnumSet; use esp_idf_sys::*; + use crate::interrupt::InterruptType; + + use super::Alert; + /// CAN timing #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Timing { @@ -80,7 +97,6 @@ pub mod config { tseg_1: 16, tseg_2: 8, sjw: 3, - triple_sampling: false, ..Default::default() }, Timing::B50K => twai_timing_config_t { @@ -88,7 +104,6 @@ pub mod config { tseg_1: 15, tseg_2: 4, sjw: 3, - triple_sampling: false, ..Default::default() }, Timing::B100K => twai_timing_config_t { @@ -96,7 +111,6 @@ pub mod config { tseg_1: 15, tseg_2: 4, sjw: 3, - triple_sampling: false, ..Default::default() }, Timing::B125K => twai_timing_config_t { @@ -104,7 +118,6 @@ pub mod config { tseg_1: 15, tseg_2: 4, sjw: 3, - triple_sampling: false, ..Default::default() }, Timing::B250K => twai_timing_config_t { @@ -112,7 +125,6 @@ pub mod config { tseg_1: 15, tseg_2: 4, sjw: 3, - triple_sampling: false, ..Default::default() }, Timing::B500K => twai_timing_config_t { @@ -120,7 +132,6 @@ pub mod config { tseg_1: 15, tseg_2: 4, sjw: 3, - triple_sampling: false, ..Default::default() }, Timing::B800K => twai_timing_config_t { @@ -128,7 +139,6 @@ pub mod config { tseg_1: 16, tseg_2: 8, sjw: 3, - triple_sampling: false, ..Default::default() }, Timing::B1M => twai_timing_config_t { @@ -136,7 +146,6 @@ pub mod config { tseg_1: 15, tseg_2: 4, sjw: 3, - triple_sampling: false, ..Default::default() }, Timing::Custom { @@ -150,7 +159,24 @@ pub mod config { tseg_1: timing_segment_1, tseg_2: timing_segment_2, sjw: synchronization_jump_width, + #[cfg(any( + esp_idf_version_major = "4", + esp_idf_version = "5.0", + esp_idf_version = "5.1", + esp_idf_version = "5.2", + esp_idf_version = "5.3", + esp_idf_version = "5.4" + ))] triple_sampling, + #[cfg(not(any( + esp_idf_version_major = "4", + esp_idf_version = "5.0", + esp_idf_version = "5.1", + esp_idf_version = "5.2", + esp_idf_version = "5.3", + esp_idf_version = "5.4" + )))] + __bindgen_anon_1: twai_timing_config_t__bindgen_ty_1 { triple_sampling }, ..Default::default() }, } @@ -211,9 +237,16 @@ pub mod config { /// ``` #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Filter { - // Filter for 11 bit standard CAN IDs + /// Filter for 11 bit standard CAN IDs Standard { filter: u16, mask: u16 }, - // Filter for 29 bit extended CAN IDs + /// Filter for two 11 bit standard CAN IDs + Dual { + filter1: u16, + mask1: u16, + filter2: u16, + mask2: u16, + }, + /// Filter for 29 bit extended CAN IDs Extended { filter: u32, mask: u32 }, } @@ -236,14 +269,45 @@ pub mod config { } #[derive(Debug, Copy, Clone, Default)] + pub enum Mode { + #[default] + Normal, + NoAck, + ListenOnly, + } + + impl From for twai_mode_t { + fn from(val: Mode) -> Self { + match val { + Mode::Normal => twai_mode_t_TWAI_MODE_NORMAL, + Mode::NoAck => twai_mode_t_TWAI_MODE_NO_ACK, + Mode::ListenOnly => twai_mode_t_TWAI_MODE_LISTEN_ONLY, + } + } + } + + #[derive(Debug, Clone)] pub struct Config { pub timing: Timing, pub filter: Filter, + pub tx_queue_len: u32, + pub rx_queue_len: u32, + pub mode: Mode, + pub alerts: EnumSet, + pub intr_flags: EnumSet, } impl Config { pub fn new() -> Self { - Default::default() + Self { + timing: Default::default(), + filter: Default::default(), + tx_queue_len: 5, + rx_queue_len: 5, + mode: Default::default(), + alerts: Default::default(), + intr_flags: EnumSet::::empty(), + } } #[must_use] @@ -257,113 +321,198 @@ pub mod config { self.filter = filter; self } + + #[must_use] + pub fn tx_queue_len(mut self, tx_queue_len: u32) -> Self { + self.tx_queue_len = tx_queue_len; + self + } + + #[must_use] + pub fn rx_queue_len(mut self, rx_queue_len: u32) -> Self { + self.rx_queue_len = rx_queue_len; + self + } + + #[must_use] + pub fn mode(mut self, mode: Mode) -> Self { + self.mode = mode; + self + } + + #[must_use] + pub fn alerts(mut self, alerts: EnumSet) -> Self { + self.alerts = alerts; + self + } + + #[must_use] + pub fn intr_flags(mut self, flags: EnumSet) -> Self { + self.intr_flags = flags; + self + } + } + + impl Default for Config { + fn default() -> Self { + Self::new() + } } } -/// CAN abstraction -pub struct CanDriver<'d>(PeripheralRef<'d, CAN>); +#[derive(Debug, EnumSetType, TryFromPrimitive)] +#[enumset(repr = "u32")] +#[repr(u32)] +pub enum Alert { + TransmitIdle = 1, + Success = 2, + Received = 3, + BelowErrorWarning = 4, + ActiveError = 5, + RecoveryInProgress = 6, + BusRecovered = 7, + ArbLost = 8, + AboveErrorWarning = 9, + BusError = 10, + TransmitFailed = 11, + ReceiveQueueFull = 12, + ErrorPass = 13, + BusOffline = 14, + ReceiveFifoOverflow = 15, + TransmitRetried = 16, + PeripheralReset = 17, + AlertAndLog = 18, +} -unsafe impl<'d> Send for CanDriver<'d> {} +/// CAN abstraction +pub struct CanDriver<'d>(PeripheralRef<'d, CAN>, EnumSet, bool); impl<'d> CanDriver<'d> { pub fn new( can: impl Peripheral

+ 'd, tx: impl Peripheral

+ 'd, - rx: impl Peripheral

+ 'd, + rx: impl Peripheral

+ 'd, config: &config::Config, ) -> Result { crate::into_ref!(can, tx, rx); + #[allow(clippy::needless_update)] let general_config = twai_general_config_t { - mode: twai_mode_t_TWAI_MODE_NORMAL, + mode: config.mode.into(), tx_io: tx.pin(), rx_io: rx.pin(), clkout_io: -1, bus_off_io: -1, - tx_queue_len: 5, - rx_queue_len: 5, - alerts_enabled: TWAI_ALERT_NONE, + tx_queue_len: config.tx_queue_len, + rx_queue_len: config.rx_queue_len, + alerts_enabled: config.alerts.as_repr(), clkout_divider: 0, - intr_flags: ESP_INTR_FLAG_LEVEL1 as i32, + intr_flags: InterruptType::to_native(config.intr_flags) as _, + ..Default::default() }; let timing_config = config.timing.into(); // modify filter and mask to be compatible with TWAI acceptance filter - let (filter, mask) = match config.filter { + let (filter, mask, single_filter) = match config.filter { config::Filter::Standard { filter, mask } => { - ((filter as u32) << 21, !((mask as u32) << 21)) + ((filter as u32) << 21, !((mask as u32) << 21), true) } - config::Filter::Extended { filter, mask } => (filter << 3, !(mask << 3)), + config::Filter::Extended { filter, mask } => (filter << 3, !(mask << 3), true), + config::Filter::Dual { + filter1, + mask1, + filter2, + mask2, + } => ( + ((filter1 as u32) << 21) | ((filter2 as u32) << 5), + !(((mask1 as u32) << 21) | ((mask2 as u32) << 5)), + false, + ), }; let filter_config = twai_filter_config_t { acceptance_code: filter, acceptance_mask: mask, - single_filter: true, + single_filter, }; esp!(unsafe { twai_driver_install(&general_config, &timing_config, &filter_config) })?; - esp!(unsafe { twai_start() })?; - Ok(Self(can)) + Ok(Self(can, config.alerts, config.tx_queue_len > 0)) + } + + pub fn start(&mut self) -> Result<(), EspError> { + esp!(unsafe { twai_start() }) } - pub fn transmit(&mut self, frame: &Frame, timeout: TickType_t) -> Result<(), EspError> { + pub fn stop(&mut self) -> Result<(), EspError> { + esp!(unsafe { twai_stop() }) + } + + pub fn transmit(&self, frame: &Frame, timeout: TickType_t) -> Result<(), EspError> { esp!(unsafe { twai_transmit(&frame.0, timeout) }) } - pub fn receive(&mut self, timeout: TickType_t) -> Result { - let mut rx_msg = twai_message_t { - ..Default::default() - }; + pub fn receive(&self, timeout: TickType_t) -> Result { + let mut rx_msg = Default::default(); match esp_result!(unsafe { twai_receive(&mut rx_msg, timeout) }, ()) { Ok(_) => Ok(Frame(rx_msg)), Err(err) => Err(err), } } + + pub fn read_alerts(&self, timeout: TickType_t) -> Result, EspError> { + let mut alerts = 0; + + esp!(unsafe { twai_read_alerts(&mut alerts, timeout) })?; + + Ok(EnumSet::from_repr_truncated(alerts)) + } } -impl<'d> Drop for CanDriver<'d> { +impl Drop for CanDriver<'_> { fn drop(&mut self) { - esp!(unsafe { twai_stop() }).unwrap(); + let _ = self.stop(); esp!(unsafe { twai_driver_uninstall() }).unwrap(); } } -impl<'d> embedded_hal_0_2::blocking::can::Can for CanDriver<'d> { +unsafe impl Send for CanDriver<'_> {} + +impl embedded_hal_0_2::blocking::can::Can for CanDriver<'_> { type Frame = Frame; type Error = Can02Error; fn transmit(&mut self, frame: &Self::Frame) -> Result<(), Self::Error> { - self.transmit(frame, BLOCK).map_err(Can02Error::other) + CanDriver::transmit(self, frame, BLOCK).map_err(Can02Error::other) } fn receive(&mut self) -> Result { - self.receive(BLOCK).map_err(Can02Error::other) + CanDriver::receive(self, BLOCK).map_err(Can02Error::other) } } -impl<'d> embedded_can::blocking::Can for CanDriver<'d> { +impl embedded_can::blocking::Can for CanDriver<'_> { type Frame = Frame; type Error = CanError; fn transmit(&mut self, frame: &Self::Frame) -> Result<(), Self::Error> { - self.transmit(frame, BLOCK).map_err(CanError::other) + CanDriver::transmit(self, frame, BLOCK).map_err(CanError::other) } fn receive(&mut self) -> Result { - self.receive(BLOCK).map_err(CanError::other) + CanDriver::receive(self, BLOCK).map_err(CanError::other) } } -impl<'d> embedded_hal_0_2::can::nb::Can for CanDriver<'d> { +impl embedded_hal_0_2::can::nb::Can for CanDriver<'_> { type Frame = Frame; type Error = Can02Error; fn transmit(&mut self, frame: &Self::Frame) -> nb::Result, Self::Error> { - match self.transmit(frame, NON_BLOCK) { + match CanDriver::transmit(self, frame, NON_BLOCK) { Ok(_) => Ok(None), Err(e) if e.code() == ESP_FAIL => Err(nb::Error::WouldBlock), Err(e) if e.code() == ESP_ERR_TIMEOUT => Err(nb::Error::WouldBlock), @@ -372,7 +521,7 @@ impl<'d> embedded_hal_0_2::can::nb::Can for CanDriver<'d> { } fn receive(&mut self) -> nb::Result { - match self.receive(NON_BLOCK) { + match CanDriver::receive(self, NON_BLOCK) { Ok(frame) => Ok(frame), Err(e) if e.code() == ESP_ERR_TIMEOUT => Err(nb::Error::WouldBlock), Err(e) => Err(nb::Error::Other(Can02Error::other(e))), @@ -380,12 +529,12 @@ impl<'d> embedded_hal_0_2::can::nb::Can for CanDriver<'d> { } } -impl<'d> embedded_can::nb::Can for CanDriver<'d> { +impl embedded_can::nb::Can for CanDriver<'_> { type Frame = Frame; type Error = CanError; fn transmit(&mut self, frame: &Self::Frame) -> nb::Result, Self::Error> { - match self.transmit(frame, NON_BLOCK) { + match CanDriver::transmit(self, frame, NON_BLOCK) { Ok(_) => Ok(None), Err(e) if e.code() == ESP_FAIL => Err(nb::Error::WouldBlock), Err(e) if e.code() == ESP_ERR_TIMEOUT => Err(nb::Error::WouldBlock), @@ -394,7 +543,7 @@ impl<'d> embedded_can::nb::Can for CanDriver<'d> { } fn receive(&mut self) -> nb::Result { - match self.receive(NON_BLOCK) { + match CanDriver::receive(self, NON_BLOCK) { Ok(frame) => Ok(frame), Err(e) if e.code() == ESP_ERR_TIMEOUT => Err(nb::Error::WouldBlock), Err(e) => Err(nb::Error::Other(CanError::other(e))), @@ -402,28 +551,241 @@ impl<'d> embedded_can::nb::Can for CanDriver<'d> { } } +fn read_alerts() -> EnumSet { + Alert::Success | Alert::Received | Alert::ReceiveQueueFull +} + +fn write_alerts() -> EnumSet { + Alert::Success | Alert::TransmitIdle | Alert::TransmitFailed | Alert::TransmitRetried +} + +pub type OwnedAsyncCanDriver<'d> = AsyncCanDriver<'d, CanDriver<'d>>; + +pub struct AsyncCanDriver<'d, T> +where + T: BorrowMut>, +{ + driver: T, + task: TaskHandle_t, + _data: PhantomData<&'d ()>, +} + +impl<'d> AsyncCanDriver<'d, CanDriver<'d>> { + pub fn new( + can: impl Peripheral

+ 'd, + tx: impl Peripheral

+ 'd, + rx: impl Peripheral

+ 'd, + config: &config::Config, + ) -> Result { + Self::wrap(CanDriver::new(can, tx, rx, config)?) + } +} + +impl<'d, T> AsyncCanDriver<'d, T> +where + T: BorrowMut>, +{ + pub fn wrap(driver: T) -> Result { + Self::wrap_custom(driver, None, None) + } + + pub fn wrap_custom( + mut driver: T, + priority: Option, + pin_to_core: Option, + ) -> Result { + let _ = driver.borrow_mut().stop(); + + let mut alerts = 0; + esp!(unsafe { + twai_reconfigure_alerts( + driver + .borrow() + .1 + .union(read_alerts()) + .union(write_alerts()) + .as_repr(), + &mut alerts, + ) + })?; + + let task = unsafe { + task::create( + Self::process_alerts, + CStr::from_bytes_until_nul(b"CAN - Alerts task\0").unwrap(), + 2048, + core::ptr::null_mut(), + priority.unwrap_or(6), + pin_to_core, + )? + }; + + Ok(Self { + driver, + task, + _data: PhantomData, + }) + } + + pub fn driver(&self) -> &CanDriver<'d> { + self.driver.borrow() + } + + pub fn driver_mut(&mut self) -> &mut CanDriver<'d> { + self.driver.borrow_mut() + } + + pub fn start(&mut self) -> Result<(), EspError> { + self.driver.borrow_mut().start() + } + + pub fn stop(&mut self) -> Result<(), EspError> { + self.driver.borrow_mut().stop() + } + + pub async fn transmit(&self, frame: &Frame) -> Result<(), EspError> { + loop { + let res = self.driver.borrow().transmit(frame, delay::NON_BLOCK); + + match res { + Ok(()) => return Ok(()), + Err(e) + if e.code() != ESP_ERR_TIMEOUT + && (e.code() != ESP_FAIL || self.driver.borrow().2) => + { + return Err(e) + } + _ => (), + } + + WRITE_NOTIFICATION.wait().await; + } + } + + pub async fn receive(&self) -> Result { + loop { + let res = self.driver.borrow().receive(delay::NON_BLOCK); + + match res { + Ok(frame) => return Ok(frame), + Err(e) if e.code() != ESP_ERR_TIMEOUT => return Err(e), + _ => (), + } + + READ_NOTIFICATION.wait().await; + } + } + + pub async fn read_alerts(&self) -> Result, EspError> { + let alerts = loop { + let alerts = EnumSet::from_repr(ALERT_NOTIFICATION.wait().await.into()) + .intersection(self.driver.borrow().1); + + if !alerts.is_empty() { + break alerts; + } + }; + + Ok(alerts) + } + + extern "C" fn process_alerts(_arg: *mut core::ffi::c_void) { + let mut alerts = 0; + + loop { + if unsafe { twai_read_alerts(&mut alerts, delay::BLOCK) } == 0 { + let ealerts: EnumSet = EnumSet::from_repr_truncated(alerts); + + if !ealerts.is_disjoint(read_alerts()) { + READ_NOTIFICATION.notify_lsb(); + } + + if !ealerts.is_disjoint(write_alerts()) { + WRITE_NOTIFICATION.notify_lsb(); + } + + if let Some(alerts) = NonZeroU32::new(alerts) { + ALERT_NOTIFICATION.notify(alerts); + } + } + } + } +} + +impl<'d, T> Drop for AsyncCanDriver<'d, T> +where + T: BorrowMut>, +{ + fn drop(&mut self) { + let _ = self.stop(); + + unsafe { task::destroy(self.task) }; + + let mut alerts = 0; + esp!(unsafe { twai_reconfigure_alerts(self.driver.borrow().1.as_repr(), &mut alerts) }) + .unwrap(); + + READ_NOTIFICATION.reset(); + WRITE_NOTIFICATION.reset(); + ALERT_NOTIFICATION.reset(); + } +} + +unsafe impl<'d, T> Send for AsyncCanDriver<'d, T> where T: BorrowMut> + Send {} + +static READ_NOTIFICATION: Notification = Notification::new(); +static WRITE_NOTIFICATION: Notification = Notification::new(); +static ALERT_NOTIFICATION: Notification = Notification::new(); + +/// Twai message flags +#[derive(Debug, EnumSetType)] +pub enum Flags { + /// Message is in Extended Frame Format (29bit ID) + Extended, + /// Message is a Remote Frame (Remote Transmission Request) + Remote, + /// Transmit message using Single Shot Transmission + /// (Message will not be retransmitted upon error or loss of arbitration). + /// Unused for received message. + SingleShot, + /// Transmit message using Self Reception Request + /// (Transmitted message will also received by the same node). + /// Unused for received message. + SelfReception, + /// Message's Data length code is larger than 8. + /// This will break compliance with TWAI + DlcNonComp, + None, +} + pub struct Frame(twai_message_t); impl Frame { - pub fn new(id: u32, extended: bool, data: &[u8]) -> Option { + pub fn new(id: u32, flags: EnumSet, data: &[u8]) -> Option { let dlc = data.len(); if dlc <= 8 { // unions are not very well supported in rust // therefore setting those union flags is quite hairy - let mut flags = twai_message_t__bindgen_ty_1::default(); - - // set bits in an union - unsafe { flags.__bindgen_anon_1.set_ss(1) }; - if extended { - unsafe { flags.__bindgen_anon_1.set_extd(1) }; + let mut twai_flags = twai_message_t__bindgen_ty_1::default(); + + // Iterate over the flags set and set the corresponding bits in the union + for flag in flags.iter() { + match flag { + Flags::Extended => unsafe { twai_flags.__bindgen_anon_1.set_extd(1) }, + Flags::Remote => unsafe { twai_flags.__bindgen_anon_1.set_rtr(1) }, + Flags::SingleShot => unsafe { twai_flags.__bindgen_anon_1.set_ss(1) }, + Flags::SelfReception => unsafe { twai_flags.__bindgen_anon_1.set_self(1) }, + Flags::DlcNonComp => unsafe { twai_flags.__bindgen_anon_1.set_dlc_non_comp(1) }, + Flags::None => {} + } } let mut payload = [0; 8]; payload[..dlc].copy_from_slice(data); let twai_message = twai_message_t { - __bindgen_anon_1: flags, + __bindgen_anon_1: twai_flags, identifier: id, data_length_code: dlc as u8, data: payload, @@ -443,7 +805,6 @@ impl Frame { // set bits in an union unsafe { flags.__bindgen_anon_1.set_rtr(1) }; - unsafe { flags.__bindgen_anon_1.set_ss(1) }; if extended { unsafe { flags.__bindgen_anon_1.set_extd(1) }; } @@ -484,24 +845,34 @@ impl Frame { impl core::fmt::Display for Frame { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!( - f, - "Frame {{ id: {}, remote: {}, data: {:?} }}", - self.identifier(), - self.is_remote_frame(), - self.data() - ) + if self.is_extended() { + write!( + f, + "Frame {{ id: {:08x}, remote: {}, data: {:?} }}", + self.identifier(), + self.is_remote_frame(), + self.data() + ) + } else { + write!( + f, + "Frame {{ id: {:03x}, remote: {}, data: {:?} }}", + self.identifier(), + self.is_remote_frame(), + self.data() + ) + } } } impl embedded_hal_0_2::can::Frame for Frame { fn new(id: impl Into, data: &[u8]) -> Option { - let (id, extended) = match id.into() { - embedded_hal_0_2::can::Id::Standard(id) => (id.as_raw() as u32, false), - embedded_hal_0_2::can::Id::Extended(id) => (id.as_raw(), true), + let (id, flags) = match id.into() { + embedded_hal_0_2::can::Id::Standard(id) => (id.as_raw() as u32, Flags::None), + embedded_hal_0_2::can::Id::Extended(id) => (id.as_raw(), Flags::Extended), }; - Self::new(id, extended, data) + Self::new(id, flags.into(), data) } fn new_remote(id: impl Into, dlc: usize) -> Option { @@ -552,12 +923,12 @@ impl embedded_hal_0_2::can::Frame for Frame { impl embedded_can::Frame for Frame { fn new(id: impl Into, data: &[u8]) -> Option { - let (id, extended) = match id.into() { - embedded_can::Id::Standard(id) => (id.as_raw() as u32, false), - embedded_can::Id::Extended(id) => (id.as_raw(), true), + let (id, flags) = match id.into() { + embedded_can::Id::Standard(id) => (id.as_raw() as u32, Flags::None), + embedded_can::Id::Extended(id) => (id.as_raw(), Flags::Extended), }; - Self::new(id, extended, data) + Self::new(id, flags.into(), data) } fn new_remote(id: impl Into, dlc: usize) -> Option { diff --git a/src/cpu.rs b/src/cpu.rs index 609825ee674..6a910f2d766 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -3,11 +3,13 @@ use core::arch::asm; use esp_idf_sys::*; +use enumset::EnumSetType; + /// Returns the number of cores supported by the esp32* chip pub const CORES: u32 = SOC_CPU_CORES_NUM; +#[derive(Debug, EnumSetType)] #[repr(C)] -#[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum Core { Core0 = 0, // PRO on dual-core systems, the one and only CPU on single-core systems #[cfg(any(esp32, esp32s3))] @@ -48,21 +50,21 @@ impl From for Core { #[inline(always)] #[link_section = ".iram1.cpu_core"] pub fn core() -> Core { - #[cfg(any(esp32c3, esp32s2))] + #[cfg(any(esp32c3, esp32s2, esp32c2, esp32h2, esp32c5, esp32c6))] let core = 0; #[allow(unused_assignments)] - #[cfg(any(esp32, esp32s3))] + #[cfg(any(esp32, esp32s3, esp32p4))] let mut core = 0; - #[cfg(any(esp32, esp32s3))] + #[cfg(any(esp32, esp32s3))] // TODO: Need a way to get the running core on esp32p4 in future unsafe { asm!("rsr.prid {0}", "extui {0},{0},13,1", out(reg) core); } match core { 0 => Core::Core0, - #[cfg(any(esp32, esp32s3))] + #[cfg(any(esp32, esp32s3, esp32p4))] 1 => Core::Core1, other => panic!("Unknown core: {}", other), } diff --git a/src/delay.rs b/src/delay.rs index 9929ae1e3df..d38f8f0fc0a 100644 --- a/src/delay.rs +++ b/src/delay.rs @@ -1,25 +1,145 @@ -use core::convert::Infallible; +//! Delay providers. +//! +//! If you don't know how large your delays will be, you'll probably want to +//! use [`Delay`]. Otherwise use [`Ets`] for delays <10ms and +//! [`FreeRtos`] for delays >=10ms. +//! +//! Example of an Ets vs. FreeRtos auto-selecting delay: +//! ``` +//! use esp_idf_hal::delay::Delay; +//! +//! let delay: Delay = Default::default(); +//! // ... +//! delay.delay_us(42); +//! // ... +//! delay.delay_ms(142); +//! // ... +//! ``` +//! +//! Example of a small microsecond delay: +//! ``` +//! use esp_idf_hal::delay::Ets; +//! +//! Ets::delay_us(42); +//! ``` +//! +//! Example of a millisecond delay: +//! ``` +//! use esp_idf_hal::delay::FreeRtos; +//! +//! FreeRtos::delay_ms(42); +//! ``` +//! +//! Example of an [embedded_hal::delay::DelayNs] consumer with an +//! Ets vs. FreeRtos auto-selecting delay: +//! ```ignore +//! use esp_idf_hal::delay::Delay; +//! +//! let mut delay: Delay = Default::default(); +//! some_trait_user(&mut delay); +//! ``` + use core::time::Duration; use esp_idf_sys::*; -#[allow(non_upper_case_globals)] +/// The raw OS tick type. Also see [TickType]. +pub use esp_idf_sys::TickType_t; + +/// Sentinel value used as "maximum delay" or "maximum blocking" marker. +/// +/// This value is also used for representing `Option` being `None` +/// when converting to/from [TickType]. pub const BLOCK: TickType_t = TickType_t::MAX; -#[allow(non_upper_case_globals)] -pub const NON_BLOCK: TickType_t = TickType_t::MIN; +/// Sentinel value used as "no delay" or "no blocking" marker. +pub const NON_BLOCK: TickType_t = 0; + +/// The configured OS tick rate in Hz. +/// There are [TICK_RATE_HZ] number of [TickType] ticks per second. +pub const TICK_RATE_HZ: u32 = configTICK_RATE_HZ; -#[allow(non_upper_case_globals)] -const portTICK_PERIOD_MS: u32 = 1000 / configTICK_RATE_HZ; +const MS_PER_S: u64 = 1_000; +const NS_PER_MS: u64 = 1_000_000; +const US_PER_MS: u32 = 1_000; +const NS_PER_US: u32 = 1_000; + +#[inline] +const fn const_min_u64(a: u64, b: u64) -> u64 { + if a < b { + a + } else { + b + } +} +/// Transparent wrapper around [TickType_t] with conversion methods. +#[repr(transparent)] pub struct TickType(pub TickType_t); +impl TickType { + /// Construct a [TickType] from a number of ticks. + #[inline] + pub const fn new(ticks: TickType_t) -> Self { + Self(ticks) + } + + /// Construct a [TickType] from a number of milliseconds. + /// This function will round the number of ticks up, if required. + pub const fn new_millis(ms: u64) -> Self { + let ticks = ms + .saturating_mul(TICK_RATE_HZ as u64) + .saturating_add(MS_PER_S - 1) + / MS_PER_S; + Self(const_min_u64(ticks, TickType_t::MAX as _) as _) + } + + /// Get the number of ticks. + #[inline] + pub const fn ticks(&self) -> TickType_t { + self.0 + } + + /// Convert the number of ticks to a number of milliseconds. + /// This function will round the number of milliseconds up, if required. + pub const fn as_millis(&self) -> u64 { + (self.0 as u64) + .saturating_mul(MS_PER_S) + .saturating_add(TICK_RATE_HZ as u64 - 1) + / TICK_RATE_HZ as u64 + } + + /// Convert the number of ticks to a number of milliseconds + /// and saturate to u32. + /// This function will round the number of milliseconds up, if required. + #[inline] + pub const fn as_millis_u32(&self) -> u32 { + const_min_u64(self.as_millis(), u32::MAX as _) as _ + } +} + +impl From for TickType { + #[inline] + fn from(value: TickType_t) -> Self { + Self::new(value) + } +} + +impl From for TickType_t { + #[inline] + fn from(value: TickType) -> Self { + value.ticks() + } +} + impl From for TickType { fn from(duration: Duration) -> Self { - TickType( - ((duration.as_millis() + portTICK_PERIOD_MS as u128 - 1) / portTICK_PERIOD_MS as u128) - as TickType_t, - ) + let sec_ms = duration.as_secs().saturating_mul(MS_PER_S); + let subsec_ns: u64 = duration.subsec_nanos().into(); + // Convert to ms and round up. Not saturating. Cannot overflow. + let subsec_ms = subsec_ns.div_ceil(NS_PER_MS); + + TickType::new_millis(sec_ms.saturating_add(subsec_ms)) } } @@ -35,7 +155,7 @@ impl From> for TickType { impl From for Duration { fn from(ticks: TickType) -> Self { - Duration::from_millis(ticks.0 as u64 * portTICK_PERIOD_MS as u64) + Duration::from_millis(ticks.as_millis()) } } @@ -49,154 +169,286 @@ impl From for Option { } } -/// Espressif built-in delay provider -/// Use only for very small delays (us or a few ms at most), or else the FreeRTOS IDLE tasks' might starve and -/// the IDLE tasks' watchdog will trigger +/// Espressif's built-in delay provider for small delays +/// +/// Use only for very small delays or else the FreeRTOS IDLE tasks might starve and +/// the IDLE task's watchdog will trigger. +/// +/// Small delays are up to `1000 /` [TICK_RATE_HZ] milliseconds, which is typically +/// 10 milliseconds. pub struct Ets; -// No longer available in the generated bindings for ESP-IDF 5 +// This binding is no longer available in the generated bindings for ESP-IDF 5 or later. +// The function itself is still available. Therefore, we define the binding here. #[cfg(not(esp_idf_version_major = "4"))] extern "C" { - pub fn ets_delay_us(us: u32); + fn ets_delay_us(us: u32); } impl Ets { + /// Pauses execution for at minimum `us` microseconds. + /// The delay can be longer due to rounding and/or runtime effects. + #[inline] pub fn delay_us(us: u32) { unsafe { ets_delay_us(us); } } + /// Pauses execution for at minimum `ms` milliseconds. + /// The delay can be longer due to rounding and/or runtime effects. + /// This delay should only be used up to `1000 /` [TICK_RATE_HZ] milliseconds. pub fn delay_ms(ms: u32) { - unsafe { - ets_delay_us(ms * 1000); - } + Self::delay_us(ms.saturating_mul(US_PER_MS)); } } impl embedded_hal_0_2::blocking::delay::DelayUs for Ets { + #[inline] fn delay_us(&mut self, us: u32) { Ets::delay_us(us); } } impl embedded_hal_0_2::blocking::delay::DelayUs for Ets { + #[inline] fn delay_us(&mut self, us: u16) { - Ets::delay_us(us as _); + Ets::delay_us(us.into()); } } impl embedded_hal_0_2::blocking::delay::DelayUs for Ets { + #[inline] fn delay_us(&mut self, us: u8) { - Ets::delay_us(us as _); + Ets::delay_us(us.into()); } } impl embedded_hal_0_2::blocking::delay::DelayMs for Ets { + #[inline] fn delay_ms(&mut self, ms: u32) { Ets::delay_ms(ms); } } impl embedded_hal_0_2::blocking::delay::DelayMs for Ets { + #[inline] fn delay_ms(&mut self, ms: u16) { - Ets::delay_ms(ms as _); + Ets::delay_ms(ms.into()); } } impl embedded_hal_0_2::blocking::delay::DelayMs for Ets { + #[inline] fn delay_ms(&mut self, ms: u8) { - Ets::delay_ms(ms as _); + Ets::delay_ms(ms.into()); } } -impl embedded_hal::delay::DelayUs for Ets { - type Error = Infallible; +impl embedded_hal::delay::DelayNs for Ets { + #[inline] + fn delay_ns(&mut self, ns: u32) { + Ets::delay_us(ns.saturating_add(NS_PER_US - 1) / NS_PER_US); + } - fn delay_us(&mut self, us: u32) -> Result<(), Self::Error> { + #[inline] + fn delay_us(&mut self, us: u32) { Ets::delay_us(us); - - Ok(()) } - fn delay_ms(&mut self, ms: u32) -> Result<(), Self::Error> { + #[inline] + fn delay_ms(&mut self, ms: u32) { Ets::delay_ms(ms); - - Ok(()) } } -/// FreeRTOS-based delay provider -/// Use for delays larger than 10ms (delays smaller than 10ms used in a loop would -/// starve the FreeRTOS IDLE tasks' as they are low prio tasks and hence the -/// the IDLE tasks' watchdog will trigger) +/// FreeRTOS-based delay provider for delays larger than 10 ms. +/// +/// Delays bigger than `1000 /` [TICK_RATE_HZ] milliseconds (typically 10 ms) used in a +/// loop would starve the FreeRTOS IDLE tasks as they are low prio tasks and hence the +/// IDLE task's watchdog could trigger. +/// This delayer avoids that by yielding to the OS during the delay. pub struct FreeRtos; impl FreeRtos { - pub fn delay_us(us: u32) { - let ms = us / 1000; - - Self::delay_ms(ms); - } - + /// Pauses execution for at minimum `ms` milliseconds. + /// The delay can be longer due to rounding and/or runtime effects. pub fn delay_ms(ms: u32) { - // divide by tick length, rounding up - let ticks = ms.saturating_add(portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS; - + let ticks = TickType::new_millis(ms.into()).ticks(); unsafe { vTaskDelay(ticks); } } + + // Internal helper: Round up to ms. + // This is not supposed to be `pub`, because the user code shall not use this + // timer for microsecond delay. Only used for trait impl below. + fn delay_us(us: u32) { + Self::delay_ms(us.saturating_add(US_PER_MS - 1) / US_PER_MS); + } } impl embedded_hal_0_2::blocking::delay::DelayUs for FreeRtos { + #[inline] fn delay_us(&mut self, us: u32) { FreeRtos::delay_us(us); } } impl embedded_hal_0_2::blocking::delay::DelayUs for FreeRtos { + #[inline] fn delay_us(&mut self, us: u16) { - FreeRtos::delay_us(us as _); + FreeRtos::delay_us(us.into()); } } impl embedded_hal_0_2::blocking::delay::DelayUs for FreeRtos { + #[inline] fn delay_us(&mut self, us: u8) { - FreeRtos::delay_us(us as _); + FreeRtos::delay_us(us.into()); } } impl embedded_hal_0_2::blocking::delay::DelayMs for FreeRtos { + #[inline] fn delay_ms(&mut self, ms: u32) { FreeRtos::delay_ms(ms); } } impl embedded_hal_0_2::blocking::delay::DelayMs for FreeRtos { + #[inline] fn delay_ms(&mut self, ms: u16) { - FreeRtos::delay_ms(ms as _); + FreeRtos::delay_ms(ms.into()); } } impl embedded_hal_0_2::blocking::delay::DelayMs for FreeRtos { + #[inline] fn delay_ms(&mut self, ms: u8) { - FreeRtos::delay_ms(ms as _); + FreeRtos::delay_ms(ms.into()); } } -impl embedded_hal::delay::DelayUs for FreeRtos { - type Error = Infallible; +impl embedded_hal::delay::DelayNs for FreeRtos { + #[inline] + fn delay_ns(&mut self, ns: u32) { + FreeRtos::delay_us(ns.saturating_add(NS_PER_US - 1) / NS_PER_US); + } - fn delay_us(&mut self, us: u32) -> Result<(), Self::Error> { + #[inline] + fn delay_us(&mut self, us: u32) { FreeRtos::delay_us(us); - - Ok(()) } - fn delay_ms(&mut self, ms: u32) -> Result<(), Self::Error> { + #[inline] + fn delay_ms(&mut self, ms: u32) { FreeRtos::delay_ms(ms); + } +} - Ok(()) +/// A delay provider that uses [`Ets`] for delays below a certain threshold +/// and [`FreeRtos`] for delays equal or above the threshold. +#[derive(Copy, Clone)] +pub struct Delay(u32); + +impl Delay { + /// Create a [Delay] with a default threshold of 1 ms. + #[inline] + pub const fn new_default() -> Self { + Self::new(1000) + } + + /// Create a [Delay] with a threshold of the specified amount of microseconds. + #[inline] + pub const fn new(threshold_us: u32) -> Self { + Self(threshold_us) + } + + /// Pauses execution for at minimum `us` microseconds. + /// The delay can be longer due to rounding and/or runtime effects. + #[inline] + pub fn delay_us(&self, us: u32) { + if us < self.0 { + Ets::delay_us(us); + } else { + FreeRtos::delay_us(us); + } + } + + /// Pauses execution for at minimum `ms` milliseconds. + /// The delay can be longer due to rounding and/or runtime effects. + pub fn delay_ms(&self, ms: u32) { + if ms.saturating_mul(US_PER_MS) < self.0 { + Ets::delay_ms(ms); + } else { + FreeRtos::delay_ms(ms); + } + } +} + +impl Default for Delay { + #[inline] + fn default() -> Self { + Self::new_default() + } +} + +impl embedded_hal::delay::DelayNs for Delay { + #[inline] + fn delay_ns(&mut self, ns: u32) { + Delay::delay_us(self, ns.saturating_add(NS_PER_US - 1) / NS_PER_US) + } + + #[inline] + fn delay_us(&mut self, us: u32) { + Delay::delay_us(self, us) + } + + #[inline] + fn delay_ms(&mut self, ms: u32) { + Delay::delay_ms(self, ms) + } +} + +impl embedded_hal_0_2::blocking::delay::DelayUs for Delay { + #[inline] + fn delay_us(&mut self, us: u8) { + Delay::delay_us(self, us.into()); + } +} + +impl embedded_hal_0_2::blocking::delay::DelayUs for Delay { + #[inline] + fn delay_us(&mut self, us: u16) { + Delay::delay_us(self, us.into()); + } +} + +impl embedded_hal_0_2::blocking::delay::DelayUs for Delay { + #[inline] + fn delay_us(&mut self, us: u32) { + Delay::delay_us(self, us); + } +} + +impl embedded_hal_0_2::blocking::delay::DelayMs for Delay { + #[inline] + fn delay_ms(&mut self, ms: u8) { + Delay::delay_ms(self, ms.into()) + } +} + +impl embedded_hal_0_2::blocking::delay::DelayMs for Delay { + #[inline] + fn delay_ms(&mut self, ms: u16) { + Delay::delay_ms(self, ms.into()) + } +} + +impl embedded_hal_0_2::blocking::delay::DelayMs for Delay { + #[inline] + fn delay_ms(&mut self, ms: u32) { + Delay::delay_ms(self, ms) } } diff --git a/src/gpio.rs b/src/gpio.rs index faf9eb069a0..df69894add3 100644 --- a/src/gpio.rs +++ b/src/gpio.rs @@ -1,19 +1,12 @@ //! GPIO and pin configuration -use core::{ffi::c_void, marker::PhantomData}; +use core::marker::PhantomData; #[cfg(feature = "alloc")] extern crate alloc; -#[cfg(feature = "alloc")] -use alloc::boxed::Box; - -#[cfg(not(feature = "riscv-ulp-hal"))] use esp_idf_sys::*; -#[cfg(feature = "riscv-ulp-hal")] -use crate::riscv_ulp_hal::sys::*; - use crate::adc::Adc; use crate::peripheral::{Peripheral, PeripheralRef}; @@ -74,14 +67,14 @@ pub trait ADCPin: sealed::ADCPin + Pin { /// A marker trait designating a pin which is capable of /// operating as a DAC pin -#[cfg(all(not(esp32c3), not(esp32s3)))] +#[cfg(any(esp32, esp32s2))] pub trait DACPin: Pin { fn dac_channel(&self) -> dac_channel_t; } /// A marker trait designating a pin which is capable of /// operating as a touch pin -#[cfg(not(esp32c3))] +#[cfg(any(esp32, esp32s2, esp32s3))] pub trait TouchPin: Pin { fn touch_channel(&self) -> touch_pad_t; } @@ -104,6 +97,12 @@ impl AnyIOPin { _p: PhantomData, } } + + /// Creates an `Option::None` for pins that are + /// optional in APIs. + pub const fn none() -> Option { + None + } } crate::impl_peripheral_trait!(AnyIOPin); @@ -136,6 +135,12 @@ impl AnyInputPin { _p: PhantomData, } } + + /// Creates an `Option::None` for pins that are + /// optional in APIs. + pub const fn none() -> Option { + None + } } crate::impl_peripheral_trait!(AnyInputPin); @@ -172,6 +177,12 @@ impl AnyOutputPin { _p: PhantomData, } } + + /// Creates an `Option::None` for pins that are + /// optional in APIs. + pub const fn none() -> Option { + None + } } crate::impl_peripheral_trait!(AnyOutputPin); @@ -191,7 +202,6 @@ impl From for AnyOutputPin { } /// Interrupt types -#[cfg(all(not(feature = "riscv-ulp-hal"), feature = "alloc"))] #[derive(Debug, Eq, PartialEq, Copy, Clone)] pub enum InterruptType { PosEdge, @@ -201,7 +211,6 @@ pub enum InterruptType { HighLevel, } -#[cfg(all(not(feature = "riscv-ulp-hal"), feature = "alloc"))] impl From for gpio_int_type_t { fn from(interrupt_type: InterruptType) -> gpio_int_type_t { match interrupt_type { @@ -214,8 +223,15 @@ impl From for gpio_int_type_t { } } +impl From for u8 { + fn from(interrupt_type: InterruptType) -> u8 { + let int_type: gpio_int_type_t = interrupt_type.into(); + + int_type as u8 + } +} + /// Drive strength (values are approximates) -#[cfg(not(feature = "riscv-ulp-hal"))] #[derive(Debug, Eq, PartialEq, Copy, Clone)] pub enum DriveStrength { I5mA = 0, @@ -224,7 +240,6 @@ pub enum DriveStrength { I40mA = 3, } -#[cfg(not(feature = "riscv-ulp-hal"))] impl From for gpio_drive_cap_t { fn from(strength: DriveStrength) -> gpio_drive_cap_t { match strength { @@ -236,7 +251,6 @@ impl From for gpio_drive_cap_t { } } -#[cfg(not(feature = "riscv-ulp-hal"))] impl From for DriveStrength { #[allow(non_upper_case_globals)] fn from(cap: gpio_drive_cap_t) -> DriveStrength { @@ -306,6 +320,47 @@ impl core::ops::Not for Level { } } +impl From for Level { + fn from(state: embedded_hal_0_2::digital::v2::PinState) -> Self { + match state { + embedded_hal_0_2::digital::v2::PinState::Low => Self::Low, + embedded_hal_0_2::digital::v2::PinState::High => Self::High, + } + } +} + +impl From for embedded_hal_0_2::digital::v2::PinState { + fn from(level: Level) -> Self { + match level { + Level::Low => Self::Low, + Level::High => Self::High, + } + } +} + +impl From for Level { + fn from(state: embedded_hal::digital::PinState) -> Self { + match state { + embedded_hal::digital::PinState::Low => Self::Low, + embedded_hal::digital::PinState::High => Self::High, + } + } +} + +impl From for embedded_hal::digital::PinState { + fn from(level: Level) -> Self { + match level { + Level::Low => Self::Low, + Level::High => Self::High, + } + } +} + +pub trait GPIOMode {} + +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] +pub trait RTCMode {} + pub trait InputMode { const RTC: bool; } @@ -318,13 +373,13 @@ pub struct Disabled; pub struct Input; pub struct Output; pub struct InputOutput; -#[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] pub struct RtcDisabled; -#[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] pub struct RtcInput; -#[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] pub struct RtcOutput; -#[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] pub struct RtcInputOutput; impl InputMode for Input { @@ -343,26 +398,43 @@ impl OutputMode for InputOutput { const RTC: bool = false; } -#[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] +impl GPIOMode for Disabled {} +impl GPIOMode for Input {} +impl GPIOMode for InputOutput {} +impl GPIOMode for Output {} + +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] impl InputMode for RtcInput { const RTC: bool = true; } -#[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] impl InputMode for RtcInputOutput { const RTC: bool = true; } -#[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] impl OutputMode for RtcOutput { const RTC: bool = true; } -#[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] impl OutputMode for RtcInputOutput { const RTC: bool = true; } +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] +impl RTCMode for RtcDisabled {} + +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] +impl RTCMode for RtcInput {} + +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] +impl RTCMode for RtcInputOutput {} + +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] +impl RTCMode for RtcOutput {} + /// A driver for a GPIO pin. /// /// The driver can set the pin as a disconnected/disabled one, input, or output pin, or both or analog. @@ -460,7 +532,7 @@ impl<'d, T: OutputPin> PinDriver<'d, T, Output> { } } -#[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] impl<'d, T: Pin + RTCPin> PinDriver<'d, T, RtcDisabled> { /// Creates the driver for a pin in disabled state. #[inline] @@ -475,7 +547,7 @@ impl<'d, T: Pin + RTCPin> PinDriver<'d, T, RtcDisabled> { } } -#[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] impl<'d, T: InputPin + RTCPin> PinDriver<'d, T, RtcInput> { /// Creates the driver for a pin in RTC input state. #[inline] @@ -490,7 +562,7 @@ impl<'d, T: InputPin + RTCPin> PinDriver<'d, T, RtcInput> { } } -#[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] impl<'d, T: InputPin + OutputPin + RTCPin> PinDriver<'d, T, RtcInputOutput> { /// Creates the driver for a pin in RTC input-output state. #[inline] @@ -505,7 +577,7 @@ impl<'d, T: InputPin + OutputPin + RTCPin> PinDriver<'d, T, RtcInputOutput> { } } -#[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] impl<'d, T: InputPin + OutputPin + RTCPin> PinDriver<'d, T, RtcInputOutput> { /// Creates the driver for a pin in RTC input-output open-drain state. #[inline] @@ -520,7 +592,7 @@ impl<'d, T: InputPin + OutputPin + RTCPin> PinDriver<'d, T, RtcInputOutput> { } } -#[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] impl<'d, T: OutputPin + RTCPin> PinDriver<'d, T, RtcOutput> { /// Creates the driver for a pin in RTC output state. #[inline] @@ -535,7 +607,7 @@ impl<'d, T: OutputPin + RTCPin> PinDriver<'d, T, RtcOutput> { } } -#[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] +#[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] impl<'d, T: OutputPin + RTCPin> PinDriver<'d, T, RtcOutput> { /// Creates the driver for a pin in RTC output open-drain state. #[inline] @@ -613,7 +685,7 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { /// Put the pin into RTC disabled mode. #[inline] - #[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] + #[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] pub fn into_rtc_disabled(self) -> Result, EspError> where T: RTCPin, @@ -623,7 +695,7 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { /// Put the pin into RTC input mode. #[inline] - #[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] + #[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] pub fn into_rtc_input(self) -> Result, EspError> where T: InputPin + RTCPin, @@ -638,7 +710,7 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { /// it to high, in which case you can read the input to figure out whether another device /// is driving the line low. #[inline] - #[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] + #[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] pub fn into_rtc_input_output(self) -> Result, EspError> where T: InputPin + OutputPin + RTCPin, @@ -653,7 +725,7 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { /// it to high, in which case you can read the input to figure out whether another device /// is driving the line low. #[inline] - #[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] + #[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] pub fn into_rtc_input_output_od(self) -> Result, EspError> where T: InputPin + OutputPin + RTCPin, @@ -663,7 +735,7 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { /// Put the pin into RTC output mode. #[inline] - #[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] + #[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] pub fn into_rtc_output(self) -> Result, EspError> where T: OutputPin + RTCPin, @@ -673,7 +745,7 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { /// Put the pin into RTC output Open Drain mode. #[inline] - #[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] + #[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] pub fn into_rtc_output_od(self) -> Result, EspError> where T: OutputPin + RTCPin, @@ -687,7 +759,6 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { T: Pin, { let pin = unsafe { self.pin.clone_unchecked() }; - drop(self); if mode != gpio_mode_t_GPIO_MODE_DISABLE { @@ -701,7 +772,7 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { } #[inline] - #[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] + #[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] fn into_rtc_mode(mut self, mode: rtc_gpio_mode_t) -> Result, EspError> where T: RTCPin, @@ -710,7 +781,7 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { drop(self); - #[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] + #[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] { esp!(unsafe { rtc_gpio_init(pin.pin()) })?; esp!(unsafe { rtc_gpio_set_direction(pin.pin(), mode) })?; @@ -723,7 +794,6 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { } #[inline] - #[cfg(not(feature = "riscv-ulp-hal"))] pub fn get_drive_strength(&self) -> Result where MODE: OutputMode, @@ -731,10 +801,10 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { let mut cap: gpio_drive_cap_t = 0; if MODE::RTC { - #[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] + #[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] esp!(unsafe { rtc_gpio_get_drive_capability(self.pin.pin(), &mut cap) })?; - #[cfg(any(feature = "riscv-ulp-hal", esp32c3))] + #[cfg(any(esp32c3, esp32c2, esp32h2, esp32c5))] unreachable!(); } else { esp!(unsafe { gpio_get_drive_capability(self.pin.pin(), &mut cap) })?; @@ -744,16 +814,15 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { } #[inline] - #[cfg(not(feature = "riscv-ulp-hal"))] pub fn set_drive_strength(&mut self, strength: DriveStrength) -> Result<(), EspError> where MODE: OutputMode, { if MODE::RTC { - #[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] + #[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] esp!(unsafe { rtc_gpio_set_drive_capability(self.pin.pin(), strength.into()) })?; - #[cfg(any(feature = "riscv-ulp-hal", esp32c3))] + #[cfg(any(esp32c3, esp32c2, esp32h2, esp32c5))] unreachable!(); } else { esp!(unsafe { gpio_set_drive_capability(self.pin.pin(), strength.into()) })?; @@ -786,7 +855,7 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { let res; if MODE::RTC { - #[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] + #[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] { res = if unsafe { rtc_gpio_get_level(self.pin.pin()) } != 0 { Level::High @@ -795,7 +864,7 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { }; } - #[cfg(any(feature = "riscv-ulp-hal", esp32c3))] + #[cfg(any(esp32c3, esp32c2, esp32h2, esp32c5))] unreachable!(); } else if unsafe { gpio_get_level(self.pin.pin()) } != 0 { res = Level::High; @@ -825,7 +894,6 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { /// What level output is set to #[inline] - #[cfg(not(feature = "riscv-ulp-hal"))] fn get_output_level(&self) -> Level where MODE: OutputMode, @@ -834,9 +902,9 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { let pin = self.pin.pin() as u32; - #[cfg(esp32c3)] + #[cfg(any(esp32c3, esp32c2, esp32h2, esp32c5))] let is_set_high = unsafe { (*(GPIO_OUT_REG as *const u32) >> pin) & 0x01 != 0 }; - #[cfg(not(esp32c3))] + #[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] let is_set_high = if pin <= 31 { // GPIO0 - GPIO31 unsafe { (*(GPIO_OUT_REG as *const u32) >> pin) & 0x01 != 0 } @@ -852,20 +920,6 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { } } - /// What level output is set to - #[inline] - #[cfg(feature = "riscv-ulp-hal")] - fn get_output_level(&self) -> Level - where - MODE: OutputMode, - { - if unsafe { gpio_get_output_level(self.pin.pin()) } != 0 { - Level::High - } else { - Level::Low - } - } - #[inline] pub fn set_high(&mut self) -> Result<(), EspError> where @@ -894,10 +948,10 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { }; if MODE::RTC { - #[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] + #[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] esp!(unsafe { rtc_gpio_set_level(self.pin.pin(), on) })?; - #[cfg(any(feature = "riscv-ulp-hal", esp32c3))] + #[cfg(any(esp32c3, esp32c2, esp32h2, esp32c5))] unreachable!(); } else { esp!(unsafe { gpio_set_level(self.pin.pin(), on) })?; @@ -925,7 +979,7 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { MODE: InputMode, { if MODE::RTC { - #[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] + #[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] unsafe { match pull { Pull::Down => { @@ -947,7 +1001,7 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { } } - #[cfg(any(feature = "riscv-ulp-hal", esp32c3))] + #[cfg(any(esp32c3, esp32c2, esp32h2, esp32c5))] unreachable!(); } else { esp!(unsafe { gpio_set_pull_mode(self.pin.pin(), pull.into()) })?; @@ -956,40 +1010,94 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { Ok(()) } + /// Subscribes the provided callback for ISR notifications. + /// As a side effect, interrupts will be disabled, so to receive a notification, one has + /// to also call `PinDriver::enable_interrupt` after calling this method. + /// + /// Note that `PinDriver::enable_interrupt` should also be called after + /// each received notification **from non-ISR context**, because the driver will automatically + /// disable ISR interrupts on each received ISR notification (so as to avoid IWDT triggers). + /// /// # Safety /// /// Care should be taken not to call STD, libc or FreeRTOS APIs (except for a few allowed ones) /// in the callback passed to this function, as it is executed in an ISR context. - #[cfg(all(not(feature = "riscv-ulp-hal"), feature = "alloc"))] - pub unsafe fn subscribe(&mut self, callback: impl FnMut() + 'static) -> Result<(), EspError> + #[cfg(feature = "alloc")] + pub unsafe fn subscribe( + &mut self, + callback: F, + ) -> Result<(), EspError> where MODE: InputMode, { - enable_isr_service()?; - - self.unsubscribe()?; + self.internal_subscribe(callback) + } - let callback: Box = Box::new(callback); + /// Subscribes the provided callback for ISR notifications. + /// As a side effect, interrupts will be disabled, so to receive a notification, one has + /// to also call `PinDriver::enable_interrupt` after calling this method. + /// + /// Note that `PinDriver::enable_interrupt` should also be called after + /// each received notification **from non-ISR context**, because the driver will automatically + /// disable ISR interrupts on each received ISR notification (so as to avoid IWDT triggers). + /// + /// # Safety + /// + /// Care should be taken not to call STD, libc or FreeRTOS APIs (except for a few allowed ones) + /// in the callback passed to this function, as it is executed in an ISR context. + /// + /// Additionally, this method - in contrast to method `subscribe` - allows + /// the passed-in callback/closure to be non-`'static`. This enables users to borrow + /// - in the closure - variables that live on the stack - or more generally - in the same + /// scope where the driver is created. + /// + /// HOWEVER: care should be taken NOT to call `core::mem::forget()` on the driver, + /// as that would immediately lead to an UB (crash). + /// Also note that forgetting the driver might happen with `Rc` and `Arc` + /// when circular references are introduced: https://github.com/rust-lang/rust/issues/24456 + /// + /// The reason is that the closure is actually sent and owned by an ISR routine, + /// which means that if the driver is forgotten, Rust is free to e.g. unwind the stack + /// and the ISR routine will end up with references to variables that no longer exist. + /// + /// The destructor of the driver takes care - prior to the driver being dropped and e.g. + /// the stack being unwind - to unsubscribe the ISR routine. + /// Unfortunately, when the driver is forgotten, the un-subscription does not happen + /// and invalid references are left dangling. + /// + /// This "local borrowing" will only be possible to express in a safe way once/if `!Leak` types + /// are introduced to Rust (i.e. the impossibility to "forget" a type and thus not call its destructor). + #[cfg(feature = "alloc")] + pub unsafe fn subscribe_nonstatic( + &mut self, + callback: F, + ) -> Result<(), EspError> + where + MODE: InputMode, + { + self.internal_subscribe(callback) + } - chip::ISR_HANDLERS[self.pin.pin() as usize] = Some(Box::new(callback)); + #[cfg(feature = "alloc")] + fn internal_subscribe(&mut self, callback: impl FnMut() + Send + 'd) -> Result<(), EspError> + where + MODE: InputMode, + { + extern crate alloc; - esp!(gpio_isr_handler_add( - self.pin.pin(), - Some(Self::handle_isr), - UnsafeCallback::from( - chip::ISR_HANDLERS[self.pin.pin() as usize] - .as_mut() - .unwrap(), - ) - .as_ptr(), - ))?; + self.disable_interrupt()?; - self.enable_interrupt()?; + let callback: alloc::boxed::Box = alloc::boxed::Box::new(callback); + unsafe { + chip::PIN_ISR_HANDLER[self.pin.pin() as usize] = Some(core::mem::transmute::< + alloc::boxed::Box, + alloc::boxed::Box, + >(callback)); + } Ok(()) } - #[cfg(all(not(feature = "riscv-ulp-hal"), feature = "alloc"))] pub fn unsubscribe(&mut self) -> Result<(), EspError> where MODE: InputMode, @@ -1001,27 +1109,42 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { Ok(()) } - #[cfg(all(not(feature = "riscv-ulp-hal"), feature = "alloc"))] + /// Enables or re-enables the interrupt + /// + /// Note that the interrupt is automatically disabled each time an interrupt is triggered + /// (or else we risk entering a constant interrupt processing loop while the pin is in low/high state + /// and the interrupt type is set to non-edge) + /// + /// Therefore - to continue receiving ISR interrupts - user needs to call `enable_interrupt` + /// - **from a non-ISR context** - after each successful interrupt triggering. pub fn enable_interrupt(&mut self) -> Result<(), EspError> where MODE: InputMode, { - esp!(unsafe { gpio_intr_enable(self.pin.pin()) })?; + enable_isr_service()?; - Ok(()) + unsafe { + esp!(gpio_isr_handler_add( + self.pin.pin(), + Some(Self::handle_isr), + self.pin.pin() as u32 as *mut core::ffi::c_void, + )) + } } - #[cfg(all(not(feature = "riscv-ulp-hal"), feature = "alloc"))] pub fn disable_interrupt(&mut self) -> Result<(), EspError> where MODE: InputMode, { - esp!(unsafe { gpio_intr_disable(self.pin.pin()) })?; + use core::sync::atomic::Ordering; + + if ISR_SERVICE_ENABLED.load(Ordering::SeqCst) { + esp!(unsafe { gpio_isr_handler_remove(self.pin.pin()) })?; + } Ok(()) } - #[cfg(all(not(feature = "riscv-ulp-hal"), feature = "alloc"))] pub fn set_interrupt_type(&mut self, interrupt_type: InterruptType) -> Result<(), EspError> where MODE: InputMode, @@ -1031,65 +1154,85 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> { Ok(()) } - #[cfg(all(not(feature = "riscv-ulp-hal"), feature = "alloc"))] - unsafe extern "C" fn handle_isr(unsafe_callback: *mut c_void) { - let mut unsafe_callback = UnsafeCallback::from_ptr(unsafe_callback); - unsafe_callback.call(); - } -} + unsafe extern "C" fn handle_isr(user_ctx: *mut core::ffi::c_void) { + let pin = user_ctx as u32; -impl<'d, T: Pin, MODE> Drop for PinDriver<'d, T, MODE> { - fn drop(&mut self) { - unsafe { reset_pin(self.pin.pin(), gpio_mode_t_GPIO_MODE_DISABLE) }.unwrap(); + // IMPORTANT: MUST be done or else the ESP IDF GPIO driver will continue calling us in a loop + // - particularly when the interrupt type is set to non-edge triggering (pin high or low) - + // which will eventually cause the Interrupt WatchDog to kick in + gpio_intr_disable(pin as _); + + PIN_NOTIF[pin as usize].notify_lsb(); + + #[cfg(feature = "alloc")] + { + if let Some(unsafe_callback) = unsafe { &mut PIN_ISR_HANDLER[pin as usize] } { + (unsafe_callback)(); + } + } } } -unsafe impl<'d, T: Pin, MODE> Send for PinDriver<'d, T, MODE> {} +impl PinDriver<'_, T, MODE> { + pub async fn wait_for(&mut self, interrupt_type: InterruptType) -> Result<(), EspError> { + self.disable_interrupt()?; -#[cfg(not(feature = "riscv-ulp-hal"))] -pub(crate) unsafe fn rtc_reset_pin(pin: i32) -> Result<(), EspError> { - reset_pin(pin, gpio_mode_t_GPIO_MODE_DISABLE)?; + let notif = &chip::PIN_NOTIF[self.pin.pin() as usize]; - #[cfg(all(not(feature = "riscv-ulp-hal"), not(esp32c3)))] - esp!(rtc_gpio_init(pin))?; + notif.reset(); - Ok(()) -} + match interrupt_type { + InterruptType::LowLevel => { + if self.is_low() { + return Ok(()); + } + } + InterruptType::HighLevel => { + if self.is_high() { + return Ok(()); + } + } + _ => (), + } -unsafe fn reset_pin(_pin: i32, _mode: gpio_mode_t) -> Result<(), EspError> { - #[cfg(not(feature = "riscv-ulp-hal"))] - let res = { - #[cfg(feature = "alloc")] - unsubscribe_pin(_pin)?; + self.set_interrupt_type(interrupt_type)?; + self.enable_interrupt()?; - esp!(gpio_reset_pin(_pin))?; - esp!(gpio_set_direction(_pin, _mode))?; + notif.wait().await; Ok(()) - }; + } - #[cfg(feature = "riscv-ulp-hal")] - let res = Ok(()); + pub async fn wait_for_high(&mut self) -> Result<(), EspError> { + self.wait_for(InterruptType::HighLevel).await + } - res -} + pub async fn wait_for_low(&mut self) -> Result<(), EspError> { + self.wait_for(InterruptType::LowLevel).await + } -#[cfg(all(not(feature = "riscv-ulp-hal"), feature = "alloc"))] -unsafe fn unsubscribe_pin(pin: i32) -> Result<(), EspError> { - let subscribed = chip::ISR_HANDLERS[pin as usize].is_some(); + pub async fn wait_for_rising_edge(&mut self) -> Result<(), EspError> { + self.wait_for(InterruptType::PosEdge).await + } - if subscribed { - esp!(gpio_intr_disable(pin))?; - esp!(gpio_set_intr_type(pin, gpio_int_type_t_GPIO_INTR_DISABLE))?; - esp!(gpio_isr_handler_remove(pin))?; + pub async fn wait_for_falling_edge(&mut self) -> Result<(), EspError> { + self.wait_for(InterruptType::NegEdge).await + } - chip::ISR_HANDLERS[pin as usize] = None; + pub async fn wait_for_any_edge(&mut self) -> Result<(), EspError> { + self.wait_for(InterruptType::AnyEdge).await } +} - Ok(()) +impl Drop for PinDriver<'_, T, MODE> { + fn drop(&mut self) { + gpio_reset_without_pull(self.pin.pin()).unwrap(); + } } -impl<'d, T: Pin, MODE> embedded_hal_0_2::digital::v2::InputPin for PinDriver<'d, T, MODE> +unsafe impl Send for PinDriver<'_, T, MODE> {} + +impl embedded_hal_0_2::digital::v2::InputPin for PinDriver<'_, T, MODE> where MODE: InputMode, { @@ -1104,24 +1247,48 @@ where } } -impl<'d, T: Pin, MODE> embedded_hal::digital::ErrorType for PinDriver<'d, T, MODE> { - type Error = EspError; +use crate::embedded_hal_error; +embedded_hal_error!( + GpioError, + embedded_hal::digital::Error, + embedded_hal::digital::ErrorKind +); + +fn to_gpio_err(err: EspError) -> GpioError { + GpioError::other(err) +} + +impl embedded_hal::digital::ErrorType for PinDriver<'_, T, MODE> { + type Error = GpioError; } -impl<'d, T: Pin, MODE> embedded_hal::digital::InputPin for PinDriver<'d, T, MODE> +impl embedded_hal::digital::InputPin for PinDriver<'_, T, MODE> where MODE: InputMode, { - fn is_high(&self) -> Result { + fn is_high(&mut self) -> Result { Ok(PinDriver::is_high(self)) } - fn is_low(&self) -> Result { + fn is_low(&mut self) -> Result { + Ok(PinDriver::is_low(self)) + } +} + +impl embedded_hal::digital::InputPin for &PinDriver<'_, T, MODE> +where + MODE: InputMode, +{ + fn is_high(&mut self) -> Result { + Ok(PinDriver::is_high(self)) + } + + fn is_low(&mut self) -> Result { Ok(PinDriver::is_low(self)) } } -impl<'d, T: Pin, MODE> embedded_hal_0_2::digital::v2::OutputPin for PinDriver<'d, T, MODE> +impl embedded_hal_0_2::digital::v2::OutputPin for PinDriver<'_, T, MODE> where MODE: OutputMode, { @@ -1136,33 +1303,47 @@ where } } -impl<'d, T: Pin, MODE> embedded_hal::digital::OutputPin for PinDriver<'d, T, MODE> +impl embedded_hal::digital::OutputPin for PinDriver<'_, T, MODE> where MODE: OutputMode, { fn set_high(&mut self) -> Result<(), Self::Error> { - self.set_level(Level::High) + self.set_level(Level::High).map_err(to_gpio_err) } fn set_low(&mut self) -> Result<(), Self::Error> { - self.set_level(Level::Low) + self.set_level(Level::Low).map_err(to_gpio_err) } } -impl<'d, T: Pin, MODE> embedded_hal::digital::StatefulOutputPin for PinDriver<'d, T, MODE> +impl embedded_hal::digital::StatefulOutputPin for PinDriver<'_, T, MODE> where MODE: OutputMode, { - fn is_set_high(&self) -> Result { + fn is_set_high(&mut self) -> Result { Ok(self.get_output_level().into()) } - fn is_set_low(&self) -> Result { + fn is_set_low(&mut self) -> Result { Ok(!bool::from(self.get_output_level())) } } -impl<'d, T: Pin, MODE> embedded_hal_0_2::digital::v2::StatefulOutputPin for PinDriver<'d, T, MODE> +// TODO: Will become possible once the `PinDriver::setXXX`` methods become non-`&mut`, which they really are, internally +// impl<'d, T: Pin, MODE> embedded_hal::digital::StatefulOutputPin for &PinDriver<'d, T, MODE> +// where +// MODE: OutputMode, +// { +// fn is_set_high(&mut self) -> Result { +// Ok(self.get_output_level().into()) +// } + +// fn is_set_low(&mut self) -> Result { +// Ok(!bool::from(self.get_output_level())) +// } +// } + +impl embedded_hal_0_2::digital::v2::StatefulOutputPin for PinDriver<'_, T, MODE> where MODE: OutputMode, { @@ -1175,7 +1356,7 @@ where } } -impl<'d, T: Pin, MODE> embedded_hal_0_2::digital::v2::ToggleableOutputPin for PinDriver<'d, T, MODE> +impl embedded_hal_0_2::digital::v2::ToggleableOutputPin for PinDriver<'_, T, MODE> where MODE: OutputMode, { @@ -1186,56 +1367,60 @@ where } } -impl<'d, T: Pin, MODE> embedded_hal::digital::ToggleableOutputPin for PinDriver<'d, T, MODE> -where - MODE: OutputMode, -{ - fn toggle(&mut self) -> Result<(), Self::Error> { - self.set_level(Level::from(!bool::from(self.get_output_level()))) +impl embedded_hal_async::digital::Wait for PinDriver<'_, T, MODE> { + async fn wait_for_high(&mut self) -> Result<(), GpioError> { + self.wait_for_high().await?; + + Ok(()) } -} -#[cfg(all(not(feature = "riscv-ulp-hal"), feature = "alloc"))] -struct UnsafeCallback(*mut Box); + async fn wait_for_low(&mut self) -> Result<(), GpioError> { + self.wait_for_low().await?; -#[cfg(all(not(feature = "riscv-ulp-hal"), feature = "alloc"))] -impl UnsafeCallback { - #[allow(clippy::type_complexity)] - pub fn from(boxed: &mut Box>) -> Self { - Self(boxed.as_mut()) + Ok(()) } - pub unsafe fn from_ptr(ptr: *mut c_void) -> Self { - Self(ptr.cast()) + async fn wait_for_rising_edge(&mut self) -> Result<(), GpioError> { + self.wait_for_rising_edge().await?; + + Ok(()) } - pub fn as_ptr(&self) -> *mut c_void { - self.0.cast() + async fn wait_for_falling_edge(&mut self) -> Result<(), GpioError> { + self.wait_for_falling_edge().await?; + + Ok(()) } - pub unsafe fn call(&mut self) { - let reference = self.0.as_mut().unwrap(); + async fn wait_for_any_edge(&mut self) -> Result<(), GpioError> { + self.wait_for_any_edge().await?; - (reference)(); + Ok(()) } } -#[cfg(all(not(feature = "riscv-ulp-hal"), feature = "alloc"))] +static ISR_ALLOC_FLAGS: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0); + static ISR_SERVICE_ENABLED: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false); -#[cfg(all(not(feature = "riscv-ulp-hal"), feature = "alloc"))] static ISR_SERVICE_ENABLED_CS: crate::task::CriticalSection = crate::task::CriticalSection::new(); -#[cfg(all(not(feature = "riscv-ulp-hal"), feature = "alloc"))] -fn enable_isr_service() -> Result<(), EspError> { +pub fn init_isr_alloc_flags(flags: enumset::EnumSet) { + ISR_ALLOC_FLAGS.store( + crate::interrupt::InterruptType::to_native(flags), + core::sync::atomic::Ordering::SeqCst, + ); +} + +pub fn enable_isr_service() -> Result<(), EspError> { use core::sync::atomic::Ordering; if !ISR_SERVICE_ENABLED.load(Ordering::SeqCst) { - let _ = ISR_SERVICE_ENABLED_CS.enter(); + let _guard = ISR_SERVICE_ENABLED_CS.enter(); if !ISR_SERVICE_ENABLED.load(Ordering::SeqCst) { - esp!(unsafe { gpio_install_isr_service(0) })?; + esp!(unsafe { gpio_install_isr_service(ISR_ALLOC_FLAGS.load(Ordering::SeqCst) as _) })?; ISR_SERVICE_ENABLED.store(true, Ordering::SeqCst); } @@ -1244,6 +1429,61 @@ fn enable_isr_service() -> Result<(), EspError> { Ok(()) } +pub(crate) unsafe fn rtc_reset_pin(pin: i32) -> Result<(), EspError> { + gpio_reset_without_pull(pin)?; + + #[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5)))] + esp!(rtc_gpio_init(pin))?; + + Ok(()) +} + +// The default esp-idf gpio_reset function sets a pull-up. If that behaviour is +// not desired this function can be used instead. +#[inline] +fn gpio_reset_without_pull(pin: gpio_num_t) -> Result<(), EspError> { + let cfg = gpio_config_t { + pin_bit_mask: (1u64 << pin), + mode: esp_idf_sys::gpio_mode_t_GPIO_MODE_DISABLE, + pull_up_en: esp_idf_sys::gpio_pullup_t_GPIO_PULLUP_DISABLE, + pull_down_en: esp_idf_sys::gpio_pulldown_t_GPIO_PULLDOWN_DISABLE, + intr_type: esp_idf_sys::gpio_int_type_t_GPIO_INTR_DISABLE, + #[cfg(all(esp32h2, not(esp_idf_version_major = "4")))] + hys_ctrl_mode: esp_idf_sys::gpio_hys_ctrl_mode_t_GPIO_HYS_SOFT_DISABLE, + }; + + unsafe { + unsubscribe_pin(pin)?; + esp!(gpio_config(&cfg))?; + } + Ok(()) +} + +unsafe fn unsubscribe_pin(pin: i32) -> Result<(), EspError> { + use core::sync::atomic::Ordering; + + if ISR_SERVICE_ENABLED.load(Ordering::SeqCst) { + esp!(gpio_isr_handler_remove(pin))?; + + chip::PIN_NOTIF[pin as usize].reset(); + + #[cfg(feature = "alloc")] + { + chip::PIN_ISR_HANDLER[pin as usize] = None; + } + } + + Ok(()) +} + +#[cfg(feature = "alloc")] +#[allow(clippy::declare_interior_mutable_const)] // OK because this is only used as an array initializer +const PIN_ISR_INIT: Option> = None; + +#[allow(clippy::declare_interior_mutable_const)] // OK because this is only used as an array initializer +const PIN_NOTIF_INIT: crate::interrupt::asynch::HalIsrNotification = + crate::interrupt::asynch::HalIsrNotification::new(); + macro_rules! impl_input { ($pxi:ident: $pin:expr) => { crate::impl_peripheral!($pxi); @@ -1318,6 +1558,7 @@ macro_rules! impl_adc { const CHANNEL: adc_channel_t = $adc; } + #[cfg(any(esp32, esp32s2, esp32s3, esp32c3))] impl ADCPin for $pxi { type Adc = ADC2; @@ -1332,7 +1573,7 @@ macro_rules! impl_adc { macro_rules! impl_dac { ($pxi:ident: $pin:expr, DAC: $dac:expr) => { - #[cfg(all(not(esp32c3), not(esp32s3)))] + #[cfg(any(esp32, esp32s2))] impl DACPin for $pxi { fn dac_channel(&self) -> dac_channel_t { $dac @@ -1345,7 +1586,7 @@ macro_rules! impl_dac { macro_rules! impl_touch { ($pxi:ident: $pin:expr, TOUCH: $touch:expr) => { - #[cfg(not(esp32c3))] + #[cfg(any(esp32, esp32s2, esp32s3))] impl TouchPin for $pxi { fn touch_channel(&self) -> touch_pad_t { $touch @@ -1376,64 +1617,53 @@ macro_rules! pin { #[cfg(esp32)] mod chip { - #[cfg(not(feature = "riscv-ulp-hal"))] + #[cfg(feature = "alloc")] + extern crate alloc; + + #[cfg(feature = "alloc")] + use alloc::boxed::Box; + use esp_idf_sys::*; - #[cfg(feature = "riscv-ulp-hal")] - use crate::riscv_ulp_hal::sys::*; + use crate::interrupt::asynch::HalIsrNotification; use crate::adc::{ADC1, ADC2}; use super::*; #[allow(clippy::type_complexity)] - #[cfg(all(not(feature = "riscv-ulp-hal"), feature = "alloc"))] - pub(crate) static mut ISR_HANDLERS: [Option>>; 40] = [ - None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, None, None, None, - ]; + #[cfg(feature = "alloc")] + pub(crate) static mut PIN_ISR_HANDLER: [Option>; 40] = + [PIN_ISR_INIT; 40]; + + #[allow(clippy::type_complexity)] + pub(crate) static PIN_NOTIF: [HalIsrNotification; 40] = [PIN_NOTIF_INIT; 40]; // NOTE: Gpio26 - Gpio32 are used by SPI0/SPI1 for external PSRAM/SPI Flash and // are not recommended for other uses pin!(Gpio0:0, IO, RTC:11, ADC2:1, NODAC:0, TOUCH:1); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio1:1, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); pin!(Gpio2:2, IO, RTC:12, ADC2:2, NODAC:0, TOUCH:2); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio3:3, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); pin!(Gpio4:4, IO, RTC:10, ADC2:0, NODAC:0, TOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio5:5, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio6:6, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio7:7, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio8:8, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio9:9, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio10:10, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio11:11, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); pin!(Gpio12:12, IO, RTC:15, ADC2:5, NODAC:0, TOUCH:5); pin!(Gpio13:13, IO, RTC:14, ADC2:4, NODAC:0, TOUCH:4); pin!(Gpio14:14, IO, RTC:16, ADC2:6, NODAC:0, TOUCH:6); pin!(Gpio15:15, IO, RTC:13, ADC2:3, NODAC:0, TOUCH:3); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio16:16, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio17:17, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio18:18, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio19:19, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] + pin!(Gpio20:20, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); pin!(Gpio21:21, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio22:22, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio23:23, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); pin!(Gpio25:25, IO, RTC:6, ADC2:8, DAC:1, NOTOUCH:0); pin!(Gpio26:26, IO, RTC:7, ADC2:9, DAC:2, NOTOUCH:0); @@ -1449,43 +1679,28 @@ mod chip { pub struct Pins { pub gpio0: Gpio0, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio1: Gpio1, pub gpio2: Gpio2, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio3: Gpio3, pub gpio4: Gpio4, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio5: Gpio5, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio6: Gpio6, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio7: Gpio7, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio8: Gpio8, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio9: Gpio9, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio10: Gpio10, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio11: Gpio11, pub gpio12: Gpio12, pub gpio13: Gpio13, pub gpio14: Gpio14, pub gpio15: Gpio15, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio16: Gpio16, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio17: Gpio17, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio18: Gpio18, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio19: Gpio19, - #[cfg(not(feature = "riscv-ulp-hal"))] + pub gpio20: Gpio20, pub gpio21: Gpio21, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio22: Gpio22, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio23: Gpio23, pub gpio25: Gpio25, pub gpio26: Gpio26, @@ -1508,43 +1723,28 @@ mod chip { pub unsafe fn new() -> Self { Self { gpio0: Gpio0::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio1: Gpio1::new(), gpio2: Gpio2::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio3: Gpio3::new(), gpio4: Gpio4::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio5: Gpio5::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio6: Gpio6::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio7: Gpio7::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio8: Gpio8::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio9: Gpio9::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio10: Gpio10::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio11: Gpio11::new(), gpio12: Gpio12::new(), gpio13: Gpio13::new(), gpio14: Gpio14::new(), gpio15: Gpio15::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio16: Gpio16::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio17: Gpio17::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio18: Gpio18::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio19: Gpio19::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] + gpio20: Gpio20::new(), gpio21: Gpio21::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio22: Gpio22::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio23: Gpio23::new(), gpio25: Gpio25::new(), gpio26: Gpio26::new(), @@ -1564,21 +1764,27 @@ mod chip { #[cfg(any(esp32s2, esp32s3))] mod chip { - #[cfg(not(feature = "riscv-ulp-hal"))] + #[cfg(feature = "alloc")] + extern crate alloc; + + #[cfg(feature = "alloc")] + use alloc::boxed::Box; + use esp_idf_sys::*; + use crate::interrupt::asynch::HalIsrNotification; + use crate::adc::{ADC1, ADC2}; use super::*; #[allow(clippy::type_complexity)] - #[cfg(all(not(feature = "riscv-ulp-hal"), feature = "alloc"))] - pub(crate) static mut ISR_HANDLERS: [Option>>; 49] = [ - None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, - ]; + #[cfg(feature = "alloc")] + pub(crate) static mut PIN_ISR_HANDLER: [Option>; 49] = + [PIN_ISR_INIT; 49]; + + #[allow(clippy::type_complexity)] + pub(crate) static PIN_NOTIF: [HalIsrNotification; 49] = [PIN_NOTIF_INIT; 49]; // NOTE: Gpio26 - Gpio32 (and Gpio33 - Gpio37 if using Octal RAM/Flash) are used // by SPI0/SPI1 for external PSRAM/SPI Flash and are not recommended for @@ -1611,53 +1817,33 @@ mod chip { pin!(Gpio19:19, IO, RTC:19, ADC2:8, NODAC:0, NOTOUCH:0); pin!(Gpio20:20, IO, RTC:20, ADC2:9, NODAC:0, NOTOUCH:0); pin!(Gpio21:21, IO, RTC:21, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio26:26, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio27:27, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio28:28, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio29:29, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio30:30, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio31:31, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio32:32, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio33:33, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio34:34, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio35:35, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio36:36, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio37:37, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio38:38, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio39:39, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio40:40, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio41:41, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio42:42, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio43:43, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio44:44, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(not(feature = "riscv-ulp-hal"))] pin!(Gpio45:45, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(all(esp32s2, not(feature = "riscv-ulp-hal")))] + #[cfg(esp32s2)] pin!(Gpio46:46, Input, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(all(esp32s3, not(feature = "riscv-ulp-hal")))] + #[cfg(esp32s3)] pin!(Gpio46:46, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(all(esp32s3, not(feature = "riscv-ulp-hal")))] + #[cfg(esp32s3)] pin!(Gpio47:47, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); - #[cfg(all(esp32s3, not(feature = "riscv-ulp-hal")))] + #[cfg(esp32s3)] pin!(Gpio48:48, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); pub struct Pins { @@ -1683,51 +1869,30 @@ mod chip { pub gpio19: Gpio19, pub gpio20: Gpio20, pub gpio21: Gpio21, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio26: Gpio26, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio27: Gpio27, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio28: Gpio28, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio29: Gpio29, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio30: Gpio30, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio31: Gpio31, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio32: Gpio32, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio33: Gpio33, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio34: Gpio34, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio35: Gpio35, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio36: Gpio36, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio37: Gpio37, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio38: Gpio38, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio39: Gpio39, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio40: Gpio40, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio41: Gpio41, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio42: Gpio42, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio43: Gpio43, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio44: Gpio44, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio45: Gpio45, - #[cfg(not(feature = "riscv-ulp-hal"))] pub gpio46: Gpio46, - #[cfg(all(esp32s3, not(feature = "riscv-ulp-hal")))] + #[cfg(esp32s3)] pub gpio47: Gpio47, - #[cfg(all(esp32s3, not(feature = "riscv-ulp-hal")))] + #[cfg(esp32s3)] pub gpio48: Gpio48, } @@ -1760,51 +1925,30 @@ mod chip { gpio19: Gpio19::new(), gpio20: Gpio20::new(), gpio21: Gpio21::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio26: Gpio26::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio27: Gpio27::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio28: Gpio28::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio29: Gpio29::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio30: Gpio30::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio31: Gpio31::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio32: Gpio32::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio33: Gpio33::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio34: Gpio34::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio35: Gpio35::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio36: Gpio36::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio37: Gpio37::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio38: Gpio38::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio39: Gpio39::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio40: Gpio40::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio41: Gpio41::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio42: Gpio42::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio43: Gpio43::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio44: Gpio44::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio45: Gpio45::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] gpio46: Gpio46::new(), - #[cfg(all(esp32s3, not(feature = "riscv-ulp-hal")))] + #[cfg(esp32s3)] gpio47: Gpio47::new(), - #[cfg(all(esp32s3, not(feature = "riscv-ulp-hal")))] + #[cfg(esp32s3)] gpio48: Gpio48::new(), } } @@ -1812,20 +1956,27 @@ mod chip { } #[cfg(esp32c3)] -#[cfg(not(feature = "riscv-ulp-hal"))] mod chip { + #[cfg(feature = "alloc")] + extern crate alloc; + + #[cfg(feature = "alloc")] + use alloc::boxed::Box; + use esp_idf_sys::*; + use crate::interrupt::asynch::HalIsrNotification; + use crate::adc::{ADC1, ADC2}; use super::*; #[allow(clippy::type_complexity)] #[cfg(feature = "alloc")] - pub(crate) static mut ISR_HANDLERS: [Option>>; 22] = [ - None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, - None, None, None, None, None, None, None, - ]; + pub(crate) static mut PIN_ISR_HANDLER: [Option>; 22] = + [PIN_ISR_INIT; 22]; + + pub(crate) static PIN_NOTIF: [HalIsrNotification; 22] = [PIN_NOTIF_INIT; 22]; // NOTE: Gpio12 - Gpio17 are used by SPI0/SPI1 for external PSRAM/SPI Flash and // are not recommended for other uses @@ -1910,3 +2061,373 @@ mod chip { } } } + +#[cfg(esp32c2)] +mod chip { + #[cfg(feature = "alloc")] + extern crate alloc; + + #[cfg(feature = "alloc")] + use alloc::boxed::Box; + + use esp_idf_sys::*; + + use crate::interrupt::asynch::HalIsrNotification; + + use crate::adc::ADC1; + + use super::*; + + #[allow(clippy::type_complexity)] + #[cfg(feature = "alloc")] + pub(crate) static mut PIN_ISR_HANDLER: [Option>; 21] = + [PIN_ISR_INIT; 21]; + + pub(crate) static PIN_NOTIF: [HalIsrNotification; 21] = [PIN_NOTIF_INIT; 21]; + + // NOTE: Gpio12 - Gpio17 are used by SPI0/SPI1 for external PSRAM/SPI Flash and + // are not recommended for other uses + pin!(Gpio0:0, IO, RTC:0, ADC1:0, NODAC:0, NOTOUCH:0); + pin!(Gpio1:1, IO, RTC:1, ADC1:1, NODAC:0, NOTOUCH:0); + pin!(Gpio2:2, IO, RTC:2, ADC1:2, NODAC:0, NOTOUCH:0); + pin!(Gpio3:3, IO, RTC:3, ADC1:3, NODAC:0, NOTOUCH:0); + pin!(Gpio4:4, IO, RTC:4, ADC1:4, NODAC:0, NOTOUCH:0); + pin!(Gpio5:5, IO, RTC:5, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio6:6, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio7:7, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio8:8, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio9:9, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio10:10, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio11:11, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio12:12, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio13:13, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio14:14, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio15:15, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio16:16, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio17:17, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio18:18, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio19:19, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio20:20, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + + pub struct Pins { + pub gpio0: Gpio0, + pub gpio1: Gpio1, + pub gpio2: Gpio2, + pub gpio3: Gpio3, + pub gpio4: Gpio4, + pub gpio5: Gpio5, + pub gpio6: Gpio6, + pub gpio7: Gpio7, + pub gpio8: Gpio8, + pub gpio9: Gpio9, + pub gpio10: Gpio10, + pub gpio11: Gpio11, + pub gpio12: Gpio12, + pub gpio13: Gpio13, + pub gpio14: Gpio14, + pub gpio15: Gpio15, + pub gpio16: Gpio16, + pub gpio17: Gpio17, + pub gpio18: Gpio18, + pub gpio19: Gpio19, + pub gpio20: Gpio20, + } + + impl Pins { + /// # Safety + /// + /// Care should be taken not to instantiate the Pins structure, if it is + /// already instantiated and used elsewhere + pub unsafe fn new() -> Self { + Self { + gpio0: Gpio0::new(), + gpio1: Gpio1::new(), + gpio2: Gpio2::new(), + gpio3: Gpio3::new(), + gpio4: Gpio4::new(), + gpio5: Gpio5::new(), + gpio6: Gpio6::new(), + gpio7: Gpio7::new(), + gpio8: Gpio8::new(), + gpio9: Gpio9::new(), + gpio10: Gpio10::new(), + gpio11: Gpio11::new(), + gpio12: Gpio12::new(), + gpio13: Gpio13::new(), + gpio14: Gpio14::new(), + gpio15: Gpio15::new(), + gpio16: Gpio16::new(), + gpio17: Gpio17::new(), + gpio18: Gpio18::new(), + gpio19: Gpio19::new(), + gpio20: Gpio20::new(), + } + } + } +} + +#[cfg(esp32h2)] +mod chip { + #[cfg(feature = "alloc")] + extern crate alloc; + + #[cfg(feature = "alloc")] + use alloc::boxed::Box; + + use esp_idf_sys::*; + + use crate::interrupt::asynch::HalIsrNotification; + + use crate::adc::ADC1; + + use super::*; + + #[allow(clippy::type_complexity)] + #[cfg(feature = "alloc")] + pub(crate) static mut PIN_ISR_HANDLER: [Option>; 28] = + [PIN_ISR_INIT; 28]; + + pub(crate) static PIN_NOTIF: [HalIsrNotification; 28] = [PIN_NOTIF_INIT; 28]; + + // NOTE: Following pins have special meaning and are not recommended for other uses. But one may use them with care. + // - Gpio12 - Gpio17 are used by SPI0/SPI1 for external PSRAM/SPI Flash + // - Gpio21 seems not to be exposed physically + // - Gpio23 + Gpio24 are used by serial debug interface + // - Gpio26 + Gpio27 are used by USB debug interface + pin!(Gpio0:0, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio1:1, IO, NORTC:0, ADC1:0, NODAC:0, NOTOUCH:0); + pin!(Gpio2:2, IO, NORTC:0, ADC1:1, NODAC:0, NOTOUCH:0); + pin!(Gpio3:3, IO, NORTC:0, ADC1:2, NODAC:0, NOTOUCH:0); + pin!(Gpio4:4, IO, NORTC:0, ADC1:3, NODAC:0, NOTOUCH:0); + pin!(Gpio5:5, IO, NORTC:0, ADC1:4, NODAC:0, NOTOUCH:0); + pin!(Gpio6:6, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio7:7, IO, RTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio8:8, IO, RTC:1, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio9:9, IO, RTC:2, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio10:10, IO, RTC:3, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio11:11, IO, RTC:4, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio12:12, IO, RTC:5, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio13:13, IO, RTC:6, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio14:14, IO, RTC:7, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio15:15, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio16:16, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio17:17, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio18:18, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio19:19, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio20:20, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio21:21, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio22:22, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio23:23, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio24:24, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio25:25, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio26:26, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio27:27, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + + pub struct Pins { + pub gpio0: Gpio0, + pub gpio1: Gpio1, + pub gpio2: Gpio2, + pub gpio3: Gpio3, + pub gpio4: Gpio4, + pub gpio5: Gpio5, + pub gpio6: Gpio6, + pub gpio7: Gpio7, + pub gpio8: Gpio8, + pub gpio9: Gpio9, + pub gpio10: Gpio10, + pub gpio11: Gpio11, + pub gpio12: Gpio12, + pub gpio13: Gpio13, + pub gpio14: Gpio14, + pub gpio15: Gpio15, + pub gpio16: Gpio16, + pub gpio17: Gpio17, + pub gpio18: Gpio18, + pub gpio19: Gpio19, + pub gpio20: Gpio20, + pub gpio21: Gpio21, + pub gpio22: Gpio22, + pub gpio23: Gpio23, + pub gpio24: Gpio24, + pub gpio25: Gpio25, + pub gpio26: Gpio26, + pub gpio27: Gpio27, + } + + impl Pins { + /// # Safety + /// + /// Care should be taken not to instantiate the Pins structure, if it is + /// already instantiated and used elsewhere + pub unsafe fn new() -> Self { + Self { + gpio0: Gpio0::new(), + gpio1: Gpio1::new(), + gpio2: Gpio2::new(), + gpio3: Gpio3::new(), + gpio4: Gpio4::new(), + gpio5: Gpio5::new(), + gpio6: Gpio6::new(), + gpio7: Gpio7::new(), + gpio8: Gpio8::new(), + gpio9: Gpio9::new(), + gpio10: Gpio10::new(), + gpio11: Gpio11::new(), + gpio12: Gpio12::new(), + gpio13: Gpio13::new(), + gpio14: Gpio14::new(), + gpio15: Gpio15::new(), + gpio16: Gpio16::new(), + gpio17: Gpio17::new(), + gpio18: Gpio18::new(), + gpio19: Gpio19::new(), + gpio20: Gpio20::new(), + gpio21: Gpio21::new(), + gpio22: Gpio22::new(), + gpio23: Gpio23::new(), + gpio24: Gpio24::new(), + gpio25: Gpio25::new(), + gpio26: Gpio26::new(), + gpio27: Gpio27::new(), + } + } + } +} + +// TODO: Implement esp32c6 glitch filters + +#[cfg(any(esp32c5, esp32c6, esp32p4))] // TODO: Implement proper pin layout for esp32c5 and esp32p4 +mod chip { + #[cfg(feature = "alloc")] + extern crate alloc; + + #[cfg(feature = "alloc")] + use alloc::boxed::Box; + + use esp_idf_sys::*; + + use crate::interrupt::asynch::HalIsrNotification; + + use crate::adc::ADC1; + + use super::*; + + #[allow(clippy::type_complexity)] + #[cfg(feature = "alloc")] + pub(crate) static mut PIN_ISR_HANDLER: [Option>; 30] = + [PIN_ISR_INIT; 30]; + + #[allow(clippy::type_complexity)] + pub(crate) static PIN_NOTIF: [HalIsrNotification; 30] = [PIN_NOTIF_INIT; 30]; + + // NOTE: Gpio26 - Gpio32 (and Gpio33 - Gpio37 if using Octal RAM/Flash) are used + // by SPI0/SPI1 for external PSRAM/SPI Flash and are not recommended for + // other uses + pin!(Gpio0:0, IO, RTC:0, ADC1:0, NODAC:0, NOTOUCH:0); + pin!(Gpio1:1, IO, RTC:1, ADC1:1, NODAC:0, NOTOUCH:0); + pin!(Gpio2:2, IO, RTC:2, ADC1:2, NODAC:0, NOTOUCH:0); + pin!(Gpio3:3, IO, RTC:3, ADC1:3, NODAC:0, NOTOUCH:0); + pin!(Gpio4:4, IO, RTC:4, ADC1:4, NODAC:0, NOTOUCH:0); + pin!(Gpio5:5, IO, RTC:5, ADC1:5, NODAC:0, NOTOUCH:0); + pin!(Gpio6:6, IO, RTC:6, ADC1:6, NODAC:0, NOTOUCH:0); + pin!(Gpio7:7, IO, RTC:7, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio8:8, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio9:9, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio10:10, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio11:11, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio12:12, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio13:13, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio14:14, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio15:15, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio16:16, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio17:17, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio18:18, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio19:19, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio20:20, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio21:21, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio22:22, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio23:23, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio24:24, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio25:25, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio26:26, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio27:27, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio28:28, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio29:29, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + pin!(Gpio30:30, IO, NORTC:0, NOADC:0, NODAC:0, NOTOUCH:0); + + pub struct Pins { + pub gpio0: Gpio0, + pub gpio1: Gpio1, + pub gpio2: Gpio2, + pub gpio3: Gpio3, + pub gpio4: Gpio4, + pub gpio5: Gpio5, + pub gpio6: Gpio6, + pub gpio7: Gpio7, + pub gpio8: Gpio8, + pub gpio9: Gpio9, + pub gpio10: Gpio10, + pub gpio11: Gpio11, + pub gpio12: Gpio12, + pub gpio13: Gpio13, + pub gpio14: Gpio14, + pub gpio15: Gpio15, + pub gpio16: Gpio16, + pub gpio17: Gpio17, + pub gpio18: Gpio18, + pub gpio19: Gpio19, + pub gpio20: Gpio20, + pub gpio21: Gpio21, + pub gpio22: Gpio22, + pub gpio23: Gpio23, + pub gpio24: Gpio24, + pub gpio25: Gpio25, + pub gpio26: Gpio26, + pub gpio27: Gpio27, + pub gpio28: Gpio28, + pub gpio29: Gpio29, + pub gpio30: Gpio30, + } + + impl Pins { + /// # Safety + /// + /// Care should be taken not to instantiate the Pins structure, if it is + /// already instantiated and used elsewhere + pub unsafe fn new() -> Self { + Self { + gpio0: Gpio0::new(), + gpio1: Gpio1::new(), + gpio2: Gpio2::new(), + gpio3: Gpio3::new(), + gpio4: Gpio4::new(), + gpio5: Gpio5::new(), + gpio6: Gpio6::new(), + gpio7: Gpio7::new(), + gpio8: Gpio8::new(), + gpio9: Gpio9::new(), + gpio10: Gpio10::new(), + gpio11: Gpio11::new(), + gpio12: Gpio12::new(), + gpio13: Gpio13::new(), + gpio14: Gpio14::new(), + gpio15: Gpio15::new(), + gpio16: Gpio16::new(), + gpio17: Gpio17::new(), + gpio18: Gpio18::new(), + gpio19: Gpio19::new(), + gpio20: Gpio20::new(), + gpio21: Gpio21::new(), + gpio22: Gpio22::new(), + gpio23: Gpio23::new(), + gpio24: Gpio24::new(), + gpio25: Gpio25::new(), + gpio26: Gpio26::new(), + gpio27: Gpio27::new(), + gpio28: Gpio28::new(), + gpio29: Gpio29::new(), + gpio30: Gpio30::new(), + } + } + } +} diff --git a/src/i2c.rs b/src/i2c.rs index 0ec6bbc7bd4..f5a415a02a1 100644 --- a/src/i2c.rs +++ b/src/i2c.rs @@ -1,4 +1,5 @@ use core::marker::PhantomData; +use core::time::Duration; use embedded_hal::i2c::{ErrorKind, NoAcknowledgeSource}; @@ -6,6 +7,7 @@ use esp_idf_sys::*; use crate::delay::*; use crate::gpio::*; +use crate::interrupt::InterruptType; use crate::peripheral::Peripheral; use crate::units::*; @@ -17,19 +19,70 @@ crate::embedded_hal_error!( embedded_hal::i2c::ErrorKind ); +#[cfg(any(esp32, esp32s2))] +const APB_TICK_PERIOD_NS: u32 = 1_000_000_000 / 80_000_000; + +#[cfg(all(esp32c2, esp_idf_xtal_freq_40))] +const XTAL_TICK_PERIOD_NS: u32 = 1_000_000_000 / 40_000_000; +#[cfg(all(esp32c2, esp_idf_xtal_freq_26))] +const XTAL_TICK_PERIOD_NS: u32 = 1_000_000_000 / 26_000_000; + +#[cfg(not(any(esp32, esp32s2, esp32c2)))] +const XTAL_TICK_PERIOD_NS: u32 = 1_000_000_000 / XTAL_CLK_FREQ; +#[derive(Copy, Clone, Debug)] +pub struct APBTickType(::core::ffi::c_int); +impl From for APBTickType { + #[cfg(any(esp32, esp32s2))] + #[allow(clippy::manual_div_ceil)] + fn from(duration: Duration) -> Self { + APBTickType( + ((duration.as_nanos() + APB_TICK_PERIOD_NS as u128 - 1) / APB_TICK_PERIOD_NS as u128) + as ::core::ffi::c_int, + ) + } + #[cfg(not(any(esp32, esp32s2)))] + /// Conversion for newer esp models, be aware, that the hardware can only represent 22 different values, values will be rounded to the next larger valid one. Calculation only valid for 40mhz clock source + fn from(duration: Duration) -> Self { + let target_ns = duration.as_nanos() as u64; + let timeout_in_xtal_clock_cycles = target_ns / (XTAL_TICK_PERIOD_NS as u64); + //ilog2 but with ceiling logic + let register_value = timeout_in_xtal_clock_cycles.ilog2() + + (if timeout_in_xtal_clock_cycles.leading_zeros() + + timeout_in_xtal_clock_cycles.trailing_zeros() + + 1 + < 64 + { + 1 + } else { + 0 + }); + if register_value <= 22 { + return APBTickType(register_value as ::core::ffi::c_int); + } + //produce an error in the lower set_i2c_timeout, so the user is informed that the requested timeout is larger than the next valid one. + APBTickType(32 as ::core::ffi::c_int) + } +} + pub type I2cConfig = config::Config; +#[cfg(not(esp32c2))] pub type I2cSlaveConfig = config::SlaveConfig; /// I2C configuration pub mod config { - use crate::units::*; + use enumset::EnumSet; + + use super::APBTickType; + use crate::{interrupt::InterruptType, units::*}; /// I2C Master configuration - #[derive(Copy, Clone)] + #[derive(Debug, Clone)] pub struct Config { pub baudrate: Hertz, pub sda_pullup_enabled: bool, pub scl_pullup_enabled: bool, + pub timeout: Option, + pub intr_flags: EnumSet, } impl Config { @@ -54,6 +107,18 @@ pub mod config { self.scl_pullup_enabled = enable; self } + + #[must_use] + pub fn timeout(mut self, timeout: APBTickType) -> Self { + self.timeout = Some(timeout); + self + } + + #[must_use] + pub fn intr_flags(mut self, flags: EnumSet) -> Self { + self.intr_flags = flags; + self + } } impl Default for Config { @@ -62,19 +127,24 @@ pub mod config { baudrate: Hertz(1_000_000), sda_pullup_enabled: true, scl_pullup_enabled: true, + timeout: None, + intr_flags: EnumSet::::empty(), } } } /// I2C Slave configuration - #[derive(Copy, Clone)] + #[cfg(not(esp32c2))] + #[derive(Debug, Clone)] pub struct SlaveConfig { pub sda_pullup_enabled: bool, pub scl_pullup_enabled: bool, pub rx_buf_len: usize, pub tx_buf_len: usize, + pub intr_flags: EnumSet, } + #[cfg(not(esp32c2))] impl SlaveConfig { pub fn new() -> Self { Default::default() @@ -103,8 +173,15 @@ pub mod config { self.tx_buf_len = len; self } + + #[must_use] + pub fn intr_flags(mut self, flags: EnumSet) -> Self { + self.intr_flags = flags; + self + } } + #[cfg(not(esp32c2))] impl Default for SlaveConfig { fn default() -> Self { Self { @@ -112,6 +189,7 @@ pub mod config { scl_pullup_enabled: true, rx_buf_len: 0, tx_buf_len: 0, + intr_flags: EnumSet::::empty(), } } } @@ -135,7 +213,7 @@ impl<'d> I2cDriver<'d> { ) -> Result { // i2c_config_t documentation says that clock speed must be no higher than 1 MHz if config.baudrate > 1.MHz().into() { - return Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()); + return Err(EspError::from_infallible::()); } crate::into_ref!(sda, scl); @@ -162,10 +240,14 @@ impl<'d> I2cDriver<'d> { i2c_mode_t_I2C_MODE_MASTER, 0, // Not used in master mode 0, // Not used in master mode - 0, - ) // TODO: set flags + InterruptType::to_native(config.intr_flags) as _, + ) })?; + if let Some(timeout) = config.timeout { + esp!(unsafe { i2c_set_timeout(I2C::port(), timeout.0) })?; + } + Ok(I2cDriver { i2c: I2C::port() as _, _p: PhantomData, @@ -303,15 +385,15 @@ impl<'d> I2cDriver<'d> { } } -impl<'d> Drop for I2cDriver<'d> { +impl Drop for I2cDriver<'_> { fn drop(&mut self) { esp!(unsafe { i2c_driver_delete(self.port()) }).unwrap(); } } -unsafe impl<'d> Send for I2cDriver<'d> {} +unsafe impl Send for I2cDriver<'_> {} -impl<'d> embedded_hal_0_2::blocking::i2c::Read for I2cDriver<'d> { +impl embedded_hal_0_2::blocking::i2c::Read for I2cDriver<'_> { type Error = I2cError; fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { @@ -319,7 +401,7 @@ impl<'d> embedded_hal_0_2::blocking::i2c::Read for I2cDriver<'d> { } } -impl<'d> embedded_hal_0_2::blocking::i2c::Write for I2cDriver<'d> { +impl embedded_hal_0_2::blocking::i2c::Write for I2cDriver<'_> { type Error = I2cError; fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> { @@ -327,7 +409,7 @@ impl<'d> embedded_hal_0_2::blocking::i2c::Write for I2cDriver<'d> { } } -impl<'d> embedded_hal_0_2::blocking::i2c::WriteRead for I2cDriver<'d> { +impl embedded_hal_0_2::blocking::i2c::WriteRead for I2cDriver<'_> { type Error = I2cError; fn write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Self::Error> { @@ -335,11 +417,11 @@ impl<'d> embedded_hal_0_2::blocking::i2c::WriteRead for I2cDriver<'d> { } } -impl<'d> embedded_hal::i2c::ErrorType for I2cDriver<'d> { +impl embedded_hal::i2c::ErrorType for I2cDriver<'_> { type Error = I2cError; } -impl<'d> embedded_hal::i2c::I2c for I2cDriver<'d> { +impl embedded_hal::i2c::I2c for I2cDriver<'_> { fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { I2cDriver::read(self, addr, buffer, BLOCK).map_err(to_i2c_err) } @@ -352,25 +434,6 @@ impl<'d> embedded_hal::i2c::I2c for I2cDrive I2cDriver::write_read(self, addr, bytes, buffer, BLOCK).map_err(to_i2c_err) } - fn write_iter(&mut self, _address: u8, _bytes: B) -> Result<(), Self::Error> - where - B: IntoIterator, - { - todo!() - } - - fn write_iter_read( - &mut self, - _address: u8, - _bytes: B, - _buffer: &mut [u8], - ) -> Result<(), Self::Error> - where - B: IntoIterator, - { - todo!() - } - fn transaction( &mut self, address: u8, @@ -378,13 +441,6 @@ impl<'d> embedded_hal::i2c::I2c for I2cDrive ) -> Result<(), Self::Error> { I2cDriver::transaction(self, address, operations, BLOCK).map_err(to_i2c_err) } - - fn transaction_iter<'a, O>(&mut self, _address: u8, _operations: O) -> Result<(), Self::Error> - where - O: IntoIterator>, - { - todo!() - } } fn to_i2c_err(err: EspError) -> I2cError { @@ -395,13 +451,16 @@ fn to_i2c_err(err: EspError) -> I2cError { } } +#[cfg(not(esp32c2))] pub struct I2cSlaveDriver<'d> { i2c: u8, _p: PhantomData<&'d mut ()>, } -unsafe impl<'d> Send for I2cSlaveDriver<'d> {} +#[cfg(not(esp32c2))] +unsafe impl Send for I2cSlaveDriver<'_> {} +#[cfg(not(esp32c2))] impl<'d> I2cSlaveDriver<'d> { pub fn new( _i2c: impl Peripheral

+ 'd, @@ -412,7 +471,6 @@ impl<'d> I2cSlaveDriver<'d> { ) -> Result { crate::into_ref!(sda, scl); - #[cfg(not(esp_idf_version = "4.3"))] let sys_config = i2c_config_t { mode: i2c_mode_t_I2C_MODE_SLAVE, sda_io_num: sda.pin(), @@ -429,22 +487,6 @@ impl<'d> I2cSlaveDriver<'d> { ..Default::default() }; - #[cfg(esp_idf_version = "4.3")] - let sys_config = i2c_config_t { - mode: i2c_mode_t_I2C_MODE_SLAVE, - sda_io_num: pins.sda.pin(), - sda_pullup_en: config.sda_pullup_enabled, - scl_io_num: pins.scl.pin(), - scl_pullup_en: config.scl_pullup_enabled, - __bindgen_anon_1: i2c_config_t__bindgen_ty_1 { - slave: i2c_config_t__bindgen_ty_1__bindgen_ty_2 { - slave_addr: slave_addr as u16, - addr_10bit_en: 0, // For now; to become configurable with embedded-hal V1.0 - }, - }, - ..Default::default() - }; - esp!(unsafe { i2c_param_config(I2C::port(), &sys_config) })?; esp!(unsafe { @@ -453,7 +495,7 @@ impl<'d> I2cSlaveDriver<'d> { i2c_mode_t_I2C_MODE_SLAVE, config.rx_buf_len, config.tx_buf_len, - 0, // TODO: set flags + InterruptType::to_native(config.intr_flags) as _, ) })?; @@ -471,7 +513,7 @@ impl<'d> I2cSlaveDriver<'d> { if n > 0 { Ok(n as usize) } else { - Err(EspError::from(ESP_ERR_TIMEOUT).unwrap()) + Err(EspError::from_infallible::()) } } @@ -483,7 +525,7 @@ impl<'d> I2cSlaveDriver<'d> { if n > 0 { Ok(n as usize) } else { - Err(EspError::from(ESP_ERR_TIMEOUT).unwrap()) + Err(EspError::from_infallible::()) } } @@ -492,7 +534,8 @@ impl<'d> I2cSlaveDriver<'d> { } } -impl<'d> Drop for I2cSlaveDriver<'d> { +#[cfg(not(esp32c2))] +impl Drop for I2cSlaveDriver<'_> { fn drop(&mut self) { esp!(unsafe { i2c_driver_delete(self.port()) }).unwrap(); } @@ -513,7 +556,7 @@ impl<'buffers> CommandLink<'buffers> { let handle = unsafe { i2c_cmd_link_create() }; if handle.is_null() { - return Err(EspError::from(ESP_ERR_NO_MEM).unwrap()); + return Err(EspError::from_infallible::()); } Ok(CommandLink(handle, PhantomData)) @@ -540,7 +583,7 @@ impl<'buffers> CommandLink<'buffers> { } } -impl<'buffers> Drop for CommandLink<'buffers> { +impl Drop for CommandLink<'_> { fn drop(&mut self) { unsafe { i2c_cmd_link_delete(self.0); @@ -562,5 +605,5 @@ macro_rules! impl_i2c { } impl_i2c!(I2C0: 0); -#[cfg(not(esp32c3))] +#[cfg(not(any(esp32c3, esp32c2, esp32c6)))] impl_i2c!(I2C1: 1); diff --git a/src/i2s.rs b/src/i2s.rs new file mode 100644 index 00000000000..312fecaf94b --- /dev/null +++ b/src/i2s.rs @@ -0,0 +1,1382 @@ +//! Driver for the Inter-IC Sound (I2S) peripheral(s). + +use core::ffi::c_void; +use core::marker::PhantomData; +use core::mem::MaybeUninit; +use core::ops::{Deref, DerefMut}; +use core::ptr::NonNull; + +use esp_idf_sys::{esp, i2s_port_t, EspError, TickType_t}; + +#[cfg(not(esp_idf_version_major = "4"))] +use { + core::ptr::null_mut, + esp_idf_sys::{ + i2s_chan_config_t, i2s_chan_handle_t, i2s_channel_disable, i2s_channel_enable, + i2s_channel_read, i2s_channel_register_event_callback, i2s_channel_write, i2s_del_channel, + i2s_event_callbacks_t, i2s_event_data_t, i2s_new_channel, + }, +}; + +#[cfg(esp_idf_version_major = "4")] +use esp_idf_sys::{ + i2s_config_t, i2s_driver_install, i2s_driver_uninstall, i2s_read, i2s_start, i2s_stop, + i2s_write, +}; + +#[cfg(not(esp_idf_version_major = "4"))] +use crate::interrupt::asynch::HalIsrNotification; +use crate::{delay, io::EspIOError}; + +// For v5+, we rely configuration options for PDM/TDM support. +// For v4, we have to examine the chip type. +#[cfg(any( + all( + not(esp_idf_version_major = "4"), + any(esp_idf_soc_i2s_supports_pdm_rx, esp_idf_soc_i2s_supports_pdm_tx) + ), + all(esp_idf_version_major = "4", any(esp32, esp32s3, esp32c3, esp32c6)) +))] +mod pdm; + +mod std; + +#[cfg(any( + all(not(esp_idf_version_major = "4"), esp_idf_soc_i2s_supports_tdm), + all(esp_idf_version_major = "4", any(esp32s3, esp32c3, esp32c6)) +))] +mod tdm; + +/// I2S channel base configuration. +pub type I2sConfig = config::Config; + +/// I2S configuration +pub mod config { + #[cfg(any( + all( + not(esp_idf_version_major = "4"), + any(esp_idf_soc_i2s_supports_pdm_rx, esp_idf_soc_i2s_supports_pdm_tx) + ), + all(esp_idf_version_major = "4", any(esp32, esp32s3, esp32c3, esp32c6)) + ))] + pub use super::pdm::config::*; + + pub use super::std::config::*; + + #[cfg(any( + all(not(esp_idf_version_major = "4"), esp_idf_soc_i2s_supports_tdm), + all(esp_idf_version_major = "4", any(esp32s3, esp32c3, esp32c6)) + ))] + pub use super::tdm::config::*; + + use esp_idf_sys::{ + i2s_mclk_multiple_t, i2s_mclk_multiple_t_I2S_MCLK_MULTIPLE_128, + i2s_mclk_multiple_t_I2S_MCLK_MULTIPLE_256, i2s_mclk_multiple_t_I2S_MCLK_MULTIPLE_384, + EspError, ESP_ERR_INVALID_ARG, + }; + + #[cfg(not(esp_idf_version_major = "4"))] + use esp_idf_sys::{ + i2s_chan_config_t, i2s_clock_src_t, i2s_data_bit_width_t, + i2s_mclk_multiple_t_I2S_MCLK_MULTIPLE_512, i2s_port_t, i2s_role_t, i2s_slot_bit_width_t, + i2s_slot_mode_t, + }; + + #[cfg(esp_idf_version_major = "4")] + use esp_idf_sys::{ + i2s_bits_per_chan_t, i2s_bits_per_sample_t, i2s_mode_t, i2s_mode_t_I2S_MODE_MASTER, + i2s_mode_t_I2S_MODE_SLAVE, + }; + + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + )))] // ESP-IDF 5.3 and later + use esp_idf_sys::i2s_chan_config_t__bindgen_ty_1; // introduces union type over auto_clear + + /// The default number of DMA buffers to use. + pub const DEFAULT_DMA_BUFFER_COUNT: u32 = 6; + + /// The default number of frames per DMA buffer. + pub const DEFAULT_FRAMES_PER_DMA_BUFFER: u32 = 240; + + /// I2S clock source. + #[derive(Clone, Copy, Debug, Eq, PartialEq, Default)] + pub enum ClockSource { + /// Use PLL_F160M as the source clock + #[cfg(not(any(esp32h2, esp32c2)))] + #[default] + Pll160M, + + /// Use PLL_F60M as the source clock + #[cfg(esp32c2)] + #[default] + Pll60M, + + /// Use PLL_F64M as the source clock + #[cfg(esp32h2)] + #[default] + Pll64M, + + /// Use APLL as the source clock + #[cfg(any(esp32, esp32s2))] + Apll, + } + + impl ClockSource { + #[cfg(not(esp_idf_version_major = "4"))] + #[allow(clippy::unnecessary_cast)] + pub(super) fn as_sdk(&self) -> i2s_clock_src_t { + match self { + #[cfg(not(any(esp32h2, esp32c2)))] + Self::Pll160M => core::convert::TryInto::try_into( + esp_idf_sys::soc_module_clk_t_SOC_MOD_CLK_PLL_F160M, + ) + .unwrap(), + #[cfg(esp32c2)] + Self::Pll60M => core::convert::TryInto::try_into( + esp_idf_sys::soc_module_clk_t_SOC_MOD_CLK_PLL_F60M, + ) + .unwrap(), + #[cfg(esp32h2)] + Self::Pll64M => core::convert::TryInto::try_into( + esp_idf_sys::soc_module_clk_t_SOC_MOD_CLK_PLL_F64M, + ) + .unwrap(), + #[cfg(any(esp32, esp32s2))] + Self::Apll => esp_idf_sys::soc_module_clk_t_SOC_MOD_CLK_APLL, + } + } + } + + /// I2S common channel configuration. + /// + /// To create a custom configuration, use the builder pattern built-in to this struct. For example: + /// ``` + /// use esp_idf_hal::i2s::config::{ChannelOpen, Config, Role}; + /// let config = Config::default().role(Role::Target).channels(ChannelOpen::Rx); + /// ``` + /// + /// The default configuration is: + /// * [`role`][Config::role]: [`Role::Controller`] (master) + /// * [`dma_buffer_count`][Config::dma_buffer_count]: 6 ([`DEFAULT_DMA_BUFFER_COUNT`]) + /// * [`frames_per_buffer`][Config::frames_per_buffer]: 240 ([`DEFAULT_FRAMES_PER_DMA_BUFFER`]) + /// * [`auto_clear`][Config::auto_clear]: `false` + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct Config { + /// The role of this channel: controller (master) or target (slave) + pub(super) role: Role, + + /// The number of DMA buffers number to use. + pub(super) dma_buffer_count: u32, + + /// The number of I2S frames in one DMA buffer. + pub(super) frames_per_buffer: u32, + + /// If true, the transmit buffer will be automatically cleared upon sending. + pub(super) auto_clear: bool, + } + + impl Default for Config { + #[inline(always)] + fn default() -> Self { + Self::new() + } + } + + impl Config { + #[inline(always)] + /// Create a new Config with the default settings. + pub const fn new() -> Self { + Self { + role: Role::Controller, + dma_buffer_count: DEFAULT_DMA_BUFFER_COUNT, + frames_per_buffer: DEFAULT_FRAMES_PER_DMA_BUFFER, + auto_clear: false, + } + } + + /// Set the role of this channel: controller (master) or target (slave). + #[must_use] + #[inline(always)] + pub fn role(mut self, role: Role) -> Self { + self.role = role; + self + } + + /// Set the number of DMA buffers to use. + #[must_use] + #[inline(always)] + pub fn dma_buffer_count(mut self, dma_buffer_count: u32) -> Self { + self.dma_buffer_count = dma_buffer_count; + self + } + + /// Set the number of I2S frames in one DMA buffer. + #[must_use] + #[inline(always)] + pub fn frames_per_buffer(mut self, frames: u32) -> Self { + self.frames_per_buffer = frames; + self + } + + /// Set if the transmit buffer will be automatically cleared upon sending. + #[must_use] + #[inline(always)] + pub fn auto_clear(mut self, auto_clear: bool) -> Self { + self.auto_clear = auto_clear; + self + } + + /// Convert to the ESP-IDF SDK `i2s_chan_config_t` representation. + #[allow(clippy::needless_update)] + #[cfg(not(esp_idf_version_major = "4"))] + #[inline(always)] + pub(super) fn as_sdk(&self, id: i2s_port_t) -> i2s_chan_config_t { + i2s_chan_config_t { + id, + role: self.role.as_sdk(), + dma_desc_num: self.dma_buffer_count, + dma_frame_num: self.frames_per_buffer, + #[cfg(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + ))] + auto_clear: self.auto_clear, + // ESP-IDF 5.3 introduced auto_clear for before_cb & after_cb + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + )))] // For ESP-IDF v5.3 and later + __bindgen_anon_1: i2s_chan_config_t__bindgen_ty_1{ + auto_clear_after_cb: self.auto_clear, + }, + ..Default::default() + } + } + } + + /// Available data bit width in one slot. + #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] + pub enum DataBitWidth { + /// Channel data bit width is 8 bits. + Bits8, + + /// Channel data bit width is 16 bits. + Bits16, + + /// Channel data bit width is 24 bits. + Bits24, + + /// Channel data bit width is 32 bits. + Bits32, + } + + impl From for u32 { + #[inline(always)] + fn from(value: DataBitWidth) -> Self { + match value { + DataBitWidth::Bits8 => 8, + DataBitWidth::Bits16 => 16, + DataBitWidth::Bits24 => 24, + DataBitWidth::Bits32 => 32, + } + } + } + + impl DataBitWidth { + /// Convert to the ESP-IDF SDK `i2s_data_bit_width_t` representation. + #[cfg(not(esp_idf_version_major = "4"))] + #[inline(always)] + pub(super) fn as_sdk(&self) -> i2s_data_bit_width_t { + match self { + Self::Bits8 => 8, + Self::Bits16 => 16, + Self::Bits24 => 24, + Self::Bits32 => 32, + } + } + + /// Convert to the ESP-IDF SDK `i2s_bits_per_sample_t` representation. + #[cfg(esp_idf_version_major = "4")] + #[inline(always)] + pub(super) fn as_sdk(&self) -> i2s_bits_per_sample_t { + match self { + Self::Bits8 => 8, + Self::Bits16 => 16, + Self::Bits24 => 24, + Self::Bits32 => 32, + } + } + } + + impl TryFrom for DataBitWidth { + type Error = EspError; + + fn try_from(value: usize) -> Result { + match value { + 8 => Ok(Self::Bits8), + 16 => Ok(Self::Bits16), + 24 => Ok(Self::Bits24), + 32 => Ok(Self::Bits32), + _ => Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()), + } + } + } + + /// The multiple of MCLK to the sample rate. + #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] + pub enum MclkMultiple { + /// MCLK = sample rate * 128 + M128, + + /// MCLK = sample rate * 256 + M256, + + /// MCLK = sample rate * 384 + M384, + + /// MCLK = sample rate * 512 + #[cfg(not(esp_idf_version_major = "4"))] + M512, + } + + impl MclkMultiple { + /// Convert to the ESP-IDF SDK `i2s_mclk_multiple_t` representation. + #[inline(always)] + pub(super) fn as_sdk(&self) -> i2s_mclk_multiple_t { + match self { + Self::M128 => i2s_mclk_multiple_t_I2S_MCLK_MULTIPLE_128, + Self::M256 => i2s_mclk_multiple_t_I2S_MCLK_MULTIPLE_256, + Self::M384 => i2s_mclk_multiple_t_I2S_MCLK_MULTIPLE_384, + #[cfg(not(esp_idf_version_major = "4"))] + Self::M512 => i2s_mclk_multiple_t_I2S_MCLK_MULTIPLE_512, + } + } + } + + impl From for u32 { + #[inline(always)] + fn from(mclk_multiple: MclkMultiple) -> Self { + match mclk_multiple { + MclkMultiple::M128 => 128, + MclkMultiple::M256 => 256, + MclkMultiple::M384 => 384, + #[cfg(not(esp_idf_version_major = "4"))] + MclkMultiple::M512 => 512, + } + } + } + + /// I2S channel operating role + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + pub enum Role { + /// Controller (master) + #[default] + Controller, + + /// Target (slave) + Target, + } + + /// I2S peripheral in controller (master) role, bclk and ws signal will be set to output. + #[cfg(not(esp_idf_version_major = "4"))] + const I2S_ROLE_CONTROLLER: i2s_role_t = 0; + + /// I2S peripheral in target (slave) role, bclk and ws signal will be set to input. + #[cfg(not(esp_idf_version_major = "4"))] + const I2S_ROLE_TARGET: i2s_role_t = 1; + + impl Role { + /// Convert to the ESP-IDF SDK `i2s_role_t` representation. + #[cfg(not(esp_idf_version_major = "4"))] + #[inline(always)] + pub(super) fn as_sdk(&self) -> i2s_role_t { + match self { + Self::Controller => I2S_ROLE_CONTROLLER, + Self::Target => I2S_ROLE_TARGET, + } + } + + /// Convert to the ESP-IDF SDK `i2s_mode_t` representation. + #[cfg(esp_idf_version_major = "4")] + #[inline(always)] + pub(super) fn as_sdk(&self) -> i2s_mode_t { + match self { + Self::Controller => i2s_mode_t_I2S_MODE_MASTER, + Self::Target => i2s_mode_t_I2S_MODE_SLAVE, + } + } + } + + /// The total slot bit width in one slot. + /// + /// This is not necessarily the number of data bits in one slot. A slot may have additional bits padded to fill out + /// the slot. + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + pub enum SlotBitWidth { + /// Slot bit width is automatically set to the data bit width. + #[default] + Auto, + + /// Slot bit width is 8 bits. + Bits8, + + /// Slot bit width is 16 bits. + Bits16, + + /// Slot bit width is 24 bits. + Bits24, + + /// Slot bit width is 32 bits. + Bits32, + } + + #[cfg(not(esp_idf_version_major = "4"))] + type SlotBitWidthSdkType = i2s_slot_bit_width_t; + + #[cfg(esp_idf_version_major = "4")] + type SlotBitWidthSdkType = i2s_bits_per_chan_t; + + impl SlotBitWidth { + /// Convert this to the ESP-IDF SDK `i2s_slot_bit_width_t`/`i2s_bits_per_chan_t` representation. + #[inline(always)] + pub(super) fn as_sdk(&self) -> SlotBitWidthSdkType { + match self { + Self::Auto => 0, + Self::Bits8 => 8, + Self::Bits16 => 16, + Self::Bits24 => 24, + Self::Bits32 => 32, + } + } + } + + impl TryFrom for SlotBitWidth { + type Error = EspError; + + fn try_from(value: u32) -> Result { + match value { + 0 => Ok(Self::Auto), + 8 => Ok(Self::Bits8), + 16 => Ok(Self::Bits16), + 24 => Ok(Self::Bits24), + 32 => Ok(Self::Bits32), + _ => Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()), + } + } + } + + /// I2S channel slot mode. + /// + /// See the documentation for the mode of operation to see how this affects the data layout: + /// * [PDM Rx][PdmRxSlotConfig] + /// * [PDM Tx][PdmTxSlotConfig] + /// * [Standard Rx/Tx][StdSlotConfig] + /// * [TDM Rx/Tx][TdmSlotConfig] + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + pub enum SlotMode { + /// Mono mode: + /// * When transmitting, transmit the same data in all slots. + /// * When receiving, only receive data from the first slot. + Mono, + + /// Stereo mode: + /// * When transmitting, transmit different data in each slot. + /// * When receiving, receive data from all slots. + #[default] + Stereo, + } + + impl SlotMode { + /// Convert this to the ESP-IDF SDK `i2s_slot_mode_t` representation. + #[cfg(not(esp_idf_version_major = "4"))] + #[inline(always)] + pub(super) fn as_sdk(&self) -> i2s_slot_mode_t { + match self { + Self::Mono => 1, + Self::Stereo => 2, + } + } + } +} + +/// Trait implemented by I2S peripherals to obtain their port number. +pub trait I2s: Send + sealed::Sealed { + /// Return the port number for the peripheral. + fn port() -> i2s_port_t; +} + +mod sealed { + pub trait Sealed {} + + impl Sealed for super::I2S0 {} + #[cfg(any(esp32, esp32s3))] + impl Sealed for super::I2S1 {} +} + +pub trait I2sPort { + /// Returns the I2S port number of this driver. + fn port(&self) -> i2s_port_t; +} + +/// Marker trait indicating that a driver supports receiving data via the [`I2sRx`] trait. +pub trait I2sRxSupported {} + +/// Concrete implementation of [`I2sRxSupported`] for use in clients. +/// +/// Example usage: +/// ``` +/// use esp_idf_hal::i2s::{config::{StdConfig, DataBitWidth}, gpio::*}; +/// let std_config = StdConfig::philips(48000, DataBitWidth::Bits16); +/// let peripherals = Peripherals::take().unwrap(); +/// let bclk = peripherals.pins.gpio1; +/// let din = peripherals.pins.gpio4; +/// let mclk = AnyIOPin::none(); +/// let ws = peripherals.pins.gpio2; +/// let i2s = I2sDriver::::new_std_rx(peripherals.i2s0, &std_config, bclk, din, mclk, ws).unwrap(); +/// ``` +pub struct I2sRx {} +impl I2sRxSupported for I2sRx {} + +/// Marker trait indicating that a driver supports transmitting data via the [`I2sTx`] trait. +pub trait I2sTxSupported {} + +/// Concrete implementation of [`I2sTxSupported`] for use in clients. +/// +/// Example usage: +/// ``` +/// use esp_idf_hal::i2s::{config::{StdConfig, DataBitWidth}, gpio::*}; +/// let std_config = StdConfig::philips(48000, DataBitWidth::Bits16); +/// let peripherals = Peripherals::take().unwrap(); +/// let bclk = peripherals.pins.gpio1; +/// let dout = peripherals.pins.gpio6; +/// let mclk = AnyIOPin::none(); +/// let ws = peripherals.pins.gpio2; +/// let i2s = I2sDriver::::new_std_tx(peripherals.i2s0, &std_config, bclk, dout, mclk, ws).unwrap(); +/// ``` +pub struct I2sTx {} +impl I2sTxSupported for I2sTx {} + +/// Concrete implementation of both [`I2sRxSupported`] and [`I2sTxSupported`] for use in clients. +/// +/// Example usage: +/// ``` +/// use esp_idf_hal::i2s::{config::{StdConfig, DataBitWidth}, gpio::*, peripherals::Peripherals}; +/// let std_config = StdConfig::philips(48000, DataBitWidth::Bits16); +/// let peripherals = Peripherals::take().unwrap(); +/// let bclk = peripherals.pins.gpio1; +/// let din = peripherals.pins.gpio4; +/// let dout = peripherals.pins.gpio6; +/// let mclk = AnyIOPin::none(); +/// let ws = peripherals.pins.gpio2; +/// let i2s = I2sDriver::::new_std_bidir(peripherals.i2s0, &std_config, bclk, din, dout, mclk, ws).unwrap(); +/// ``` +pub struct I2sBiDir {} +impl I2sRxSupported for I2sBiDir {} +impl I2sTxSupported for I2sBiDir {} + +/// Reference for I2S driver +pub struct I2sDriverRef<'d, Dir>(NonNull>); + +impl<'d, Dir> Deref for I2sDriverRef<'d, Dir> { + type Target = I2sDriver<'d, Dir>; + + fn deref(&self) -> &Self::Target { + unsafe { self.0.as_ref() } + } +} + +impl

DerefMut for I2sDriverRef<'_, Dir> { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { self.0.as_mut() } + } +} + +/// Inter-IC Sound (I2S) driver. +pub struct I2sDriver<'d, Dir> { + /// The Rx channel, possibly null. + #[cfg(not(esp_idf_version_major = "4"))] + rx_handle: i2s_chan_handle_t, + + /// The Tx channel, possibly null. + #[cfg(not(esp_idf_version_major = "4"))] + tx_handle: i2s_chan_handle_t, + + /// The I2S peripheral number. Either 0 (all devices) or 1 (ESP32 and ESP32-S3 only). + port: u8, + + /// Driver lifetime -- mimics the lifetime of the peripheral. + _p: PhantomData<&'d ()>, + + /// Directionality -- mimics the directionality of the peripheral. + _dir: PhantomData, +} + +impl I2sDriver<'_, Dir> { + /// Create a new standard mode driver for the given I2S peripheral with both the receive and transmit channels open. + #[cfg(not(esp_idf_version_major = "4"))] + fn internal_new( + config: &i2s_chan_config_t, + rx: bool, + tx: bool, + ) -> Result { + let port = I2S::port(); + + let mut rx_handle: i2s_chan_handle_t = null_mut(); + let mut tx_handle: i2s_chan_handle_t = null_mut(); + + unsafe { + esp!(i2s_new_channel( + config, + if tx { + &mut tx_handle as _ + } else { + core::ptr::null_mut() + }, + if rx { + &mut rx_handle as _ + } else { + core::ptr::null_mut() + }, + ))? + }; + + let mut this = Self { + port: port as u8, + rx_handle, + tx_handle, + _p: PhantomData, + _dir: PhantomData, + }; + + this.subscribe_channel(this.rx_handle)?; + this.subscribe_channel(this.tx_handle)?; + + Ok(this) + } + + #[cfg(esp_idf_version_major = "4")] + #[allow(clippy::too_many_arguments)] + pub fn internal_new(config: &i2s_config_t) -> Result { + let port = I2S::port(); + + unsafe { + esp!(i2s_driver_install(port, config, 0, core::ptr::null_mut()))?; + } + + Ok(Self { + port: port as u8, + _p: PhantomData, + _dir: PhantomData, + }) + } + + #[cfg(not(esp_idf_version_major = "4"))] + fn subscribe_channel(&mut self, handle: i2s_chan_handle_t) -> Result<(), EspError> { + if !handle.is_null() { + let callbacks = i2s_event_callbacks_t { + on_recv: Some(dispatch_recv), + on_recv_q_ovf: Some(dispatch_recv), + on_sent: Some(dispatch_send), + on_send_q_ovf: Some(dispatch_send), + }; + + // Safety: chan_handle is a valid pointer to an i2s_chan_handle_t and callbacks is initialized. + esp!(unsafe { + i2s_channel_register_event_callback( + handle, + &callbacks, + self.port as u32 as *mut core::ffi::c_void, + ) + })?; + } + + Ok(()) + } + + #[cfg(not(esp_idf_version_major = "4"))] + fn unsubscribe_channel(&mut self, handle: i2s_chan_handle_t) -> Result<(), EspError> { + if !handle.is_null() { + let callbacks = i2s_event_callbacks_t { + on_recv: None, + on_recv_q_ovf: None, + on_sent: None, + on_send_q_ovf: None, + }; + + // Safety: chan_handle is a valid pointer to an i2s_chan_handle_t and callbacks is initialized. + esp!(unsafe { + i2s_channel_register_event_callback( + handle, + &callbacks, + self.port as u32 as *mut core::ffi::c_void, + ) + })?; + } + + Ok(()) + } + + #[cfg(not(esp_idf_version_major = "4"))] + fn del_channel(&mut self, handle: i2s_chan_handle_t) -> Result<(), EspError> { + if !handle.is_null() { + let callbacks = i2s_event_callbacks_t { + on_recv: None, + on_recv_q_ovf: None, + on_sent: None, + on_send_q_ovf: None, + }; + + // Safety: chan_handle is a valid pointer to an i2s_chan_handle_t and callbacks is initialized. + esp!(unsafe { + i2s_channel_register_event_callback(handle, &callbacks, core::ptr::null_mut()) + })?; + + // Safety: chan_handle is a valid, non-null i2s_chan_handle_t. + esp!(unsafe { i2s_del_channel(handle) })?; + } + + Ok(()) + } + + fn remap_result( + result: Result<(), EspError>, + bytes_processed: usize, + ) -> Result { + match result { + Ok(_) => Ok(bytes_processed), + Err(err) if err.code() == esp_idf_sys::ESP_ERR_TIMEOUT && bytes_processed > 0 => { + Ok(bytes_processed) + } + Err(err) => Err(err), + } + } + + /// Borrow the I2S driver by a reference + pub fn as_ref(&mut self) -> I2sDriverRef { + I2sDriverRef(unsafe { NonNull::new_unchecked(self) }) + } +} + +/// Functions for receive channels. +impl I2sDriver<'_, Dir> +where + Dir: I2sRxSupported, +{ + /// Enable the I2S receive channel. + /// + /// # Note + /// This can only be called when the channel is in the `READY` state: initialized but not yet started from a driver + /// constructor, or disabled from the `RUNNING` state via [`rx_disable()`][I2sDriver::rx_disable]. The channel + /// will enter the `RUNNING` state if it is enabled successfully. + /// + /// Enabling the channel will start I2S communications on the hardware. BCLK and WS signals will be generated if + /// this is a controller. MCLK will be generated once initialization is finished. + /// + /// # Errors + /// This will return an [`EspError`] with `ESP_ERR_INVALID_STATE` if the channel is not in the `READY` state. + #[cfg(esp_idf_version_major = "4")] + pub fn rx_enable(&mut self) -> Result<(), EspError> { + unsafe { esp!(i2s_start(self.port as _)) } + } + + /// Enable the I2S receive channel. + /// + /// # Note + /// This can only be called when the channel is in the `READY` state: initialized but not yet started from a driver + /// constructor, or disabled from the `RUNNING` state via [`rx_enable()`][I2sRxChannel::rx_disable]. The channel + /// will enter the `RUNNING` state if it is enabled successfully. + /// + /// Enabling the channel will start I2S communications on the hardware. BCLK and WS signals will be generated if + /// this is a controller. MCLK will be generated once initialization is finished. + /// + /// # Errors + /// This will return an [`EspError`] with `ESP_ERR_INVALID_STATE` if the channel is not in the `READY` state. + #[cfg(not(esp_idf_version_major = "4"))] + pub fn rx_enable(&mut self) -> Result<(), EspError> { + unsafe { esp!(i2s_channel_enable(self.rx_handle)) } + } + + /// Disable the I2S receive channel. + /// + /// # Note + /// This can only be called when the channel is in the `RUNNING` state: the channel has been previously enabled + /// via a call to [`rx_enable()`][I2sRxChannel::rx_enable]. The channel will enter the `READY` state if it is + /// disabled successfully. + /// + /// Disabling the channel will stop I2S communications on the hardware. BCLK and WS signals will stop being + /// generated if this is a controller. MCLK will continue to be generated. + /// + /// # Errors + /// This will return an [`EspError`] with `ESP_ERR_INVALID_STATE` if the channel is not in the `RUNNING` state. + #[cfg(esp_idf_version_major = "4")] + pub fn rx_disable(&mut self) -> Result<(), EspError> { + unsafe { esp!(i2s_stop(self.port as _)) } + } + + /// Disable the I2S receive channel. + /// + /// # Note + /// This can only be called when the channel is in the `RUNNING` state: the channel has been previously enabled + /// via a call to [`rx_enable()`][I2sRxChannel::rx_enable]. The channel will enter the `READY` state if it is + /// disabled successfully. + /// + /// Disabling the channel will stop I2S communications on the hardware. BCLK and WS signals will stop being + /// generated if this is a controller. MCLK will continue to be generated. + /// + /// # Errors + /// This will return an [`EspError`] with `ESP_ERR_INVALID_STATE` if the channel is not in the `RUNNING` state. + #[cfg(not(esp_idf_version_major = "4"))] + pub fn rx_disable(&mut self) -> Result<(), EspError> { + unsafe { esp!(i2s_channel_disable(self.rx_handle)) } + } + + /// Read data from the channel asynchronously. + /// + /// This may be called only when the channel is in the `RUNNING` state. + /// + /// # Returns + /// This returns the number of bytes read, or an [`EspError`] if an error occurred. + #[cfg(not(esp_idf_version_major = "4"))] + pub async fn read_async(&mut self, buffer: &mut [u8]) -> Result { + loop { + match self.read(buffer, crate::delay::NON_BLOCK) { + Err(err) if err.code() == esp_idf_sys::ESP_ERR_TIMEOUT => { + RECV_NOTIFIER[self.port as usize].wait().await; + } + other => break other, + } + } + } + + /// Read data from the channel. + /// + /// This may be called only when the channel is in the `RUNNING` state. + /// + /// # Returns + /// This returns the number of bytes read, or an [`EspError`] if an error occurred. + #[cfg(esp_idf_version_major = "4")] + pub fn read(&mut self, buffer: &mut [u8], timeout: TickType_t) -> Result { + if buffer.is_empty() { + Ok(0) + } else { + let mut bytes_read: usize = 0; + + Self::remap_result( + unsafe { + esp!(i2s_read( + self.port as _, + buffer.as_mut_ptr() as *mut c_void, + buffer.len(), + &mut bytes_read, + crate::delay::TickType(timeout).as_millis_u32(), + )) + }, + bytes_read, + ) + } + } + + /// Read data from the channel. + /// + /// This may be called only when the channel is in the `RUNNING` state. + /// + /// # Returns + /// This returns the number of bytes read, or an [`EspError`] if an error occurred. + #[cfg(not(esp_idf_version_major = "4"))] + pub fn read(&mut self, buffer: &mut [u8], timeout: TickType_t) -> Result { + if buffer.is_empty() { + Ok(0) + } else { + let mut bytes_read: usize = 0; + + Self::remap_result( + unsafe { + esp!(i2s_channel_read( + self.rx_handle, + buffer.as_mut_ptr() as *mut c_void, + buffer.len(), + &mut bytes_read, + crate::delay::TickType(timeout).as_millis_u32(), + )) + }, + bytes_read, + ) + } + } + + /// Read data from the channel into an uninitalized buffer asynchronously. + /// + /// This may be called only when the channel is in the `RUNNING` state. + /// + /// # Returns + /// This returns the number of bytes read, or an [EspError] if an error occurred. + /// + /// # Safety + /// Upon a successful return with `Ok(n_read)`, `buffer[..n_read]` will be initialized. + #[cfg(not(esp_idf_version_major = "4"))] + pub async fn read_uninit_async( + &mut self, + buffer: &mut [MaybeUninit], + ) -> Result { + loop { + match self.read_uninit(buffer, crate::delay::NON_BLOCK) { + Err(err) if err.code() == esp_idf_sys::ESP_ERR_TIMEOUT => { + RECV_NOTIFIER[self.port as usize].wait().await; + } + other => break other, + } + } + } + + /// Read data from the channel into an uninitalized buffer. + /// + /// This may be called only when the channel is in the `RUNNING` state. + /// + /// # Returns + /// This returns the number of bytes read, or an [`EspError`] if an error occurred. + /// + /// # Safety + /// Upon a successful return with `Ok(n_read)`, `buffer[..n_read]` will be initialized. + #[cfg(esp_idf_version_major = "4")] + pub fn read_uninit( + &mut self, + buffer: &mut [MaybeUninit], + timeout: TickType_t, + ) -> Result { + if buffer.is_empty() { + Ok(0) + } else { + let mut bytes_read: usize = 0; + + Self::remap_result( + unsafe { + esp!(i2s_read( + self.port as _, + buffer.as_mut_ptr() as *mut c_void, + buffer.len(), + &mut bytes_read, + crate::delay::TickType(timeout).as_millis_u32(), + )) + }, + bytes_read, + ) + } + } + + /// Read data from the channel into an uninitalized buffer. + /// + /// This may be called only when the channel is in the `RUNNING` state. + /// + /// # Returns + /// This returns the number of bytes read, or an [`EspError`] if an error occurred. + /// + /// # Safety + /// Upon a successful return with `Ok(n_read)`, `buffer[..n_read]` will be initialized. + #[cfg(not(esp_idf_version_major = "4"))] + pub fn read_uninit( + &mut self, + buffer: &mut [MaybeUninit], + timeout: TickType_t, + ) -> Result { + if buffer.is_empty() { + Ok(0) + } else { + let mut bytes_read: usize = 0; + + Self::remap_result( + unsafe { + esp!(i2s_channel_read( + self.rx_handle, + buffer.as_mut_ptr() as *mut c_void, + buffer.len(), + &mut bytes_read, + crate::delay::TickType(timeout).as_millis_u32(), + )) + }, + bytes_read, + ) + } + } +} + +/// Functions for transmit channels. +impl I2sDriver<'_, Dir> +where + Dir: I2sTxSupported, +{ + /// Enable the I2S transmit channel. + /// + /// # Note + /// This can only be called when the channel is in the `READY` state: initialized but not yet started from a driver + /// constructor, or disabled from the `RUNNING` state via [`tx_disable()`][I2sTxChannel::tx_disable]. The channel + /// will enter the `RUNNING` state if it is enabled successfully. + /// + /// Enabling the channel will start I2S communications on the hardware. BCLK and WS signals will be generated if + /// this is a controller. MCLK will be generated once initialization is finished. + /// + /// # Errors + /// This will return an [`EspError`] with `ESP_ERR_INVALID_STATE` if the channel is not in the `READY` state. + #[cfg(esp_idf_version_major = "4")] + pub fn tx_enable(&mut self) -> Result<(), EspError> { + unsafe { esp!(i2s_start(self.port as _)) } + } + + /// Enable the I2S transmit channel. + /// + /// # Note + /// This can only be called when the channel is in the `READY` state: initialized but not yet started from a driver + /// constructor, or disabled from the `RUNNING` state via [`tx_disable()`][I2sTxChannel::tx_disable]. The channel + /// will enter the `RUNNING` state if it is enabled successfully. + /// + /// Enabling the channel will start I2S communications on the hardware. BCLK and WS signals will be generated if + /// this is a controller. MCLK will be generated once initialization is finished. + /// + /// # Errors + /// This will return an [`EspError`] with `ESP_ERR_INVALID_STATE` if the channel is not in the `READY` state. + #[cfg(not(esp_idf_version_major = "4"))] + pub fn tx_enable(&mut self) -> Result<(), EspError> { + unsafe { esp!(i2s_channel_enable(self.tx_handle)) } + } + + /// Disable the I2S transmit channel. + /// + /// # Note + /// This can only be called when the channel is in the `RUNNING` state: the channel has been previously enabled + /// via a call to [`tx_enable()`][I2sTxChannel::tx_enable]. The channel will enter the `READY` state if it is + /// disabled successfully. + /// + /// Disabling the channel will stop I2S communications on the hardware. BCLK and WS signals will stop being + /// generated if this is a controller. MCLK will continue to be generated. + /// + /// # Errors + /// This will return an [`EspError`] with `ESP_ERR_INVALID_STATE` if the channel is not in the `RUNNING` state. + #[cfg(esp_idf_version_major = "4")] + pub fn tx_disable(&mut self) -> Result<(), EspError> { + unsafe { esp!(i2s_stop(self.port())) } + } + + /// Disable the I2S transmit channel. + /// + /// # Note + /// This can only be called when the channel is in the `RUNNING` state: the channel has been previously enabled + /// via a call to [`tx_enable()`][I2sTxChannel::tx_enable]. The channel will enter the `READY` state if it is disabled + /// successfully. + /// + /// Disabling the channel will stop I2S communications on the hardware. BCLK and WS signals will stop being + /// generated if this is a controller. MCLK will continue to be generated. + /// + /// # Errors + /// This will return an [`EspError`] with `ESP_ERR_INVALID_STATE` if the channel is not in the `RUNNING` state. + #[cfg(not(esp_idf_version_major = "4"))] + pub fn tx_disable(&mut self) -> Result<(), EspError> { + unsafe { esp!(i2s_channel_disable(self.tx_handle)) } + } + + /// Preload data into the transmit channel DMA buffer. + /// + /// This may be called only when the channel is in the `READY` state: initialized but not yet started. + /// + /// This is used to preload data into the DMA buffer so that valid data can be transmitted immediately after the + /// channel is enabled via [`tx_enable()`][I2sTxChannel::tx_enable]. If this function is not called before enabling the channel, + /// empty data will be transmitted. + /// + /// This function can be called multiple times before enabling the channel. Additional calls will concatenate the + /// data to the end of the buffer until the buffer is full. + /// + /// # Returns + /// This returns the number of bytes that have been loaded into the buffer. If this is less than the length of + /// the data provided, the buffer is full and no more data can be loaded. + #[cfg(all( + not(esp_idf_version_major = "4"), + not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")) + ))] + pub fn preload_data(&mut self, data: &[u8]) -> Result { + let mut bytes_loaded: usize = 0; + + unsafe { + esp!(esp_idf_sys::i2s_channel_preload_data( + self.tx_handle, + data.as_ptr() as *const c_void, + data.len(), + &mut bytes_loaded as *mut usize + ))?; + } + + Ok(bytes_loaded) + } + + /// Write data to the channel asynchronously. + /// + /// This may be called only when the channel is in the `RUNNING` state. + /// + /// # Returns + /// This returns the number of bytes sent. This may be less than the length of the data provided. + #[cfg(not(esp_idf_version_major = "4"))] + pub async fn write_async(&mut self, data: &[u8]) -> Result { + loop { + match self.write(data, crate::delay::NON_BLOCK) { + Err(err) if err.code() == esp_idf_sys::ESP_ERR_TIMEOUT => { + SEND_NOTIFIER[self.port as usize].wait().await; + } + other => break other, + } + } + } + + /// Write all data to the channel asynchronously. + /// + /// This may be called only when the channel is in the `RUNNING` state. + #[cfg(not(esp_idf_version_major = "4"))] + pub async fn write_all_async(&mut self, data: &[u8]) -> Result<(), EspError> { + let mut offset = 0; + + while offset < data.len() { + offset += self.write_async(&data[offset..]).await?; + } + + Ok(()) + } + + /// Write data to the channel. + /// + /// This may be called only when the channel is in the `RUNNING` state. + /// + /// # Returns + /// This returns the number of bytes sent. This may be less than the length of the data provided. + #[cfg(esp_idf_version_major = "4")] + pub fn write(&mut self, data: &[u8], timeout: TickType_t) -> Result { + if data.is_empty() { + Ok(0) + } else { + let mut bytes_written: usize = 0; + + Self::remap_result( + unsafe { + esp!(i2s_write( + self.port(), + data.as_ptr() as *mut c_void, + data.len(), + &mut bytes_written, + crate::delay::TickType(timeout).as_millis_u32(), + )) + }, + bytes_written, + ) + } + } + + /// Write data to the channel. + /// + /// This may be called only when the channel is in the `RUNNING` state. + /// + /// # Returns + /// This returns the number of bytes sent. This may be less than the length of the data provided. + #[cfg(not(esp_idf_version_major = "4"))] + pub fn write(&mut self, data: &[u8], timeout: TickType_t) -> Result { + if data.is_empty() { + Ok(0) + } else { + let mut bytes_written: usize = 0; + + Self::remap_result( + unsafe { + esp!(i2s_channel_write( + self.tx_handle, + data.as_ptr() as *mut c_void, + data.len(), + &mut bytes_written, + crate::delay::TickType(timeout).as_millis_u32(), + )) + }, + bytes_written, + ) + } + } + + /// Write all data to the channel. + /// + /// This may be called only when the channel is in the `RUNNING` state. + pub fn write_all(&mut self, data: &[u8], timeout: TickType_t) -> Result<(), EspError> { + let mut offset = 0; + + while offset < data.len() { + offset += self.write(&data[offset..], timeout)?; + } + + Ok(()) + } +} + +impl I2sDriver<'_, I2sBiDir> { + /// Split the bidirectional I2S driver into two parts (Rx, Tx) + /// + /// # Safety + /// It is safe to use the two parts separately + /// - esp-idf guarantees thread safety + /// - esp-idf-hal guarantees asynchronous safety + pub fn split(&mut self) -> (I2sDriverRef, I2sDriverRef) { + // Safe because self cannot be null + let this = unsafe { NonNull::new_unchecked(self) }; + + (I2sDriverRef(this.cast()), I2sDriverRef(this.cast())) + } +} + +impl Drop for I2sDriver<'_, Dir> { + fn drop(&mut self) { + #[cfg(esp_idf_version_major = "4")] + { + let _ = unsafe { esp!(i2s_stop(self.port as _)) }; + + esp!(unsafe { i2s_driver_uninstall(self.port as _) }).unwrap(); + } + + #[cfg(not(esp_idf_version_major = "4"))] + { + if !self.rx_handle.is_null() { + let _ = unsafe { esp!(i2s_channel_disable(self.rx_handle)) }; + } + + if !self.tx_handle.is_null() { + let _ = unsafe { esp!(i2s_channel_disable(self.tx_handle)) }; + } + + self.unsubscribe_channel(self.rx_handle).unwrap(); + self.unsubscribe_channel(self.tx_handle).unwrap(); + + if !self.rx_handle.is_null() { + self.del_channel(self.rx_handle).unwrap(); + } + + if !self.tx_handle.is_null() { + self.del_channel(self.tx_handle).unwrap(); + } + + SEND_NOTIFIER[self.port as usize].reset(); + RECV_NOTIFIER[self.port as usize].reset(); + } + } +} + +unsafe impl Send for I2sDriver<'_, Dir> {} + +impl I2sPort for I2sDriver<'_, Dir> { + fn port(&self) -> i2s_port_t { + self.port as _ + } +} + +impl embedded_io::ErrorType for I2sDriver<'_, Dir> { + type Error = EspIOError; +} + +impl embedded_io::Read for I2sDriver<'_, Dir> +where + Dir: I2sRxSupported, +{ + fn read(&mut self, buf: &mut [u8]) -> Result { + self.read(buf, delay::BLOCK).map_err(EspIOError) + } +} + +impl embedded_io::Write for I2sDriver<'_, Dir> +where + Dir: I2sTxSupported, +{ + fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf, delay::BLOCK).map_err(EspIOError) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +#[cfg(not(esp_idf_version_major = "4"))] +impl embedded_io_async::Read for I2sDriver<'_, Dir> +where + Dir: I2sRxSupported, +{ + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read_async(buf).await.map_err(EspIOError) + } +} + +#[cfg(not(esp_idf_version_major = "4"))] +impl embedded_io_async::Write for I2sDriver<'_, Dir> +where + Dir: I2sTxSupported, +{ + async fn write(&mut self, buf: &[u8]) -> Result { + self.write_async(buf).await.map_err(EspIOError) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +/// C-facing ISR dispatcher for on_send_* callbacks. +#[cfg(not(esp_idf_version_major = "4"))] +unsafe extern "C" fn dispatch_send( + _handle: i2s_chan_handle_t, + _raw_event: *mut i2s_event_data_t, + user_ctx: *mut c_void, +) -> bool { + let port = user_ctx as u32 as i2s_port_t; + + SEND_NOTIFIER[port as usize].notify_lsb() +} + +/// C-facing ISR dispatcher for on_recv_* callbacks. +#[cfg(not(esp_idf_version_major = "4"))] +unsafe extern "C" fn dispatch_recv( + _handle: i2s_chan_handle_t, + _raw_event: *mut i2s_event_data_t, + user_ctx: *mut c_void, +) -> bool { + let port = user_ctx as u32 as i2s_port_t; + + RECV_NOTIFIER[port as usize].notify_lsb() +} + +macro_rules! impl_i2s { + ($i2s:ident: $port:expr) => { + crate::impl_peripheral!($i2s); + + impl I2s for $i2s { + #[inline(always)] + fn port() -> i2s_port_t { + $port + } + } + }; +} + +impl_i2s!(I2S0: 0); +#[cfg(any(esp32, esp32s3))] +impl_i2s!(I2S1: 1); + +#[cfg(not(esp_idf_version_major = "4"))] +#[cfg(not(any(esp32, esp32s3)))] +static SEND_NOTIFIER: [HalIsrNotification; 1] = [HalIsrNotification::new()]; +#[cfg(not(esp_idf_version_major = "4"))] +#[cfg(not(any(esp32, esp32s3)))] +static RECV_NOTIFIER: [HalIsrNotification; 1] = [HalIsrNotification::new()]; + +#[cfg(not(esp_idf_version_major = "4"))] +#[cfg(any(esp32, esp32s3))] +static SEND_NOTIFIER: [HalIsrNotification; 2] = + [HalIsrNotification::new(), HalIsrNotification::new()]; +#[cfg(not(esp_idf_version_major = "4"))] +#[cfg(any(esp32, esp32s3))] +static RECV_NOTIFIER: [HalIsrNotification; 2] = + [HalIsrNotification::new(), HalIsrNotification::new()]; diff --git a/src/i2s/pdm.rs b/src/i2s/pdm.rs new file mode 100644 index 00000000000..98e2ceffff1 --- /dev/null +++ b/src/i2s/pdm.rs @@ -0,0 +1,1429 @@ +//! Pulse density modulation (PDM) driver for the ESP32 I2S peripheral. +//! +//! # Microcontroller support for PDM mode +//! +//! | Microcontroller | PDM Rx | PDM Tx | +//! |--------------------|------------------|--------------------------| +//! | ESP32 | I2S0 | I2S0, hardware version 1 | +//! | ESP32-S2 | _not supported_ | _not supported_ | +//! | ESP32-S3 | I2S0 | I2S0, hardware version 2 | +//! | ESP32-C2 (ESP8684) | _not supported_ | _not supported_ | +//! | ESP32-C3 | _not supported_* | I2S0, hardware version 2 | +//! | ESP32-C6 | _not supported_* | I2S0, hardware version 2 | +//! | ESP32-H2 | _not supported_* | I2S0, hardware version 2 | +//! +//! \* These microcontrollers have PDM Rx capabilities but lack a PDM-to-PCM decoder required by the ESP-IDF SDK. +//! +//! ## Hardware versions +//! +//! Hardware version 1 (ESP32) provides only a single output line, requiring external hardware to demultiplex stereo +//! signals in a time-critical manner; it is unlikely you will see accurate results here. +//! +//! Harware version 2 (all others with PDM Tx support) provide two output lines, allowing for separate left/right +//! channels. +//! +//! See the [`PdmTxSlotConfig documentation`][PdmTxSlotConfig] for more details. + +use super::*; +use crate::{gpio::*, peripheral::Peripheral}; + +// Note on cfg settings: +// esp_idf_soc_i2s_hw_version_1 and esp_idf_soc_i2s_hw_version_2 are defined *only* for ESP-IDF v5.0+. +// When v4.4 support is needed, actual microcontroller names must be used: esp32 for esp_idf_soc_i2s_hw_version_1, +// any(esp32s3,esp32c3,esp32c6,esp32h2) for esp_idf_soc_i2s_hw_version_2. + +#[cfg(esp_idf_version_major = "4")] +use esp_idf_sys::*; + +pub(super) mod config { + #[allow(unused)] + use crate::{gpio::*, i2s::config::*, peripheral::*}; + use esp_idf_sys::*; + + /// I2S pulse density modulation (PDM) downsampling mode. + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + pub enum PdmDownsample { + /// Downsample 8 samples. + #[default] + Samples8, + + /// Downsample 16 samples. + Samples16, + + /// Maximum downsample rate. + Max, + } + + #[cfg(any(esp_idf_soc_i2s_supports_pdm_rx, esp32, esp32s3))] + impl PdmDownsample { + /// Convert to the ESP-IDF SDK `i2s_pdm_downsample_t` representation. + #[inline(always)] + pub(super) fn as_sdk(&self) -> i2s_pdm_dsr_t { + match self { + Self::Samples8 => 0, + Self::Samples16 => 1, + Self::Max => 2, + } + } + } + + /// Pulse density modulation (PDM) mode receive clock configuration for the I2S peripheral. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct PdmRxClkConfig { + /// The sample rate in Hz. + pub(super) sample_rate_hz: u32, + + /// The clock source. + clk_src: ClockSource, + + /// The multiple of the MCLK signal to the sample rate. + mclk_multiple: MclkMultiple, + + /// Downsampling rate mode. + pub(super) downsample_mode: PdmDownsample, + } + + impl PdmRxClkConfig { + /// Create a PDM clock configuration with the specified sample rate in Hz. This will set the clock source to + /// PLL_F160M, the MCLK multiple to 256 times the sample rate, and the downsampling mode to 8 samples. + #[inline(always)] + pub fn from_sample_rate_hz(rate: u32) -> Self { + Self { + sample_rate_hz: rate, + clk_src: ClockSource::default(), + mclk_multiple: MclkMultiple::M256, + downsample_mode: PdmDownsample::Samples8, + } + } + + /// Set the clock source on this PDM receive clock configuration. + #[inline(always)] + pub fn clk_src(mut self, clk_src: ClockSource) -> Self { + self.clk_src = clk_src; + self + } + + /// Set the MCLK multiple on this PDM receive clock configuration. + #[inline(always)] + pub fn mclk_multiple(mut self, mclk_multiple: MclkMultiple) -> Self { + self.mclk_multiple = mclk_multiple; + self + } + + /// Set the downsampling mode on this PDM receive clock configuration. + #[inline(always)] + pub fn downsample_mode(mut self, downsample_mode: PdmDownsample) -> Self { + self.downsample_mode = downsample_mode; + self + } + + /// Convert to the ESP-IDF SDK `i2s_pdm_rx_clk_config_t` representation. + #[cfg(all( + any(esp_idf_soc_i2s_supports_pdm_rx, esp32, esp32s3), + not(esp_idf_version_major = "4") + ))] + #[inline(always)] + pub(super) fn as_sdk(&self) -> i2s_pdm_rx_clk_config_t { + #[allow(clippy::needless_update)] + i2s_pdm_rx_clk_config_t { + sample_rate_hz: self.sample_rate_hz, + clk_src: self.clk_src.as_sdk(), + mclk_multiple: self.mclk_multiple.as_sdk(), + dn_sample_mode: self.downsample_mode.as_sdk(), + ..Default::default() + } + } + } + + /// Pulse density modulation (PDM) mode receive configuration for the I2S peripheral. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct PdmRxConfig { + /// The base channel configuration. + pub(super) channel_cfg: Config, + + /// PDM mode channel receive clock configuration. + pub(super) clk_cfg: PdmRxClkConfig, + + /// PDM mode channel slot configuration. + pub(super) slot_cfg: PdmRxSlotConfig, + + /// PDM mode channel GPIO configuration. + #[cfg(not(esp_idf_version_major = "4"))] + pub(super) gpio_cfg: PdmRxGpioConfig, + } + + impl PdmRxConfig { + /// Create a new PDM mode receive configuration from the specified clock, slot, and GPIO configurations. + pub fn new( + channel_cfg: Config, + clk_cfg: PdmRxClkConfig, + slot_cfg: PdmRxSlotConfig, + #[cfg(not(esp_idf_version_major = "4"))] gpio_cfg: PdmRxGpioConfig, + ) -> Self { + Self { + channel_cfg, + clk_cfg, + slot_cfg, + #[cfg(not(esp_idf_version_major = "4"))] + gpio_cfg, + } + } + + /// Convert this PDM mode receive configuration into the ESP-IDF SDK `i2s_pdm_rx_config_t` representation. + #[cfg(esp_idf_soc_i2s_supports_pdm_rx)] + #[inline(always)] + pub(super) fn as_sdk<'d>( + &self, + clk: PeripheralRef<'d, impl OutputPin>, + din: PeripheralRef<'d, impl InputPin>, + ) -> i2s_pdm_rx_config_t { + i2s_pdm_rx_config_t { + clk_cfg: self.clk_cfg.as_sdk(), + slot_cfg: self.slot_cfg.as_sdk(), + gpio_cfg: self.gpio_cfg.as_sdk(clk, din), + } + } + + /// Convert this PDM mode receive configuration into the ESP-IDF SDK `i2s_pdm_rx_config_t` representation. + /// + /// Supported on ESP-IDF 5.1+. + #[cfg(all( + esp_idf_soc_i2s_supports_pdm_rx, // Implicitly selects 5.0+ + not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")) + ))] + #[inline(always)] + pub(super) fn as_sdk_multi<'d>( + &self, + clk: PeripheralRef<'d, impl OutputPin>, + dins: &[PeripheralRef<'d, impl InputPin>], + ) -> i2s_pdm_rx_config_t { + i2s_pdm_rx_config_t { + clk_cfg: self.clk_cfg.as_sdk(), + slot_cfg: self.slot_cfg.as_sdk(), + gpio_cfg: self.gpio_cfg.as_sdk_multi(clk, dins), + } + } + + /// Convert this PDM mode receive configuration into the ESP-IDF SDK `i2s_driver_config_t` representation. + #[cfg(all(any(esp32, esp32s3), esp_idf_version_major = "4"))] + #[inline(always)] + pub(super) fn as_sdk(&self) -> i2s_driver_config_t { + let chan_fmt = match self.slot_cfg.slot_mode { + SlotMode::Stereo => i2s_channel_fmt_t_I2S_CHANNEL_FMT_RIGHT_LEFT, + SlotMode::Mono => match self.slot_cfg.slot_mask { + PdmSlotMask::Both => i2s_channel_fmt_t_I2S_CHANNEL_FMT_RIGHT_LEFT, + PdmSlotMask::Left => i2s_channel_fmt_t_I2S_CHANNEL_FMT_ONLY_LEFT, + PdmSlotMask::Right => i2s_channel_fmt_t_I2S_CHANNEL_FMT_ONLY_RIGHT, + }, + }; + + i2s_driver_config_t { + mode: self.channel_cfg.role.as_sdk() + | i2s_mode_t_I2S_MODE_RX + | i2s_mode_t_I2S_MODE_PDM, + sample_rate: self.clk_cfg.sample_rate_hz, + bits_per_sample: 16, // fixed for PDM, + channel_format: chan_fmt, + communication_format: 0, // ? + intr_alloc_flags: 1 << 1, // ESP_INTR_FLAG_LEVEL1 + dma_buf_count: self.channel_cfg.dma_buffer_count as i32, + dma_buf_len: self.channel_cfg.frames_per_buffer as i32, + #[cfg(any(esp32, esp32s2))] + use_apll: matches!(self.clk_cfg.clk_src, ClockSource::Apll), + #[cfg(not(any(esp32, esp32s2)))] + use_apll: false, + tx_desc_auto_clear: self.channel_cfg.auto_clear, + fixed_mclk: 0, + mclk_multiple: self.clk_cfg.mclk_multiple.as_sdk(), + bits_per_chan: 16, // fixed for PDM + + // The following are TDM-only fields and are not present on chips that don't support TDM mode. + // There's no cfg option for this (it's a constant in esp-idf-sys). + #[cfg(not(any(esp32, esp32s2)))] + chan_mask: 0, + #[cfg(not(any(esp32, esp32s2)))] + total_chan: 0, + #[cfg(not(any(esp32, esp32s2)))] + left_align: false, + #[cfg(not(any(esp32, esp32s2)))] + big_edin: false, + #[cfg(not(any(esp32, esp32s2)))] + bit_order_msb: false, + #[cfg(not(any(esp32, esp32s2)))] + skip_msk: true, + } + } + } + + /// PDM mode GPIO (general purpose input/output) receive configuration. + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + pub struct PdmRxGpioConfig { + /// Whether the clock output is inverted. + pub(super) clk_inv: bool, + } + + /// The maximum number of data input pins that can be used in PDM mode. + /// + /// This is 1 on the ESP32 and 4 on the ESP32-S3. + #[cfg(esp32)] + pub const SOC_I2S_PDM_MAX_RX_LINES: usize = 1; + + /// The maximum number of data input pins that can be used in PDM mode. + /// + /// This is 1 on the ESP32 and 4 on the ESP32-S3. + #[cfg(esp32s3)] + pub const SOC_I2S_PDM_MAX_RX_LINES: usize = 4; + + impl PdmRxGpioConfig { + /// Create a new PDM mode GPIO receive configuration with the specified inversion flag for the clock output. + #[inline(always)] + pub fn new(clk_inv: bool) -> Self { + Self { clk_inv } + } + + /// Set the clock inversion flag on this PDM GPIO configuration. + #[inline(always)] + pub fn clk_invert(mut self, clk_inv: bool) -> Self { + self.clk_inv = clk_inv; + self + } + + /// Convert to the ESP-IDF SDK `i2s_pdm_rx_gpio_config_t` representation. + /// + /// Note: The bitfields are renamed in ESP-IDF 5.1+. + #[cfg(all( + esp_idf_soc_i2s_supports_pdm_rx, + esp_idf_version_major = "5", + esp_idf_version_minor = "0" + ))] + pub(crate) fn as_sdk<'d>( + &self, + clk: PeripheralRef<'d, impl OutputPin>, + din: PeripheralRef<'d, impl InputPin>, + ) -> i2s_pdm_rx_gpio_config_t { + let invert_flags = i2s_pdm_rx_gpio_config_t__bindgen_ty_1 { + _bitfield_1: i2s_pdm_rx_gpio_config_t__bindgen_ty_1::new_bitfield_1( + self.clk_inv as u32, + ), + ..Default::default() + }; + + i2s_pdm_rx_gpio_config_t { + clk: clk.pin(), + din: din.pin(), + invert_flags, + } + } + + /// Convert to the ESP-IDF SDK `i2s_pdm_rx_gpio_config_t` representation. + #[cfg(all( + esp_idf_soc_i2s_supports_pdm_rx, + not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")) + ))] + pub(crate) fn as_sdk<'d>( + &self, + clk: PeripheralRef<'d, impl OutputPin>, + din: PeripheralRef<'d, impl InputPin>, + ) -> i2s_pdm_rx_gpio_config_t { + #[allow(clippy::unnecessary_cast)] + let mut dins: [gpio_num_t; SOC_I2S_PDM_MAX_RX_LINES as usize] = + [-1; SOC_I2S_PDM_MAX_RX_LINES as usize]; + dins[0] = din.pin(); + + let pins = i2s_pdm_rx_gpio_config_t__bindgen_ty_1 { dins }; + + let invert_flags = i2s_pdm_rx_gpio_config_t__bindgen_ty_2 { + _bitfield_1: i2s_pdm_rx_gpio_config_t__bindgen_ty_2::new_bitfield_1( + self.clk_inv as u32, + ), + ..Default::default() + }; + + i2s_pdm_rx_gpio_config_t { + clk: clk.pin(), + __bindgen_anon_1: pins, + invert_flags, + } + } + + /// Convert to the ESP-IDF SDK `i2s_pdm_rx_gpio_config_t` representation. + /// + /// This will ignore any din pins beyond [`SOC_I2S_PDM_MAX_RX_LINES`]. + /// + /// Supported on ESP-IDF 5.1+ only. + #[cfg(all( + esp_idf_soc_i2s_supports_pdm_rx, + not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")) + ))] + pub(crate) fn as_sdk_multi<'d>( + &self, + clk: PeripheralRef<'d, impl OutputPin>, + dins: &[PeripheralRef<'d, impl InputPin>], + ) -> i2s_pdm_rx_gpio_config_t { + #[allow(clippy::unnecessary_cast)] + let mut din_pins: [gpio_num_t; SOC_I2S_PDM_MAX_RX_LINES as usize] = + [-1; SOC_I2S_PDM_MAX_RX_LINES as usize]; + + #[allow(clippy::unnecessary_cast)] + for (i, din) in dins.iter().enumerate() { + if i >= SOC_I2S_PDM_MAX_RX_LINES as usize { + break; + } + + din_pins[i] = din.pin(); + } + + let pins = i2s_pdm_rx_gpio_config_t__bindgen_ty_1 { dins: din_pins }; + + let invert_flags = i2s_pdm_rx_gpio_config_t__bindgen_ty_2 { + _bitfield_1: i2s_pdm_rx_gpio_config_t__bindgen_ty_2::new_bitfield_1( + self.clk_inv as u32, + ), + ..Default::default() + }; + + i2s_pdm_rx_gpio_config_t { + clk: clk.pin(), + __bindgen_anon_1: pins, + invert_flags, + } + } + } + + /// PDM mode channel receive slot configuration. + /// + /// # Note + /// The `slot_mode` and `slot_mask` cause data to be interpreted in different ways, as noted below. + /// WS is the "word select" signal, sometimes called LRCLK (left/right clock). + /// + /// Assuming the received data contains the following samples (when converted from PDM to PCM), where a sample may be 8, 16, 24, or 32 bits, depending on `data_bit_width`: + /// + /// | **WS Low** | **WS High** | **WS Low** | **WS High** | **WS Low** | **WS High** | **WS Low** | **WS High** | | + /// |-------------|-------------|-------------|-------------|-------------|-------------|-------------|-------------|-----| + /// | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ... | + /// + /// The actual data in the buffer will be (1-4 bytes, depending on `data_bit_width`): + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
slot_modeslot_maskBuffer Contents
d[0]d[1]d[2]d[3]d[4]d[5]d[6]d[7]
Mono Left 1113151719212325
Right1214161820222426
Both Unspecified behavior
Stereo (ESP32) Any 1112131415161718
Stereo (ESP32-S3)Any 1211141316151817
+ /// + /// Note that, on the ESP32-S3, the right channel is received first. This can be switched by setting + /// [`PdmRxGpioConfig::clk_invert`] to `true` in the merged [`PdmRxConfig`]. + /// + /// For details, refer to the + /// _ESP-IDF Programming Guide_ PDM Rx Usage details for your specific microcontroller: + /// * [ESP32](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html#pdm-rx-usage) + /// * [ESP32-S3](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#pdm-rx-usage) + /// + /// Other microcontrollers do not support PDM receive mode, or do not have a PDM-to-PCM peripheral that allows for decoding + /// the PDM data as required by ESP-IDF. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct PdmRxSlotConfig { + /// I2S sample data bit width (valid data bits per sample). + #[allow(dead_code)] + pub(super) data_bit_width: DataBitWidth, + + /// I2s slot bit width (total bits per slot). + #[allow(dead_code)] + pub(super) slot_bit_width: SlotBitWidth, + + /// Mono or stereo mode operation. + #[allow(dead_code)] + pub(super) slot_mode: SlotMode, + + /// Are we using the left, right, or both data slots? + #[allow(dead_code)] + pub(super) slot_mask: PdmSlotMask, + } + + impl PdmRxSlotConfig { + /// Configure the PDM mode channel receive slot configuration for the specified bits per sample and slot mode + /// in 2 slots. + pub fn from_bits_per_sample_and_slot_mode( + bits_per_sample: DataBitWidth, + slot_mode: SlotMode, + ) -> Self { + let slot_mask = if slot_mode == SlotMode::Mono { + PdmSlotMask::Left + } else { + PdmSlotMask::Both + }; + + Self { + data_bit_width: bits_per_sample, + slot_bit_width: SlotBitWidth::Auto, + slot_mode, + slot_mask, + } + } + + /// Update the data bit width on this PDM receive slot configuration. + #[inline(always)] + pub fn data_bit_width(mut self, data_bit_width: DataBitWidth) -> Self { + self.data_bit_width = data_bit_width; + self + } + + /// Update the slot bit width on this PDM receive slot configuration. + #[inline(always)] + pub fn slot_bit_width(mut self, slot_bit_width: SlotBitWidth) -> Self { + self.slot_bit_width = slot_bit_width; + self + } + + /// Update the slot mode and mask on this PDM receive slot configuration. + #[inline(always)] + pub fn slot_mode_mask(mut self, slot_mode: SlotMode, slot_mask: PdmSlotMask) -> Self { + self.slot_mode = slot_mode; + self.slot_mask = slot_mask; + self + } + + /// Convert this PDM mode channel receive slot configuration into the ESP-IDF SDK `i2s_pdm_rx_slot_config_t` + /// representation. + #[cfg(esp_idf_soc_i2s_supports_pdm_rx)] + #[inline(always)] + #[allow(clippy::needless_update)] + pub(super) fn as_sdk(&self) -> i2s_pdm_rx_slot_config_t { + i2s_pdm_rx_slot_config_t { + data_bit_width: self.data_bit_width.as_sdk(), + slot_bit_width: self.slot_bit_width.as_sdk(), + slot_mode: self.slot_mode.as_sdk(), + slot_mask: self.slot_mask.as_sdk(), + ..Default::default() + } + } + } + + /// Pulse density modulation (PDM) transmit signal scaling mode. + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + pub enum PdmSignalScale { + /// Divide the PDM signal by 2. + Div2, + + /// No scaling. + #[default] + None, + + /// Multiply the PDM signal by 2. + Mul2, + + /// Multiply the PDM signal by 4. + Mul4, + } + + impl PdmSignalScale { + /// Convert to the ESP-IDF SDK `i2s_pdm_signal_scale_t` representation. + #[cfg_attr(esp_idf_version_major = "4", allow(unused))] + #[inline(always)] + pub(crate) fn as_sdk(&self) -> i2s_pdm_sig_scale_t { + match self { + Self::Div2 => 0, + Self::None => 1, + Self::Mul2 => 2, + Self::Mul4 => 3, + } + } + } + + /// I2S slot selection in PDM mode. + /// + /// The default is `PdmSlotMask::Both`. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub enum PdmSlotMask { + /// I2S transmits or receives the left slot. + Left, + + /// I2S transmits or receives the right slot. + Right, + + /// I2S transmits or receives both slots. + Both, + } + + impl Default for PdmSlotMask { + #[inline(always)] + fn default() -> Self { + Self::Both + } + } + + impl PdmSlotMask { + /// Convert to the ESP-IDF SDK `i2s_pdm_slot_mask_t` representation. + #[cfg(not(esp_idf_version_major = "4"))] + #[inline(always)] + #[allow(unused)] + pub(crate) fn as_sdk(&self) -> i2s_pdm_slot_mask_t { + match self { + Self::Left => 1 << 0, + Self::Right => 1 << 1, + Self::Both => (1 << 0) | (1 << 1), + } + } + } + + /// The I2s pulse density modulation (PDM) mode transmit clock configuration. + /// + /// # Note + /// The PDM transmit clock can only be set to the following two upsampling rate configurations: + /// * `upsampling_fp = 960`, `upsampling_fs = sample_rate_hz / 100`. In this case, `Fpdm = 128 * 48000 = 6.144 MHz`. + /// * `upsampling_fp = 960`, `upsampling_fs = 480`. In this case, `Fpdm = 128 * sample_rate_hz`. + /// + /// If the PDM receiver does not use the PDM serial clock, the first configuration should be used. Otherwise, + /// use the second configuration. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct PdmTxClkConfig { + /// I2S sample rate in Hz. + pub(super) sample_rate_hz: u32, + + /// The clock source. + pub(super) clk_src: ClockSource, + + /// The multiple of MCLK to the sample rate. + pub(super) mclk_multiple: MclkMultiple, + + /// Upsampling `fp` parameter. + upsample_fp: u32, + + /// Upsampling `fs` parameter. + upsample_fs: u32, + } + + impl PdmTxClkConfig { + /// Create a new PDM mode transmit clock configuration from the specified sample rate in Hz. This will set the + /// clock source to PLL_F160M, the MCLK multiple to 256 times the sample rate, `upsample_fp` to 960, and + /// `upsample_fs` to 480. + #[inline(always)] + pub fn from_sample_rate_hz(sample_rate_hz: u32) -> Self { + Self { + sample_rate_hz, + clk_src: ClockSource::default(), + mclk_multiple: MclkMultiple::M256, + upsample_fp: 960, + upsample_fs: 480, + } + } + + /// Set the sample rate on this PDM mode transmit clock configuration. + #[inline(always)] + pub fn sample_rate_hz(mut self, sample_rate_hz: u32) -> Self { + self.sample_rate_hz = sample_rate_hz; + self + } + + /// Set the clock source on this PDM mode transmit clock configuration. + #[inline(always)] + pub fn clk_src(mut self, clk_src: ClockSource) -> Self { + self.clk_src = clk_src; + self + } + + /// Set the MCLK multiple on this PDM mode transmit clock configuration. + #[inline(always)] + pub fn mclk_multiple(mut self, mclk_multiple: MclkMultiple) -> Self { + self.mclk_multiple = mclk_multiple; + self + } + + /// Set the upsampling parameters on this PDM mode transmit clock configuration. + #[inline(always)] + pub fn upsample(mut self, upsample_fp: u32, upsample_fs: u32) -> Self { + self.upsample_fp = upsample_fp; + self.upsample_fs = upsample_fs; + self + } + + /// Convert to the ESP-IDF SDK `i2s_pdm_tx_clk_config_t` representation. + #[allow(clippy::needless_update)] + #[cfg(not(esp_idf_version_major = "4"))] + #[inline(always)] + pub(super) fn as_sdk(&self) -> i2s_pdm_tx_clk_config_t { + i2s_pdm_tx_clk_config_t { + sample_rate_hz: self.sample_rate_hz, + clk_src: self.clk_src.as_sdk(), + mclk_multiple: self.mclk_multiple.as_sdk(), + up_sample_fp: self.upsample_fp, + up_sample_fs: self.upsample_fs, + ..Default::default() // bclk_div in ESP IDF > 5.1 + } + } + + /// Convert to the ESP-IDF SDK `i2s_pdm_tx_upsample_cfg_t` representation. + #[cfg(esp_idf_version_major = "4")] + #[inline(always)] + pub(super) fn as_sdk(&self) -> i2s_pdm_tx_upsample_cfg_t { + i2s_pdm_tx_upsample_cfg_t { + sample_rate: self.sample_rate_hz as i32, + fp: self.upsample_fp as i32, + fs: self.upsample_fs as i32, + } + } + } + + /// The I2S pulse density modulation (PDM) mode transmit configuration for the I2S peripheral. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct PdmTxConfig { + /// The base channel configuration. + pub(super) channel_cfg: Config, + + /// PDM mode channel transmit clock configuration. + pub(super) clk_cfg: PdmTxClkConfig, + + /// PDM mode channel transmit slot configuration. + pub(super) slot_cfg: PdmTxSlotConfig, + + /// PDM mode channel transmit GPIO configuration. + #[cfg(not(esp_idf_version_major = "4"))] + pub(super) gpio_cfg: PdmTxGpioConfig, + } + + impl PdmTxConfig { + /// Create a new PDM mode transmit configuration from the specified clock, slot, and GPIO configurations. + pub fn new( + channel_cfg: Config, + clk_cfg: PdmTxClkConfig, + slot_cfg: PdmTxSlotConfig, + #[cfg(not(esp_idf_version_major = "4"))] gpio_cfg: PdmTxGpioConfig, + ) -> Self { + Self { + channel_cfg, + clk_cfg, + slot_cfg, + #[cfg(not(esp_idf_version_major = "4"))] + gpio_cfg, + } + } + + /// Convert to the ESP-IDF `i2s_pdm_tx_config_t` representation. + #[cfg(all(not(esp_idf_version_major = "4"), not(esp_idf_soc_i2s_hw_version_2)))] + #[inline(always)] + pub(crate) fn as_sdk<'d>( + &self, + clk: PeripheralRef<'d, impl OutputPin>, + dout: PeripheralRef<'d, impl OutputPin>, + ) -> i2s_pdm_tx_config_t { + i2s_pdm_tx_config_t { + clk_cfg: self.clk_cfg.as_sdk(), + slot_cfg: self.slot_cfg.as_sdk(), + gpio_cfg: self.gpio_cfg.as_sdk(clk, dout), + } + } + + /// Convert to the ESP-IDF `i2s_pdm_tx_config_t` representation. + #[cfg(esp_idf_soc_i2s_hw_version_2)] + #[inline(always)] + pub(crate) fn as_sdk<'d>( + &self, + clk: PeripheralRef<'d, impl OutputPin>, + dout: PeripheralRef<'d, impl OutputPin>, + dout2: Option>, + ) -> i2s_pdm_tx_config_t { + i2s_pdm_tx_config_t { + clk_cfg: self.clk_cfg.as_sdk(), + slot_cfg: self.slot_cfg.as_sdk(), + gpio_cfg: self.gpio_cfg.as_sdk(clk, dout, dout2), + } + } + + /// Convert to the ESP-IDF `i2s_driver_config_t` representation. + #[cfg(esp_idf_version_major = "4")] + pub(crate) fn as_sdk(&self) -> i2s_driver_config_t { + let chan_fmt = match self.slot_cfg.slot_mode { + SlotMode::Stereo => i2s_channel_fmt_t_I2S_CHANNEL_FMT_RIGHT_LEFT, + SlotMode::Mono => match self.slot_cfg.slot_mask { + PdmSlotMask::Both => i2s_channel_fmt_t_I2S_CHANNEL_FMT_RIGHT_LEFT, + PdmSlotMask::Left => i2s_channel_fmt_t_I2S_CHANNEL_FMT_ONLY_LEFT, + PdmSlotMask::Right => i2s_channel_fmt_t_I2S_CHANNEL_FMT_ONLY_RIGHT, + }, + }; + + i2s_driver_config_t { + mode: self.channel_cfg.role.as_sdk() + | i2s_mode_t_I2S_MODE_TX + | i2s_mode_t_I2S_MODE_PDM, + sample_rate: self.clk_cfg.sample_rate_hz, + bits_per_sample: 16, // fixed for PDM, + channel_format: chan_fmt, + communication_format: 0, // ? + intr_alloc_flags: 1 << 1, // ESP_INTR_FLAG_LEVEL1 + dma_buf_count: self.channel_cfg.dma_buffer_count as i32, + dma_buf_len: self.channel_cfg.frames_per_buffer as i32, + #[cfg(any(esp32, esp32s2))] + use_apll: matches!(self.clk_cfg.clk_src, ClockSource::Apll), + #[cfg(not(any(esp32, esp32s2)))] + use_apll: false, + tx_desc_auto_clear: self.channel_cfg.auto_clear, + fixed_mclk: 0, + mclk_multiple: self.clk_cfg.mclk_multiple.as_sdk(), + bits_per_chan: 16, // fixed for PDM + + // The following are TDM-only fields and are not present on chips that don't support TDM mode. + // There's no cfg option for this (it's a constant in esp-idf-sys). + #[cfg(not(any(esp32, esp32s2)))] + chan_mask: 0, + #[cfg(not(any(esp32, esp32s2)))] + total_chan: 0, + #[cfg(not(any(esp32, esp32s2)))] + left_align: false, + #[cfg(not(any(esp32, esp32s2)))] + big_edin: false, + #[cfg(not(any(esp32, esp32s2)))] + bit_order_msb: false, + #[cfg(not(any(esp32, esp32s2)))] + skip_msk: true, + } + } + } + + /// PDM mode GPIO (general purpose input/output) transmit configuration. + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + pub struct PdmTxGpioConfig { + /// Whether the clock output is inverted. + pub(super) clk_inv: bool, + } + + /// The maximum number of data output pins that can be used in PDM mode. + /// + /// This is 1 on the ESP32, and 2 on the ESP32-S3, ESP32-C3, ESP32-C6, and ESP32-H2. + #[cfg(esp32)] + pub const SOC_I2S_PDM_MAX_TX_LINES: usize = 1; + + /// The maximum number of data input pins that can be used in PDM mode. + /// + /// This is 1 on the ESP32, and 2 on the ESP32-S3, ESP32-C3, ESP32-C6, and ESP32-H2. + #[cfg(any(esp32s3, esp32c3, esp32c6, esp32h2))] + pub const SOC_I2S_PDM_MAX_TX_LINES: usize = 2; + + impl PdmTxGpioConfig { + /// Create a new PDM mode GPIO transmit configuration with the specified inversion flag for the clock output. + #[inline(always)] + pub fn new(clk_inv: bool) -> Self { + Self { clk_inv } + } + + /// Set the clock inversion flag on this PDM GPIO transmit configuration. + #[inline(always)] + pub fn clk_invert(mut self, clk_inv: bool) -> Self { + self.clk_inv = clk_inv; + self + } + + /// Convert to the ESP-IDF SDK `i2s_pdm_tx_gpio_config_t` representation. + #[cfg(esp_idf_soc_i2s_hw_version_1)] + pub(crate) fn as_sdk<'d>( + &self, + clk: PeripheralRef<'d, impl OutputPin>, + dout: PeripheralRef<'d, impl OutputPin>, + ) -> i2s_pdm_tx_gpio_config_t { + let invert_flags = i2s_pdm_tx_gpio_config_t__bindgen_ty_1 { + _bitfield_1: i2s_pdm_tx_gpio_config_t__bindgen_ty_1::new_bitfield_1( + self.clk_inv as u32, + ), + ..Default::default() + }; + i2s_pdm_tx_gpio_config_t { + clk: clk.pin(), + dout: dout.pin(), + invert_flags, + } + } + + /// Convert to the ESP-IDF SDK `i2s_pdm_tx_gpio_config_t` representation. + #[cfg(esp_idf_soc_i2s_hw_version_2)] + pub(crate) fn as_sdk<'d>( + &self, + clk: PeripheralRef<'d, impl OutputPin>, + dout: PeripheralRef<'d, impl OutputPin>, + dout2: Option>, + ) -> i2s_pdm_tx_gpio_config_t { + let invert_flags = i2s_pdm_tx_gpio_config_t__bindgen_ty_1 { + _bitfield_1: i2s_pdm_tx_gpio_config_t__bindgen_ty_1::new_bitfield_1( + self.clk_inv as u32, + ), + ..Default::default() + }; + let dout2 = if let Some(dout2) = dout2 { + dout2.pin() + } else { + -1 + }; + + i2s_pdm_tx_gpio_config_t { + clk: clk.pin(), + dout: dout.pin(), + dout2, + invert_flags, + } + } + } + + /// I2S pulse density modulation (PDM) transmit line mode + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] + pub enum PdmTxLineMode { + /// Standard PDM format output: left and right slot data on a single line. + #[default] + OneLineCodec, + + /// PDM DAC format output: left or right slot data on a single line. + OneLineDac, + + /// PDM DAC format output: left and right slot data on separate lines. + TwoLineDac, + } + + impl PdmTxLineMode { + /// Convert this to the ESP-IDF SDK `i2s_pdm_tx_line_mode_t` representation. + #[cfg(esp_idf_soc_i2s_hw_version_2)] + #[inline(always)] + pub(super) fn as_sdk(&self) -> i2s_pdm_tx_line_mode_t { + match self { + Self::OneLineCodec => i2s_pdm_tx_line_mode_t_I2S_PDM_TX_ONE_LINE_CODEC, + Self::OneLineDac => i2s_pdm_tx_line_mode_t_I2S_PDM_TX_ONE_LINE_DAC, + Self::TwoLineDac => i2s_pdm_tx_line_mode_t_I2S_PDM_TX_TWO_LINE_DAC, + } + } + } + + /// PDM mode channel transmit slot configuration. + /// + /// # Note + /// The `slot_mode` and `line_mode` (microcontrollers new than ESP32) or `slot_mask` (ESP32) cause data to be + /// interpreted in different ways, as noted below. + /// + /// Assuming the buffered data contains the following samples (where a sample may be 1, 2, 3, or 4 bytes, depending + /// on `data_bit_width`): + /// + /// | **`d[0]`** | **`d[1]`** | **`d[2]`** | **`d[3]`** | **`d[4]`** | **`d[5]`** | **`d[6]`** | **`d[7]`** | + /// |------------|------------|------------|------------|------------|------------|------------|------------| + /// | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | + /// + /// The actual data on the line will be: + /// + /// ## All microcontrollers except ESP32 + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
line_modeslot_modeLineTransmitted Data
WS LowWS HighWS LowWS HighWS LowWS HighWS LowWS High
OneLineCodecMono dout110120130140
Stereodout1112131415161718
OneLineDac Mono dout1111121213131414
TwoLineDac Monodout1212141416161818
dout200000000
Stereodout1212141416161818
dout21111131315151717
+ /// + /// ## ESP32 + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
slot_modeslot_maskTransmitted Data
WS LowWS HighWS LowWS HighWS LowWS HighWS LowWS High
Mono Left 110120130140
Right011012013014
Both1112131415161718
Mono Left1111131315151717
Right1212141416161818
Both 1112131415161718
+ /// + /// Modes combinations other than [`SlotMode::Mono`]/[`PdmSlotMask::Both`], + /// [`SlotMode::Stereo`]/[`PdmSlotMask::Left`], and [`SlotMode::Stereo`]/[`PdmSlotMask::Right`] are unlikely to be + /// useful since it requires precise demutiplexing on the bit stream based on the WS clock. + /// + /// For details, refer to the + /// _ESP-IDF Programming Guide_ PDM Tx Usage details for your specific microcontroller: + /// * [ESP32](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html#pdm-tx-usage) + /// * [ESP32-S3](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#pdm-tx-usage) + /// * [ESP32-C3](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/i2s.html#pdm-tx-usage) + /// * [ESP32-C6](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c6/api-reference/peripherals/i2s.html#pdm-tx-usage) + /// * [ESP32-H2](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c6/api-reference/peripherals/i2s.html#pdm-tx-usage) + #[derive(Clone, Copy, Debug, PartialEq)] + pub struct PdmTxSlotConfig { + // data_bit_width and slot_bit_width are omitted; they are always 16 bits. + /// Mono or stereo mode operation. + pub(super) slot_mode: SlotMode, + + /// Slot mask to choose the left or right slot. + #[cfg(not(esp_idf_soc_i2s_hw_version_2))] + pub(super) slot_mask: PdmSlotMask, + + /// Sigma-delta filter prescale. + sd_prescale: u32, + + /// Sigma-delta filter saling value. + sd_scale: PdmSignalScale, + + /// High-pass filter scaling value. + hp_scale: PdmSignalScale, + + /// Low-pass filter scaling value + lp_scale: PdmSignalScale, + + /// Sinc-filter scaling value. + sinc_scale: PdmSignalScale, + + /// PDM transmit line mode. + #[cfg(esp_idf_soc_i2s_hw_version_2)] + line_mode: PdmTxLineMode, + + /// High-pass filter enable + #[cfg(esp_idf_soc_i2s_hw_version_2)] + hp_enable: bool, + + /// High-pass filter cutoff frequence. + /// The range of this is 23.3Hz to 185Hz. + #[cfg(esp_idf_soc_i2s_hw_version_2)] + hp_cutoff_freq: f32, + + /// Sigma-delta filter dither. + #[cfg(esp_idf_soc_i2s_hw_version_2)] + sd_dither: u32, + + /// Sigma-delta filter dither 2. + #[cfg(esp_idf_soc_i2s_hw_version_2)] + sd_dither2: u32, + } + + impl Default for PdmTxSlotConfig { + #[inline(always)] + fn default() -> Self { + Self::from_slot_mode(SlotMode::Stereo) + } + } + + // We don't care about NaN in hp_cutoff_freq; go ahead and force it to be Eq. + impl Eq for PdmTxSlotConfig {} + + impl PdmTxSlotConfig { + /// Configure the PDM mode channel transmit slot configuration for the specified slot mode in 2 slots. + /// + /// This sets the sigma-delta, low-pass, and sinc scaling to None. + /// + /// For hardware version 1, the high-pass filter scaling is set to None. + /// + /// For hardware version 2, the high-pass filter is enabled, scaled to dividing by 2 and set to 35.5 Hz. + #[inline(always)] + pub fn from_slot_mode(slot_mode: SlotMode) -> Self { + Self { + slot_mode, + #[cfg(not(esp_idf_soc_i2s_hw_version_2))] + slot_mask: PdmSlotMask::Both, + sd_prescale: 0, + sd_scale: PdmSignalScale::None, + #[cfg(not(esp_idf_soc_i2s_hw_version_2))] + hp_scale: PdmSignalScale::None, + #[cfg(esp_idf_soc_i2s_hw_version_2)] + hp_scale: PdmSignalScale::Div2, + lp_scale: PdmSignalScale::None, + sinc_scale: PdmSignalScale::None, + #[cfg(esp_idf_soc_i2s_hw_version_2)] + line_mode: PdmTxLineMode::OneLineCodec, + #[cfg(esp_idf_soc_i2s_hw_version_2)] + hp_enable: true, + #[cfg(esp_idf_soc_i2s_hw_version_2)] + hp_cutoff_freq: 32.5, + #[cfg(esp_idf_soc_i2s_hw_version_2)] + sd_dither: 0, + #[cfg(esp_idf_soc_i2s_hw_version_2)] + sd_dither2: 1, + } + } + + /// Sets the slot mode on this PDM transmit slot configuration. + #[inline(always)] + pub fn slot_mode(mut self, slot_mode: SlotMode) -> Self { + self.slot_mode = slot_mode; + self + } + + /// Sets the slot mask on this PDM transmit slot configuration. + #[cfg(esp_idf_soc_i2s_hw_version_1)] + #[cfg_attr( + feature = "nightly", + doc(cfg(all(esp32, not(esp_idf_version_major = "4")))) + )] + #[inline(always)] + pub fn slot_mask(mut self, slot_mask: PdmSlotMask) -> Self { + self.slot_mask = slot_mask; + self + } + + /// Sets the sigma-delta filter prescale on this PDM transmit slot configuration. + #[inline(always)] + pub fn sd_prescale(mut self, sd_prescale: u32) -> Self { + self.sd_prescale = sd_prescale; + self + } + + /// Sets the sigma-delta filter scaling on this PDM transmit slot configuration. + #[inline(always)] + pub fn sd_scale(mut self, sd_scale: PdmSignalScale) -> Self { + self.sd_scale = sd_scale; + self + } + + /// Sets the high-pass filter scaling on this PDM transmit slot configuration. + #[inline(always)] + pub fn hp_scale(mut self, hp_scale: PdmSignalScale) -> Self { + self.hp_scale = hp_scale; + self + } + + /// Sets the low-pass filter scaling on this PDM transmit slot configuration. + #[inline(always)] + pub fn lp_scale(mut self, lp_scale: PdmSignalScale) -> Self { + self.lp_scale = lp_scale; + self + } + + /// Sets the sinc filter scaling on this PDM transmit slot configuration. + #[inline(always)] + pub fn sinc_scale(mut self, sinc_scale: PdmSignalScale) -> Self { + self.sinc_scale = sinc_scale; + self + } + + /// Sets the PDM transmit line mode on this PDM transmit slot configuration. + #[cfg(esp_idf_soc_i2s_hw_version_2)] + #[cfg_attr( + feature = "nightly", + doc(cfg(all( + any(esp32s3, esp32c3, esp32c6, esp32h2), + not(esp_idf_version_major = "4") + ))) + )] + #[inline(always)] + pub fn line_mode(mut self, line_mode: PdmTxLineMode) -> Self { + self.line_mode = line_mode; + self + } + + /// Sets the high-pass filter enable on this PDM transmit slot configuration. + #[cfg(esp_idf_soc_i2s_hw_version_2)] + #[cfg_attr( + feature = "nightly", + doc(cfg(all( + any(esp32s3, esp32c3, esp32c6, esp32h2), + not(esp_idf_version_major = "4") + ))) + )] + #[inline(always)] + pub fn hp_enable(mut self, hp_enable: bool) -> Self { + self.hp_enable = hp_enable; + self + } + + /// Sets the high-pass filter cutoff frequency on this PDM transmit slot configuration. + #[cfg(esp_idf_soc_i2s_hw_version_2)] + #[cfg_attr( + feature = "nightly", + doc(cfg(all( + any(esp32s3, esp32c3, esp32c6, esp32h2), + not(esp_idf_version_major = "4") + ))) + )] + #[inline(always)] + pub fn hp_cutoff_freq(mut self, hp_cutoff_freq: f32) -> Self { + self.hp_cutoff_freq = hp_cutoff_freq; + self + } + + /// Sets the sigma-delta filter dither on this PDM transmit slot configuration. + #[cfg(esp_idf_soc_i2s_hw_version_2)] + #[cfg_attr( + feature = "nightly", + doc(cfg(all( + any(esp32s3, esp32c3, esp32c6, esp32h2), + not(esp_idf_version_major = "4") + ))) + )] + #[inline(always)] + pub fn sd_dither(mut self, sd_dither: u32, sd_dither2: u32) -> Self { + self.sd_dither = sd_dither; + self.sd_dither2 = sd_dither2; + self + } + + /// Convert this to the ESP-IDF SDK `i2s_pdm_tx_slot_config_t` type. + #[cfg(not(esp_idf_version_major = "4"))] + #[inline(always)] + #[allow(clippy::needless_update)] + pub(super) fn as_sdk(&self) -> i2s_pdm_tx_slot_config_t { + i2s_pdm_tx_slot_config_t { + data_bit_width: DataBitWidth::Bits16.as_sdk(), + slot_bit_width: SlotBitWidth::Bits16.as_sdk(), + slot_mode: self.slot_mode.as_sdk(), + #[cfg(esp_idf_soc_i2s_hw_version_1)] + slot_mask: self.slot_mask.as_sdk(), + sd_prescale: self.sd_prescale, + sd_scale: self.sd_scale.as_sdk(), + hp_scale: self.hp_scale.as_sdk(), + lp_scale: self.lp_scale.as_sdk(), + sinc_scale: self.sinc_scale.as_sdk(), + #[cfg(esp_idf_soc_i2s_hw_version_2)] + line_mode: self.line_mode.as_sdk(), + #[cfg(esp_idf_soc_i2s_hw_version_2)] + hp_en: self.hp_enable, + #[cfg(esp_idf_soc_i2s_hw_version_2)] + hp_cut_off_freq_hz: self.hp_cutoff_freq, + #[cfg(esp_idf_soc_i2s_hw_version_2)] + sd_dither: self.sd_dither, + #[cfg(esp_idf_soc_i2s_hw_version_2)] + sd_dither2: self.sd_dither2, + // i2s_pdm_data_fmt_t::I2S_PDM_DATA_FMT_PCM + ..Default::default() + } + } + } +} + +#[cfg(esp_idf_soc_i2s_supports_pdm_rx)] +#[cfg_attr( + feature = "nightly", + doc(cfg(all(any(esp32, esp32s3), not(esp_idf_version_major = "4")))) +)] +impl<'d> I2sDriver<'d, I2sRx> { + /// Create a new pulse density modulation (PDM) mode driver for the given I2S peripheral with only the receive + /// channel open. + #[allow(clippy::too_many_arguments)] + pub fn new_pdm_rx( + _i2s: impl Peripheral

+ 'd, + rx_cfg: &config::PdmRxConfig, + clk: impl Peripheral

+ 'd, + din: impl Peripheral

+ 'd, + ) -> Result { + let chan_cfg = rx_cfg.channel_cfg.as_sdk(I2S::port()); + + let this = Self::internal_new::(&chan_cfg, true, false)?; + + let rx_cfg = rx_cfg.as_sdk(clk.into_ref(), din.into_ref()); + + // Safety: rx.chan_handle is a valid, non-null i2s_chan_handle_t, + // and &rx_cfg is a valid pointer to an i2s_pdm_rx_config_t. + unsafe { + // Open the RX channel. + esp!(esp_idf_sys::i2s_channel_init_pdm_rx_mode( + this.rx_handle, + &rx_cfg + ))?; + } + + Ok(this) + } + + /// Create a new pulse density modulation (PDM) mode driver for the given I2S peripheral with only the receive + /// channel open using multiple DIN pins to receive data. + #[cfg(not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")))] + #[cfg_attr( + feature = "nightly", + doc(cfg(not(all(esp_idf_version_major = "5", esp_idf_version_minor = "0")))) + )] + #[allow(clippy::too_many_arguments)] + pub fn new_pdm_rx_multi( + _i2s: I2SP, + rx_cfg: &config::PdmRxConfig, + clk: CLKP, + dins: [DINP; DINC], + ) -> Result + where + I2SP: Peripheral

+ 'd, + I2S: I2s, + CLKP: Peripheral

+ 'd, + CLK: OutputPin, + DINP: Peripheral

+ 'd, + DIN: InputPin + Sized, + { + let chan_cfg = rx_cfg.channel_cfg.as_sdk(I2S::port()); + + let this = Self::internal_new::(&chan_cfg, true, true)?; + + // Safety: assume_init is safe to call because we are only claiming to have "initialized" the + // MaybeUninit, not the PeripheralRef itself. + let mut dins_ref: [MaybeUninit>; DINC] = + unsafe { MaybeUninit::uninit().assume_init() }; + for (i, din) in IntoIterator::into_iter(dins).enumerate() { + dins_ref[i].write(din.into_ref()); + } + + // Safety: everything is initialized, so we can safely transmute the array to the initialized type. + let dins_ref = unsafe { + core::mem::transmute_copy::<_, [crate::peripheral::PeripheralRef<'d, DIN>; DINC]>( + &dins_ref, + ) + }; + + // Create the channel configuration. + let rx_cfg = rx_cfg.as_sdk_multi(clk.into_ref(), &dins_ref); + + // Safety: rx.chan_handle is a valid, non-null i2s_chan_handle_t, + // and &rx_cfg is a valid pointer to an i2s_pdm_rx_config_t. + unsafe { + // Open the RX channel. + esp!(esp_idf_sys::i2s_channel_init_pdm_rx_mode( + this.rx_handle, + &rx_cfg + ))?; + } + + Ok(this) + } +} + +#[cfg(all(any(esp32, esp32s3), esp_idf_version_major = "4"))] +#[cfg_attr( + feature = "nightly", + doc(cfg(all(any(esp32, esp32s3), esp_idf_version_major = "4"))) +)] +impl<'d> I2sDriver<'d, I2sRx> { + /// Create a new pulse density modulation (PDM) mode driver for the given I2S peripheral with only the receive + /// channel open. + #[allow(clippy::too_many_arguments)] + pub fn new_pdm_rx( + _i2s: impl Peripheral

+ 'd, + rx_cfg: &config::PdmRxConfig, + clk: impl Peripheral

+ 'd, + din: impl Peripheral

+ 'd, + ) -> Result { + let driver_cfg = rx_cfg.as_sdk(); + + let this = Self::internal_new::(&driver_cfg)?; + + // Set the rate and downsampling configuration. + let downsample = rx_cfg.clk_cfg.downsample_mode.as_sdk(); + unsafe { + esp!(i2s_set_pdm_rx_down_sample(I2S::port(), downsample))?; + } + + // Set the pin configuration. + let pin_cfg = i2s_pin_config_t { + bck_io_num: clk.into_ref().pin(), + data_in_num: din.into_ref().pin(), + data_out_num: -1, + mck_io_num: -1, + ws_io_num: -1, + }; + + // Safety: &pin_cfg is a valid pointer to an i2s_pin_config_t. + unsafe { + esp!(i2s_set_pin(I2S::port(), &pin_cfg))?; + } + + Ok(this) + } +} + +#[cfg(esp_idf_soc_i2s_supports_pdm_tx)] +#[cfg_attr( + feature = "nightly", + doc(cfg(all( + any(esp32, esp32s3, esp32c3, esp32c6, esp32h2), + not(esp_idf_version_major = "4") + ))) +)] +impl<'d> I2sDriver<'d, I2sTx> { + /// Create a new pulse density modulation (PDM) mode driver for the given I2S peripheral with only the transmit + /// channel open. + #[allow(clippy::too_many_arguments)] + pub fn new_pdm_tx( + _i2s: impl Peripheral

+ 'd, + tx_cfg: &config::PdmTxConfig, + clk: impl Peripheral

+ 'd, + dout: impl Peripheral

+ 'd, + #[cfg(esp_idf_soc_i2s_hw_version_2)] dout2: Option< + impl Peripheral

+ 'd, + >, + ) -> Result { + let chan_cfg = tx_cfg.channel_cfg.as_sdk(I2S::port()); + + let this = Self::internal_new::(&chan_cfg, false, true)?; + + // Create the channel configuration. + let tx_cfg = tx_cfg.as_sdk( + clk.into_ref(), + dout.into_ref(), + #[cfg(esp_idf_soc_i2s_hw_version_2)] + dout2.map(|dout2| dout2.into_ref()), + ); + + // Safety: tx.chan_handle is a valid, non-null i2s_chan_handle_t, + // and &tx_cfg is a valid pointer to an i2s_pdm_tx_config_t. + unsafe { + // Open the TX channel. + esp!(esp_idf_sys::i2s_channel_init_pdm_tx_mode( + this.tx_handle, + &tx_cfg + ))?; + } + + Ok(this) + } +} + +#[cfg(all( + esp_idf_version_major = "4", + any(esp32, esp32s3, esp32c3, esp32c6, esp32h2) +))] +#[cfg_attr( + feature = "nightly", + doc(cfg(all( + any(esp32, esp32s3, esp32c3, esp32c6, esp32h2), + esp_idf_version_major = "4" + ))) +)] +impl<'d> I2sDriver<'d, I2sTx> { + /// Create a new pulse density modulation (PDM) mode driver for the given I2S peripheral with only the transmit + /// channel open. + #[allow(clippy::too_many_arguments)] + pub fn new_pdm_tx( + _i2s: impl Peripheral

+ 'd, + tx_cfg: &config::PdmTxConfig, + clk: impl Peripheral

+ 'd, + dout: impl Peripheral

+ 'd, + ) -> Result { + let driver_cfg = tx_cfg.as_sdk(); + + let this = Self::internal_new::(&driver_cfg)?; + + // Set the upsampling configuration. + let upsample = tx_cfg.clk_cfg.as_sdk(); + unsafe { + esp!(esp_idf_sys::i2s_set_pdm_tx_up_sample( + I2S::port(), + &upsample + ))?; + } + + // Set the pin configuration. + let pin_cfg = i2s_pin_config_t { + bck_io_num: clk.into_ref().pin(), + data_in_num: -1, + data_out_num: dout.into_ref().pin(), + mck_io_num: -1, + ws_io_num: -1, + }; + + // Safety: &pin_cfg is a valid pointer to an i2s_pin_config_t. + unsafe { + esp!(i2s_set_pin(I2S::port(), &pin_cfg))?; + } + + Ok(this) + } +} diff --git a/src/i2s/std.rs b/src/i2s/std.rs new file mode 100644 index 00000000000..71a3f925194 --- /dev/null +++ b/src/i2s/std.rs @@ -0,0 +1,870 @@ +//! Standard mode driver for the ESP32 I2S peripheral. +//! +//! # Microcontroller support for Standard mode +//! +//! | Microcontroller | Standard Rx | Standard Tx | +//! |--------------------|-----------------|-----------------| +//! | ESP32 | I2S0, I2S1 | I2S0, I2S11 | +//! | ESP32-S2 | I2S0 | I2S0 | +//! | ESP32-S3 | I2S0, I2S1 | I2S0, I2S1 | +//! | ESP32-C2 (ESP8684) | _not supported_ | _not supported_ | +//! | ESP32-C3 | I2S0 | I2S0 | +//! | ESP32-C6 | I2S0 | I2S0 | +//! | ESP32-H2 | I2S0 | I2S0 | + +use super::*; +use crate::{gpio::*, peripheral::*}; + +use esp_idf_sys::*; + +pub(super) mod config { + #[allow(unused)] + use crate::{gpio::*, i2s::config::*, peripheral::*}; + use esp_idf_sys::*; + + /// Standard mode configuration for the I2S peripheral. + pub struct StdConfig { + /// The base channel configuration. + #[allow(dead_code)] + pub(super) channel_cfg: Config, + + /// Standard mode channel clock configuration. + #[allow(dead_code)] + clk_cfg: StdClkConfig, + + /// Standard mode channel slot configuration. + #[allow(dead_code)] + slot_cfg: StdSlotConfig, + + /// Standard mode channel GPIO configuration. + #[cfg(not(esp_idf_version_major = "4"))] + #[allow(dead_code)] + gpio_cfg: StdGpioConfig, + } + + impl StdConfig { + /// Create a new standard mode channel configuration from the given clock configuration, slot configuration, + /// and GPIO configuration. + pub fn new( + channel_cfg: Config, + clk_cfg: StdClkConfig, + slot_cfg: StdSlotConfig, + #[cfg(not(esp_idf_version_major = "4"))] gpio_cfg: StdGpioConfig, + ) -> Self { + Self { + channel_cfg, + clk_cfg, + slot_cfg, + #[cfg(not(esp_idf_version_major = "4"))] + gpio_cfg, + } + } + + /// Create a new standard mode channel configuration for the Philips I2S protocol with the specified sample + /// rate and bits per sample, in stereo mode, with MCLK set to 256 times the sample rate. + #[inline(always)] + pub fn philips(sample_rate_hz: u32, bits_per_sample: DataBitWidth) -> Self { + Self { + channel_cfg: Config::default(), + clk_cfg: StdClkConfig::from_sample_rate_hz(sample_rate_hz), + slot_cfg: StdSlotConfig::philips_slot_default(bits_per_sample, SlotMode::Stereo), + #[cfg(not(esp_idf_version_major = "4"))] + gpio_cfg: StdGpioConfig::default(), + } + } + + /// Create a new standard mode channel configuration for the PCM I2S protocol with the specified sample rate + /// and bits per sample, in stereo mode, with MCLK set to 256 times the sample rate. + #[inline(always)] + pub fn pcm(sample_rate_hz: u32, bits_per_sample: DataBitWidth) -> Self { + Self { + channel_cfg: Config::default(), + clk_cfg: StdClkConfig::from_sample_rate_hz(sample_rate_hz), + slot_cfg: StdSlotConfig::pcm_slot_default(bits_per_sample, SlotMode::Stereo), + #[cfg(not(esp_idf_version_major = "4"))] + gpio_cfg: StdGpioConfig::default(), + } + } + + /// Create a new standard mode channel configuration for the MSB I2S protocol with the specified sample rate + /// and bits per sample, in stereo mode, with MCLK set to 256 times the sample rate. + #[inline(always)] + pub fn msb(sample_rate_hz: u32, bits_per_sample: DataBitWidth) -> Self { + Self { + channel_cfg: Config::default(), + clk_cfg: StdClkConfig::from_sample_rate_hz(sample_rate_hz), + slot_cfg: StdSlotConfig::msb_slot_default(bits_per_sample, SlotMode::Stereo), + #[cfg(not(esp_idf_version_major = "4"))] + gpio_cfg: StdGpioConfig::default(), + } + } + + /// Convert to the ESP-IDF SDK `i2s_std_config_t` representation. + #[cfg(not(esp_idf_version_major = "4"))] + #[inline(always)] + pub(crate) fn as_sdk<'d>( + &self, + bclk: PeripheralRef<'d, impl InputPin + OutputPin>, + din: Option>, + dout: Option>, + mclk: Option>, + ws: PeripheralRef<'d, impl InputPin + OutputPin>, + ) -> i2s_std_config_t { + i2s_std_config_t { + clk_cfg: self.clk_cfg.as_sdk(), + slot_cfg: self.slot_cfg.as_sdk(), + gpio_cfg: self.gpio_cfg.as_sdk(bclk, din, dout, mclk, ws), + } + } + + /// Convert to the ESP-IDF SDK `i2s_driver_config_t` representation. + /// + /// # Note + /// The mode field is not fully set by this function. Only the controller/target field is set. Before using, + /// the following bits must be considered: `I2S_MODE_TX`, `I2S_MODE_RX`, `I2S_MODE_DAC_BUILT_IN`, and + /// `I2S_MODE_ADC_BUILT_IN`. (`I2S_MODE_PDM` should not be used here.) + #[cfg(esp_idf_version_major = "4")] + pub(crate) fn as_sdk(&self) -> i2s_driver_config_t { + let chan_fmt = match self.slot_cfg.slot_mode { + SlotMode::Stereo => i2s_channel_fmt_t_I2S_CHANNEL_FMT_RIGHT_LEFT, + SlotMode::Mono => match self.slot_cfg.slot_mask { + StdSlotMask::Both => i2s_channel_fmt_t_I2S_CHANNEL_FMT_RIGHT_LEFT, + StdSlotMask::Left => i2s_channel_fmt_t_I2S_CHANNEL_FMT_ONLY_LEFT, + StdSlotMask::Right => i2s_channel_fmt_t_I2S_CHANNEL_FMT_ONLY_RIGHT, + }, + }; + + i2s_driver_config_t { + mode: self.channel_cfg.role.as_sdk(), + sample_rate: self.clk_cfg.sample_rate_hz, + bits_per_sample: self.slot_cfg.data_bit_width.as_sdk(), + channel_format: chan_fmt, + communication_format: self.slot_cfg.comm_fmt.as_sdk(), + intr_alloc_flags: 1 << 1, // ESP_INTR_FLAG_LEVEL1 + dma_buf_count: self.channel_cfg.dma_buffer_count as i32, + dma_buf_len: self.channel_cfg.frames_per_buffer as i32, + #[cfg(any(esp32, esp32s2))] + use_apll: matches!(self.clk_cfg.clk_src, ClockSource::Apll), + #[cfg(not(any(esp32, esp32s2)))] + use_apll: false, + tx_desc_auto_clear: self.channel_cfg.auto_clear, + fixed_mclk: 0, + mclk_multiple: self.clk_cfg.mclk_multiple.as_sdk(), + bits_per_chan: self.slot_cfg.slot_bit_width.as_sdk(), + + // The following are TDM-only fields and are not present on chips that don't support TDM mode. + // There's no cfg option for this (it's a constant in esp-idf-sys). + #[cfg(not(any(esp32, esp32s2)))] + chan_mask: 0, + #[cfg(not(any(esp32, esp32s2)))] + total_chan: 0, + #[cfg(not(any(esp32, esp32s2)))] + left_align: self.slot_cfg.left_align, + #[cfg(not(any(esp32, esp32s2)))] + big_edin: self.slot_cfg.big_endian, + #[cfg(not(any(esp32, esp32s2)))] + bit_order_msb: !self.slot_cfg.bit_order_lsb, + #[cfg(not(any(esp32, esp32s2)))] + skip_msk: true, + } + } + } + + /// Standard mode channel clock configuration. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct StdClkConfig { + /// I2S sample rate. + sample_rate_hz: u32, + + /// Clock source. + clk_src: ClockSource, + + /// The multiple of MCLK to the sample rate. + mclk_multiple: MclkMultiple, + } + + impl StdClkConfig { + /// Create a standard clock configuration with the specified rate (in Hz), clock source, and MCLK multiple of + /// the sample rate. + #[inline(always)] + pub fn new(sample_rate_hz: u32, clk_src: ClockSource, mclk_multiple: MclkMultiple) -> Self { + Self { + sample_rate_hz, + clk_src, + mclk_multiple, + } + } + + /// Create a standard clock configuration with the specified rate in Hz. This will set the clock source to + /// PLL_F160M and the MCLK multiple to 256 times the sample rate. + /// + /// # Note + /// Set the mclk_multiple to [MclkMultiple::M384] when using 24-bit data width. Otherwise, the sample rate + /// might be imprecise since the BCLK division is not an integer. + #[inline(always)] + pub fn from_sample_rate_hz(rate: u32) -> Self { + Self { + sample_rate_hz: rate, + clk_src: ClockSource::default(), + mclk_multiple: MclkMultiple::M256, + } + } + + /// Set the clock source on this standard clock configuration. + #[inline(always)] + pub fn clk_src(mut self, clk_src: ClockSource) -> Self { + self.clk_src = clk_src; + self + } + + /// Set the MCLK multiple on this standard clock configuration. + #[inline(always)] + pub fn mclk_multiple(mut self, mclk_multiple: MclkMultiple) -> Self { + self.mclk_multiple = mclk_multiple; + self + } + + /// Convert to the ESP-IDF SDK `i2s_std_clk_config_t` representation. + #[cfg(not(esp_idf_version_major = "4"))] + #[allow(clippy::needless_update)] + #[inline(always)] + pub(crate) fn as_sdk(&self) -> i2s_std_clk_config_t { + i2s_std_clk_config_t { + sample_rate_hz: self.sample_rate_hz, + clk_src: self.clk_src.as_sdk(), + mclk_multiple: self.mclk_multiple.as_sdk(), + ..Default::default() + } + } + } + + /// The communication format used by the v4 driver. + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + pub enum StdCommFormat { + /// Standard I2S/Philips format. + #[default] + Philips, + + /// MSB-aligned format (data present at first bit clock). + Msb, + + /// PCM short standard. Word select is one bit clock. + PcmShort, + + /// PCM long standard. Word select is the same as the data bit width. + PcmLong, + } + + impl StdCommFormat { + #[cfg(esp_idf_version_major = "4")] + #[inline(always)] + pub(in crate::i2s) fn as_sdk(&self) -> i2s_comm_format_t { + match self { + Self::Philips => i2s_comm_format_t_I2S_COMM_FORMAT_STAND_I2S, + Self::Msb => i2s_comm_format_t_I2S_COMM_FORMAT_STAND_MSB, + Self::PcmShort => i2s_comm_format_t_I2S_COMM_FORMAT_PCM_SHORT, + Self::PcmLong => i2s_comm_format_t_I2S_COMM_FORMAT_PCM_LONG, + } + } + } + + /// Standard mode GPIO (general purpose input/output) configuration. + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + pub struct StdGpioConfig { + /// Invert the BCLK signal. + bclk_invert: bool, + + /// Invert the MCLK signal. + mclk_invert: bool, + + /// Invert the WS signal. + ws_invert: bool, + } + + impl StdGpioConfig { + /// Create a new standard mode GPIO configuration with the specified inversion flags for BCLK, MCLK, and WS. + pub fn new(bclk_invert: bool, mclk_invert: bool, ws_invert: bool) -> Self { + Self { + bclk_invert, + mclk_invert, + ws_invert, + } + } + + /// Set the BCLK inversion flag on this standard GPIO configuration. + #[inline(always)] + pub fn bclk_invert(mut self, bclk_invert: bool) -> Self { + self.bclk_invert = bclk_invert; + self + } + + /// Set the MCLK inversion flag on this standard GPIO configuration. + #[inline(always)] + pub fn mclk_invert(mut self, mclk_invert: bool) -> Self { + self.mclk_invert = mclk_invert; + self + } + + /// Set the WS inversion flag on this standard GPIO configuration. + #[inline(always)] + pub fn ws_invert(mut self, ws_invert: bool) -> Self { + self.ws_invert = ws_invert; + self + } + + /// Convert to the ESP-IDF SDK `i2s_std_gpio_config_t` representation. + #[cfg(not(esp_idf_version_major = "4"))] + pub(crate) fn as_sdk<'d>( + &self, + bclk: PeripheralRef<'d, impl InputPin + OutputPin>, + din: Option>, + dout: Option>, + mclk: Option>, + ws: PeripheralRef<'d, impl InputPin + OutputPin>, + ) -> i2s_std_gpio_config_t { + let invert_flags = i2s_std_gpio_config_t__bindgen_ty_1 { + _bitfield_1: i2s_std_gpio_config_t__bindgen_ty_1::new_bitfield_1( + self.mclk_invert as u32, + self.bclk_invert as u32, + self.ws_invert as u32, + ), + ..Default::default() + }; + + i2s_std_gpio_config_t { + bclk: bclk.pin(), + din: if let Some(din) = din { din.pin() } else { -1 }, + dout: if let Some(dout) = dout { + dout.pin() + } else { + -1 + }, + mclk: if let Some(mclk) = mclk { + mclk.pin() + } else { + -1 + }, + ws: ws.pin(), + invert_flags, + } + } + } + + /// Standard mode channel slot configuration. + /// + /// To create a slot configuration, use [`StdSlotConfig::philips_slot_default`], [`StdSlotConfig::pcm_slot_default`], or + /// [`StdSlotConfig::msb_slot_default`], then customize it as needed. + /// + /// # Note + /// The `slot_mode` and `slot_mask` cause the data to be interpreted in different ways, as noted below. + /// WS is the "word select" signal, sometimes called LRCLK (left/right clock). + /// + /// ## Transmit + /// + /// Assuming the buffered data contains the following samples (where a sample may be 1, 2, 3, or 4 bytes, depending + /// on `data_bit_width`): + /// + /// | **`d[0]`** | **`d[1]`** | **`d[2]`** | **`d[3]`** | **`d[4]`** | **`d[5]`** | **`d[6]`** | **`d[7]`** | + /// |------------|------------|------------|------------|------------|------------|------------|------------| + /// | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | + /// + /// The actual data on the line will be: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
slot_modeslot_maskTransmitted Data
WS LowWS HighWS LowWS HighWS LowWS HighWS LowWS High
MonoLeft 110120130140
Right011012013014
Both 1111121213131414
StereoLeft 110130150170
Right012014016018
Both 1112131415161718
+ /// + /// + /// ## Receive + /// + /// Assuming the received data contains the following samples (where a sample may be 8, 16, 24, or 32 bits, depending on `data_bit_width`): + /// + /// | **WS Low** | **WS High** | **WS Low** | **WS High** | **WS Low** | **WS High** | **WS Low** | **WS High** | | + /// |-------------|-------------|-------------|-------------|-------------|-------------|-------------|-------------|-----| + /// | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ... | + /// + /// The actual data in the buffer will be (1-4 bytes, depending on `data_bit_width`): + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
slot_modeslot_maskBuffer Contents
d[0]d[1]d[2]d[3]d[4]d[5]d[6]d[7]
Mono Left 1113151719212325
Right1214161820222426
Both Unspecified behavior
StereoAny 1112131415161718
+ /// + /// For details, refer to the + /// _ESP-IDF Programming Guide_ details for your specific microcontroller: + /// * ESP32: [STD Tx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html#std-tx-mode) / [STD Rx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2s.html#std-rx-mode). + /// * ESP32-S2: [STD Tx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/i2s.html#std-tx-mode) / [STD Rx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/i2s.html#std-tx-mode) + /// * ESP32-S3: [STD Tx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#std-tx-mode) / [STD Rx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#std-tx-mode) + /// * ESP32-C3: [STD Tx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/i2s.html#std-tx-mode) / [STD Rx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/i2s.html#std-tx-mode). + /// * ESP32-C6: [STD Tx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c6/api-reference/peripherals/i2s.html#std-tx-mode) / [STD Rx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c6/api-reference/peripherals/i2s.html#std-tx-mode). + /// * ESP32-H2: [STD Tx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32h2/api-reference/peripherals/i2s.html#std-tx-mode) / [STD Rx Mode](https://docs.espressif.com/projects/esp-idf/en/latest/esp32h2/api-reference/peripherals/i2s.html#std-tx-mode). + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct StdSlotConfig { + /// I2S sample data bit width (valid data bits per sample). + data_bit_width: DataBitWidth, + + /// I2S slot bit width (total bits per slot). + slot_bit_width: SlotBitWidth, + + /// Mono or stereo mode operation. + slot_mode: SlotMode, + + /// Are we using the left, right, or both data slots? + slot_mask: StdSlotMask, + + /// The word select (WS) signal width, in terms of the bit clock (BCK) periods. + #[cfg(not(esp_idf_version_major = "4"))] + ws_width: u32, + + /// The word select signal polarity; true enables the light lever first. + #[cfg(not(esp_idf_version_major = "4"))] + ws_polarity: bool, + + /// Set to enable the additional bit-shift needed in Philips mode. + #[cfg(not(esp_idf_version_major = "4"))] + bit_shift: bool, + + /// ESP32/ESP32S2 only: place the right slot data in the MSB in the FIFO. + #[cfg(all(any(esp32, esp32s2), not(esp_idf_version_major = "4")))] + msb_right: bool, + + /// The communication format used by the driver. + #[cfg(esp_idf_version_major = "4")] + comm_fmt: StdCommFormat, + + /// Non-ESP32/ESP32S2: enable left-alignment + #[cfg(not(any(esp32, esp32s2)))] + left_align: bool, + + /// Non-ESP32/ESP32S2: Enable big-endian. + #[cfg(not(any(esp32, esp32s2)))] + big_endian: bool, + + /// Non-ESP32/ESP32S2: Enable LSB-first. + #[cfg(not(any(esp32, esp32s2)))] + bit_order_lsb: bool, + } + + impl StdSlotConfig { + /// Update the data bit width on this standard slot configuration. + #[inline(always)] + pub fn data_bit_width(mut self, data_bit_width: DataBitWidth) -> Self { + self.data_bit_width = data_bit_width; + self + } + + /// Update the slot bit width on this standard slot configuration. + #[inline(always)] + pub fn slot_bit_width(mut self, slot_bit_width: SlotBitWidth) -> Self { + self.slot_bit_width = slot_bit_width; + self + } + + /// Update the slot mode and mask on this standard slot configuration. + #[inline(always)] + pub fn slot_mode_mask(mut self, slot_mode: SlotMode, slot_mask: StdSlotMask) -> Self { + self.slot_mode = slot_mode; + self.slot_mask = slot_mask; + self + } + + /// Update the word select signal width on this standard slot configuration. + #[cfg(not(esp_idf_version_major = "4"))] + #[inline(always)] + pub fn ws_width(mut self, ws_width: u32) -> Self { + self.ws_width = ws_width; + self + } + + /// Update the word select signal polarity on this standard slot configuration. + #[cfg(not(esp_idf_version_major = "4"))] + #[inline(always)] + pub fn ws_polarity(mut self, ws_polarity: bool) -> Self { + self.ws_polarity = ws_polarity; + self + } + + /// Update the bit shift flag on this standard slot configuration. + #[cfg(not(esp_idf_version_major = "4"))] + #[inline(always)] + pub fn bit_shift(mut self, bit_shift: bool) -> Self { + self.bit_shift = bit_shift; + self + } + + /// Update the MSB-right flag on this standard slot configuration. + #[cfg(all(any(esp32, esp32s2), not(esp_idf_version_major = "4")))] + #[inline(always)] + pub fn msb_right(mut self, msb_right: bool) -> Self { + self.msb_right = msb_right; + self + } + + /// Update the communication format on this standard slot configuration. + #[cfg(esp_idf_version_major = "4")] + #[inline(always)] + pub fn comm_fmt(mut self, comm_fmt: StdCommFormat) -> Self { + self.comm_fmt = comm_fmt; + self + } + + /// Update the left-alignment flag on this standard slot configuration. + #[cfg(not(any(esp32, esp32s2)))] + #[inline(always)] + pub fn left_align(mut self, left_align: bool) -> Self { + self.left_align = left_align; + self + } + + /// Update the big-endian flag on this standard slot configuration. + #[cfg(not(any(esp32, esp32s2)))] + #[inline(always)] + pub fn big_endian(mut self, big_endian: bool) -> Self { + self.big_endian = big_endian; + self + } + + /// Update the LSB-first flag on this standard slot configuration. + #[cfg(not(any(esp32, esp32s2)))] + #[inline(always)] + pub fn bit_order_lsb(mut self, bit_order_lsb: bool) -> Self { + self.bit_order_lsb = bit_order_lsb; + self + } + + /// Configure in Philips format in 2 slots. + pub fn philips_slot_default(bits_per_sample: DataBitWidth, slot_mode: SlotMode) -> Self { + let slot_mask = if slot_mode == SlotMode::Mono { + StdSlotMask::Left + } else { + StdSlotMask::Both + }; + + Self { + data_bit_width: bits_per_sample, + slot_bit_width: SlotBitWidth::Auto, + slot_mode, + slot_mask, + #[cfg(not(esp_idf_version_major = "4"))] + ws_width: bits_per_sample.into(), + #[cfg(not(esp_idf_version_major = "4"))] + ws_polarity: false, + #[cfg(not(esp_idf_version_major = "4"))] + bit_shift: true, + #[cfg(all(esp32, not(esp_idf_version_major = "4")))] + msb_right: bits_per_sample <= DataBitWidth::Bits16, + #[cfg(all(esp32s2, not(esp_idf_version_major = "4")))] + msb_right: true, + #[cfg(esp_idf_version_major = "4")] + comm_fmt: StdCommFormat::Philips, + #[cfg(not(any(esp32, esp32s2)))] + left_align: false, + #[cfg(not(any(esp32, esp32s2)))] + big_endian: false, + #[cfg(not(any(esp32, esp32s2)))] + bit_order_lsb: false, + } + } + + /// Configure in PCM (short) format in 2 slots. + pub fn pcm_slot_default(bits_per_sample: DataBitWidth, slot_mode: SlotMode) -> Self { + let slot_mask = if slot_mode == SlotMode::Mono { + StdSlotMask::Left + } else { + StdSlotMask::Both + }; + + Self { + data_bit_width: bits_per_sample, + slot_bit_width: SlotBitWidth::Auto, + slot_mode, + slot_mask, + #[cfg(not(esp_idf_version_major = "4"))] + ws_width: 1, + #[cfg(not(esp_idf_version_major = "4"))] + ws_polarity: true, + #[cfg(not(esp_idf_version_major = "4"))] + bit_shift: true, + #[cfg(all(esp32, not(esp_idf_version_major = "4")))] + msb_right: bits_per_sample <= DataBitWidth::Bits16, + #[cfg(all(esp32s2, not(esp_idf_version_major = "4")))] + msb_right: true, + #[cfg(esp_idf_version_major = "4")] + comm_fmt: StdCommFormat::PcmShort, + #[cfg(not(any(esp32, esp32s2)))] + left_align: false, + #[cfg(not(any(esp32, esp32s2)))] + big_endian: false, + #[cfg(not(any(esp32, esp32s2)))] + bit_order_lsb: false, + } + } + + /// Configure in MSB format in 2 slots. + pub fn msb_slot_default(bits_per_sample: DataBitWidth, slot_mode: SlotMode) -> Self { + let slot_mask = if slot_mode == SlotMode::Mono { + StdSlotMask::Left + } else { + StdSlotMask::Both + }; + + Self { + data_bit_width: bits_per_sample, + slot_bit_width: SlotBitWidth::Auto, + slot_mode, + slot_mask, + #[cfg(not(esp_idf_version_major = "4"))] + ws_width: bits_per_sample.into(), + #[cfg(not(esp_idf_version_major = "4"))] + ws_polarity: false, + #[cfg(not(esp_idf_version_major = "4"))] + bit_shift: false, + #[cfg(all(esp32, not(esp_idf_version_major = "4")))] + msb_right: bits_per_sample <= DataBitWidth::Bits16, + #[cfg(all(esp32s2, not(esp_idf_version_major = "4")))] + msb_right: true, + #[cfg(esp_idf_version_major = "4")] + comm_fmt: StdCommFormat::Msb, + #[cfg(not(any(esp32, esp32s2)))] + left_align: false, + #[cfg(not(any(esp32, esp32s2)))] + big_endian: false, + #[cfg(not(any(esp32, esp32s2)))] + bit_order_lsb: false, + } + } + + /// Convert to the ESP-IDF SDK `i2s_std_slot_config_t` representation. + #[cfg(not(esp_idf_version_major = "4"))] + pub(crate) fn as_sdk(&self) -> i2s_std_slot_config_t { + i2s_std_slot_config_t { + data_bit_width: self.data_bit_width.as_sdk(), + slot_bit_width: self.slot_bit_width.as_sdk(), + slot_mode: self.slot_mode.as_sdk(), + slot_mask: self.slot_mask.as_sdk(), + ws_width: self.ws_width, + ws_pol: self.ws_polarity, + bit_shift: self.bit_shift, + #[cfg(any(esp32, esp32s2))] + msb_right: self.msb_right, + #[cfg(not(any(esp32, esp32s2)))] + left_align: self.left_align, + #[cfg(not(any(esp32, esp32s2)))] + big_endian: self.big_endian, + #[cfg(not(any(esp32, esp32s2)))] + bit_order_lsb: self.bit_order_lsb, + } + } + } + + /// I2S slot selection in standard mode. + /// + /// The default is `StdSlotMask::Both`. + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + pub enum StdSlotMask { + /// I2S transmits or receives the left slot. + Left, + + /// I2S transmits or receives the right slot. + Right, + + /// I2S transmits or receives both slots. + #[default] + Both, + } + + impl StdSlotMask { + /// Convert to the ESP-IDF SDK `i2s_std_slot_mask_t` representation. + #[cfg(not(esp_idf_version_major = "4"))] + #[inline(always)] + pub(crate) fn as_sdk(&self) -> i2s_std_slot_mask_t { + match self { + Self::Left => 1 << 0, + Self::Right => 1 << 1, + Self::Both => (1 << 0) | (1 << 1), + } + } + } +} + +impl<'d, Dir> I2sDriver<'d, Dir> { + #[cfg(not(esp_idf_version_major = "4"))] + #[allow(clippy::too_many_arguments)] + fn internal_new_std( + _i2s: impl Peripheral

+ 'd, + config: &config::StdConfig, + rx: bool, + tx: bool, + bclk: impl Peripheral

+ 'd, + din: Option + 'd>, + dout: Option + 'd>, + mclk: Option + 'd>, + ws: impl Peripheral

+ 'd, + ) -> Result { + let chan_cfg = config.channel_cfg.as_sdk(I2S::port()); + + let this = Self::internal_new::(&chan_cfg, rx, tx)?; + + // Create the channel configuration. + let std_config = config.as_sdk( + bclk.into_ref(), + din.map(|d_in| d_in.into_ref()), + dout.map(|d_out| d_out.into_ref()), + mclk.map(|m_clk| m_clk.into_ref()), + ws.into_ref(), + ); + + if rx { + unsafe { + // Open the RX channel. + esp!(i2s_channel_init_std_mode(this.rx_handle, &std_config))?; + } + } + + if tx { + unsafe { + // Open the TX channel. + esp!(i2s_channel_init_std_mode(this.tx_handle, &std_config))?; + } + } + + Ok(this) + } + + #[cfg(esp_idf_version_major = "4")] + #[allow(clippy::too_many_arguments)] + pub fn internal_new_std( + _i2s: impl Peripheral

+ 'd, + config: &config::StdConfig, + rx: bool, + tx: bool, + bclk: impl Peripheral

+ 'd, + din: Option + 'd>, + dout: Option + 'd>, + mclk: Option + 'd>, + ws: impl Peripheral

+ 'd, + ) -> Result { + let mut driver_cfg = config.as_sdk(); + + if rx { + driver_cfg.mode |= i2s_mode_t_I2S_MODE_RX; + } + + if tx { + driver_cfg.mode |= i2s_mode_t_I2S_MODE_TX; + } + + let this = Self::internal_new::(&driver_cfg)?; + + // Set the pin configuration. + let pin_cfg = i2s_pin_config_t { + bck_io_num: bclk.into_ref().pin(), + data_in_num: din.map(|din| din.into_ref().pin()).unwrap_or(-1), + data_out_num: dout.map(|dout| dout.into_ref().pin()).unwrap_or(-1), + mck_io_num: mclk.map(|mclk| mclk.into_ref().pin()).unwrap_or(-1), + ws_io_num: ws.into_ref().pin(), + }; + + // Safety: &pin_cfg is a valid pointer to an i2s_pin_config_t. + unsafe { + esp!(i2s_set_pin(I2S::port(), &pin_cfg))?; + } + + Ok(this) + } +} + +impl<'d> I2sDriver<'d, I2sBiDir> { + /// Create a new standard mode driver for the given I2S peripheral with both the receive and transmit channels open. + #[allow(clippy::too_many_arguments)] + pub fn new_std_bidir( + i2s: impl Peripheral

+ 'd, + config: &config::StdConfig, + bclk: impl Peripheral

+ 'd, + din: impl Peripheral

+ 'd, + dout: impl Peripheral

+ 'd, + mclk: Option + 'd>, + ws: impl Peripheral

+ 'd, + ) -> Result { + Self::internal_new_std( + i2s, + config, + true, + true, + bclk, + Some(din), + Some(dout), + mclk, + ws, + ) + } +} + +impl<'d> I2sDriver<'d, I2sRx> { + /// Create a new standard mode driver for the given I2S peripheral with only the receive channel open. + #[allow(clippy::too_many_arguments)] + pub fn new_std_rx( + i2s: impl Peripheral

+ 'd, + config: &config::StdConfig, + bclk: impl Peripheral

+ 'd, + din: impl Peripheral

+ 'd, + mclk: Option + 'd>, + ws: impl Peripheral

+ 'd, + ) -> Result { + Self::internal_new_std( + i2s, + config, + true, + false, + bclk, + Some(din), + AnyIOPin::none(), + mclk, + ws, + ) + } +} + +impl<'d> I2sDriver<'d, I2sTx> { + /// Create a new standard mode driver for the given I2S peripheral with only the transmit channel open. + #[allow(clippy::too_many_arguments)] + pub fn new_std_tx( + i2s: impl Peripheral

+ 'd, + config: &config::StdConfig, + bclk: impl Peripheral

+ 'd, + dout: impl Peripheral

+ 'd, + mclk: Option + 'd>, + ws: impl Peripheral

+ 'd, + ) -> Result { + Self::internal_new_std( + i2s, + config, + false, + true, + bclk, + AnyIOPin::none(), + Some(dout), + mclk, + ws, + ) + } +} diff --git a/src/i2s/tdm.rs b/src/i2s/tdm.rs new file mode 100644 index 00000000000..26e8840939e --- /dev/null +++ b/src/i2s/tdm.rs @@ -0,0 +1,1140 @@ +//! Time-division multiplexing (TDM) support for I2S. +use super::*; +use crate::{gpio::*, peripheral::*}; + +use esp_idf_sys::*; + +pub(super) mod config { + #[allow(unused)] + use crate::{gpio::*, i2s::config::*, peripheral::*}; + use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not}; + use esp_idf_sys::*; + + /// Automatic total number of slots, equivalent to the maximum active slot number. + pub const TDM_AUTO_SLOT_NUM: u32 = 0; + + /// Automatic word-select signal width, equivalent to half the width of a frame. + pub const TDM_AUTO_WS_WIDTH: u32 = 0; + + /// Time-division multiplexing (TDM) mode configuration for the I2S peripheral. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct TdmConfig { + /// The base channel configuration. + pub(super) channel_cfg: Config, + + /// TDM mode channel clock configuration. + clk_cfg: TdmClkConfig, + + /// TDM mode channel slot configuration. + slot_cfg: TdmSlotConfig, + + /// TDM mode channel data configuration. + #[cfg(not(esp_idf_version_major = "4"))] + gpio_cfg: TdmGpioConfig, + } + + impl TdmConfig { + /// Create a new TDM mode channel configuration from the given base configuration, clock configuration, slot + /// configuration, and GPIO configuration. + #[inline(always)] + pub fn new( + channel_cfg: Config, + clk_cfg: TdmClkConfig, + slot_cfg: TdmSlotConfig, + #[cfg(not(esp_idf_version_major = "4"))] gpio_cfg: TdmGpioConfig, + ) -> Self { + Self { + channel_cfg, + clk_cfg, + slot_cfg, + #[cfg(not(esp_idf_version_major = "4"))] + gpio_cfg, + } + } + + /// Convert to the ESP-IDF SDK `i2s_tdm_config_t` representation. + #[cfg(not(esp_idf_version_major = "4"))] + #[inline(always)] + pub(super) fn as_sdk<'d>( + &self, + bclk: PeripheralRef<'d, impl InputPin + OutputPin>, + din: Option>, + dout: Option>, + mclk: Option>, + ws: PeripheralRef<'d, impl InputPin + OutputPin>, + ) -> i2s_tdm_config_t { + i2s_tdm_config_t { + clk_cfg: self.clk_cfg.as_sdk(), + slot_cfg: self.slot_cfg.as_sdk(), + gpio_cfg: self.gpio_cfg.as_sdk(bclk, din, dout, mclk, ws), + } + } + + /// Convert to the ESP-IDF SDK `i2s_driver_config_t` representation. + /// + /// # Note + /// The mode field is not fully set by this function. Only the controller/target field is set. Before using, + /// the following bits must be considered: `I2S_MODE_TX`, `I2S_MODE_RX`. `I2S_MODE_DAC_BUILT_IN`, and + /// `I2S_MODE_ADC_BUILT_IN`, and `I2S_MODE_PDM` should not be used here. + #[cfg(esp_idf_version_major = "4")] + pub(crate) fn as_sdk(&self) -> i2s_driver_config_t { + i2s_driver_config_t { + mode: self.channel_cfg.role.as_sdk(), + sample_rate: self.clk_cfg.sample_rate_hz, + bits_per_sample: self.slot_cfg.data_bit_width.as_sdk(), + channel_format: i2s_channel_fmt_t_I2S_CHANNEL_FMT_MULTIPLE, // mono mode doesn't make sense in TDM + communication_format: self.slot_cfg.comm_fmt.as_sdk(), + intr_alloc_flags: 1 << 1, // ESP_INTR_FLAG_LEVEL1 + dma_buf_count: self.channel_cfg.dma_buffer_count as i32, + dma_buf_len: self.channel_cfg.frames_per_buffer as i32, + #[cfg(any(esp32, esp32s2))] + use_apll: matches!(self.clk_cfg.clk_src, ClockSource::Apll), + #[cfg(not(any(esp32, esp32s2)))] + use_apll: false, + tx_desc_auto_clear: self.channel_cfg.auto_clear, + fixed_mclk: 0, + mclk_multiple: self.clk_cfg.mclk_multiple.as_sdk(), + bits_per_chan: self.slot_cfg.slot_bit_width.as_sdk(), + chan_mask: self.slot_cfg.slot_mask.as_sdk(), + total_chan: self.slot_cfg.slot_mask.0.count_ones(), + left_align: self.slot_cfg.left_align, + big_edin: self.slot_cfg.big_endian, + bit_order_msb: !self.slot_cfg.bit_order_lsb, + skip_msk: self.slot_cfg.skip_mask, + } + } + } + + /// TDM mode channel clock configuration. + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct TdmClkConfig { + /// I2S sample rate. + sample_rate_hz: u32, + + /// Clock source. + clk_src: ClockSource, + + /// The multiple of MCLK to the sample rate. + mclk_multiple: MclkMultiple, + + /// The division from MCLK to BCLK. This is used only in I2S target (slave) mode. This should not be smaller + /// than TDM_BCLK_DIV_MIN (8). Increase this field if the target device is not able to transmit data in time. + #[cfg(all(esp_idf_version_major = "5", not(esp_idf_version_minor = "0")))] + bclk_div: u32, + } + + /// The minimum division from MCLK to BCLK. + pub const TDM_BCLK_DIV_MIN: u32 = 8; + + impl TdmClkConfig { + /// Create a TDM clock configuration with the specified rate (in Hz), clock source, and MCLK multiple of + /// the sample rate. + #[cfg(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0") + ))] + #[inline(always)] + pub fn new(sample_rate_hz: u32, clk_src: ClockSource, mclk_multiple: MclkMultiple) -> Self { + Self { + sample_rate_hz, + clk_src, + mclk_multiple, + } + } + + /// Create a TDM clock configuration with the specified rate (in Hz), clock source, and MCLK multiple of + /// the sample rate. + #[cfg(all(esp_idf_version_major = "5", not(esp_idf_version_minor = "0")))] + #[inline(always)] + pub fn new(sample_rate_hz: u32, clk_src: ClockSource, mclk_multiple: MclkMultiple) -> Self { + Self { + sample_rate_hz, + clk_src, + mclk_multiple, + bclk_div: TDM_BCLK_DIV_MIN, + } + } + + /// Create a TDM clock configuration with the specified rate in Hz. This will set the clock source to + /// PLL_F160M and the MCLK multiple to 256 times the sample rate. + /// + /// # Note + /// Set the mclk_multiple to [`MclkMultiple::M384`] when using 24-bit data width. Otherwise, the sample rate + /// might be imprecise since the BCLK division is not an integer. + #[cfg(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0") + ))] + #[inline(always)] + pub fn from_sample_rate_hz(rate: u32) -> Self { + Self { + sample_rate_hz: rate, + clk_src: ClockSource::default(), + mclk_multiple: MclkMultiple::M256, + } + } + + /// Create a TDM clock configuration with the specified rate in Hz. This will set the clock source to + /// PLL_F160M, MCLK multiple to 256 times the sample rate, and MCLK to BCLK division to 8. + /// + /// # Note + /// Set the mclk_multiple to [MclkMultiple::M384] when using 24-bit data width. Otherwise, the sample rate + /// might be imprecise since the BCLK division is not an integer. + #[cfg(all(esp_idf_version_major = "5", not(esp_idf_version_minor = "0")))] + #[inline(always)] + pub fn from_sample_rate_hz(rate: u32) -> Self { + Self { + sample_rate_hz: rate, + clk_src: ClockSource::default(), + mclk_multiple: MclkMultiple::M256, + bclk_div: TDM_BCLK_DIV_MIN, + } + } + + /// Set the clock source on this TDM clock configuration. + #[inline(always)] + pub fn clk_src(mut self, clk_src: ClockSource) -> Self { + self.clk_src = clk_src; + self + } + + /// Set the MCLK multiple on this TDM clock configuration. + #[inline(always)] + pub fn mclk_multiple(mut self, mclk_multiple: MclkMultiple) -> Self { + self.mclk_multiple = mclk_multiple; + self + } + + /// Set the MCLK to BCLK division on this TDM clock configuration. + #[cfg(all(esp_idf_version_major = "5", not(esp_idf_version_minor = "0")))] + #[inline(always)] + pub fn bclk_div(mut self, bclk_div: u32) -> Self { + self.bclk_div = bclk_div; + self + } + + /// Convert to the ESP-IDF SDK `i2s_tdm_clk_config_t` representation. + #[cfg(all(esp_idf_version_major = "5", esp_idf_version_minor = "0"))] + #[inline(always)] + pub(crate) fn as_sdk(&self) -> i2s_tdm_clk_config_t { + i2s_tdm_clk_config_t { + sample_rate_hz: self.sample_rate_hz, + clk_src: self.clk_src.as_sdk(), + mclk_multiple: self.mclk_multiple.as_sdk(), + } + } + + /// Convert to the ESP-IDF SDK `i2s_tdm_clk_config_t` representation. + #[cfg(all(esp_idf_version_major = "5", not(esp_idf_version_minor = "0")))] + #[allow(clippy::needless_update)] + #[inline(always)] + pub(crate) fn as_sdk(&self) -> i2s_tdm_clk_config_t { + i2s_tdm_clk_config_t { + sample_rate_hz: self.sample_rate_hz, + clk_src: self.clk_src.as_sdk(), + mclk_multiple: self.mclk_multiple.as_sdk(), + bclk_div: self.bclk_div, + ..Default::default() + } + } + } + + #[cfg(esp_idf_version_major = "4")] + pub type TdmCommFormat = crate::i2s::std::config::StdCommFormat; + + /// TDM mode GPIO (general purpose input/output) configuration. + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] + pub struct TdmGpioConfig { + /// Invert the BCLK signal. + bclk_invert: bool, + + /// Invert the MCLK signal. + mclk_invert: bool, + + /// Invert the WS signal. + ws_invert: bool, + } + + impl TdmGpioConfig { + /// Create a new TDM mode GPIO configuration with the specified inversion flags for BCLK, MCLK, and WS. + pub fn new(bclk_invert: bool, mclk_invert: bool, ws_invert: bool) -> Self { + Self { + bclk_invert, + mclk_invert, + ws_invert, + } + } + + /// Set the BCLK inversion flag on this TDM GPIO configuration. + #[inline(always)] + pub fn bclk_invert(mut self, bclk_invert: bool) -> Self { + self.bclk_invert = bclk_invert; + self + } + + /// Set the MCLK inversion flag on this TDM GPIO configuration. + #[inline(always)] + pub fn mclk_invert(mut self, mclk_invert: bool) -> Self { + self.mclk_invert = mclk_invert; + self + } + + /// Set the WS inversion flag on this TDM GPIO configuration. + #[inline(always)] + pub fn ws_invert(mut self, ws_invert: bool) -> Self { + self.ws_invert = ws_invert; + self + } + + /// Convert to the ESP-IDF SDK `i2s_tdm_gpio_config_t` representation. + #[cfg(not(esp_idf_version_major = "4"))] + pub(crate) fn as_sdk<'d>( + &self, + bclk: PeripheralRef<'d, impl InputPin + OutputPin>, + din: Option>, + dout: Option>, + mclk: Option>, + ws: PeripheralRef<'d, impl InputPin + OutputPin>, + ) -> i2s_tdm_gpio_config_t { + let invert_flags = i2s_tdm_gpio_config_t__bindgen_ty_1 { + _bitfield_1: i2s_tdm_gpio_config_t__bindgen_ty_1::new_bitfield_1( + self.mclk_invert as u32, + self.bclk_invert as u32, + self.ws_invert as u32, + ), + ..Default::default() + }; + + i2s_tdm_gpio_config_t { + bclk: bclk.pin(), + din: if let Some(din) = din { din.pin() } else { -1 }, + dout: if let Some(dout) = dout { + dout.pin() + } else { + -1 + }, + mclk: if let Some(mclk) = mclk { + mclk.pin() + } else { + -1 + }, + ws: ws.pin(), + invert_flags, + } + } + } + + /// TDM mode slot configuration. + /// + /// To create a slot configuration, use [`TdmSlotConfig::philips_slot_default`], + /// [`TdmSlotConfig::pcm_short_slot_default`], [`TdmSlotConfig::pcm_long_slot_default`], or + /// [`TdmSlotConfig::msb_slot_default`], then customize it as needed. + /// + /// In TDM mode, WS (word select, sometimes called LRCLK or left/right clock) becomes a frame synchronization + /// signal that signals the first slot of a frame. The two sides of the TDM link must agree on the number + /// of channels, data bit width, and frame synchronization pattern; this cannot be determined by examining the + /// signal itself. + /// + /// The Philips default pulls the WS line low one BCK period before the first data bit of the first slot is + /// sent and holds it low for 50% of the frame. + /// + #[doc = include_str!("tdm_slot_philips.svg")] + /// + /// MSB (most-significant bit) mode is similar to Philips mode, except the WS line is pulled low at the same time + /// the first data bit of the first slot is sent. It is held low for 50% of the frame. + /// + #[doc = include_str!("tdm_slot_msb.svg")] + /// + /// PCM (pulse-code modulation) short mode pulls the WS line *high* one BCK period before the first data bit of + /// the first slot is sent, keeps it high for one BCK, then pulls it low for the remainder of the frame. + #[doc = include_str!("tdm_slot_pcm_short.svg")] + /// PCM long mode pulls the WS line *high* one BCK period before the first data bit of the first slot is sent, + /// keeps it high until just before the last data bit of the first slot is sent, then pulls it low for the + /// remainder of the frame. + #[doc = include_str!("tdm_slot_pcm_long.svg")] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + /// + /// Diagrams from _ESP-IDF Programming Guide_; rendered by Wavedrom. + pub struct TdmSlotConfig { + /// I2S sample data bit width (valid data bits per sample). + data_bit_width: DataBitWidth, + + /// I2S slot bit width (total bits per slot). + slot_bit_width: SlotBitWidth, + + /// Which slots are active in the TDM frame. + slot_mask: TdmSlotMask, + + /// The word select (WS) signal width, in terms of the bit clock (BCK) periods. + #[cfg(not(esp_idf_version_major = "4"))] + ws_width: u32, + + /// The word select signal polarity; `true` enables the high level first. + #[cfg(not(esp_idf_version_major = "4"))] + ws_polarity: bool, + + /// Set to enable the additional bit-shift needed in Philips mode. + #[cfg(not(esp_idf_version_major = "4"))] + bit_shift: bool, + + #[cfg(esp_idf_version_major = "4")] + comm_fmt: TdmCommFormat, + + /// Enable left-alignment. + left_align: bool, + + /// Enable big-endian. + big_endian: bool, + + /// Enable LSB-first. + bit_order_lsb: bool, + + /// Set to enable the skip mask. When enabled, only the data of the enabled channels will be sent. Otherwise, + /// all data stored in the DMA transmit buffer will be sent. + skip_mask: bool, + + /// The total number of slots. If this is smaller than the highest activated channel number, it will be set + /// to that number automatically. + total_slots: u32, + } + + impl TdmSlotConfig { + /// Update the data bit width on this TDM slot configuration. + #[inline(always)] + #[must_use] + pub fn data_bit_width(mut self, data_bit_width: DataBitWidth) -> Self { + self.data_bit_width = data_bit_width; + self + } + + /// Update the slot bit width on this TDM slot configuration. + /// + /// This is normally set to [`SlotBitWidth::Auto`] to match `[data_bit_width][TdmSlotConfig::data_bit_width()]`. + #[inline(always)] + #[must_use] + pub fn slot_bit_width(mut self, slot_bit_width: SlotBitWidth) -> Self { + self.slot_bit_width = slot_bit_width; + self + } + + /// Update the slot mask on this TDM slot configuration. + #[inline(always)] + #[must_use] + pub fn slot_mask(mut self, slot_mask: TdmSlotMask) -> Self { + self.slot_mask = slot_mask; + self + } + + /// Update the word select signal width on this TDM slot configuration. + /// + /// This sets the number of bits to keep the word select signal active at the start of each frame. If this is + /// set to 0 ([`TDM_AUTO_WS_WIDTH`]), the word select signal will be kept active for half of the frame. + #[cfg(not(esp_idf_version_major = "4"))] + #[inline(always)] + #[must_use] + pub fn ws_width(mut self, ws_width: u32) -> Self { + self.ws_width = ws_width; + self + } + + /// Update the word select signal polarity on this TDM slot configuration. + /// + /// Setting this to `true` will make the word select (WS) signal active high at the start (PCM modes). + /// Setting this to `false` will make the WS signal active low at the start (Philips and MSB modes). + #[cfg(not(esp_idf_version_major = "4"))] + #[inline(always)] + #[must_use] + pub fn ws_polarity(mut self, ws_polarity: bool) -> Self { + self.ws_polarity = ws_polarity; + self + } + + /// Update the bit shift flag on this TDM slot configuration. + /// + /// Setting this to `true` will activate the word select (WS) signal lone BCK period before the first data bit + /// of the first slot is sent (Philips and PCM modes). Setting this to `false` will activate the WS + /// signal at the same time the first data bit of the first slot is sent (MSB mode). + #[cfg(not(esp_idf_version_major = "4"))] + #[inline(always)] + #[must_use] + pub fn bit_shift(mut self, bit_shift: bool) -> Self { + self.bit_shift = bit_shift; + self + } + + /// Update the communication format on this TDM slot configuration. + #[cfg(esp_idf_version_major = "4")] + #[inline(always)] + #[must_use] + pub fn comm_fmt(mut self, comm_fmt: TdmCommFormat) -> Self { + self.comm_fmt = comm_fmt; + self + } + + /// Update the left-alignment flag on this TDM slot configuration. + /// + /// This only has an effect when `[slot_bit_width][TdmSlotMask::slot_bit_width()]` is greater than + /// `[data_bit_width][TdmSlotMask::data_bit_width()]`. Setting this to `true` will left-align the data in the slot and + /// fill the right-most bits (usually the least-significant bits) with zeros. Setting this to `false` will right-align the + /// data in the slot and fill the left-most bits (usually the most-significant bits) with zeros. + #[inline(always)] + #[must_use] + pub fn left_align(mut self, left_align: bool) -> Self { + self.left_align = left_align; + self + } + + /// Update the big-endian flag on this TDM slot configuration. + /// + /// This affects the interpretation of the data when `[data_bit_width][TdmSlotMask::data_bit_width()]` is + /// greater than 8. Setting this to + /// `true` will interpret the data as big-endian. Setting this to `false` will interpret the data as + /// little-endian (the default, and the native endian-ness of all ESP32 microcontrollers). + #[inline(always)] + #[must_use] + pub fn big_endian(mut self, big_endian: bool) -> Self { + self.big_endian = big_endian; + self + } + + /// Update the LSB-first flag on this TDM slot configuration. + /// + /// Setting this to `true` will transmit data LSB-first (no known modes do this). Setting this to `false` + /// will transmit data MSB-first (the default for all known modes). + #[inline(always)] + #[must_use] + pub fn bit_order_lsb(mut self, bit_order_lsb: bool) -> Self { + self.bit_order_lsb = bit_order_lsb; + self + } + + /// Update the skip mask flag on this TDM slot configuration. + /// + /// Setting this to `true` will ignore `[slot_mask][TdmSlotMask::slot_mask()]` and transmit all slots. Setting this to `false` will + /// respect the slot mask. + #[inline(always)] + #[must_use] + pub fn skip_mask(mut self, skip_mask: bool) -> Self { + self.skip_mask = skip_mask; + self + } + + /// Update the total number of slots on this TDM slot configuration. + /// + /// Setting this to 0 ([`TDM_AUTO_SLOT_NUM`]) will automatically set the total number of slots to the + /// the number of active slots in `[slot_mask][TdmSlotMask::slot_mask()]`. + #[inline(always)] + #[must_use] + pub fn total_slots(mut self, total_slots: u32) -> Self { + self.total_slots = total_slots; + self + } + + /// Configure in Philips format with the active slots enabled by the specified mask. + #[inline(always)] + #[must_use] + pub fn philips_slot_default(bits_per_sample: DataBitWidth, slot_mask: TdmSlotMask) -> Self { + Self { + data_bit_width: bits_per_sample, + slot_bit_width: SlotBitWidth::Auto, + slot_mask, + #[cfg(not(esp_idf_version_major = "4"))] + ws_width: TDM_AUTO_WS_WIDTH, + #[cfg(not(esp_idf_version_major = "4"))] + ws_polarity: false, + #[cfg(not(esp_idf_version_major = "4"))] + bit_shift: true, + #[cfg(esp_idf_version_major = "4")] + comm_fmt: TdmCommFormat::Philips, + left_align: false, + big_endian: false, + bit_order_lsb: false, + skip_mask: false, + total_slots: TDM_AUTO_SLOT_NUM, + } + } + + /// Configure in MSB format with the active slots enabled by the specified mask. + #[inline(always)] + #[must_use] + pub fn msb_slot_default(bits_per_sample: DataBitWidth, slot_mask: TdmSlotMask) -> Self { + Self { + data_bit_width: bits_per_sample, + slot_bit_width: SlotBitWidth::Auto, + slot_mask, + #[cfg(not(esp_idf_version_major = "4"))] + ws_width: TDM_AUTO_WS_WIDTH, + #[cfg(not(esp_idf_version_major = "4"))] + ws_polarity: false, + #[cfg(not(esp_idf_version_major = "4"))] + bit_shift: false, + #[cfg(esp_idf_version_major = "4")] + comm_fmt: TdmCommFormat::Msb, + left_align: false, + big_endian: false, + bit_order_lsb: false, + skip_mask: false, + total_slots: TDM_AUTO_SLOT_NUM, + } + } + + /// Configure in PCM (short) format with the active slots enabled by the specified mask. + #[inline(always)] + #[must_use] + pub fn pcm_short_slot_default( + bits_per_sample: DataBitWidth, + slot_mask: TdmSlotMask, + ) -> Self { + Self { + data_bit_width: bits_per_sample, + slot_bit_width: SlotBitWidth::Auto, + slot_mask, + #[cfg(not(esp_idf_version_major = "4"))] + ws_width: 1, + #[cfg(not(esp_idf_version_major = "4"))] + ws_polarity: true, + #[cfg(not(esp_idf_version_major = "4"))] + bit_shift: false, + #[cfg(esp_idf_version_major = "4")] + comm_fmt: TdmCommFormat::PcmShort, + left_align: false, + big_endian: false, + bit_order_lsb: false, + skip_mask: false, + total_slots: TDM_AUTO_SLOT_NUM, + } + } + + /// Configure in PCM (long) format with the active slots enabled by the specified mask. + #[inline(always)] + #[must_use] + pub fn pcm_long_slot_default( + bits_per_sample: DataBitWidth, + slot_mask: TdmSlotMask, + ) -> Self { + Self { + data_bit_width: bits_per_sample, + slot_bit_width: SlotBitWidth::Auto, + slot_mask, + #[cfg(not(esp_idf_version_major = "4"))] + ws_width: bits_per_sample.into(), + #[cfg(not(esp_idf_version_major = "4"))] + ws_polarity: true, + #[cfg(not(esp_idf_version_major = "4"))] + bit_shift: false, + #[cfg(esp_idf_version_major = "4")] + comm_fmt: TdmCommFormat::PcmLong, + left_align: false, + big_endian: false, + bit_order_lsb: false, + skip_mask: false, + total_slots: TDM_AUTO_SLOT_NUM, + } + } + + /// Convert to the ESP-IDF SDK `i2s_tdm_slot_config_t` representation. + #[cfg(not(esp_idf_version_major = "4"))] + #[inline(always)] + pub(crate) fn as_sdk(&self) -> i2s_tdm_slot_config_t { + i2s_tdm_slot_config_t { + data_bit_width: self.data_bit_width.as_sdk(), + slot_bit_width: self.slot_bit_width.as_sdk(), + slot_mode: SlotMode::Stereo.as_sdk(), // mono mode doesn't make sense in TDM + slot_mask: self.slot_mask.as_sdk(), + ws_width: self.ws_width, + ws_pol: self.ws_polarity, + bit_shift: self.bit_shift, + left_align: self.left_align, + big_endian: self.big_endian, + bit_order_lsb: self.bit_order_lsb, + skip_mask: self.skip_mask, + total_slot: self.total_slots, + } + } + } + + /// An individual TDM slot. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum TdmSlot { + /// TDM slot #0 + Slot0, + + /// TDM slot #1 + Slot1, + + /// TDM slot #2 + Slot2, + + /// TDM slot #3 + Slot3, + + /// TDM slot #4 + Slot4, + + /// TDM slot #5 + Slot5, + + /// TDM slot #6 + Slot6, + + /// TDM slot #7 + Slot7, + + /// TDM slot #8 + Slot8, + + /// TDM slot #9 + Slot9, + + /// TDM slot #10 + Slot10, + + /// TDM slot #11 + Slot11, + + /// TDM slot #12 + Slot12, + + /// TDM slot #13 + Slot13, + + /// TDM slot #14 + Slot14, + + /// TDM slot #15 + Slot15, + } + + /// Mask of TDM slots to enable. + #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] + pub struct TdmSlotMask(u16); + + /// Attempt to convert from a `u8` to a `TdmSlot`. + impl TryFrom for TdmSlot { + type Error = EspError; + + fn try_from(slot: u8) -> Result { + match slot { + 0 => Ok(Self::Slot0), + 1 => Ok(Self::Slot1), + 2 => Ok(Self::Slot2), + 3 => Ok(Self::Slot3), + 4 => Ok(Self::Slot4), + 5 => Ok(Self::Slot5), + 6 => Ok(Self::Slot6), + 7 => Ok(Self::Slot7), + 8 => Ok(Self::Slot8), + 9 => Ok(Self::Slot9), + 10 => Ok(Self::Slot10), + 11 => Ok(Self::Slot11), + 12 => Ok(Self::Slot12), + 13 => Ok(Self::Slot13), + 14 => Ok(Self::Slot14), + 15 => Ok(Self::Slot15), + _ => Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()), + } + } + } + + /// Convert a `TdmSlot` to a `u8`. + impl From for u8 { + fn from(slot: TdmSlot) -> u8 { + match slot { + TdmSlot::Slot0 => 0, + TdmSlot::Slot1 => 1, + TdmSlot::Slot2 => 2, + TdmSlot::Slot3 => 3, + TdmSlot::Slot4 => 4, + TdmSlot::Slot5 => 5, + TdmSlot::Slot6 => 6, + TdmSlot::Slot7 => 7, + TdmSlot::Slot8 => 8, + TdmSlot::Slot9 => 9, + TdmSlot::Slot10 => 10, + TdmSlot::Slot11 => 11, + TdmSlot::Slot12 => 12, + TdmSlot::Slot13 => 13, + TdmSlot::Slot14 => 14, + TdmSlot::Slot15 => 15, + } + } + } + + /// Convert a `TdmSlot` into a `TdmSlotMask`. + impl From for TdmSlotMask { + #[inline(always)] + fn from(slot: TdmSlot) -> TdmSlotMask { + TdmSlotMask(1 << u8::from(slot)) + } + } + + /// Bitwise AND a`TdmSlot` with another `TdmSlot` to produce a `TdmSlotMask`. + /// + /// If the slots are the same, the result is a `TdmSlotMask` containing that slot. + /// Otherwise, the result is an empty slot mask. + impl BitAnd for TdmSlot { + type Output = TdmSlotMask; + + #[inline(always)] + fn bitand(self, rhs: Self) -> Self::Output { + TdmSlotMask::from(self) & TdmSlotMask::from(rhs) + } + } + + /// Bitwise AND a `TdmSlot` with a `TdmSlotMask` to produce a `TdmSlotMask`. + /// + /// If the slot mask contains the slot, the result is a `TdmSlotMask` containing that slot. + /// Otherwise, the result is an empty slot mask. + impl BitAnd for TdmSlot { + type Output = TdmSlotMask; + + #[inline(always)] + fn bitand(self, rhs: TdmSlotMask) -> Self::Output { + TdmSlotMask::from(self) & rhs + } + } + + /// Bitwise AND a `TdmSlotMask` with a `TdmSlot` to produce a `TdmSlotMask`. + /// + /// If the slot mask contains the slot, the result is a `TdmSlotMask` containing that slot. + /// Otherwise, the result is an empty slot mask. + impl BitAnd for TdmSlotMask { + type Output = TdmSlotMask; + + #[inline(always)] + fn bitand(self, rhs: TdmSlot) -> Self::Output { + self & TdmSlotMask::from(rhs) + } + } + + /// Bitwise AND a `TdmSlotMask` with another `TdmSlotMask` to produce a `TdmSlotMask`. + /// + /// The result is a slot mask containing the slots that are common to both slot masks. + impl BitAnd for TdmSlotMask { + type Output = Self; + + #[inline(always)] + fn bitand(self, rhs: Self) -> Self::Output { + Self(self.0 & rhs.0) + } + } + + /// Bitwise AND a `TdmSlotMask` with a `TdmSlot` and assign the result to `self`. + /// + /// If the slot mask contains the slot, the result is a `TdmSlotMask` containing that slot. + /// Otherwise, the result is an empty slot mask. + impl BitAndAssign for TdmSlotMask { + #[inline(always)] + fn bitand_assign(&mut self, rhs: TdmSlot) { + self.0 &= TdmSlotMask::from(rhs).0; + } + } + + /// Bitwise AND a `TdmSlotMask` with another `TdmSlotMask` and assign the result to `self`. + /// + /// The result is a slot mask containing the slots that are common to both slot masks. + impl BitAndAssign for TdmSlotMask { + #[inline(always)] + fn bitand_assign(&mut self, rhs: Self) { + self.0 &= rhs.0; + } + } + + /// Bitwise OR a`TdmSlot` with another `TdmSlot` to produce a `TdmSlotMask`. + /// + /// The result is a `TdmSlotMask` containing both slots. + impl BitOr for TdmSlot { + type Output = TdmSlotMask; + + #[inline(always)] + fn bitor(self, rhs: Self) -> Self::Output { + TdmSlotMask::from(self) | TdmSlotMask::from(rhs) + } + } + + /// Bitwise OR a`TdmSlot` with a `TdmSlotMask` to produce a `TdmSlotMask`. + /// + /// The result is a `TdmSlotMask` containing the slot and all slots in the slot mask. + impl BitOr for TdmSlot { + type Output = TdmSlotMask; + + #[inline(always)] + fn bitor(self, rhs: TdmSlotMask) -> Self::Output { + TdmSlotMask::from(self) | rhs + } + } + + /// Bitwise OR a`TdmSlotMask` with a `TdmSlot` to produce a `TdmSlotMask`. + /// + /// The result is a `TdmSlotMask` containing the slot and all slots in the slot mask. + impl BitOr for TdmSlotMask { + type Output = TdmSlotMask; + + #[inline(always)] + fn bitor(self, rhs: TdmSlot) -> Self::Output { + self | TdmSlotMask::from(rhs) + } + } + + /// Bitwise OR a`TdmSlotMask` with another `TdmSlotMask` to produce a `TdmSlotMask`. + /// + /// The result is a `TdmSlotMask` containing the slots in either slot mask. + impl BitOr for TdmSlotMask { + type Output = Self; + + #[inline(always)] + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } + } + + /// Bitwise OR a`TdmSlotMask` with a `TdmSlot` and assign the result to `self`. + /// + /// The result is a `TdmSlotMask` containing the slot and all slots in the slot mask. + impl BitOrAssign for TdmSlotMask { + #[inline(always)] + fn bitor_assign(&mut self, rhs: TdmSlot) { + self.0 |= TdmSlotMask::from(rhs).0; + } + } + + /// Bitwise OR a`TdmSlotMask` with another `TdmSlotMask` and assign the result to `self. + /// + /// The result is a `TdmSlotMask` containing the slots in either slot mask. + impl BitOrAssign for TdmSlotMask { + #[inline(always)] + fn bitor_assign(&mut self, rhs: Self) { + self.0 |= rhs.0; + } + } + + /// Produce the bitwise NOT of a `TdmSlot` to produce a `TdmSlotMask` containing all slots + /// except the original slot. + impl Not for TdmSlot { + type Output = TdmSlotMask; + + #[inline(always)] + fn not(self) -> Self::Output { + !TdmSlotMask::from(self) + } + } + + /// Produce the bitwise NOT of a `TdmSlotMask` to produce a `TdmSlotMask` containing all slots + /// except the slots in the original slot mask. + impl Not for TdmSlotMask { + type Output = Self; + + fn not(self) -> Self::Output { + Self(!self.0) + } + } + + impl TdmSlotMask { + /// Creates a `TdmSlotMask` from the raw bit mask value. + #[inline(always)] + pub fn from_mask_value(value: u16) -> Self { + Self(value) + } + + /// Indicates whether this slot mask is empty. + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.0 == 0 + } + + /// Returns the number of slots in the slot mask. + #[inline(always)] + pub fn len(&self) -> usize { + self.0.count_ones() as usize + } + + /// Returns the mask value as a `u16`. + #[inline(always)] + pub fn mask_value(&self) -> u16 { + self.0 + } + + /// Converts this mask to an ESP-IDF SDK `i2s_tdm_slot_mask_t` value. + #[cfg(not(esp_idf_version_major = "4"))] + #[inline(always)] + pub(super) fn as_sdk(&self) -> i2s_tdm_slot_mask_t { + self.0 as i2s_tdm_slot_mask_t + } + + /// Converts this mask to an ESP-IDF SDK `i2s_channel_t` value. + #[cfg(esp_idf_version_major = "4")] + #[inline(always)] + pub(super) fn as_sdk(&self) -> i2s_channel_t { + ((self.0 as u32) << 16) as i2s_channel_t + } + } +} + +impl<'d, Dir> I2sDriver<'d, Dir> { + #[cfg(not(esp_idf_version_major = "4"))] + #[allow(clippy::too_many_arguments)] + fn internal_new_tdm( + _i2s: impl Peripheral

+ 'd, + config: &config::TdmConfig, + rx: bool, + tx: bool, + bclk: impl Peripheral

+ 'd, + din: Option + 'd>, + dout: Option + 'd>, + mclk: Option + 'd>, + ws: impl Peripheral

+ 'd, + ) -> Result { + let chan_cfg = config.channel_cfg.as_sdk(I2S::port()); + + let this = Self::internal_new::(&chan_cfg, rx, tx)?; + + // Create the channel configuration. + let tdm_config = config.as_sdk( + bclk.into_ref(), + din.map(|d_in| d_in.into_ref()), + dout.map(|d_out| d_out.into_ref()), + mclk.map(|m_clk| m_clk.into_ref()), + ws.into_ref(), + ); + + if rx { + unsafe { + // Open the RX channel. + esp!(i2s_channel_init_tdm_mode(this.rx_handle, &tdm_config))?; + } + } + + if tx { + unsafe { + // Open the TX channel. + esp!(i2s_channel_init_tdm_mode(this.tx_handle, &tdm_config))?; + } + } + + Ok(this) + } + + #[cfg(esp_idf_version_major = "4")] + #[allow(clippy::too_many_arguments)] + fn internal_new_tdm( + _i2s: impl Peripheral

+ 'd, + config: &config::TdmConfig, + rx: bool, + tx: bool, + bclk: impl Peripheral

+ 'd, + din: Option + 'd>, + dout: Option + 'd>, + mclk: Option + 'd>, + ws: impl Peripheral

+ 'd, + ) -> Result { + let mut driver_cfg = config.as_sdk(); + + if rx { + driver_cfg.mode |= i2s_mode_t_I2S_MODE_RX; + } + + if tx { + driver_cfg.mode |= i2s_mode_t_I2S_MODE_TX; + } + + let this = Self::internal_new::(&driver_cfg)?; + + // Set the pin configuration. + let pin_cfg = i2s_pin_config_t { + bck_io_num: bclk.into_ref().pin(), + data_in_num: din.map(|din| din.into_ref().pin()).unwrap_or(-1), + data_out_num: dout.map(|dout| dout.into_ref().pin()).unwrap_or(-1), + mck_io_num: mclk.map(|mclk| mclk.into_ref().pin()).unwrap_or(-1), + ws_io_num: ws.into_ref().pin(), + }; + + // Safety: &pin_cfg is a valid pointer to an i2s_pin_config_t. + unsafe { + esp!(i2s_set_pin(this.port as _, &pin_cfg))?; + } + + Ok(this) + } +} + +impl<'d> I2sDriver<'d, I2sBiDir> { + /// Create a new TDM mode driver for the given I2S peripheral with both the receive and transmit channels open. + #[cfg(not(any(esp32, esp32s2)))] + #[cfg_attr(feature = "nightly", doc(cfg(not(any(esp32, esp32s2)))))] + #[allow(clippy::too_many_arguments)] + pub fn new_tdm_bidir( + i2s: impl Peripheral

+ 'd, + config: &config::TdmConfig, + bclk: impl Peripheral

+ 'd, + din: impl Peripheral

+ 'd, + dout: impl Peripheral

+ 'd, + mclk: Option + 'd>, + ws: impl Peripheral

+ 'd, + ) -> Result { + Self::internal_new_tdm( + i2s, + config, + true, + true, + bclk, + Some(din), + Some(dout), + mclk, + ws, + ) + } +} + +impl<'d> I2sDriver<'d, I2sRx> { + /// Create a new TDM mode driver for the given I2S peripheral with only the receive channel open. + #[cfg(not(any(esp32, esp32s2)))] + #[cfg_attr(feature = "nightly", doc(cfg(not(any(esp32, esp32s2)))))] + #[allow(clippy::too_many_arguments)] + pub fn new_tdm_rx( + i2s: impl Peripheral

+ 'd, + config: &config::TdmConfig, + bclk: impl Peripheral

+ 'd, + din: impl Peripheral

+ 'd, + mclk: Option + 'd>, + ws: impl Peripheral

+ 'd, + ) -> Result { + Self::internal_new_tdm( + i2s, + config, + true, + false, + bclk, + Some(din), + AnyIOPin::none(), + mclk, + ws, + ) + } +} + +impl<'d> I2sDriver<'d, I2sTx> { + /// Create a new TDM mode driver for the given I2S peripheral with only the transmit channel open. + #[cfg(not(any(esp32, esp32s2)))] + #[cfg_attr(feature = "nightly", doc(cfg(not(any(esp32, esp32s2)))))] + #[allow(clippy::too_many_arguments)] + pub fn new_tdm_tx( + i2s: impl Peripheral

+ 'd, + config: &config::TdmConfig, + bclk: impl Peripheral

+ 'd, + dout: impl Peripheral

+ 'd, + mclk: Option + 'd>, + ws: impl Peripheral

+ 'd, + ) -> Result { + Self::internal_new_tdm( + i2s, + config, + false, + true, + bclk, + AnyIOPin::none(), + Some(dout), + mclk, + ws, + ) + } +} diff --git a/src/i2s/tdm_slot_msb.svg b/src/i2s/tdm_slot_msb.svg new file mode 100644 index 00000000000..2bb64577ba5 --- /dev/null +++ b/src/i2s/tdm_slot_msb.svg @@ -0,0 +1,4 @@ + + + +TDM MSB FrameBCLKWSDIN / DOUTMSBLSBMSBLSBMSBLSBMSBLSBMSBFirst (Left) SlotsSecond (Right) SlotsSlot 1Slot 2...Slot nSlot n+1... \ No newline at end of file diff --git a/src/i2s/tdm_slot_msb.wavedrom b/src/i2s/tdm_slot_msb.wavedrom new file mode 100644 index 00000000000..a10541bb698 --- /dev/null +++ b/src/i2s/tdm_slot_msb.wavedrom @@ -0,0 +1,60 @@ +{ + "head": { + "text": "TDM MSB Frame" + }, + "signal": [ + { + "node": ".E.........F.........G", + "phase": -0.35 + }, + { + "name": "BCLK", + "wave": "p.d.p.d.pd.pd.p.d.pd.p" + }, + { + "name": "WS", + "wave": "10dd0.dd0dd1uu1.uu1uu0.", + "node": "TH...................R", + "phase": -0.35 + }, + { + "name": "DIN / DOUT", + "wave": "x2x|22x|2x|2x|22x|2x|2", + "data": [ + "MSB", + "LSB", + "MSB", + "LSB", + "MSB", + "LSB", + "MSB", + "LSB", + "MSB" + ], + "node": ".H...K...I.M...N...P.Q", + "phase": -0.35 + }, + { + "node": "UA...B...C.D...J...L.S", + "phase": -0.35 + } + ], + "edge": [ + "E<->F First (Left) Slots", + "F<->G Second (Right) Slots", + "A<->B Slot 1", + "B<->C Slot 2", + "C<->D ...", + "D<->J Slot n", + "J<->L Slot n+1", + "L<->S ...", + "A-E", + "K-B", + "C-I", + "D-F", + "J-N", + "L-P", + "Q-G", + "S-R" + ] +} \ No newline at end of file diff --git a/src/i2s/tdm_slot_pcm_long.svg b/src/i2s/tdm_slot_pcm_long.svg new file mode 100644 index 00000000000..d00ad67db23 --- /dev/null +++ b/src/i2s/tdm_slot_pcm_long.svg @@ -0,0 +1 @@ +TDM PCM Long FrameBCLKWSDIN / DOUTMSBLSBMSBLSBMSBFramebit shiftSlot 1...Slot n \ No newline at end of file diff --git a/src/i2s/tdm_slot_pcm_long.wavedrom b/src/i2s/tdm_slot_pcm_long.wavedrom new file mode 100644 index 00000000000..3ef50c266a2 --- /dev/null +++ b/src/i2s/tdm_slot_pcm_long.wavedrom @@ -0,0 +1,52 @@ +{ + "head": { + "text": "TDM PCM Long Frame" + }, + "signal": [ + { + "node": ".UE.........G", + "phase": -0.35 + }, + { + "name": "BCLK", + "wave": "p..d.p.d.pd.pd" + }, + { + "name": "WS", + "wave": "01u.10d...01u.", + "node": ".NH", + "phase": -0.35 + }, + { + "name": "DIN / DOUT", + "wave": "xx2x|2x|2x|22x", + "data": [ + "MSB", + "LSB", + "MSB", + "LSB", + "MSB" + ], + "node": "..H..FK.I...M", + "phase": -0.35 + }, + { + "node": ".ZA...B.C...D...J", + "phase": -0.35 + } + ], + "edge": [ + "E<->G Frame", + "Z<->A bit shift", + "A<->B Slot 1", + "B<->C ...", + "C<->D Slot n", + "U-Z", + "A-E", + "K-B", + "C-I", + "D-M", + "D-G", + "L-F" + ] +} diff --git a/src/i2s/tdm_slot_pcm_short.svg b/src/i2s/tdm_slot_pcm_short.svg new file mode 100644 index 00000000000..d10462f2058 --- /dev/null +++ b/src/i2s/tdm_slot_pcm_short.svg @@ -0,0 +1 @@ +TDM PCM Short FrameBCLKWSDIN / DOUTMSBLSBMSBLSBMSBFramebit shiftSlot 1...Slot n \ No newline at end of file diff --git a/src/i2s/tdm_slot_pcm_short.wavedrom b/src/i2s/tdm_slot_pcm_short.wavedrom new file mode 100644 index 00000000000..d6048710f88 --- /dev/null +++ b/src/i2s/tdm_slot_pcm_short.wavedrom @@ -0,0 +1,52 @@ +{ + "head": { + "text": "TDM PCM Short Frame" + }, + "signal": [ + { + "node": ".UE.........G", + "phase": -0.35 + }, + { + "name": "BCLK", + "wave": "p..d.p.d.pd.pd" + }, + { + "name": "WS", + "wave": "010........10.", + "node": ".NH", + "phase": -0.35 + }, + { + "name": "DIN / DOUT", + "wave": "xx2x|2x|2x|22x", + "data": [ + "MSB", + "LSB", + "MSB", + "LSB", + "MSB" + ], + "node": "..H..FK.I...M", + "phase": -0.35 + }, + { + "node": ".ZA...B.C...D...J", + "phase": -0.35 + } + ], + "edge": [ + "E<->G Frame", + "Z<->A bit shift", + "A<->B Slot 1", + "B<->C ...", + "C<->D Slot n", + "U-Z", + "A-E", + "K-B", + "C-I", + "D-M", + "D-G", + "L-F" + ] +} diff --git a/src/i2s/tdm_slot_philips.svg b/src/i2s/tdm_slot_philips.svg new file mode 100644 index 00000000000..9eb063321e3 --- /dev/null +++ b/src/i2s/tdm_slot_philips.svg @@ -0,0 +1,4 @@ + + + +TDM Philips FrameBCLKWSDIN / DOUTMSBLSBMSBLSBMSBLSBMSBLSBMSBFirst (Left) SlotsSecond (Right) Slotsbit shiftSlot 1Slot 2...Slot nSlot n+1... \ No newline at end of file diff --git a/src/i2s/tdm_slot_philips.wavedrom b/src/i2s/tdm_slot_philips.wavedrom new file mode 100644 index 00000000000..49de6d888a3 --- /dev/null +++ b/src/i2s/tdm_slot_philips.wavedrom @@ -0,0 +1,62 @@ +td{ + "head": { + "text": "TDM Philips Frame" + }, + "signal": [ + { + "node": "..E.........F.........G", + "phase": -0.35 + }, + { + "name": "BCLK", + "wave": "p..d.p.d.pd.pd.p.d.pd.p" + }, + { + "name": "WS", + "wave": "10dd0.dd0dd1uu1.uu1uu0.", + "node": ".TH...................R", + "phase": -0.35 + }, + { + "name": "DIN / DOUT", + "wave": "xx2x|22x|2x|2x|22x|2x|2", + "data": [ + "MSB", + "LSB", + "MSB", + "LSB", + "MSB", + "LSB", + "MSB", + "LSB", + "MSB" + ], + "node": "..H...K...I.M...N...P.Q", + "phase": -0.35 + }, + { + "node": ".UA...B...C.D...J...L.S", + "phase": -0.35 + } + ], + "edge": [ + "E<->F First (Left) Slots", + "F<->G Second (Right) Slots", + "U<->A bit shift", + "A<->B Slot 1", + "B<->C Slot 2", + "C<->D ...", + "D<->J Slot n", + "J<->L Slot n+1", + "L<->S ...", + "A-E", + "K-B", + "C-I", + "D-F", + "J-N", + "L-P", + "Q-G", + "S-R", + "T-U" + ] +} \ No newline at end of file diff --git a/src/interrupt.rs b/src/interrupt.rs index 63a0112fb97..9f11d1915ac 100644 --- a/src/interrupt.rs +++ b/src/interrupt.rs @@ -1,7 +1,85 @@ -use core::sync::atomic::{AtomicU64, Ordering}; +use enumset::{EnumSet, EnumSetType}; use esp_idf_sys::*; +/// For backwards compatibility +pub type IntrFlags = InterruptType; + +/// Interrupt allocation flags. +/// These flags can be used to specify which interrupt qualities the code calling esp_intr_alloc* needs. +#[derive(Debug, EnumSetType)] +pub enum InterruptType { + // Accept a Level 1 interrupt vector (lowest priority) + Level1, + // Accept a Level 2 interrupt vector. + Level2, + // Accept a Level 3 interrupt vector. + Level3, + // Accept a Level 4 interrupt vector. + Level4, + // Accept a Level 5 interrupt vector. + Level5, + // Accept a Level 6 interrupt vector. + Level6, + // Accept a Level 7 interrupt vector (highest priority) + Nmi, + // Interrupt can be shared between ISRs. + Shared, + // Edge-triggered interrupt. + Edge, + // ISR can be called if cache is disabled. + // Must be used with a proper option *_ISR_IN_IRAM in SDKCONFIG + Iram, + // Return with this interrupt disabled. + IntrDisabled, + // Low and medium prio interrupts. These can be handled in C. + LowMed, + // High level interrupts. Need to be handled in assembly. + High, +} + +impl InterruptType { + pub fn levels(&self) -> EnumSet { + Self::Level1 + | Self::Level2 + | Self::Level3 + | Self::Level4 + | Self::Level5 + | Self::Level6 + | Self::Nmi + } + + pub(crate) fn to_native(flags: EnumSet) -> u32 { + let mut result = 0; + + for flag in flags { + result |= u32::from(flag); + } + + result + } +} + +impl From for u32 { + fn from(flag: InterruptType) -> Self { + match flag { + InterruptType::Level1 => esp_idf_sys::ESP_INTR_FLAG_LEVEL1, + InterruptType::Level2 => esp_idf_sys::ESP_INTR_FLAG_LEVEL2, + InterruptType::Level3 => esp_idf_sys::ESP_INTR_FLAG_LEVEL3, + InterruptType::Level4 => esp_idf_sys::ESP_INTR_FLAG_LEVEL4, + InterruptType::Level5 => esp_idf_sys::ESP_INTR_FLAG_LEVEL5, + InterruptType::Level6 => esp_idf_sys::ESP_INTR_FLAG_LEVEL6, + InterruptType::Nmi => esp_idf_sys::ESP_INTR_FLAG_NMI, + InterruptType::Shared => esp_idf_sys::ESP_INTR_FLAG_SHARED, + InterruptType::Edge => esp_idf_sys::ESP_INTR_FLAG_EDGE, + InterruptType::Iram => esp_idf_sys::ESP_INTR_FLAG_IRAM, + InterruptType::IntrDisabled => esp_idf_sys::ESP_INTR_FLAG_INTRDISABLED, + InterruptType::LowMed => esp_idf_sys::ESP_INTR_FLAG_LOWMED, + InterruptType::High => esp_idf_sys::ESP_INTR_FLAG_HIGH, + } + } +} + pub(crate) static CS: IsrCriticalSection = IsrCriticalSection::new(); /// Returns true if the currently active core is executing an ISR request @@ -34,21 +112,21 @@ unsafe fn do_yield_signal(arg: *mut ()) { *signaled = true } -static ISR_YIELDER: AtomicU64 = AtomicU64::new(0); +#[allow(clippy::type_complexity)] +static mut ISR_YIELDER: Option<(unsafe fn(*mut ()), *mut ())> = None; #[allow(clippy::type_complexity)] #[inline(always)] #[link_section = ".iram1.interrupt_get_isr_yielder"] pub(crate) unsafe fn get_isr_yielder() -> Option<(unsafe fn(*mut ()), *mut ())> { if active() { - let value = ISR_YIELDER.load(Ordering::SeqCst); - if value == 0 { - None - } else { - let func: fn(*mut ()) = core::mem::transmute((value >> 32) as usize); - let arg = (value & 0xffffffff) as usize as *mut (); - Some((func, arg)) - } + free(|| { + if let Some((func, arg)) = unsafe { ISR_YIELDER } { + Some((func, arg)) + } else { + None + } + }) } else { None } @@ -72,33 +150,26 @@ pub unsafe fn set_isr_yielder( yielder: Option<(unsafe fn(*mut ()), *mut ())>, ) -> Option<(unsafe fn(*mut ()), *mut ())> { if active() { - let value = if let Some((func, arg)) = yielder { - ((func as usize as u64) << 32) | (arg as usize as u64) - } else { - 0 - }; - - let value = ISR_YIELDER.swap(value, Ordering::SeqCst); - if value == 0 { - None - } else { - let func: fn(*mut ()) = core::mem::transmute((value >> 32) as usize); - let arg = (value & 0xffffffff) as usize as *mut (); - Some((func, arg)) - } + free(|| { + let previous = unsafe { ISR_YIELDER }; + + unsafe { ISR_YIELDER = yielder }; + + previous + }) } else { None } } /// A critical section allows the user to disable interrupts -#[cfg(not(esp32c3))] +#[cfg(any(esp32, esp32s2, esp32s3, esp32p4))] pub struct IsrCriticalSection(core::cell::UnsafeCell); -#[cfg(esp32c3)] +#[cfg(not(any(esp32, esp32s2, esp32s3, esp32p4)))] pub struct IsrCriticalSection(core::marker::PhantomData<*const ()>); -#[cfg(esp32c3)] +#[cfg(not(any(esp32, esp32s2, esp32s3, esp32p4)))] #[inline(always)] #[link_section = ".iram1.interrupt_enter"] fn enter(_cs: &IsrCriticalSection) { @@ -107,22 +178,16 @@ fn enter(_cs: &IsrCriticalSection) { } } -#[cfg(not(esp32c3))] +#[cfg(any(esp32, esp32s2, esp32s3, esp32p4))] #[inline(always)] #[link_section = ".iram1.interrupt_enter"] fn enter(cs: &IsrCriticalSection) { - #[cfg(esp_idf_version = "4.3")] - unsafe { - vPortEnterCritical(cs.0.get()); - } - - #[cfg(not(esp_idf_version = "4.3"))] unsafe { xPortEnterCriticalTimeout(cs.0.get(), portMUX_NO_TIMEOUT); } } -#[cfg(esp32c3)] +#[cfg(not(any(esp32, esp32s2, esp32s3, esp32p4)))] #[inline(always)] #[link_section = ".iram1.interrupt_exit"] fn exit(_cs: &IsrCriticalSection) { @@ -131,7 +196,7 @@ fn exit(_cs: &IsrCriticalSection) { } } -#[cfg(not(esp32c3))] +#[cfg(any(esp32, esp32s2, esp32s3, esp32p4))] #[inline(always)] #[link_section = ".iram1.interrupt_exit"] fn exit(cs: &IsrCriticalSection) { @@ -145,7 +210,7 @@ impl IsrCriticalSection { #[inline(always)] #[link_section = ".iram1.interrupt_cs_new"] pub const fn new() -> Self { - #[cfg(not(esp32c3))] + #[cfg(any(esp32, esp32s2, esp32s3, esp32p4))] let mux = core::cell::UnsafeCell::new(portMUX_TYPE { owner: portMUX_FREE_VAL, count: 0, @@ -155,7 +220,7 @@ impl IsrCriticalSection { lastLockedLine: -1, }); - #[cfg(esp32c3)] + #[cfg(not(any(esp32, esp32s2, esp32s3, esp32p4)))] let mux = core::marker::PhantomData; Self(mux) @@ -197,7 +262,7 @@ unsafe impl Sync for IsrCriticalSection {} pub struct IsrCriticalSectionGuard<'a>(&'a IsrCriticalSection); -impl<'a> Drop for IsrCriticalSectionGuard<'a> { +impl Drop for IsrCriticalSectionGuard<'_> { /// Drops the critical section guard thus potentially re-enabling /// al interrupts for the currently active core. /// @@ -221,10 +286,378 @@ pub fn free(f: impl FnOnce() -> R) -> R { f() } +#[cfg(feature = "wake-from-isr")] +pub mod asynch { + pub type HalIsrNotification = crate::task::asynch::Notification; +} + +#[cfg(not(feature = "wake-from-isr"))] +pub mod asynch { + use core::{ + cell::UnsafeCell, + ffi::{c_void, CStr}, + future::Future, + num::NonZeroU32, + sync::atomic::{AtomicPtr, Ordering}, + task::{Context, Poll, Waker}, + }; + + use esp_idf_sys::EspError; + + use ::log::info; + + use crate::{ + cpu::Core, + delay, + task::{asynch::Notification, CriticalSection}, + }; + + use super::IsrCriticalSection; + + /// The HAL-global wake runner. + /// You should use no more than 64 tasks with it. + /// + /// `*IsrNotification` instances use this wake runner when they are triggered from an ISR context. + pub static HAL_ISR_REACTOR: IsrReactor<64> = IsrReactor::new(IsrReactorConfig::new()); + + /// Wake runner configuration + #[derive(Clone, Debug)] + pub struct IsrReactorConfig { + pub task_name: &'static CStr, + pub task_stack_size: usize, + pub task_priority: u8, + pub task_pin_to_core: Option, + } + + impl IsrReactorConfig { + pub const fn new() -> Self { + Self { + task_name: unsafe { CStr::from_bytes_with_nul_unchecked(b"IsrReactor\0") }, + task_stack_size: 3084, + task_priority: 11, + task_pin_to_core: None, + } + } + } + + impl Default for IsrReactorConfig { + fn default() -> Self { + Self::new() + } + } + + /// IsrReactor is a utility allowing `Waker` instances to be awoken fron an ISR context. + /// + /// General problem: + /// In an interrupt, using Waker instances coming from generic executors is impossible, + /// because these are not designed with an ISR-safety in mind. + /// + /// Waking a waker means that its task would be scheduled on the executor queue, which might involve + /// allocation, and/or synchronization primitives which are not safe to use from an ISR context. + /// + /// Similarly, dropping a waker might also drop the executor task, resulting in a deallocation, which is also + /// not safe in an ISR context. + /// + /// These problems are alleviated by replacing direct `waker.wake()` calls to `WakerRunner::schedule(waker)`. + /// What `IsrReactor::schedule` does is to push the waker into a bounded queue and then notify a hidden FreeRTOS task. + /// Once the FreeRTOS task gets awoken, it wakes all wakers scheduled on the bounded queue and empties the queue. + pub struct IsrReactor { + wakers_cs: IsrCriticalSection, + wakers: UnsafeCell>, + task_cs: CriticalSection, + task: AtomicPtr, + task_config: IsrReactorConfig, + } + + impl IsrReactor { + /// Create a new `IsrReactor` instance. + pub const fn new(config: IsrReactorConfig) -> Self { + Self { + wakers_cs: IsrCriticalSection::new(), + wakers: UnsafeCell::new(heapless::Deque::new()), + task_cs: CriticalSection::new(), + task: AtomicPtr::new(core::ptr::null_mut()), + task_config: config, + } + } + + /// Returns `true` if the wake runner is started. + pub fn is_started(&self) -> bool { + !self.task.load(Ordering::SeqCst).is_null() + } + + /// Starts the wake runner. Returns `false` if it had been already started. + pub fn start(&'static self) -> Result { + let _guard = self.task_cs.enter(); + + if self.task.load(Ordering::SeqCst).is_null() { + let task = unsafe { + crate::task::create( + Self::task_run, + self.task_config.task_name, + self.task_config.task_stack_size, + self as *const _ as *const c_void as *mut _, + self.task_config.task_priority, + self.task_config.task_pin_to_core, + )? + }; + + self.task.store(task as _, Ordering::SeqCst); + + info!("IsrReactor {:?} started.", self.task_config.task_name); + + Ok(true) + } else { + Ok(false) + } + } + + /// Stops the wake runner. Returns `false` if it had been already stopped. + pub fn stop(&self) -> bool { + let _guard = self.task_cs.enter(); + + let task = self.task.swap(core::ptr::null_mut(), Ordering::SeqCst); + + if !task.is_null() { + unsafe { + crate::task::destroy(task as _); + } + + info!("IsrReactor {:?} stopped.", self.task_config.task_name); + + true + } else { + false + } + } + + /// Schedules a waker to be awoken by the hidden FreeRTOS task running in the background. + /// If not called from within an ISR context, calls `waker.wake()` directly instead of scheduling the waker. + /// NOTE: If the wake runner is not started yet, scheduling fron an ISR content would fail silently. + /// + /// This and only this method is safe to call from an ISR context. + pub fn schedule(&self, waker: Waker) { + if super::active() { + self.wakers(|wakers| { + let earlier_waker = wakers.iter_mut().find(|a_waker| a_waker.will_wake(&waker)); + + if let Some(earlier_waker) = earlier_waker { + *earlier_waker = waker; + } else if wakers.push_back(waker).is_err() { + panic!("IsrReactor queue overflow"); + } + + let task = self.task.load(Ordering::SeqCst); + + if !task.is_null() { + unsafe { + crate::task::notify_and_yield(task as _, NonZeroU32::new(1).unwrap()); + } + } + }) + } else { + waker.wake(); + } + } + + fn run(&self) { + loop { + loop { + let waker = self.wakers(|wakers| wakers.pop_front()); + + if let Some(waker) = waker { + waker.wake(); + } else { + break; + } + } + + crate::task::wait_notification(delay::BLOCK); + } + } + + fn wakers) -> R, R>(&self, f: F) -> R { + // if super::active() { + // let wakers = unsafe { self.wakers.get().as_mut().unwrap() }; + + // f(wakers) + // } else { + let _guard = self.wakers_cs.enter(); + + let wakers = unsafe { self.wakers.get().as_mut().unwrap() }; + + f(wakers) + // } + } + + extern "C" fn task_run(ctx: *mut c_void) { + let this = + unsafe { (ctx as *mut IsrReactor as *const IsrReactor).as_ref() }.unwrap(); + + this.run(); + } + } + + impl Drop for IsrReactor { + fn drop(&mut self) { + self.stop(); + } + } + + unsafe impl Send for IsrReactor {} + unsafe impl Sync for IsrReactor {} + + /// Single-slot lock-free signaling primitive supporting signalling with a `u32` bit-set. + /// + /// A variation of the `Notification` HAL primitive which is however safe to be notified from an ISR context. + /// + /// It is useful for sending data between an ISR routine (or a regular task context) and an async task when the + /// receiver only cares about the latest data, and therefore it's fine to "lose" messages. + /// This is often the case for "state" updates. + /// + /// The sending part of the primitive is non-blocking and ISR-safe, so it can be called from anywhere. + /// + /// Similar in spirit to the ESP-IDF FreeRTOS task notifications in that it is light-weight and operates on bit-sets, + /// but for synchronization between an asynchronous task, and another one, which might be blocking or asynchronous. + pub struct IsrNotification { + inner: Notification, + reactor: &'static IsrReactor, + } + + impl IsrNotification { + /// Creates a new `IsrNotification`. + /// This method is safe to call from an ISR context, yet such use cases should not normally occur in practice. + pub const fn new(reactor: &'static IsrReactor) -> Self { + Self { + inner: Notification::new(), + reactor, + } + } + + /// Marks the least significant bit (bit 0) in this `IsrNotification` as nofified. + /// This method is safe to call from an ISR context. + /// Returns `true` if there was a registered waker which got awoken. + pub fn notify_lsb(&self) -> bool { + self.notify(NonZeroU32::new(1).unwrap()) + } + + /// Marks the supplied bits in this `IsrNotification` as notified. + /// This method is safe to call from an ISR context. + /// Returns `true` if there was a registered waker which got awoken. + pub fn notify(&self, bits: NonZeroU32) -> bool { + if let Some(waker) = self.inner.notify_waker(bits) { + self.reactor.schedule(waker); + + true + } else { + false + } + } + + /// Clears the state of this notification by removing any registered waker and setting all bits to 0. + /// This method is NOT safe to call from an ISR context. + pub fn reset(&self) { + self.inner.reset(); + } + + /// Future that completes when this `IsrNotification` has been notified. + /// This method is NOT safe to call from an ISR context. + #[allow(unused)] + pub fn wait(&self) -> impl Future + '_ { + self.reactor.start().unwrap(); + + self.inner.wait() + } + + /// Non-blocking method to check whether this notification has been notified. + /// This method is NOT safe to call from an ISR context. + pub fn poll_wait(&self, cx: &Context<'_>) -> Poll { + self.reactor.start().unwrap(); + + self.inner.poll_wait(cx) + } + } + + /// Single-slot lock-free signaling primitive supporting signalling with a `u32` bit-set. + /// + /// A variation of the `IsrNotification` HAL primitive which is however safe to be notified from an ISR context. + /// The difference between this primitive and `IsrNotification` is that this one is hard-wired to the + /// global HAL wake runner (`HAL_WAKE_RUNNER`) and is thus occupying less space. + /// + /// It is useful for sending data between an ISR routine (or a regular task context) and an async task when the + /// receiver only cares about the latest data, and therefore it's fine to "lose" messages. + /// This is often the case for "state" updates. + /// + /// The sending part of the primitive is non-blocking and ISR-safe, so it can be called from anywhere. + /// + /// Similar in spirit to the ESP-IDF FreeRTOS task notifications in that it is light-weight and operates on bit-sets, + /// but for synchronization between an asynchronous task, and another one, which might be blocking or asynchronous. + pub struct HalIsrNotification { + inner: Notification, + } + + impl Default for HalIsrNotification { + fn default() -> Self { + Self::new() + } + } + + impl HalIsrNotification { + /// Creates a new `HalIsrNotification`. + /// This method is safe to call from an ISR context, yet such use cases should not normally occur in practice. + pub const fn new() -> Self { + Self { + inner: Notification::new(), + } + } + + /// Marks the least significant bit (bit 0) in this `IsrNotification` as nofified. + /// This method is safe to call from an ISR context. + /// Returns `true` if there was a registered waker which got awoken. + pub fn notify_lsb(&self) -> bool { + self.notify(NonZeroU32::new(1).unwrap()) + } + + /// Marks the supplied bits in this `HalIsrNotification` as notified. + /// This method is safe to call from an ISR context. + /// Returns `true` if there was a registered waker which got awoken. + pub fn notify(&self, bits: NonZeroU32) -> bool { + if let Some(waker) = self.inner.notify_waker(bits) { + HAL_ISR_REACTOR.schedule(waker); + + true + } else { + false + } + } + + /// Clears the state of this notification by removing any registered waker and setting all bits to 0. + /// This method is NOT safe to call from an ISR context. + pub fn reset(&self) { + self.inner.reset(); + } + + /// Future that completes when this `HalIsrNotification` has been notified. + /// This method is NOT safe to call from an ISR context. + #[allow(unused)] + pub fn wait(&self) -> impl Future + '_ { + HAL_ISR_REACTOR.start().unwrap(); + + self.inner.wait() + } + + /// Non-blocking method to check whether this notification has been notified. + /// This method is NOT safe to call from an ISR context. + pub fn poll_wait(&self, cx: &Context<'_>) -> Poll { + HAL_ISR_REACTOR.start().unwrap(); + + self.inner.poll_wait(cx) + } + } +} + #[cfg(feature = "embassy-sync")] pub mod embassy_sync { - use core::marker::PhantomData; - use embassy_sync::blocking_mutex::raw::RawMutex; /// A mutex that allows borrowing data across executors and interrupts. @@ -232,18 +665,15 @@ pub mod embassy_sync { /// # Safety /// /// This mutex is safe to share between different executors and interrupts. - pub struct IsrRawMutex { - _phantom: PhantomData<()>, - } + pub struct IsrRawMutex(()); + unsafe impl Send for IsrRawMutex {} unsafe impl Sync for IsrRawMutex {} impl IsrRawMutex { /// Create a new `IsrRawMutex`. pub const fn new() -> Self { - Self { - _phantom: PhantomData, - } + Self(()) } } diff --git a/src/io.rs b/src/io.rs new file mode 100644 index 00000000000..aabcfc87763 --- /dev/null +++ b/src/io.rs @@ -0,0 +1,35 @@ +//! Error types + +use core::fmt::{self, Display, Formatter}; + +pub use embedded_io::*; + +use crate::sys::EspError; + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub struct EspIOError(pub EspError); + +impl Error for EspIOError { + fn kind(&self) -> ErrorKind { + ErrorKind::Other + } +} + +impl From for EspIOError { + fn from(e: EspError) -> Self { + Self(e) + } +} + +impl Display for EspIOError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for EspIOError {} + +pub mod asynch { + pub use embedded_io_async::*; +} diff --git a/src/ledc.rs b/src/ledc.rs index 9ac38e634a5..b5d276b1ac4 100644 --- a/src/ledc.rs +++ b/src/ledc.rs @@ -3,21 +3,20 @@ //! Interface to the [LED Control (LEDC) //! peripheral](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html) //! -//! This is an initial implementation supporting the generation of PWM signals -//! but no chrome and spoilers like fading. //! //! # Examples //! //! Create a 25 kHz PWM signal with 75 % duty cycle on GPIO 1 //! ``` -//! use esp_idf_hal::ledc::{config::TimerConfig, Channel, LedcDriver, Timer}; +//! use esp_idf_hal::ledc::{config::TimerConfig, Channel, LedcDriver, LedcTimerDriver, Timer}; //! use esp_idf_hal::peripherals::Peripherals; //! use esp_idf_hal::prelude::*; //! //! let peripherals = Peripherals::take().unwrap(); -//! let mut driver = LedcDriver::new(peripherals.ledc.channel0, peripherals.ledc.timer0, peripherals.pins.gpio1, &TimerConfig::default().frequency(25.kHz().into()))?; +//! let timer_driver = LedcTimerDriver::new(peripherals.ledc.timer0, &TimerConfig::default().frequency(25.kHz().into()))?; +//! let mut driver = LedcDriver::new(peripherals.ledc.channel0, timer_driver, peripherals.pins.gpio1)?; //! -//! let max_duty = driver.get_max_duty()?; +//! let max_duty = driver.get_max_duty(); //! driver.set_duty(max_duty * 3 / 4)?; //! ``` //! @@ -30,8 +29,9 @@ use core::sync::atomic::{AtomicBool, Ordering}; use esp_idf_sys::*; use crate::gpio::OutputPin; -use crate::peripheral::Peripheral; +use crate::peripheral::{Peripheral, PeripheralRef}; use crate::task::CriticalSection; +use crate::units::*; pub use chip::*; @@ -43,10 +43,15 @@ const IDLE_LEVEL: u32 = 0; static FADE_FUNC_INSTALLED: AtomicBool = AtomicBool::new(false); static FADE_FUNC_INSTALLED_CS: CriticalSection = CriticalSection::new(); +crate::embedded_hal_error!( + PwmError, + embedded_hal::pwm::Error, + embedded_hal::pwm::ErrorKind +); + /// Types for configuring the LED Control peripheral pub mod config { use super::*; - use crate::units::*; pub use chip::Resolution; @@ -54,12 +59,14 @@ pub mod config { pub struct TimerConfig { pub frequency: Hertz, pub resolution: Resolution, - pub speed_mode: SpeedMode, } impl TimerConfig { - pub fn new() -> Self { - Default::default() + pub const fn new() -> Self { + Self { + frequency: Hertz(1000), + resolution: Resolution::Bits8, + } } #[must_use] @@ -73,41 +80,38 @@ pub mod config { self.resolution = r; self } - - #[must_use] - pub fn speed_mode(mut self, mode: SpeedMode) -> Self { - self.speed_mode = mode; - self - } } impl Default for TimerConfig { fn default() -> Self { - TimerConfig { - frequency: 1000.Hz(), - resolution: Resolution::Bits8, - speed_mode: SpeedMode::LowSpeed, - } + Self::new() } } } /// LED Control timer driver -pub struct LedcTimerDriver<'d> { - timer: u8, - speed_mode: SpeedMode, +pub struct LedcTimerDriver<'d, T> +where + T: LedcTimer, +{ + _timer: PeripheralRef<'d, T>, max_duty: Duty, _p: PhantomData<&'d mut ()>, } -impl<'d> LedcTimerDriver<'d> { - pub fn new( - _timer: impl Peripheral

+ 'd, +impl<'d, T> LedcTimerDriver<'d, T> +where + T: LedcTimer, +{ + pub fn new( + timer: impl Peripheral

+ 'd, config: &config::TimerConfig, ) -> Result { + crate::into_ref!(timer); + let timer_config = ledc_timer_config_t { - speed_mode: config.speed_mode.into(), - timer_num: T::timer(), + speed_mode: T::SpeedMode::SPEED_MODE, + timer_num: T::timer() as _, #[cfg(esp_idf_version_major = "4")] __bindgen_anon_1: ledc_timer_config_t__bindgen_ty_1 { duty_resolution: config.resolution.timer_bits(), @@ -115,15 +119,23 @@ impl<'d> LedcTimerDriver<'d> { #[cfg(not(esp_idf_version_major = "4"))] duty_resolution: config.resolution.timer_bits(), freq_hz: config.frequency.into(), + #[cfg(any(esp_idf_version_major = "4", esp_idf_version_minor = "0"))] clk_cfg: ledc_clk_cfg_t_LEDC_AUTO_CLK, + #[cfg(not(any(esp_idf_version_major = "4", esp_idf_version_minor = "0")))] + clk_cfg: soc_periph_ledc_clk_src_legacy_t_LEDC_AUTO_CLK, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1") + )))] + deconfigure: false, }; // SAFETY: We own the instance and therefor are safe to configure it. esp!(unsafe { ledc_timer_config(&timer_config) })?; Ok(Self { - timer: T::timer() as _, - speed_mode: config.speed_mode, + _timer: timer, max_duty: config.resolution.max_duty(), _p: PhantomData, }) @@ -131,33 +143,42 @@ impl<'d> LedcTimerDriver<'d> { /// Pauses the timer. Operation can be resumed with [`resume_timer()`]. pub fn pause(&mut self) -> Result<(), EspError> { - esp!(unsafe { ledc_timer_pause(self.speed_mode.into(), self.timer()) })?; + esp!(unsafe { ledc_timer_pause(T::SpeedMode::SPEED_MODE, self.timer()) })?; Ok(()) } /// Resumes the operation of the previously paused timer pub fn resume(&mut self) -> Result<(), EspError> { - esp!(unsafe { ledc_timer_resume(self.speed_mode.into(), self.timer()) })?; + esp!(unsafe { ledc_timer_resume(T::SpeedMode::SPEED_MODE, self.timer()) })?; + Ok(()) + } + + /// Set the frequency of the timer. + pub fn set_frequency(&mut self, frequency: Hertz) -> Result<(), EspError> { + esp!(unsafe { ledc_set_freq(T::SpeedMode::SPEED_MODE, T::timer(), frequency.into()) })?; Ok(()) } fn reset(&mut self) -> Result<(), EspError> { - esp!(unsafe { ledc_timer_rst(self.speed_mode.into(), self.timer()) })?; + esp!(unsafe { ledc_timer_rst(T::SpeedMode::SPEED_MODE, T::timer()) })?; Ok(()) } pub fn timer(&self) -> ledc_timer_t { - self.timer as _ + T::timer() } } -impl<'d> Drop for LedcTimerDriver<'d> { +impl Drop for LedcTimerDriver<'_, T> +where + T: LedcTimer, +{ fn drop(&mut self) { self.reset().unwrap(); } } -unsafe impl<'d> Send for LedcTimerDriver<'d> {} +unsafe impl Send for LedcTimerDriver<'_, T> where T: LedcTimer {} /// LED Control driver pub struct LedcDriver<'d> { @@ -165,7 +186,7 @@ pub struct LedcDriver<'d> { timer: u8, duty: Duty, hpoint: HPoint, - speed_mode: SpeedMode, + speed_mode: ledc_mode_t, max_duty: Duty, _p: PhantomData<&'d mut ()>, } @@ -175,30 +196,18 @@ pub struct LedcDriver<'d> { // and implementing Drop. impl<'d> LedcDriver<'d> { /// Creates a new LED Control driver - pub fn new>>( + pub fn new( _channel: impl Peripheral

+ 'd, timer_driver: B, pin: impl Peripheral

+ 'd, - config: &config::TimerConfig, - ) -> Result { - crate::into_ref!(pin); - - let duty = 0; - let hpoint = 0; - - let channel_config = ledc_channel_config_t { - speed_mode: config.speed_mode.into(), - channel: C::channel(), - timer_sel: timer_driver.borrow().timer(), - intr_type: ledc_intr_type_t_LEDC_INTR_DISABLE, - gpio_num: pin.pin(), - duty, - hpoint: hpoint as _, - ..Default::default() - }; - + ) -> Result + where + C: LedcChannel::SpeedMode>, + T: LedcTimer + 'd, + B: Borrow>, + { if !FADE_FUNC_INSTALLED.load(Ordering::SeqCst) { - let _ = FADE_FUNC_INSTALLED_CS.enter(); + let _guard = FADE_FUNC_INSTALLED_CS.enter(); if !FADE_FUNC_INSTALLED.load(Ordering::SeqCst) { // It looks like ledc_channel_config requires the face function to @@ -213,19 +222,44 @@ impl<'d> LedcDriver<'d> { } } - // SAFETY: As long as we have borrowed the timer, we are safe to use - // it. - esp!(unsafe { ledc_channel_config(&channel_config) })?; - - Ok(LedcDriver { - duty, - hpoint, - speed_mode: timer_driver.borrow().speed_mode, + let mut driver = LedcDriver { + duty: 0, + hpoint: 0, + speed_mode: T::SpeedMode::SPEED_MODE, max_duty: timer_driver.borrow().max_duty, timer: timer_driver.borrow().timer() as _, channel: C::channel() as _, _p: PhantomData, - }) + }; + + driver.config_with_pin(pin)?; + + Ok(driver) + } + + /// Applies LEDC configuration with a specific pin + /// Can be used to reconfigure the LEDC driver with a different pin + pub fn config_with_pin( + &mut self, + pin: impl Peripheral

+ 'd, + ) -> Result<(), EspError> { + crate::into_ref!(pin); + + let channel_config = ledc_channel_config_t { + speed_mode: self.speed_mode, + channel: self.channel as u32, + timer_sel: self.timer as u32, + intr_type: ledc_intr_type_t_LEDC_INTR_DISABLE, + gpio_num: pin.pin(), + duty: self.duty, + hpoint: self.hpoint as _, + ..Default::default() + }; + + // SAFETY: As long as we have borrowed the timer, we are safe to use + // it. + esp!(unsafe { ledc_channel_config(&channel_config) })?; + Ok(()) } pub fn get_duty(&self) -> Duty { @@ -271,14 +305,12 @@ impl<'d> LedcDriver<'d> { } fn stop(&mut self) -> Result<(), EspError> { - esp!(unsafe { ledc_stop(self.speed_mode.into(), self.channel(), IDLE_LEVEL,) })?; + esp!(unsafe { ledc_stop(self.speed_mode, self.channel(), IDLE_LEVEL,) })?; Ok(()) } fn update_duty(&mut self, duty: Duty, hpoint: HPoint) -> Result<(), EspError> { - esp!(unsafe { - ledc_set_duty_and_update(self.speed_mode.into(), self.channel(), duty, hpoint) - })?; + esp!(unsafe { ledc_set_duty_and_update(self.speed_mode, self.channel(), duty, hpoint) })?; Ok(()) } @@ -289,43 +321,121 @@ impl<'d> LedcDriver<'d> { pub fn timer(&self) -> ledc_timer_t { self.timer as _ } + + /// Fade the LED to a target duty cycle over a specified time + pub fn fade_with_time( + &mut self, + target_duty: u32, + fade_time_ms: i32, + wait: bool, + ) -> Result<(), EspError> { + let max_duty = self.get_max_duty(); + if target_duty > max_duty { + return Err(EspError::from_infallible::()); + } + + let fade_mode = if wait { + ledc_fade_mode_t_LEDC_FADE_WAIT_DONE + } else { + ledc_fade_mode_t_LEDC_FADE_NO_WAIT + }; + + unsafe { + esp!(ledc_set_fade_with_time( + self.speed_mode, + self.channel(), + target_duty, + fade_time_ms + ))?; + esp!(ledc_fade_start(self.speed_mode, self.channel(), fade_mode))?; + } + Ok(()) + } + + /// Fade the LED to a target duty cycle using steps + pub fn fade_with_step( + &mut self, + target_duty: u32, + step_size: u32, + step_time_ms: u32, + wait: bool, + ) -> Result<(), EspError> { + let max_duty = self.get_max_duty(); + if target_duty > max_duty { + return Err(EspError::from_infallible::()); + } + + let fade_mode = if wait { + ledc_fade_mode_t_LEDC_FADE_WAIT_DONE + } else { + ledc_fade_mode_t_LEDC_FADE_NO_WAIT + }; + + unsafe { + esp!(ledc_set_fade_with_step( + self.speed_mode, + self.channel(), + target_duty, + step_size, + step_time_ms, + ))?; + + esp!(ledc_fade_start(self.speed_mode, self.channel(), fade_mode))?; + } + Ok(()) + } } -impl<'d> Drop for LedcDriver<'d> { +impl Drop for LedcDriver<'_> { fn drop(&mut self) { self.stop().unwrap(); } } -unsafe impl<'d> Send for LedcDriver<'d> {} +unsafe impl Send for LedcDriver<'_> {} -// PwmPin temporarily removed from embedded-hal-1.0.alpha7 in anticipation of e-hal 1.0 release -// impl<'d> embedded_hal::pwm::blocking::PwmPin for LedcDriver<'d> { -// type Duty = Duty; -// type Error = EspError; +impl embedded_hal::pwm::ErrorType for LedcDriver<'_> { + type Error = PwmError; +} -// fn disable(&mut self) -> Result<(), Self::Error> { -// self.disable() -// } +fn to_pwm_err(err: EspError) -> PwmError { + PwmError::other(err) +} -// fn enable(&mut self) -> Result<(), Self::Error> { -// self.enable() -// } +impl embedded_hal::pwm::SetDutyCycle for LedcDriver<'_> { + fn max_duty_cycle(&self) -> u16 { + let duty = self.get_max_duty(); + let duty_cap: u16 = if duty > u16::MAX as u32 { + u16::MAX + } else { + duty as u16 + }; + duty_cap + } -// fn get_duty(&self) -> Result { -// Ok(self.get_duty()) -// } + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), PwmError> { + self.set_duty(duty as u32).map_err(to_pwm_err) + } + + fn set_duty_cycle_fully_on(&mut self) -> Result<(), PwmError> { + self.set_duty(self.get_max_duty()).map_err(to_pwm_err) + } -// fn get_max_duty(&self) -> Result { -// Ok(self.get_max_duty()) -// } + fn set_duty_cycle_fully_off(&mut self) -> Result<(), PwmError> { + self.set_duty(0).map_err(to_pwm_err) + } + + fn set_duty_cycle_fraction(&mut self, num: u16, denom: u16) -> Result<(), PwmError> { + let duty = num as u32 * self.max_duty_cycle() as u32 / denom as u32; + self.set_duty_cycle(duty as u16) + } -// fn set_duty(&mut self, duty: Duty) -> Result<(), Self::Error> { -// self.set_duty(duty) -// } -// } + fn set_duty_cycle_percent(&mut self, percent: u8) -> Result<(), PwmError> { + self.set_duty_cycle_fraction(percent as u16, 100) + } +} -impl<'d> embedded_hal_0_2::PwmPin for LedcDriver<'d> { +impl embedded_hal_0_2::PwmPin for LedcDriver<'_> { type Duty = Duty; fn disable(&mut self) { @@ -421,7 +531,12 @@ mod chip { } pub const fn max_duty(&self) -> u32 { - (1 << self.bits()) - 1 + // when using the maximum resultion, the duty cycle must not exceed 2^N - 1 to avoid timer overflow + if cfg!(esp32) && self.bits() == 20 || cfg!(not(esp32)) && self.bits() == 14 { + (1 << self.bits()) - 1 + } else { + 1 << self.bits() + } } pub(crate) const fn timer_bits(&self) -> ledc_timer_bit_t { @@ -456,47 +571,53 @@ mod chip { } } - /// Ledc Speed Mode - #[derive(PartialEq, Eq, Copy, Clone, Debug)] - pub enum SpeedMode { - #[cfg(esp_idf_soc_ledc_support_hs_mode)] - /// High Speed Mode. Currently only supported on the ESP32. - HighSpeed, - /// Low Speed Mode. The only configuration supported on ESP32S2, ESP32S3, ESP32C2 and ESP32C3. - LowSpeed, + /// Speed mode for the LED Control peripheral + /// The ESP32 supports two speed modes: low and high speed + /// All others support only low speed mode. + pub trait SpeedMode: Send + Sync + 'static { + const SPEED_MODE: ledc_mode_t; + const HIGH_SPEED: bool; } - impl Default for SpeedMode { - fn default() -> Self { - Self::LowSpeed - } + /// Low speed mode for the LED Control peripheral + pub struct LowSpeed; + + impl SpeedMode for LowSpeed { + const SPEED_MODE: ledc_mode_t = ledc_mode_t_LEDC_LOW_SPEED_MODE; + const HIGH_SPEED: bool = false; } - impl From for ledc_mode_t { - fn from(speed_mode: SpeedMode) -> Self { - match speed_mode { - #[cfg(esp_idf_soc_ledc_support_hs_mode)] - SpeedMode::HighSpeed => ledc_mode_t_LEDC_HIGH_SPEED_MODE, - SpeedMode::LowSpeed => ledc_mode_t_LEDC_LOW_SPEED_MODE, - } - } + #[cfg(esp32)] + /// High speed mode for the LED Control peripheral (ESP32 only) + pub struct HighSpeed; + + #[cfg(esp32)] + impl SpeedMode for HighSpeed { + const SPEED_MODE: ledc_mode_t = ledc_mode_t_LEDC_HIGH_SPEED_MODE; + const HIGH_SPEED: bool = true; } /// LED Control peripheral timer pub trait LedcTimer { + type SpeedMode: SpeedMode; + fn timer() -> ledc_timer_t; } /// LED Control peripheral output channel pub trait LedcChannel { + type SpeedMode: SpeedMode; + fn channel() -> ledc_channel_t; } macro_rules! impl_timer { - ($instance:ident: $timer:expr) => { + ($typ:ty; $instance:ident: $timer:expr) => { crate::impl_peripheral!($instance); impl LedcTimer for $instance { + type SpeedMode = $typ; + fn timer() -> ledc_timer_t { $timer } @@ -504,16 +625,27 @@ mod chip { }; } - impl_timer!(TIMER0: ledc_timer_t_LEDC_TIMER_0); - impl_timer!(TIMER1: ledc_timer_t_LEDC_TIMER_1); - impl_timer!(TIMER2: ledc_timer_t_LEDC_TIMER_2); - impl_timer!(TIMER3: ledc_timer_t_LEDC_TIMER_3); + impl_timer!(LowSpeed; TIMER0: ledc_timer_t_LEDC_TIMER_0); + impl_timer!(LowSpeed; TIMER1: ledc_timer_t_LEDC_TIMER_1); + impl_timer!(LowSpeed; TIMER2: ledc_timer_t_LEDC_TIMER_2); + impl_timer!(LowSpeed; TIMER3: ledc_timer_t_LEDC_TIMER_3); + + #[cfg(esp32)] + impl_timer!(HighSpeed; HTIMER0: ledc_timer_t_LEDC_TIMER_0); + #[cfg(esp32)] + impl_timer!(HighSpeed; HTIMER1: ledc_timer_t_LEDC_TIMER_1); + #[cfg(esp32)] + impl_timer!(HighSpeed; HTIMER2: ledc_timer_t_LEDC_TIMER_2); + #[cfg(esp32)] + impl_timer!(HighSpeed; HTIMER3: ledc_timer_t_LEDC_TIMER_3); macro_rules! impl_channel { - ($instance:ident: $channel:expr) => { + ($typ:ty; $instance:ident: $channel:expr) => { crate::impl_peripheral!($instance); impl LedcChannel for $instance { + type SpeedMode = $typ; + fn channel() -> ledc_channel_t { $channel } @@ -521,16 +653,33 @@ mod chip { }; } - impl_channel!(CHANNEL0: ledc_channel_t_LEDC_CHANNEL_0); - impl_channel!(CHANNEL1: ledc_channel_t_LEDC_CHANNEL_1); - impl_channel!(CHANNEL2: ledc_channel_t_LEDC_CHANNEL_2); - impl_channel!(CHANNEL3: ledc_channel_t_LEDC_CHANNEL_3); - impl_channel!(CHANNEL4: ledc_channel_t_LEDC_CHANNEL_4); - impl_channel!(CHANNEL5: ledc_channel_t_LEDC_CHANNEL_5); + impl_channel!(LowSpeed; CHANNEL0: ledc_channel_t_LEDC_CHANNEL_0); + impl_channel!(LowSpeed; CHANNEL1: ledc_channel_t_LEDC_CHANNEL_1); + impl_channel!(LowSpeed; CHANNEL2: ledc_channel_t_LEDC_CHANNEL_2); + impl_channel!(LowSpeed; CHANNEL3: ledc_channel_t_LEDC_CHANNEL_3); + impl_channel!(LowSpeed; CHANNEL4: ledc_channel_t_LEDC_CHANNEL_4); + impl_channel!(LowSpeed; CHANNEL5: ledc_channel_t_LEDC_CHANNEL_5); #[cfg(any(esp32, esp32s2, esp32s3, esp8684))] - impl_channel!(CHANNEL6: ledc_channel_t_LEDC_CHANNEL_6); + impl_channel!(LowSpeed; CHANNEL6: ledc_channel_t_LEDC_CHANNEL_6); #[cfg(any(esp32, esp32s2, esp32s3, esp8684))] - impl_channel!(CHANNEL7: ledc_channel_t_LEDC_CHANNEL_7); + impl_channel!(LowSpeed; CHANNEL7: ledc_channel_t_LEDC_CHANNEL_7); + + #[cfg(esp32)] + impl_channel!(HighSpeed; HCHANNEL0: ledc_channel_t_LEDC_CHANNEL_0); + #[cfg(esp32)] + impl_channel!(HighSpeed; HCHANNEL1: ledc_channel_t_LEDC_CHANNEL_1); + #[cfg(esp32)] + impl_channel!(HighSpeed; HCHANNEL2: ledc_channel_t_LEDC_CHANNEL_2); + #[cfg(esp32)] + impl_channel!(HighSpeed; HCHANNEL3: ledc_channel_t_LEDC_CHANNEL_3); + #[cfg(esp32)] + impl_channel!(HighSpeed; HCHANNEL4: ledc_channel_t_LEDC_CHANNEL_4); + #[cfg(esp32)] + impl_channel!(HighSpeed; HCHANNEL5: ledc_channel_t_LEDC_CHANNEL_5); + #[cfg(esp32)] + impl_channel!(HighSpeed; HCHANNEL6: ledc_channel_t_LEDC_CHANNEL_6); + #[cfg(esp32)] + impl_channel!(HighSpeed; HCHANNEL7: ledc_channel_t_LEDC_CHANNEL_7); /// The LED Control device peripheral pub struct LEDC { @@ -579,4 +728,50 @@ mod chip { } } } + + /// The LED Control device peripheral (high speed channels, ESP32 only) + #[cfg(esp32)] + pub struct HLEDC { + pub timer0: HTIMER0, + pub timer1: HTIMER1, + pub timer2: HTIMER2, + pub timer3: HTIMER3, + pub channel0: HCHANNEL0, + pub channel1: HCHANNEL1, + pub channel2: HCHANNEL2, + pub channel3: HCHANNEL3, + pub channel4: HCHANNEL4, + pub channel5: HCHANNEL5, + pub channel6: HCHANNEL6, + pub channel7: HCHANNEL7, + } + + #[cfg(esp32)] + impl HLEDC { + /// Creates a new instance of the HLEDC peripheral. Typically one wants + /// to use the instance [`ledc`](crate::peripherals::Peripherals::fledc) from + /// the device peripherals obtained via + /// [`peripherals::Peripherals::take()`](crate::peripherals::Peripherals::take()). + /// + /// # Safety + /// + /// It is safe to instantiate the HLEDC peripheral exactly one time. + /// Care has to be taken that this has not already been done elsewhere. + pub unsafe fn new() -> Self { + Self { + timer0: HTIMER0::new(), + timer1: HTIMER1::new(), + timer2: HTIMER2::new(), + timer3: HTIMER3::new(), + channel0: HCHANNEL0::new(), + channel1: HCHANNEL1::new(), + channel2: HCHANNEL2::new(), + channel3: HCHANNEL3::new(), + channel4: HCHANNEL4::new(), + channel5: HCHANNEL5::new(), + channel6: HCHANNEL6::new(), + channel7: HCHANNEL7::new(), + } + } + } } diff --git a/src/lib.rs b/src/lib.rs index cd74d91f7ec..b1bb79a882f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,46 +1,47 @@ -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] +#![allow(async_fn_in_trait)] +#![allow(unknown_lints)] +#![allow(renamed_and_removed_lints)] +#![allow(clippy::unused_unit)] // enumset +#![allow(unexpected_cfgs)] +#![warn(clippy::large_futures)] +#![cfg_attr(feature = "nightly", feature(doc_cfg))] #![cfg_attr(target_arch = "xtensa", feature(asm_experimental_arch))] -#[cfg(all(not(feature = "riscv-ulp-hal"), not(esp_idf_comp_driver_enabled)))] +#[cfg(not(esp_idf_comp_driver_enabled))] compile_error!("esp-idf-hal requires the `driver` ESP-IDF component to be enabled"); -#[cfg(all( - any( - feature = "std", - feature = "alloc", - feature = "critical-section-interrupt", - feature = "critical-section-mutex" - ), - feature = "riscv-ulp-hal" -))] -compile_error!("Enabling feature `riscv-ulp-hal` implies no other feature is enabled"); +// mutually exclusive features assert +#[cfg(all(feature = "rmt-legacy", esp_idf_comp_espressif__onewire_bus_enabled))] +compile_error!("the onewire component cannot be used with the legacy rmt peripheral"); -#[cfg(all(feature = "riscv-ulp-hal", not(esp32s2)))] -compile_error!("Feature `riscv-ulp-hal` is currently only supported on esp32s2"); +#[cfg(feature = "std")] +#[allow(unused_imports)] +#[macro_use] +extern crate std; +#[cfg(feature = "alloc")] +#[allow(unused_imports)] #[macro_use] -pub mod riscv_ulp_hal; +extern crate alloc; pub mod adc; -#[cfg(not(feature = "riscv-ulp-hal"))] pub mod can; -#[cfg(not(feature = "riscv-ulp-hal"))] pub mod cpu; -#[cfg(not(feature = "riscv-ulp-hal"))] pub mod delay; pub mod gpio; #[cfg(all(esp32, esp_idf_version_major = "4"))] pub mod hall; -#[cfg(not(feature = "riscv-ulp-hal"))] pub mod i2c; -#[cfg(not(feature = "riscv-ulp-hal"))] +#[cfg_attr( + feature = "nightly", + doc(cfg(all(esp_idf_soc_i2s_supported, esp_idf_comp_driver_enabled))) +)] +pub mod i2s; pub mod interrupt; -#[cfg(not(feature = "riscv-ulp-hal"))] +pub mod io; pub mod ledc; -#[cfg(all( - any(all(esp32, esp_idf_eth_use_esp32_emac), esp_idf_eth_use_openeth), - not(feature = "riscv-ulp-hal") -))] +#[cfg(any(all(esp32, esp_idf_eth_use_esp32_emac), esp_idf_eth_use_openeth))] pub mod mac; #[cfg(all( any(esp32, esp32s3), @@ -50,27 +51,37 @@ pub mod mac; pub mod mcpwm; #[cfg(not(feature = "riscv-ulp-hal"))] pub mod modem; +#[cfg(all( + esp_idf_soc_rmt_supported, + not(esp_idf_version_major = "4"), + esp_idf_comp_espressif__onewire_bus_enabled, +))] +pub mod onewire; +#[cfg(any(esp32, esp32s2, esp32s3, esp32c6))] +pub mod pcnt; pub mod peripheral; pub mod peripherals; pub mod prelude; -#[cfg(not(feature = "riscv-ulp-hal"))] pub mod reset; -#[cfg(not(feature = "riscv-ulp-hal"))] pub mod rmt; -#[cfg(not(feature = "riscv-ulp-hal"))] +pub mod rom; +#[cfg(feature = "experimental")] +pub mod sd; pub mod spi; -#[cfg(not(feature = "riscv-ulp-hal"))] +pub mod sys; pub mod task; -#[cfg(not(feature = "riscv-ulp-hal"))] +#[cfg(all(esp_idf_soc_temp_sensor_supported, esp_idf_version_major = "5"))] +pub mod temp_sensor; pub mod timer; -#[cfg(not(feature = "riscv-ulp-hal"))] pub mod uart; -#[cfg(all(any(esp32, esp32s2, esp32s3), not(feature = "riscv-ulp-hal")))] +#[cfg(all( + any(esp32, esp32s2, esp32s3, esp32c6, esp32p4), + esp_idf_comp_ulp_enabled +))] pub mod ulp; pub mod units; - -#[cfg(feature = "riscv-ulp-hal")] -pub use crate::riscv_ulp_hal::delay; +#[cfg(esp_idf_soc_usb_serial_jtag_supported)] +pub mod usb_serial; // This is used to create `embedded_hal` compatible error structs // that preserve original `EspError`. @@ -90,16 +101,13 @@ macro_rules! embedded_hal_error { pub fn new(kind: $kind, cause: esp_idf_sys::EspError) -> Self { Self { kind, cause } } - pub fn other(cause: esp_idf_sys::EspError) -> Self { Self::new(<$kind>::Other, cause) } - pub fn cause(&self) -> esp_idf_sys::EspError { self.cause } } - impl From for $error { fn from(e: esp_idf_sys::EspError) -> Self { Self::other(e) diff --git a/src/mcpwm/comparator.rs b/src/mcpwm/comparator.rs index e70e22bd90c..4f5c94e33c5 100644 --- a/src/mcpwm/comparator.rs +++ b/src/mcpwm/comparator.rs @@ -49,6 +49,7 @@ impl Comparator { #[derive(Debug, Clone, Copy)] pub struct ComparatorConfig { + intr_priority: i32, flags: mcpwm_comparator_config_t__bindgen_ty_1, } @@ -57,7 +58,10 @@ impl ComparatorConfig { self, operator_handle: mcpwm_oper_handle_t, ) -> Result { - let cfg = mcpwm_comparator_config_t { flags: self.flags }; + let cfg = mcpwm_comparator_config_t { + intr_priority: self.intr_priority, + flags: self.flags, + }; let mut cmp = ptr::null_mut(); unsafe { @@ -75,6 +79,9 @@ impl Default for ComparatorConfig { flags.set_update_cmp_on_tep(0); flags.set_update_cmp_on_tez(1); flags.set_update_cmp_on_sync(0); - Self { flags } + Self { + intr_priority: 0, + flags, + } } } diff --git a/src/mcpwm/operator.rs b/src/mcpwm/operator.rs index c834c1d5594..b2ea66a7391 100644 --- a/src/mcpwm/operator.rs +++ b/src/mcpwm/operator.rs @@ -83,6 +83,7 @@ where let config = mcpwm_operator_config_t { group_id: G::ID, + intr_priority: cfg.intr_priority, flags, }; @@ -120,7 +121,7 @@ where }) } -impl<'d, const N: u8, G> Operator<'d, N, G> +impl Operator<'_, N, G> where G: Group, { @@ -162,7 +163,7 @@ where } } -impl<'d, const N: u8, G> Operator<'d, N, G> +impl Operator<'_, N, G> where G: Group, { @@ -195,7 +196,7 @@ where } pub trait OptionalOperator {} -impl<'d, const N: u8, G> OptionalOperator for Operator<'d, N, G> where G: Group {} +impl OptionalOperator for Operator<'_, N, G> where G: Group {} pub struct NoOperator; impl OptionalOperator for NoOperator {} diff --git a/src/mcpwm/operator_config.rs b/src/mcpwm/operator_config.rs index 60506108239..47512220b2d 100644 --- a/src/mcpwm/operator_config.rs +++ b/src/mcpwm/operator_config.rs @@ -9,6 +9,9 @@ use super::{ #[derive(Default)] pub struct OperatorConfig<'d> { + /// Interrupt priority for the operator + pub(crate) intr_priority: i32, + /// Configuration for Comparator X pub(crate) comparator_x: ComparatorConfig, @@ -47,6 +50,8 @@ impl<'d> OperatorConfig<'d> { flags.set_update_dead_time_on_sync(1); OperatorConfig { + intr_priority: 0, + comparator_x: Default::default(), // SOC_MCPWM_COMPARATORS_PER_OPERATOR is 2 for ESP32 and ESP32-S3 comparator_y: Default::default(), @@ -56,7 +61,7 @@ impl<'d> OperatorConfig<'d> { } } -impl<'d> OperatorConfig<'d> { +impl OperatorConfig<'_> { fn cmp_x(mut self, config: ComparatorConfig) -> Self { self.comparator_x = config; self diff --git a/src/mcpwm/timer.rs b/src/mcpwm/timer.rs index 52822ae55ae..86d73e49d12 100644 --- a/src/mcpwm/timer.rs +++ b/src/mcpwm/timer.rs @@ -22,6 +22,7 @@ use super::timer_connection::TimerConnection; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct TimerConfig { + intr_priority: i32, resolution: Hertz, period_ticks: u16, count_mode: CountMode, @@ -34,6 +35,7 @@ pub struct TimerConfig { impl Default for TimerConfig { fn default() -> Self { Self { + intr_priority: 0, resolution: 80.MHz().into(), period_ticks: 8_000, // 10kHz count_mode: CountMode::Up, @@ -48,6 +50,13 @@ impl TimerConfig { self }*/ + /// Set the priority of the interrupt. A value of 0 means the default priority. + #[must_use] + pub fn intr_priority(mut self, intr_priority: i32) -> Self { + self.intr_priority = intr_priority; + self + } + // TODO: make sure this description is accurate /// Set number of ticks per period /// @@ -100,6 +109,7 @@ impl TimerDriver { let cfg = mcpwm_timer_config_t { group_id: G::ID, + intr_priority: config.intr_priority, clk_src: soc_periph_mcpwm_timer_clk_src_t_MCPWM_TIMER_CLK_SRC_DEFAULT, resolution_hz: config.resolution.0, count_mode: config.count_mode.into(), diff --git a/src/modem.rs b/src/modem.rs index f838233f2d6..a04b835bb3e 100644 --- a/src/modem.rs +++ b/src/modem.rs @@ -5,14 +5,29 @@ use crate::peripheral::{sealed, Peripheral}; #[cfg(not(esp32s2))] pub use split::*; +#[cfg(not(any(esp32h2, esp32h4)))] pub trait WifiModemPeripheral: Peripheral

{} +#[cfg(any(esp32h2, esp32h4, esp32c6))] +pub trait ThreadModemPeripheral: Peripheral

{} + #[cfg(not(esp32s2))] pub trait BluetoothModemPeripheral: Peripheral

{} -#[cfg(not(esp32s2))] +#[cfg(not(any(esp32s2, esp32h2, esp32h4, esp32c6)))] pub struct Modem(PhantomData<*const ()>, WifiModem, BluetoothModem); +#[cfg(any(esp32h2, esp32h4))] +pub struct Modem(PhantomData<*const ()>, ThreadModem, BluetoothModem); + +#[cfg(esp32c6)] +pub struct Modem( + PhantomData<*const ()>, + WifiModem, + ThreadModem, + BluetoothModem, +); + #[cfg(esp32s2)] pub struct Modem(PhantomData<*const ()>); @@ -21,24 +36,55 @@ impl Modem { /// /// Care should be taken not to instantiate this Mac instance, if it is already instantiated and used elsewhere pub unsafe fn new() -> Self { - #[cfg(not(esp32s2))] + #[cfg(not(any(esp32s2, esp32h2, esp32h4, esp32c6)))] let this = Modem(PhantomData, WifiModem::new(), BluetoothModem::new()); + #[cfg(any(esp32h2, esp32h4))] + let this = Modem(PhantomData, ThreadModem::new(), BluetoothModem::new()); + + #[cfg(esp32c6)] + let this = Modem( + PhantomData, + WifiModem::new(), + ThreadModem::new(), + BluetoothModem::new(), + ); + #[cfg(esp32s2)] let this = Modem(PhantomData); this } - #[cfg(all(not(esp32s2), esp_idf_esp32_wifi_sw_coexist_enable))] + #[cfg(not(any(esp32s2, esp32h2, esp32h4, esp32c6)))] pub fn split(self) -> (WifiModem, BluetoothModem) { unsafe { (WifiModem::new(), BluetoothModem::new()) } } - #[cfg(all(not(esp32s2), esp_idf_esp32_wifi_sw_coexist_enable))] + #[cfg(not(any(esp32s2, esp32h2, esp32h4, esp32c6)))] pub fn split_ref(&mut self) -> (&mut WifiModem, &mut BluetoothModem) { (&mut self.1, &mut self.2) } + + #[cfg(any(esp32h2, esp32h4))] + pub fn split(self) -> (ThreadModem, BluetoothModem) { + unsafe { (ThreadModem::new(), BluetoothModem::new()) } + } + + #[cfg(any(esp32h2, esp32h4))] + pub fn split_ref(&mut self) -> (&mut ThreadModem, &mut BluetoothModem) { + (&mut self.1, &mut self.2) + } + + #[cfg(esp32c6)] + pub fn split(self) -> (WifiModem, ThreadModem, BluetoothModem) { + unsafe { (WifiModem::new(), ThreadModem::new(), BluetoothModem::new()) } + } + + #[cfg(esp32c6)] + pub fn split_ref(&mut self) -> (&mut WifiModem, &mut ThreadModem, &mut BluetoothModem) { + (&mut self.1, &mut self.2, &mut self.3) + } } unsafe impl Send for Modem {} @@ -53,17 +99,29 @@ impl Peripheral for Modem { } } +#[cfg(not(esp32h2))] impl WifiModemPeripheral for Modem {} +#[cfg(any(esp32h2, esp32c6))] +impl ThreadModemPeripheral for Modem {} + #[cfg(not(esp32s2))] impl BluetoothModemPeripheral for Modem {} #[cfg(not(esp32s2))] mod split { + #[cfg(not(esp32h2))] crate::impl_peripheral!(WifiModem); + #[cfg(not(esp32h2))] impl super::WifiModemPeripheral for WifiModem {} + #[cfg(any(esp32h2, esp32c6))] + crate::impl_peripheral!(ThreadModem); + + #[cfg(any(esp32h2, esp32c6))] + impl super::ThreadModemPeripheral for ThreadModem {} + crate::impl_peripheral!(BluetoothModem); impl super::BluetoothModemPeripheral for BluetoothModem {} diff --git a/src/onewire.rs b/src/onewire.rs new file mode 100644 index 00000000000..f086868a1e2 --- /dev/null +++ b/src/onewire.rs @@ -0,0 +1,161 @@ +//! RMT-based Onewire Implementation +//! +//! The Onewire module driver can be used to communicate with onewire (1-Wire) +//! devices. +//! +//! This module is an abstraction around the esp-idf component [onewire_bus](https://components.espressif.com/components/espressif/onewire_bus) +//! implementation. It is recommended to read the usage of the C API in this [example](https://github.com/espressif/esp-idf/tree/v5.2.2/examples/peripherals/rmt/onewire) +//! +//! +//! This implementation currently supports the one-wire API from the new (v5) esp-idf API. +//! +//! The pin this peripheral is attached to must be +//! externally pulled-up with a 4.7kOhm resistor. +//! +//! todo: +//! - crc checking on messages +//! - helper methods on the driver for executing commands +//! +//! See the `examples/` folder of this repository for more. + +use core::marker::PhantomData; +use core::ptr; + +use esp_idf_sys::*; + +use crate::peripheral::Peripheral; +use crate::rmt::RmtChannel; + +/// Onewire Address type +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct OWAddress(u64); + +impl OWAddress { + pub fn address(&self) -> u64 { + self.0 + } + + pub fn family_code(&self) -> u8 { + (self.0 & u64::from(0xffu8)) as u8 + } +} + +/// Wrapper around a device iterator to search for available devices on the bus +pub struct DeviceSearch<'a, 'b> { + search: onewire_device_iter_handle_t, + _bus: &'a mut OWDriver<'b>, +} + +impl<'a, 'b> DeviceSearch<'a, 'b> { + fn new(bus: &'a mut OWDriver<'b>) -> Result { + let mut my_iter: onewire_device_iter_handle_t = ptr::null_mut(); + + esp!(unsafe { onewire_new_device_iter(bus.bus, &mut my_iter) })?; + + Ok(Self { + search: my_iter, + _bus: bus, + }) + } + + /// Search for the next device on the bus and yield it. + fn next_device(&mut self) -> Result { + let mut next_onewire_device = onewire_device_t::default(); + esp!(unsafe { onewire_device_iter_get_next(self.search, &mut next_onewire_device) })?; + Ok(OWAddress(next_onewire_device.address)) + } +} + +impl<'a, 'b> Iterator for DeviceSearch<'a, 'b> { + type Item = Result; + + fn next(&mut self) -> Option { + match self.next_device() { + Ok(addr) => Some(Ok(addr)), + Err(err) if err.code() == ESP_ERR_NOT_FOUND => None, + Err(err) => Some(Err(err)), + } + } +} + +impl<'a, 'b> Drop for DeviceSearch<'a, 'b> { + fn drop(&mut self) { + esp!(unsafe { onewire_del_device_iter(self.search) }).unwrap(); + } +} + +#[derive(Debug)] +pub struct OWDriver<'a> { + bus: onewire_bus_handle_t, + _channel: u8, + _p: PhantomData<&'a mut ()>, +} + +impl<'a> OWDriver<'a> { + /// Create a new One Wire driver on the allocated pin. + /// + /// The pin will be used as an open drain output. + pub fn new( + pin: impl Peripheral

+ 'a, + _channel: impl Peripheral

+ 'a, + ) -> Result { + let mut bus: onewire_bus_handle_t = ptr::null_mut(); + + let pin = pin.into_ref().pin(); + let bus_config = esp_idf_sys::onewire_bus_config_t { bus_gpio_num: pin }; + + let rmt_config = esp_idf_sys::onewire_bus_rmt_config_t { max_rx_bytes: 10 }; + + esp!(unsafe { onewire_new_bus_rmt(&bus_config, &rmt_config, &mut bus as _) })?; + + Ok(Self { + bus, + _channel: C::channel() as _, + _p: PhantomData, + }) + } + + pub fn read(&self, buff: &mut [u8]) -> Result<(), EspError> { + esp!(unsafe { onewire_bus_read_bytes(self.bus, buff.as_mut_ptr() as *mut _, buff.len()) })?; + + Ok(()) + } + + pub fn write(&self, data: &[u8]) -> Result<(), EspError> { + esp!(unsafe { onewire_bus_write_bytes(self.bus, data.as_ptr(), data.len() as u8) })?; + + Ok(()) + } + + /// Send reset pulse to the bus, and check if there are devices attached to the bus + /// + /// If there are no devices on the bus, this will result in an error. + pub fn reset(&self) -> Result<(), EspError> { + esp!(unsafe { onewire_bus_reset(self.bus) }) + } + + /// Start a search for devices attached to the OneWire bus. + pub fn search(&mut self) -> Result, EspError> { + DeviceSearch::new(self) + } +} + +impl<'d> Drop for OWDriver<'d> { + fn drop(&mut self) { + esp!(unsafe { onewire_bus_del(self.bus) }).unwrap(); + } +} + +unsafe impl<'d> Send for OWDriver<'d> {} + +/// Command codes +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[repr(u8)] +pub enum OWCommand { + Search = 0xF0, //Obtain IDs of all devices on the bus + MatchRom = 0x55, //Address specific device + SkipRom = 0xCC, //Skip addressing + ReadRom = 0x33, //Identification + SearchAlarm = 0xEC, // Conditional search for all devices in an alarm state. + ReadPowerSupply = 0xB4, +} diff --git a/src/pcnt.rs b/src/pcnt.rs new file mode 100644 index 00000000000..b23e251fba4 --- /dev/null +++ b/src/pcnt.rs @@ -0,0 +1,660 @@ +use core::fmt::Debug; +use core::marker::PhantomData; + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +use esp_idf_sys::*; + +use enumset::EnumSetType; + +use crate::gpio::InputPin; +use crate::peripheral::Peripheral; + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum PcntChannel { + Channel0, + Channel1, +} + +impl From for pcnt_channel_t { + fn from(value: PcntChannel) -> Self { + match value { + PcntChannel::Channel0 => pcnt_channel_t_PCNT_CHANNEL_0, + PcntChannel::Channel1 => pcnt_channel_t_PCNT_CHANNEL_1, + } + } +} + +/// PCNT channel action on signal edge +#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)] +pub enum PcntCountMode { + /// Hold current count value + Hold, + /// Increase count value + #[default] + Increment, + /// Decrease count value + Decrement, +} + +impl From for pcnt_count_mode_t { + fn from(value: PcntCountMode) -> Self { + match value { + PcntCountMode::Hold => pcnt_channel_edge_action_t_PCNT_CHANNEL_EDGE_ACTION_HOLD, + PcntCountMode::Increment => { + pcnt_channel_edge_action_t_PCNT_CHANNEL_EDGE_ACTION_INCREASE + } + PcntCountMode::Decrement => { + pcnt_channel_edge_action_t_PCNT_CHANNEL_EDGE_ACTION_DECREASE + } + } + } +} + +/// PCNT channel action on control level +#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)] +pub enum PcntControlMode { + /// Keep current count mode + Keep, + /// Invert current count mode (increase -> decrease, decrease -> increase) + #[default] + Reverse, + /// Hold current count value + Disable, +} + +impl From for pcnt_ctrl_mode_t { + fn from(value: PcntControlMode) -> Self { + match value { + PcntControlMode::Keep => pcnt_channel_level_action_t_PCNT_CHANNEL_LEVEL_ACTION_KEEP, + PcntControlMode::Reverse => { + pcnt_channel_level_action_t_PCNT_CHANNEL_LEVEL_ACTION_INVERSE + } + PcntControlMode::Disable => pcnt_channel_level_action_t_PCNT_CHANNEL_LEVEL_ACTION_HOLD, + } + } +} + +#[derive(Debug, EnumSetType)] +#[enumset(repr = "u32")] +pub enum PcntEvent { + /// PCNT watch point event: threshold1 value event + Threshold1 = 2, // pcnt_evt_type_t_PCNT_EVT_THRES_1 = 0x04, + /// PCNT watch point event: threshold0 value event + Threshold0 = 3, // pcnt_evt_type_t_PCNT_EVT_THRES_0 = 0x08, + /// PCNT watch point event: Minimum counter value + LowLimit = 4, // pcnt_evt_type_t_PCNT_EVT_L_LIM = 0x10, + /// PCNT watch point event: Maximum counter value + HighLimit = 5, // pcnt_evt_type_t_PCNT_EVT_H_LIM = 0x20, + /// PCNT watch point event: counter value zero event + Zero = 6, // pcnt_evt_type_t_PCNT_EVT_ZERO = 0x40, +} + +pub type PcntEventType = enumset::EnumSet; + +/// Pulse Counter configuration for a single channel +#[derive(Debug, Copy, Clone, Default)] +pub struct PcntChannelConfig { + /// PCNT low control mode + pub lctrl_mode: PcntControlMode, + /// PCNT high control mode + pub hctrl_mode: PcntControlMode, + /// PCNT positive edge count mode + pub pos_mode: PcntCountMode, + /// PCNT negative edge count mode + pub neg_mode: PcntCountMode, + /// Maximum counter value + pub counter_h_lim: i16, + /// Minimum counter value + pub counter_l_lim: i16, +} + +impl PcntChannelConfig { + pub fn new() -> Self { + Default::default() + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum PinIndex { + Pin0 = 0, + Pin1 = 1, + Pin2 = 2, + Pin3 = 3, +} + +pub struct PcntDriver<'d> { + unit: pcnt_unit_t, + pins: [i32; 4], + _p: PhantomData<&'d mut ()>, +} + +macro_rules! pin_to_number { + ($pin:ident) => { + match $pin { + Some(pin) => { + crate::into_ref!(pin); + pin.pin() + } + None => PCNT_PIN_NOT_USED, + } + }; +} + +impl<'d> PcntDriver<'d> { + pub fn new( + _pcnt: impl Peripheral

+ 'd, + pin0: Option + 'd>, + pin1: Option + 'd>, + pin2: Option + 'd>, + pin3: Option + 'd>, + ) -> Result { + // consume the pins and keep only the pin number. + let pins = [ + pin_to_number!(pin0), + pin_to_number!(pin1), + pin_to_number!(pin2), + pin_to_number!(pin3), + ]; + Ok(Self { + unit: PCNT::unit(), + pins, + _p: PhantomData, + }) + } + + /// Configure Pulse Counter chanel + /// @note + /// This function will disable three events: PCNT_EVT_L_LIM, PCNT_EVT_H_LIM, PCNT_EVT_ZERO. + /// + /// @param channel Channel to configure + /// @param pulse_pin Pulse signal input pin + /// @param ctrl_pin Control signal input pin + /// @param pconfig Reference of PcntConfig + /// + /// @note Set the signal input to PCNT_PIN_NOT_USED if unused. + /// + /// returns + /// - () + /// - EspError + pub fn channel_config( + &mut self, + channel: PcntChannel, + pulse_pin: PinIndex, + ctrl_pin: PinIndex, + pconfig: &PcntChannelConfig, + ) -> Result<(), EspError> { + let config = pcnt_config_t { + pulse_gpio_num: self.pins[pulse_pin as usize], + ctrl_gpio_num: self.pins[ctrl_pin as usize], + lctrl_mode: pconfig.lctrl_mode.into(), + hctrl_mode: pconfig.hctrl_mode.into(), + pos_mode: pconfig.pos_mode.into(), + neg_mode: pconfig.neg_mode.into(), + counter_h_lim: pconfig.counter_h_lim, + counter_l_lim: pconfig.counter_l_lim, + channel: channel.into(), + unit: self.unit, + }; + + unsafe { esp!(pcnt_unit_config(&config as *const pcnt_config_t)) } + } + + /// Get pulse counter value + /// + /// returns + /// - i16 + /// - EspError + pub fn get_counter_value(&self) -> Result { + let mut value = 0i16; + unsafe { + esp!(pcnt_get_counter_value(self.unit, &mut value as *mut i16))?; + } + Ok(value) + } + + /// Pause PCNT counter of PCNT unit + /// + /// returns + /// - () + /// - EspError + pub fn counter_pause(&self) -> Result<(), EspError> { + unsafe { esp!(pcnt_counter_pause(self.unit)) } + } + + /// Resume counting for PCNT counter + /// + /// returns + /// - () + /// - EspError + pub fn counter_resume(&self) -> Result<(), EspError> { + unsafe { esp!(pcnt_counter_resume(self.unit)) } + } + + /// Clear and reset PCNT counter value to zero + /// + /// returns + /// - () + /// - EspError + pub fn counter_clear(&self) -> Result<(), EspError> { + unsafe { esp!(pcnt_counter_clear(self.unit)) } + } + + /// Enable PCNT interrupt for PCNT unit + /// @note + /// Each Pulse counter unit has five watch point events that share the same interrupt. + /// Configure events with pcnt_event_enable() and pcnt_event_disable() + /// + /// returns + /// - () + /// - EspError + pub fn intr_enable(&self) -> Result<(), EspError> { + unsafe { esp!(pcnt_intr_enable(self.unit)) } + } + + /// Disable PCNT interrupt for PCNT unit + /// + /// returns + /// - () + /// - EspError + pub fn intr_disable(&self) -> Result<(), EspError> { + unsafe { esp!(pcnt_intr_disable(self.unit)) } + } + + /// Enable PCNT event of PCNT unit + /// + /// @param evt_type Watch point event type. + /// All enabled events share the same interrupt (one interrupt per pulse counter unit). + /// returns + /// - () + /// - EspError + pub fn event_enable(&self, evt_type: PcntEvent) -> Result<(), EspError> { + let evt_type: pcnt_evt_type_t = PcntEventType::only(evt_type).as_repr(); + unsafe { esp!(pcnt_event_enable(self.unit, evt_type)) } + } + + /// Disable PCNT event of PCNT unit + /// + /// @param evt_type Watch point event type. + /// All enabled events share the same interrupt (one interrupt per pulse counter unit). + /// returns + /// - () + /// - EspError + pub fn event_disable(&self, evt_type: PcntEvent) -> Result<(), EspError> { + let evt_type: pcnt_evt_type_t = PcntEventType::only(evt_type).as_repr(); + unsafe { esp!(pcnt_event_disable(self.unit, evt_type)) } + } + + fn only_one_event_type(evt_type: PcntEventType) -> Result { + match evt_type.iter().count() { + 1 => Ok(evt_type.as_repr()), + _ => Err(EspError::from(ESP_ERR_INVALID_ARG as esp_err_t).unwrap()), + } + } + + /// Set PCNT event value of PCNT unit + /// + /// @param evt_type Watch point event type. + /// All enabled events share the same interrupt (one interrupt per pulse counter unit). + /// + /// returns + /// - () + /// - EspError + pub fn set_event_value(&self, evt_type: PcntEventType, value: i16) -> Result<(), EspError> { + let evt_type = Self::only_one_event_type(evt_type)?; + unsafe { esp!(pcnt_set_event_value(self.unit, evt_type, value)) } + } + + /// Get PCNT event value of PCNT unit + /// + /// @param evt_type Watch point event type. + /// All enabled events share the same interrupt (one interrupt per pulse counter unit). + /// + /// returns + /// - i16 + /// - EspError + pub fn get_event_value(&self, evt_type: PcntEventType) -> Result { + let evt_type = Self::only_one_event_type(evt_type)?; + let mut value = 0i16; + unsafe { + esp!(pcnt_get_event_value( + self.unit, + evt_type, + &mut value as *mut i16 + ))?; + } + Ok(value) + } + + /// Get PCNT event status of PCNT unit + /// + /// returns + /// - i32 + /// - EspError + // TODO: status is a bit field! + pub fn get_event_status(&self) -> Result { + let mut value = 0u32; + unsafe { + esp!(pcnt_get_event_status(self.unit, &mut value as *mut u32))?; + } + Ok(value) + } + + // TODO: not implementing until we can do it safely! Will need to reconfigure channels? + // + // /// Configure PCNT pulse signal input pin and control input pin + // /// + // /// @param channel PcntChannel + // /// @param pulse_io Pulse signal input pin + // /// @param ctrl_io Control signal input pin + // /// + // /// @note Set the signal input to PCNT_PIN_NOT_USED if unused. + // /// + // /// returns + // /// - () + // /// - EspError + // pub fn set_pin<'a>( + // &mut self, + // channel: PcntChannel, + // pulse_pin: Option + 'a>, + // ctrl_pin: Option + 'a>, + // ) -> Result<(), EspError> { + // } + + /// Enable PCNT input filter + /// + /// returns + /// - () + /// - EspError + pub fn filter_enable(&self) -> Result<(), EspError> { + unsafe { esp!(pcnt_filter_enable(self.unit)) } + } + + /// Disable PCNT input filter + /// + /// returns + /// - () + /// - EspError + pub fn filter_disable(&self) -> Result<(), EspError> { + unsafe { esp!(pcnt_filter_disable(self.unit)) } + } + + /// Set PCNT filter value + /// + /// @param filter_val PCNT signal filter value, counter in APB_CLK cycles. + /// Any pulses lasting shorter than this will be ignored when the filter is enabled. + /// @note + /// filter_val is a 10-bit value, so the maximum filter_val should be limited to 1023. + /// + /// returns + /// - () + /// - EspError + pub fn set_filter_value(&self, value: u16) -> Result<(), EspError> { + unsafe { esp!(pcnt_set_filter_value(self.unit, value)) } + } + + /// Get PCNT filter value + /// + /// returns + /// - i16 + /// - EspError + pub fn get_filter_value(&self) -> Result { + let mut value = 0u16; + unsafe { + esp!(pcnt_get_filter_value(self.unit, &mut value as *mut u16))?; + } + Ok(value) + } + + /// Set PCNT counter mode + /// + /// @param channel PCNT channel number + /// @param pos_mode Counter mode when detecting positive edge + /// @param neg_mode Counter mode when detecting negative edge + /// @param hctrl_mode Counter mode when control signal is high level + /// @param lctrl_mode Counter mode when control signal is low level + /// + /// returns + /// - () + /// - EspError + pub fn set_mode( + &self, + channel: PcntChannel, + pos_mode: PcntCountMode, + neg_mode: PcntCountMode, + hctrl_mode: PcntControlMode, + lctrl_mode: PcntControlMode, + ) -> Result<(), EspError> { + unsafe { + esp!(pcnt_set_mode( + self.unit, + channel.into(), + pos_mode.into(), + neg_mode.into(), + hctrl_mode.into(), + lctrl_mode.into() + )) + } + } + + /// Add ISR handler for specified unit. + /// + /// This ISR handler will be called from an ISR. So there is a stack + /// size limit (configurable as \"ISR stack size\" in menuconfig). This + /// limit is smaller compared to a global PCNT interrupt handler due + /// to the additional level of indirection. + /// + /// # Safety + /// + /// Care should be taken not to call STD, libc or FreeRTOS APIs (except for a few allowed ones) + /// in the callback passed to this function, as it is executed in an ISR context. + /// + /// @param callback Interrupt handler function. + /// + /// returns + /// - () + /// - EspError + #[cfg(feature = "alloc")] + pub unsafe fn subscribe(&self, callback: F) -> Result<(), EspError> + where + F: FnMut(u32) + Send + 'static, + { + self.internal_subscribe(callback) + } + + /// Add ISR handler for specified unit. + /// + /// This ISR handler will be called from an ISR. So there is a stack + /// size limit (configurable as \"ISR stack size\" in menuconfig). This + /// limit is smaller compared to a global PCNT interrupt handler due + /// to the additional level of indirection. + /// + /// # Safety + /// + /// Care should be taken not to call STD, libc or FreeRTOS APIs (except for a few allowed ones) + /// in the callback passed to this function, as it is executed in an ISR context. + /// + /// Additionally, this method - in contrast to method `subscribe` - allows + /// the passed-in callback/closure to be non-`'static`. This enables users to borrow + /// - in the closure - variables that live on the stack - or more generally - in the same + /// scope where the driver is created. + /// + /// HOWEVER: care should be taken NOT to call `core::mem::forget()` on the driver, + /// as that would immediately lead to an UB (crash). + /// Also note that forgetting the driver might happen with `Rc` and `Arc` + /// when circular references are introduced: https://github.com/rust-lang/rust/issues/24456 + /// + /// The reason is that the closure is actually sent and owned by an ISR routine, + /// which means that if the driver is forgotten, Rust is free to e.g. unwind the stack + /// and the ISR routine will end up with references to variables that no longer exist. + /// + /// The destructor of the driver takes care - prior to the driver being dropped and e.g. + /// the stack being unwind - to unsubscribe the ISR routine. + /// Unfortunately, when the driver is forgotten, the un-subscription does not happen + /// and invalid references are left dangling. + /// + /// This "local borrowing" will only be possible to express in a safe way once/if `!Leak` types + /// are introduced to Rust (i.e. the impossibility to "forget" a type and thus not call its destructor). + /// + /// @param callback Interrupt handler function. + /// + /// returns + /// - () + /// - EspError + #[cfg(feature = "alloc")] + pub unsafe fn subscribe_nonstatic(&self, callback: F) -> Result<(), EspError> + where + F: FnMut(u32) + Send + 'd, + { + self.internal_subscribe(callback) + } + + #[cfg(feature = "alloc")] + fn internal_subscribe(&self, callback: F) -> Result<(), EspError> + where + F: FnMut(u32) + Send + 'd, + { + enable_isr_service()?; + + self.unsubscribe()?; + let callback: alloc::boxed::Box = alloc::boxed::Box::new(callback); + + unsafe { + ISR_HANDLERS[self.unit as usize] = Some(core::mem::transmute::< + alloc::boxed::Box, + alloc::boxed::Box, + >(callback)); + } + esp!(unsafe { + pcnt_isr_handler_add( + self.unit, + Some(Self::handle_isr), + self.unit as *mut core::ffi::c_void, + ) + })?; + + Ok(()) + } + + /// Remove ISR handler for specified unit. + /// + /// returns + /// - () + /// - EspError + #[cfg(feature = "alloc")] + pub fn unsubscribe(&self) -> Result<(), EspError> { + unsafe { + esp!(pcnt_isr_handler_remove(self.unit))?; + ISR_HANDLERS[self.unit as usize] = None; + } + Ok(()) + } + + #[cfg(feature = "alloc")] + unsafe extern "C" fn handle_isr(data: *mut core::ffi::c_void) { + let unit = data as pcnt_unit_t; + if let Some(f) = &mut ISR_HANDLERS[unit as usize] { + let mut value = 0u32; + esp!(pcnt_get_event_status(unit, &mut value as *mut u32)) + .expect("failed to fetch event status!"); + f(value); + } + } +} + +impl Drop for PcntDriver<'_> { + fn drop(&mut self) { + let _ = self.counter_pause(); + let _ = self.intr_disable(); + #[cfg(feature = "alloc")] + unsafe { + pcnt_isr_handler_remove(self.unit); + ISR_HANDLERS[self.unit as usize] = None + }; + } +} + +static ISR_ALLOC_FLAGS: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0); + +#[cfg(feature = "alloc")] +static ISR_SERVICE_ENABLED: core::sync::atomic::AtomicBool = + core::sync::atomic::AtomicBool::new(false); + +#[cfg(feature = "alloc")] +static PCNT_CS: crate::task::CriticalSection = crate::task::CriticalSection::new(); + +pub fn init_isr_alloc_flags(flags: enumset::EnumSet) { + ISR_ALLOC_FLAGS.store( + crate::interrupt::InterruptType::to_native(flags), + core::sync::atomic::Ordering::SeqCst, + ); +} + +#[cfg(feature = "alloc")] +fn enable_isr_service() -> Result<(), EspError> { + use core::sync::atomic::Ordering; + + if !ISR_SERVICE_ENABLED.load(Ordering::SeqCst) { + let _cs = PCNT_CS.enter(); + + if !ISR_SERVICE_ENABLED.load(Ordering::SeqCst) { + esp!(unsafe { pcnt_isr_service_install(ISR_ALLOC_FLAGS.load(Ordering::SeqCst) as _) })?; + + ISR_SERVICE_ENABLED.store(true, Ordering::SeqCst); + } + } + + Ok(()) +} + +#[cfg(feature = "alloc")] +type IsrHandler = Option>; +#[cfg(feature = "alloc")] +static mut ISR_HANDLERS: [IsrHandler; pcnt_unit_t_PCNT_UNIT_MAX as usize] = [ + None, + None, + None, + None, + #[cfg(esp32)] + None, + #[cfg(esp32)] + None, + #[cfg(esp32)] + None, + #[cfg(esp32)] + None, +]; + +pub trait Pcnt { + fn unit() -> pcnt_unit_t; +} + +macro_rules! impl_pcnt { + ($pcnt:ident: $unit:expr) => { + crate::impl_peripheral!($pcnt); + + impl Pcnt for $pcnt { + #[inline(always)] + fn unit() -> pcnt_unit_t { + $unit + } + } + }; +} + +impl_pcnt!(PCNT0: pcnt_unit_t_PCNT_UNIT_0); +impl_pcnt!(PCNT1: pcnt_unit_t_PCNT_UNIT_1); +impl_pcnt!(PCNT2: pcnt_unit_t_PCNT_UNIT_2); +impl_pcnt!(PCNT3: pcnt_unit_t_PCNT_UNIT_3); +#[cfg(esp32)] +impl_pcnt!(PCNT4: pcnt_unit_t_PCNT_UNIT_4); +#[cfg(esp32)] +impl_pcnt!(PCNT5: pcnt_unit_t_PCNT_UNIT_5); +#[cfg(esp32)] +impl_pcnt!(PCNT6: pcnt_unit_t_PCNT_UNIT_6); +#[cfg(esp32)] +impl_pcnt!(PCNT7: pcnt_unit_t_PCNT_UNIT_7); diff --git a/src/peripheral.rs b/src/peripheral.rs index 5145bef4f55..9eb1cb9cbad 100644 --- a/src/peripheral.rs +++ b/src/peripheral.rs @@ -75,7 +75,7 @@ impl<'a, T> PeripheralRef<'a, T> { } } -impl<'a, T> Deref for PeripheralRef<'a, T> { +impl Deref for PeripheralRef<'_, T> { type Target = T; #[inline] @@ -84,7 +84,7 @@ impl<'a, T> Deref for PeripheralRef<'a, T> { } } -impl<'a, T> DerefMut for PeripheralRef<'a, T> { +impl DerefMut for PeripheralRef<'_, T> { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner diff --git a/src/peripherals.rs b/src/peripherals.rs index 4c95293ce2f..23e93f53a65 100644 --- a/src/peripherals.rs +++ b/src/peripherals.rs @@ -1,15 +1,11 @@ use crate::adc; -#[cfg(not(feature = "riscv-ulp-hal"))] use crate::can; use crate::gpio; -#[cfg(not(feature = "riscv-ulp-hal"))] use crate::i2c; -#[cfg(not(feature = "riscv-ulp-hal"))] +#[cfg(esp_idf_soc_i2s_supported)] +use crate::i2s; use crate::ledc; -#[cfg(all( - any(all(esp32, esp_idf_eth_use_esp32_emac), esp_idf_eth_use_openeth), - not(feature = "riscv-ulp-hal") -))] +#[cfg(any(all(esp32, esp_idf_eth_use_esp32_emac), esp_idf_eth_use_openeth))] use crate::mac; #[cfg(all( any(esp32, esp32s3), @@ -19,46 +15,72 @@ use crate::mac; use crate::mcpwm; #[cfg(not(feature = "riscv-ulp-hal"))] use crate::modem; -#[cfg(not(feature = "riscv-ulp-hal"))] +#[cfg(any(esp32, esp32s2, esp32s3, esp32c6))] +use crate::pcnt; use crate::rmt; -#[cfg(not(feature = "riscv-ulp-hal"))] +#[cfg(all(esp_idf_soc_sdmmc_host_supported, feature = "experimental"))] +use crate::sd; use crate::spi; -#[cfg(not(feature = "riscv-ulp-hal"))] +#[cfg(any( + all( + not(any(esp_idf_version_major = "4", esp_idf_version = "5.0")), + esp_idf_esp_task_wdt_en + ), + any(esp_idf_version_major = "4", esp_idf_version = "5.0") +))] +use crate::task::watchdog; +#[cfg(all(esp_idf_soc_temp_sensor_supported, esp_idf_version_major = "5"))] +use crate::temp_sensor; use crate::timer; -#[cfg(not(feature = "riscv-ulp-hal"))] use crate::uart; #[cfg(all( - any(esp32, esp32s2, esp32s3), - not(feature = "riscv-ulp-hal"), + any(esp32, esp32s2, esp32s3, esp32c6, esp32p4), esp_idf_comp_ulp_enabled ))] use crate::ulp; +#[cfg(esp_idf_soc_usb_serial_jtag_supported)] +use crate::usb_serial; pub struct Peripherals { pub pins: gpio::Pins, - #[cfg(not(feature = "riscv-ulp-hal"))] pub uart0: uart::UART0, - #[cfg(not(feature = "riscv-ulp-hal"))] pub uart1: uart::UART1, - #[cfg(all(any(esp32, esp32s3), not(feature = "riscv-ulp-hal")))] + #[cfg(any(esp32, esp32s3))] pub uart2: uart::UART2, - #[cfg(not(feature = "riscv-ulp-hal"))] pub i2c0: i2c::I2C0, - #[cfg(all(not(esp32c3), not(feature = "riscv-ulp-hal")))] + #[cfg(not(any(esp32c3, esp32c2, esp32c6)))] pub i2c1: i2c::I2C1, - #[cfg(not(feature = "riscv-ulp-hal"))] + #[cfg(esp_idf_soc_i2s_supported)] + pub i2s0: i2s::I2S0, + #[cfg(all(esp_idf_soc_i2s_supported, any(esp32, esp32s3)))] + pub i2s1: i2s::I2S1, pub spi1: spi::SPI1, - #[cfg(not(feature = "riscv-ulp-hal"))] pub spi2: spi::SPI2, - #[cfg(all(not(esp32c3), not(feature = "riscv-ulp-hal")))] + #[cfg(any(esp32, esp32s2, esp32s3))] pub spi3: spi::SPI3, pub adc1: adc::ADC1, + #[cfg(any(esp32, esp32s2, esp32s3, esp32c3))] pub adc2: adc::ADC2, + // TODO: Check the pulse counter story for c2, h2, c5, and p4 + #[cfg(any(esp32, esp32s2, esp32s3, esp32c6))] + pub pcnt0: pcnt::PCNT0, + #[cfg(any(esp32, esp32s2, esp32s3, esp32c6))] + pub pcnt1: pcnt::PCNT1, + #[cfg(any(esp32, esp32s2, esp32s3, esp32c6))] + pub pcnt2: pcnt::PCNT2, + #[cfg(any(esp32, esp32s2, esp32s3, esp32c6))] + pub pcnt3: pcnt::PCNT3, + #[cfg(esp32)] + pub pcnt4: pcnt::PCNT4, + #[cfg(esp32)] + pub pcnt5: pcnt::PCNT5, + #[cfg(esp32)] + pub pcnt6: pcnt::PCNT6, + #[cfg(esp32)] + pub pcnt7: pcnt::PCNT7, #[cfg(all(esp32, esp_idf_version_major = "4"))] pub hall_sensor: crate::hall::HallSensor, - #[cfg(not(feature = "riscv-ulp-hal"))] pub can: can::CAN, - #[cfg(not(feature = "riscv-ulp-hal"))] pub ledc: ledc::LEDC, #[cfg(all( any(esp32, esp32s3), @@ -72,80 +94,64 @@ pub struct Peripherals { not(feature = "riscv-ulp-hal") ))] pub mcpwm1: mcpwm::MCPWM, + #[cfg(esp32)] + pub hledc: ledc::HLEDC, #[cfg(not(feature = "riscv-ulp-hal"))] pub rmt: rmt::RMT, #[cfg(all( - any(esp32, esp32s2, esp32s3), - not(feature = "riscv-ulp-hal"), + any(esp32, esp32s2, esp32s3, esp32c6, esp32p4), esp_idf_comp_ulp_enabled ))] pub ulp: ulp::ULP, - #[cfg(all( - any(all(esp32, esp_idf_eth_use_esp32_emac), esp_idf_eth_use_openeth), - not(feature = "riscv-ulp-hal") - ))] + #[cfg(any(all(esp32, esp_idf_eth_use_esp32_emac), esp_idf_eth_use_openeth))] pub mac: mac::MAC, - #[cfg(not(feature = "riscv-ulp-hal"))] pub modem: modem::Modem, - #[cfg(all( - not(feature = "riscv-ulp-hal"), - not(feature = "embassy-time-isr-queue-timer00") - ))] + #[cfg(all(esp_idf_soc_sdmmc_host_supported, feature = "experimental"))] + pub sdmmc0: sd::mmc::SDMMC0, + #[cfg(all(esp_idf_soc_sdmmc_host_supported, feature = "experimental"))] + pub sdmmc1: sd::mmc::SDMMC1, + #[cfg(all(esp_idf_soc_temp_sensor_supported, esp_idf_version_major = "5"))] + pub temp_sensor: temp_sensor::TempSensor, + // TODO: Check the timer story for c2, h2, c5, c6, and p4 pub timer00: timer::TIMER00, - #[cfg(all( - not(esp32c3), - not(feature = "riscv-ulp-hal"), - not(feature = "embassy-time-isr-queue-timer01") - ))] + #[cfg(any(esp32, esp32s2, esp32s3))] pub timer01: timer::TIMER01, - #[cfg(all( - not(feature = "riscv-ulp-hal"), - not(feature = "embassy-time-isr-queue-timer10") - ))] + #[cfg(not(esp32c2))] pub timer10: timer::TIMER10, - #[cfg(all( - not(esp32c3), - not(feature = "riscv-ulp-hal"), - not(feature = "embassy-time-isr-queue-timer11") - ))] + #[cfg(any(esp32, esp32s2, esp32s3))] pub timer11: timer::TIMER11, + #[cfg(any( + all( + not(any(esp_idf_version_major = "4", esp_idf_version = "5.0")), + esp_idf_esp_task_wdt_en + ), + any(esp_idf_version_major = "4", esp_idf_version = "5.0") + ))] + pub twdt: watchdog::TWDT, + #[cfg(esp_idf_soc_usb_serial_jtag_supported)] + pub usb_serial: usb_serial::USB_SERIAL, } -#[cfg(feature = "riscv-ulp-hal")] -static mut TAKEN: bool = false; - -#[cfg(not(feature = "riscv-ulp-hal"))] static TAKEN: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false); - -#[cfg(not(feature = "riscv-ulp-hal"))] static TAKEN_CS: crate::task::CriticalSection = crate::task::CriticalSection::new(); impl Peripherals { - #[cfg(feature = "riscv-ulp-hal")] - pub fn take() -> Option { - if unsafe { TAKEN } { - None - } else { - unsafe { - TAKEN = true; - } - Some(unsafe { Peripherals::new() }) - } - } - - #[cfg(not(feature = "riscv-ulp-hal"))] - pub fn take() -> Option { + pub fn take() -> Result { if TAKEN.load(core::sync::atomic::Ordering::SeqCst) { - None + Err(crate::sys::EspError::from_infallible::< + { crate::sys::ESP_ERR_INVALID_STATE }, + >()) } else { - let _ = TAKEN_CS.enter(); + let _guard = TAKEN_CS.enter(); if !TAKEN.load(core::sync::atomic::Ordering::SeqCst) { TAKEN.store(true, core::sync::atomic::Ordering::SeqCst); - Some(unsafe { Peripherals::new() }) + Ok(unsafe { Peripherals::new() }) } else { - None + Err(crate::sys::EspError::from_infallible::< + { crate::sys::ESP_ERR_INVALID_STATE }, + >()) } } } @@ -156,29 +162,43 @@ impl Peripherals { pub unsafe fn new() -> Self { Self { pins: gpio::Pins::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] uart0: uart::UART0::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] uart1: uart::UART1::new(), - #[cfg(all(any(esp32, esp32s3), not(feature = "riscv-ulp-hal")))] + #[cfg(any(esp32, esp32s3))] uart2: uart::UART2::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] i2c0: i2c::I2C0::new(), - #[cfg(all(not(esp32c3), not(feature = "riscv-ulp-hal")))] + #[cfg(not(any(esp32c3, esp32c2, esp32c6)))] i2c1: i2c::I2C1::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] + #[cfg(esp_idf_soc_i2s_supported)] + i2s0: i2s::I2S0::new(), + #[cfg(all(esp_idf_soc_i2s_supported, any(esp32, esp32s3)))] + i2s1: i2s::I2S1::new(), spi1: spi::SPI1::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] spi2: spi::SPI2::new(), - #[cfg(all(not(esp32c3), not(feature = "riscv-ulp-hal")))] + #[cfg(any(esp32, esp32s2, esp32s3))] spi3: spi::SPI3::new(), adc1: adc::ADC1::new(), + #[cfg(any(esp32, esp32s2, esp32s3, esp32c3))] adc2: adc::ADC2::new(), + #[cfg(any(esp32, esp32s2, esp32s3, esp32c6))] + pcnt0: pcnt::PCNT0::new(), + #[cfg(any(esp32, esp32s2, esp32s3, esp32c6))] + pcnt1: pcnt::PCNT1::new(), + #[cfg(any(esp32, esp32s2, esp32s3, esp32c6))] + pcnt2: pcnt::PCNT2::new(), + #[cfg(any(esp32, esp32s2, esp32s3, esp32c6))] + pcnt3: pcnt::PCNT3::new(), + #[cfg(esp32)] + pcnt4: pcnt::PCNT4::new(), + #[cfg(esp32)] + pcnt5: pcnt::PCNT5::new(), + #[cfg(esp32)] + pcnt6: pcnt::PCNT6::new(), + #[cfg(esp32)] + pcnt7: pcnt::PCNT7::new(), #[cfg(all(esp32, esp_idf_version_major = "4"))] hall_sensor: crate::hall::HallSensor::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] can: can::CAN::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] ledc: ledc::LEDC::new(), #[cfg(all( any(esp32, esp32s3), @@ -192,43 +212,40 @@ impl Peripherals { esp_idf_version_major = "5" ))] mcpwm1: mcpwm::MCPWM::::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] + #[cfg(esp32)] + hledc: ledc::HLEDC::new(), rmt: rmt::RMT::new(), #[cfg(all( - any(esp32, esp32s2, esp32s3), - not(feature = "riscv-ulp-hal"), + any(esp32, esp32s2, esp32s3, esp32c6, esp32p4), esp_idf_comp_ulp_enabled ))] ulp: ulp::ULP::new(), - #[cfg(all( - any(all(esp32, esp_idf_eth_use_esp32_emac), esp_idf_eth_use_openeth), - not(feature = "riscv-ulp-hal") - ))] + #[cfg(any(all(esp32, esp_idf_eth_use_esp32_emac), esp_idf_eth_use_openeth))] mac: mac::MAC::new(), - #[cfg(not(feature = "riscv-ulp-hal"))] modem: modem::Modem::new(), - #[cfg(all( - not(feature = "riscv-ulp-hal"), - not(feature = "embassy-time-isr-queue-timer00") - ))] + #[cfg(all(esp_idf_soc_sdmmc_host_supported, feature = "experimental"))] + sdmmc0: sd::mmc::SDMMC0::new(), + #[cfg(all(esp_idf_soc_sdmmc_host_supported, feature = "experimental"))] + sdmmc1: sd::mmc::SDMMC1::new(), + #[cfg(all(esp_idf_soc_temp_sensor_supported, esp_idf_version_major = "5"))] + temp_sensor: temp_sensor::TempSensor::new(), timer00: timer::TIMER00::new(), - #[cfg(all( - not(esp32c3), - not(feature = "riscv-ulp-hal"), - not(feature = "embassy-time-isr-queue-timer01") - ))] + #[cfg(any(esp32, esp32s2, esp32s3))] timer01: timer::TIMER01::new(), - #[cfg(all( - not(feature = "riscv-ulp-hal"), - not(feature = "embassy-time-isr-queue-timer10") - ))] + #[cfg(not(esp32c2))] timer10: timer::TIMER10::new(), - #[cfg(all( - not(esp32c3), - not(feature = "riscv-ulp-hal"), - not(feature = "embassy-time-isr-queue-timer11") - ))] + #[cfg(any(esp32, esp32s2, esp32s3))] timer11: timer::TIMER11::new(), + #[cfg(any( + all( + not(any(esp_idf_version_major = "4", esp_idf_version = "5.0")), + esp_idf_esp_task_wdt_en + ), + any(esp_idf_version_major = "4", esp_idf_version = "5.0") + ))] + twdt: watchdog::TWDT::new(), + #[cfg(esp_idf_soc_usb_serial_jtag_supported)] + usb_serial: usb_serial::USB_SERIAL::new(), } } } diff --git a/src/reset.rs b/src/reset.rs index 81b1d23c50a..38b669b9dd1 100644 --- a/src/reset.rs +++ b/src/reset.rs @@ -27,6 +27,53 @@ pub enum ResetReason { TaskWatchdog, /// Reset after exiting deep sleep mode DeepSleep, + /// Reset by USB peripheral (introduced in IDF v5.1.4) + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + esp_idf_version_full = "5.1.0", + esp_idf_version_full = "5.1.1", + esp_idf_version_full = "5.1.2", + esp_idf_version_full = "5.1.3", + )))] + USBPeripheral, + /// Reset by JTAG (introduced in IDF v5.1.4) + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + esp_idf_version_full = "5.1.0", + esp_idf_version_full = "5.1.1", + esp_idf_version_full = "5.1.2", + esp_idf_version_full = "5.1.3", + )))] + JTAG, + /// Reset due to efuse error (introduced in IDF v5.2.2) + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + esp_idf_version_full = "5.2.0", + esp_idf_version_full = "5.2.1", + )))] + EfuseError, + /// Reset due to power glitch detected (introduced in IDF v5.2.2) + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + esp_idf_version_full = "5.2.0", + esp_idf_version_full = "5.2.1", + )))] + PowerGlitch, + /// Reset due to CPU lock up (introduced in IDF v5.2.2) + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + esp_idf_version_full = "5.2.0", + esp_idf_version_full = "5.2.1", + )))] + CPULockup, } impl From for ResetReason { @@ -44,6 +91,48 @@ impl From for ResetReason { esp_reset_reason_t_ESP_RST_BROWNOUT => Self::Brownout, esp_reset_reason_t_ESP_RST_TASK_WDT => Self::TaskWatchdog, esp_reset_reason_t_ESP_RST_DEEPSLEEP => Self::DeepSleep, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + esp_idf_version_full = "5.1.0", + esp_idf_version_full = "5.1.1", + esp_idf_version_full = "5.1.2", + esp_idf_version_full = "5.1.3", + )))] + esp_reset_reason_t_ESP_RST_USB => Self::USBPeripheral, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + esp_idf_version_full = "5.1.0", + esp_idf_version_full = "5.1.1", + esp_idf_version_full = "5.1.2", + esp_idf_version_full = "5.1.3", + )))] + esp_reset_reason_t_ESP_RST_JTAG => Self::JTAG, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + esp_idf_version_full = "5.2.0", + esp_idf_version_full = "5.2.1", + )))] + esp_reset_reason_t_ESP_RST_EFUSE => Self::EfuseError, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + esp_idf_version_full = "5.2.0", + esp_idf_version_full = "5.2.1", + )))] + esp_reset_reason_t_ESP_RST_PWR_GLITCH => Self::PowerGlitch, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + esp_idf_version_full = "5.2.0", + esp_idf_version_full = "5.2.1", + )))] + esp_reset_reason_t_ESP_RST_CPU_LOCKUP => Self::CPULockup, _ => unreachable!(), } } @@ -78,7 +167,9 @@ impl From for WakeupReason { fn from(value: esp_sleep_source_t) -> Self { match value { esp_sleep_source_t_ESP_SLEEP_WAKEUP_UNDEFINED => Self::Unknown, + esp_sleep_source_t_ESP_SLEEP_WAKEUP_EXT0 => Self::Button, esp_sleep_source_t_ESP_SLEEP_WAKEUP_EXT1 => Self::Button, + esp_sleep_source_t_ESP_SLEEP_WAKEUP_ULP => Self::ULP, esp_sleep_source_t_ESP_SLEEP_WAKEUP_COCPU => Self::ULP, esp_sleep_source_t_ESP_SLEEP_WAKEUP_TIMER => Self::Timer, other => Self::Other(other), @@ -95,8 +186,6 @@ impl WakeupReason { } } -pub fn restart() { - unsafe { - esp_restart(); - } +pub fn restart() -> ! { + unsafe { esp_restart() } } diff --git a/src/riscv_ulp_hal.rs b/src/riscv_ulp_hal.rs deleted file mode 100644 index 39337b78f46..00000000000 --- a/src/riscv_ulp_hal.rs +++ /dev/null @@ -1,12 +0,0 @@ -#[cfg(feature = "riscv-ulp-hal")] -mod pac; -#[cfg(feature = "riscv-ulp-hal")] -mod reg; - -#[cfg(feature = "riscv-ulp-hal")] -#[macro_use] -pub mod sys; -#[cfg(feature = "riscv-ulp-hal")] -pub mod delay; -#[cfg(feature = "riscv-ulp-hal")] -pub mod start; diff --git a/src/riscv_ulp_hal/delay.rs b/src/riscv_ulp_hal/delay.rs deleted file mode 100644 index 89d7075e9e8..00000000000 --- a/src/riscv_ulp_hal/delay.rs +++ /dev/null @@ -1,61 +0,0 @@ -use super::sys::*; - -/// Busy-loop based delay for the RiscV ULP coprocessor -pub struct Ulp; - -impl embedded_hal_0_2::blocking::delay::DelayUs for Ulp { - fn delay_us(&mut self, us: u32) { - delay_cycles(us * ULP_RISCV_CYCLES_PER_US_NUM / ULP_RISCV_CYCLES_PER_US_DENUM); - } -} - -impl embedded_hal_0_2::blocking::delay::DelayUs for Ulp { - fn delay_us(&mut self, us: u16) { - delay_cycles(us as u32 * ULP_RISCV_CYCLES_PER_US_NUM / ULP_RISCV_CYCLES_PER_US_DENUM); - } -} - -impl embedded_hal_0_2::blocking::delay::DelayUs for Ulp { - fn delay_us(&mut self, us: u8) { - delay_cycles(us as u32 * ULP_RISCV_CYCLES_PER_US_NUM / ULP_RISCV_CYCLES_PER_US_DENUM); - } -} - -impl embedded_hal_0_2::blocking::delay::DelayMs for Ulp { - fn delay_ms(&mut self, ms: u32) { - delay_cycles(ms * ULP_RISCV_CYCLES_PER_MS); - } -} - -impl embedded_hal_0_2::blocking::delay::DelayMs for Ulp { - fn delay_ms(&mut self, ms: u16) { - delay_cycles(ms as u32 * ULP_RISCV_CYCLES_PER_MS); - } -} - -impl embedded_hal_0_2::blocking::delay::DelayMs for Ulp { - fn delay_ms(&mut self, ms: u8) { - delay_cycles(ms as u32 * ULP_RISCV_CYCLES_PER_MS); - } -} - -impl embedded_hal::delay::DelayUs for Ulp { - type Error = core::convert::Infallible; - - fn delay_us(&mut self, us: u32) -> Result<(), Self::Error> { - delay_cycles(us * ULP_RISCV_CYCLES_PER_US_NUM / ULP_RISCV_CYCLES_PER_US_DENUM); - Ok(()) - } - - fn delay_ms(&mut self, ms: u32) -> Result<(), Self::Error> { - delay_cycles(ms * ULP_RISCV_CYCLES_PER_MS); - Ok(()) - } -} - -#[inline(always)] -fn delay_cycles(cycles: u32) { - let start = get_ccount(); - - while get_ccount() - start < cycles { /* Wait */ } -} diff --git a/src/riscv_ulp_hal/pac.rs b/src/riscv_ulp_hal/pac.rs deleted file mode 100644 index 1284c4df948..00000000000 --- a/src/riscv_ulp_hal/pac.rs +++ /dev/null @@ -1,66 +0,0 @@ -#![allow(dead_code)] - -/// This module is a manual translation of a bunch of C files from current ESP-IDF master (currently ESP32-S2 specific): -/// - https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/soc.h (a subset) -/// - https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/sens_reg.h (a subset) -/// - https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s2/include/soc/rtc_io_reg.h (a subset) -use super::reg::bit; - -pub const DR_REG_SENS_BASE: u32 = 0x3f408800; -pub const DR_REG_RTCIO_BASE: u32 = 0x3ff48400; -pub const DR_REG_RTCCNTL_BASE: u32 = 0x3f408000; - -pub const RTC_CNTL_COCPU_CTRL_REG: u32 = DR_REG_RTCCNTL_BASE + 0x0100; -pub const RTC_CNTL_COCPU_DONE: u32 = bit(25); -pub const RTC_CNTL_COCPU_SHUT_RESET_EN: u32 = bit(22); -pub const RTC_CNTL_COCPU_SHUT_2_CLK_DIS: u32 = 0x000000FF; -pub const RTC_CNTL_COCPU_SHUT_2_CLK_DIS_V: u32 = 0xFF; -pub const RTC_CNTL_COCPU_SHUT_2_CLK_DIS_S: u32 = 14; - -pub const RTC_CNTL_STATE0_REG: u32 = DR_REG_RTCCNTL_BASE + 0x0018; -pub const RTC_CNTL_SW_CPU_INT: u32 = bit(0); -pub const RTC_CNTL_ULP_CP_SLP_TIMER_EN: u32 = bit(31); -pub const RTC_CNTL_ULP_CP_SLP_TIMER_EN_V: u32 = 0x1; -pub const RTC_CNTL_ULP_CP_SLP_TIMER_EN_S: u32 = 31; - -pub const SENS_SAR_IO_MUX_CONF_REG: u32 = DR_REG_SENS_BASE + 0x0144; -pub const SENS_IOMUX_CLK_GATE_EN_M: u32 = bit(31); - -pub const RTC_IO_TOUCH_PAD0_REG: u32 = DR_REG_RTCIO_BASE + 0x84; -pub const RTC_IO_TOUCH_PAD0_DRV: u32 = 0x00000003; -pub const RTC_IO_TOUCH_PAD0_DRV_V: u32 = 0x3; -pub const RTC_IO_TOUCH_PAD0_DRV_S: u32 = 29; -pub const RTC_IO_TOUCH_PAD0_MUX_SEL: u32 = bit(19); -pub const RTC_IO_TOUCH_PAD0_FUN_SEL: u32 = 0x00000003; -pub const RTC_IO_TOUCH_PAD0_FUN_SEL_V: u32 = 0x3; -pub const RTC_IO_TOUCH_PAD0_FUN_SEL_S: u32 = 17; -pub const RTC_IO_TOUCH_PAD0_FUN_IE: u32 = bit(13); -pub const RTC_IO_TOUCH_PAD0_FUN_IE_V: u32 = 0x01; -pub const RTC_IO_TOUCH_PAD0_FUN_IE_S: u32 = 13; -pub const RTC_IO_TOUCH_PAD0_RUE: u32 = bit(27); -pub const RTC_IO_TOUCH_PAD0_RDE: u32 = bit(28); - -pub const RTC_GPIO_ENABLE_W1TS_REG: u32 = DR_REG_RTCIO_BASE + 0x10; -pub const RTC_GPIO_ENABLE_W1TS: u32 = 0x0003FFFF; -pub const RTC_GPIO_ENABLE_W1TS_V: u32 = 0x3FFFF; -pub const RTC_GPIO_ENABLE_W1TS_S: u32 = 10; - -pub const RTC_GPIO_ENABLE_W1TC_REG: u32 = DR_REG_RTCIO_BASE + 0x14; -pub const RTC_GPIO_ENABLE_W1TC: u32 = 0x0003FFFF; -pub const RTC_GPIO_ENABLE_W1TC_V: u32 = 0x3FFFF; -pub const RTC_GPIO_ENABLE_W1TC_S: u32 = 10; - -pub const RTC_GPIO_IN_REG: u32 = DR_REG_RTCIO_BASE + 0x24; -pub const RTC_GPIO_IN_NEXT: u32 = 0x0003FFFF; -pub const RTC_GPIO_IN_NEXT_V: u32 = 0x3FFFF; -pub const RTC_GPIO_IN_NEXT_S: u32 = 10; - -pub const RTC_GPIO_OUT_W1TS_REG: u32 = DR_REG_RTCIO_BASE + 0x4; -pub const RTC_GPIO_OUT_DATA_W1TS: u32 = 0x0003FFFF; -pub const RTC_GPIO_OUT_DATA_W1TS_V: u32 = 0x3FFFF; -pub const RTC_GPIO_OUT_DATA_W1TS_S: u32 = 10; - -pub const RTC_GPIO_OUT_W1TC_REG: u32 = DR_REG_RTCIO_BASE + 0x8; -pub const RTC_GPIO_OUT_DATA_W1TC: u32 = 0x0003FFFF; -pub const RTC_GPIO_OUT_DATA_W1TC_V: u32 = 0x3FFFF; -pub const RTC_GPIO_OUT_DATA_W1TC_S: u32 = 10; diff --git a/src/riscv_ulp_hal/reg.rs b/src/riscv_ulp_hal/reg.rs deleted file mode 100644 index b37efffdbce..00000000000 --- a/src/riscv_ulp_hal/reg.rs +++ /dev/null @@ -1,173 +0,0 @@ -#![allow(dead_code)] - -/// This module is a manual translation of the following C file from current ESP-IDF master: -/// - https://github.com/espressif/esp-idf/blob/master/components/ulp/ulp_riscv/include/ulp_riscv/ulp_riscv_register_ops.h -use core::ptr::{read_volatile, write_volatile}; - -/* - * When COCPU accesses the RTC register, it needs to convert the access address. - * When COCPU accesses the RTC memory, dont need to convert the access address. - */ -#[inline(always)] -pub unsafe fn write_rtc_mem(addr: u32, val: i32) { - write_volatile(addr as *mut i32, val); -} - -#[inline(always)] -pub unsafe fn read_rtc_mem(addr: u32) -> i32 { - read_volatile(addr as *const i32) -} - -/* - * When COCPU accesses the RTC register, it needs to convert the access address. - * When COCPU accesses the RTC memory, dont need to convert the access address. - */ -#[inline(always)] -pub const fn riscv_reg_conv(addr: u32) -> u32 { - ((addr & 0xffff) << 3) & 0xe000 | addr & 0x1fff | 0x8000 -} - -#[inline(always)] -pub const fn ets_uncached_addr(addr: u32) -> u32 { - riscv_reg_conv(addr) -} - -#[inline(always)] -pub const fn bit(nr: u32) -> u32 { - 1u32 << nr -} - -// Write value to register -#[inline(always)] -pub unsafe fn reg_write(r: u32, v: u32) { - write_volatile(riscv_reg_conv(r) as *mut u32, v); -} - -// Read value from register -#[inline(always)] -pub unsafe fn reg_read(r: u32) -> u32 { - read_volatile(riscv_reg_conv(r) as *const u32) -} - -// Get bit or get bits from register -#[inline(always)] -pub unsafe fn reg_get_bit(r: u32, b: u32) -> u32 { - read_volatile(riscv_reg_conv(r) as *const u32) & b -} - -// Get bit or get bits from register -#[inline(always)] -pub unsafe fn reg_set_bit(r: u32, b: u32) { - let addr = riscv_reg_conv(r) as *mut u32; - write_volatile(addr, read_volatile(addr) | b); -} - -// Clear bit or clear bits of register -#[inline(always)] -pub unsafe fn reg_clr_bit(r: u32, b: u32) { - let addr = riscv_reg_conv(r) as *mut u32; - write_volatile(addr, read_volatile(addr) & !b); -} - -// Set bits of register controlled by mask -#[inline(always)] -pub unsafe fn reg_set_bits(r: u32, b: u32, m: u32) { - let addr = riscv_reg_conv(r) as *mut u32; - write_volatile(addr, read_volatile(addr) & !m | b & m); -} - -// Get field from register, uses field _S & _V to determine mask -#[inline(always)] -pub unsafe fn reg_get_field(r: u32, f_s: u32, f_v: u32) -> u32 { - (reg_read(r) >> f_s) & f_v -} - -// Set field of a register from variable, uses field _S & _V to determine mask -#[inline(always)] -pub unsafe fn reg_set_field(r: u32, f_s: u32, f_v: u32, v: u32) { - reg_write(r, (reg_read(r) & !(f_v << f_s)) | ((v & f_v) << f_s)); -} - -// Get field value from a variable, used when _f is not left shifted by _f##_S -#[inline(always)] -pub const fn value_get_field(r: u32, f: u32, f_s: u32) -> u32 { - (r >> f_s) & f -} - -// Get field value from a variable, used when _f is left shifted by _f##_S -#[inline(always)] -pub const fn value_get_field2(r: u32, f: u32, f_s: u32) -> u32 { - (r & f) >> f_s -} - -// Set field value to a variable, used when _f is not left shifted by _f##_S -#[inline(always)] -pub const fn value_set_field(r: u32, f: u32, f_s: u32, v: u32) -> u32 { - r & !(f << f_s) | (v << f_s) -} - -// Set field value to a variable, used when _f is left shifted by _f##_S -#[inline(always)] -pub const fn value_set_field2(r: u32, f: u32, f_s: u32, v: u32) -> u32 { - r & !f | (v << f_s) -} - -// Generate a value from a field value, used when _f is not left shifted by _f##_S -#[inline(always)] -pub const fn field_to_value(f: u32, f_s: u32, v: u32) -> u32 { - (v & f) << f_s -} - -// Generate a value from a field value, used when _f is left shifted by _f##_S -#[inline(always)] -pub const fn field_to_value2(f: u32, f_s: u32, v: u32) -> u32 { - (v << f_s) & f -} - -// Read value from register -#[inline(always)] -pub unsafe fn read_peri_reg(addr: u32) -> u32 { - read_volatile(ets_uncached_addr(addr) as *const u32) -} - -// Write value to register -#[inline(always)] -pub unsafe fn write_peri_reg(addr: u32, v: u32) { - write_volatile(ets_uncached_addr(addr) as *mut u32, v); -} - -// Clear bits of register controlled by mask -#[inline(always)] -pub unsafe fn clear_peri_reg_mask(addr: u32, mask: u32) { - write_peri_reg(addr, read_peri_reg(addr) & !mask); -} - -#[inline(always)] -pub unsafe fn set_peri_reg_mask(addr: u32, mask: u32) { - write_peri_reg(addr, read_peri_reg(addr) | mask); -} - -// Get bits of register controlled by mask -#[inline(always)] -pub unsafe fn get_peri_reg_mask(addr: u32, mask: u32) -> u32 { - read_peri_reg(addr) & mask -} - -// Get bits of register controlled by highest bit and lowest bit -#[inline(always)] -pub unsafe fn get_peri_reg_bits(addr: u32, bit_map: u32, shift: u8) -> u32 { - (read_peri_reg(addr) & (bit_map << shift)) >> shift -} - -// Set bits of register controlled by mask and shift -pub unsafe fn set_peri_reg_bits(addr: u32, bit_map: u32, value: u32, shift: u8) { - write_peri_reg( - addr, - read_peri_reg(addr) & !(bit_map << shift) | ((value & bit_map) << shift), - ); -} - -// Get field of register -pub unsafe fn get_peri_reg_bits2(addr: u32, mask: u32, shift: u8) -> u32 { - (read_peri_reg(addr) >> shift) & mask -} diff --git a/src/riscv_ulp_hal/start.rs b/src/riscv_ulp_hal/start.rs deleted file mode 100644 index d8a6d668bf2..00000000000 --- a/src/riscv_ulp_hal/start.rs +++ /dev/null @@ -1,179 +0,0 @@ -//! Minimal startup / runtime for ESP32-SXX RISC-V ULPs -//! Adapted from riscv-rt/src/lib.rs - -#![deny(missing_docs)] - -use super::sys::cpu; - -#[export_name = "error: ulp_start appears more than once in the dependency graph"] -#[doc(hidden)] -pub static __ONCE__: () = (); - -/// # Safety -/// -/// Rust entry point (_start_rust) -/// This function is NOT supposed to be called from use code -/// -/// Calls main. This function never returns. -#[link_section = ".start.rust"] -#[export_name = "_start_rust"] -pub unsafe extern "C" fn start_rust() -> ! { - #[rustfmt::skip] - extern "Rust" { - // This symbol will be provided by the user - fn main(); - } - - cpu::rescue_from_monitor(); - - main(); - - cpu::shutdown(); -} - -/// Registers saved in trap handler -#[allow(missing_docs)] -#[repr(C)] -pub struct TrapFrame { - pub ra: usize, - pub t0: usize, - pub t1: usize, - pub t2: usize, - pub t3: usize, - pub t4: usize, - pub t5: usize, - pub t6: usize, - pub a0: usize, - pub a1: usize, - pub a2: usize, - pub a3: usize, - pub a4: usize, - pub a5: usize, - pub a6: usize, - pub a7: usize, -} - -/// # Safety -/// -/// Trap entry point rust (_start_trap_rust) -/// This function is NOT supposed to be called from use code -/// -/// `mcause` is read to determine the cause of the trap. XLEN-1 bit indicates -/// if it's an interrupt or an exception. The result is examined and ExceptionHandler -/// or one of the core interrupt handlers is called. -#[link_section = ".trap.rust"] -#[export_name = "_start_trap_rust"] -pub unsafe extern "C" fn start_trap_rust(trap_frame: *const TrapFrame) { - // use riscv::register::mcause; - - extern "C" { - fn ExceptionHandler(trap_frame: &TrapFrame); - #[allow(dead_code)] - fn DefaultHandler(); - } - - // let cause = mcause::read(); - // if cause.is_exception() { - ExceptionHandler(trap_frame.as_ref().unwrap()) - // } else { - // let code = cause.code(); - // if code < __INTERRUPTS.len() { - // let h = &__INTERRUPTS[code]; - // if h.reserved == 0 { - // DefaultHandler(); - // } else { - // (h.handler)(); - // } - // } else { - // DefaultHandler(); - // } - // } -} - -#[doc(hidden)] -#[no_mangle] -#[allow(unused_variables, non_snake_case)] -pub fn DefaultExceptionHandler(trap_frame: &TrapFrame) -> ! { - loop { - // Prevent this from turning into a UDF instruction - // see rust-lang/rust#28728 for details - continue; - } -} - -#[doc(hidden)] -#[no_mangle] -#[allow(unused_variables, non_snake_case)] -pub fn DefaultInterruptHandler() { - loop { - // Prevent this from turning into a UDF instruction - // see rust-lang/rust#28728 for details - continue; - } -} - -/* Interrupts */ -#[doc(hidden)] -pub enum Interrupt { - UserSoft, - SupervisorSoft, - MachineSoft, - UserTimer, - SupervisorTimer, - MachineTimer, - UserExternal, - SupervisorExternal, - MachineExternal, -} - -pub use self::Interrupt as interrupt; - -extern "C" { - fn UserSoft(); - fn SupervisorSoft(); - fn MachineSoft(); - fn UserTimer(); - fn SupervisorTimer(); - fn MachineTimer(); - fn UserExternal(); - fn SupervisorExternal(); - fn MachineExternal(); -} - -#[doc(hidden)] -pub union Vector { - handler: unsafe extern "C" fn(), - reserved: usize, -} - -#[doc(hidden)] -#[allow(dead_code)] -#[no_mangle] -pub static __INTERRUPTS: [Vector; 12] = [ - Vector { handler: UserSoft }, - Vector { - handler: SupervisorSoft, - }, - Vector { reserved: 0 }, - Vector { - handler: MachineSoft, - }, - Vector { handler: UserTimer }, - Vector { - handler: SupervisorTimer, - }, - Vector { reserved: 0 }, - Vector { - handler: MachineTimer, - }, - Vector { - handler: UserExternal, - }, - Vector { - handler: SupervisorExternal, - }, - Vector { reserved: 0 }, - Vector { - handler: MachineExternal, - }, -]; diff --git a/src/riscv_ulp_hal/sys.rs b/src/riscv_ulp_hal/sys.rs deleted file mode 100644 index 20536c5c844..00000000000 --- a/src/riscv_ulp_hal/sys.rs +++ /dev/null @@ -1,28 +0,0 @@ -/// A mini "esp-idf-ulp-sys" module exposing stuff on top of which the ULP HAL support is implemented -/// (currently, only GPIO) + some utilities for the riscv ULP processor -pub use self::cpu::*; -pub use self::gpio::*; - -pub mod cpu; -#[allow(clippy::missing_safety_doc)] -pub mod gpio; - -pub type EspError = core::convert::Infallible; - -#[macro_export] -macro_rules! esp_result { - ($err:expr, $value:expr) => {{ - $err; - - Ok($value) - }}; -} - -#[macro_export] -macro_rules! esp { - ($err:expr) => {{ - $err; - - core::result::Result::<(), EspError>::Ok(()) - }}; -} diff --git a/src/riscv_ulp_hal/sys/cpu.rs b/src/riscv_ulp_hal/sys/cpu.rs deleted file mode 100644 index f758f4252f0..00000000000 --- a/src/riscv_ulp_hal/sys/cpu.rs +++ /dev/null @@ -1,71 +0,0 @@ -#![allow(dead_code)] - -/// This module is a manual translation of the following C file from current ESP-IDF master: -/// - https://github.com/espressif/esp-idf/blob/master/components/ulp/ulp_riscv/include/ulp_riscv/ulp_utils.h -/// - https://github.com/espressif/esp-idf/blob/master/components/ulp/ulp_riscv/ulp_utils.c -use core::arch::asm; - -use crate::riscv_ulp_hal::pac::*; -use crate::riscv_ulp_hal::reg::*; - -pub const ULP_RISCV_CYCLES_PER_US_NUM: u32 = 85; -pub const ULP_RISCV_CYCLES_PER_US_DENUM: u32 = 10; -pub const ULP_RISCV_CYCLES_PER_MS: u32 = - ULP_RISCV_CYCLES_PER_US_NUM * (1000 / ULP_RISCV_CYCLES_PER_US_DENUM); - -#[inline(always)] -pub fn get_ccount() -> u32 { - #[allow(unused_assignments)] - let mut ccount = 0; - - unsafe { - asm!("rdcycle {}", out(reg) ccount); - } - - ccount -} - -pub fn wakeup_main_processor() { - unsafe { set_peri_reg_mask(RTC_CNTL_STATE0_REG, RTC_CNTL_SW_CPU_INT) }; -} - -pub fn rescue_from_monitor() { - // Rescue RISCV from monitor state - unsafe { - clear_peri_reg_mask( - RTC_CNTL_COCPU_CTRL_REG, - RTC_CNTL_COCPU_DONE | RTC_CNTL_COCPU_SHUT_RESET_EN, - ) - }; -} - -pub fn enable_timer(enable: bool) { - unsafe { - if enable { - set_peri_reg_mask(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); - } else { - clear_peri_reg_mask(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); - } - } -} - -pub fn shutdown() -> ! { - unsafe { - // Setting the delay time after RISCV recv `DONE` signal, Ensure that action `RESET` can be executed in time. - reg_set_field( - RTC_CNTL_COCPU_CTRL_REG, - RTC_CNTL_COCPU_SHUT_2_CLK_DIS_S, - RTC_CNTL_COCPU_SHUT_2_CLK_DIS_V, - 0x3F, - ); - - // Suspends the ulp operation - set_peri_reg_mask(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_DONE); - - // Resets the processor - set_peri_reg_mask(RTC_CNTL_COCPU_CTRL_REG, RTC_CNTL_COCPU_SHUT_RESET_EN); - } - - #[allow(clippy::empty_loop)] - loop {} -} diff --git a/src/riscv_ulp_hal/sys/gpio.rs b/src/riscv_ulp_hal/sys/gpio.rs deleted file mode 100644 index 64e516f80a8..00000000000 --- a/src/riscv_ulp_hal/sys/gpio.rs +++ /dev/null @@ -1,204 +0,0 @@ -/// A mini "esp-idf-ulp-sys" module exposing stuff on top of which the ULP HAL support is implemented -/// (currently, only GPIO) -/// Implemented as a manual transation of a few C fields from current ESP-IDF S2 master: -/// - https://github.com/espressif/esp-idf/blob/master/components/ulp/ulp_riscv/include/ulp_riscv/ulp_riscv_gpio.h -use crate::riscv_ulp_hal::pac::*; -use crate::riscv_ulp_hal::reg::*; - -#[allow(non_camel_case_types)] -pub type adc_unit_t = i32; -#[allow(non_camel_case_types)] -pub type adc_channel_t = i32; -#[allow(non_camel_case_types)] -pub type dac_channel_t = i32; -#[allow(non_camel_case_types)] -pub type touch_pad_t = i32; -#[allow(non_camel_case_types)] -pub type adc_atten_t = i32; -#[allow(non_camel_case_types)] -pub type gpio_mode_t = u8; -#[allow(non_camel_case_types)] -pub type gpio_pull_mode_t = u8; - -#[allow(non_upper_case_globals)] -pub const adc_unit_t_ADC_UNIT_1: adc_unit_t = 0; -#[allow(non_upper_case_globals)] -pub const adc_unit_t_ADC_UNIT_2: adc_unit_t = 1; - -#[allow(non_upper_case_globals)] -pub const adc_atten_t_ADC_ATTEN_DB_0: adc_atten_t = 0; -#[allow(non_upper_case_globals)] -pub const adc_atten_t_ADC_ATTEN_DB_2_5: adc_atten_t = 1; -#[allow(non_upper_case_globals)] -pub const adc_atten_t_ADC_ATTEN_DB_6: adc_atten_t = 2; -#[allow(non_upper_case_globals)] -pub const adc_atten_t_ADC_ATTEN_DB_11: adc_atten_t = 3; - -#[allow(non_upper_case_globals)] -pub const gpio_mode_t_GPIO_MODE_DISABLE: u8 = 0; -#[allow(non_upper_case_globals)] -pub const gpio_mode_t_GPIO_MODE_INPUT: u8 = 1; -#[allow(non_upper_case_globals)] -pub const gpio_mode_t_GPIO_MODE_OUTPUT: u8 = 2; -#[allow(non_upper_case_globals)] -pub const gpio_mode_t_GPIO_MODE_INPUT_OUTPUT: u8 = 3; -#[allow(non_upper_case_globals)] -pub const gpio_mode_t_GPIO_MODE_OUTPUT_OD: u8 = 4; -#[allow(non_upper_case_globals)] -pub const gpio_mode_t_GPIO_MODE_INPUT_OUTPUT_OD: u8 = 5; - -#[allow(non_upper_case_globals)] -pub const gpio_pull_mode_t_GPIO_PULLUP_ONLY: u8 = 0; -#[allow(non_upper_case_globals)] -pub const gpio_pull_mode_t_GPIO_PULLDOWN_ONLY: u8 = 1; -#[allow(non_upper_case_globals)] -pub const gpio_pull_mode_t_GPIO_PULLUP_PULLDOWN: u8 = 2; -#[allow(non_upper_case_globals)] -pub const gpio_pull_mode_t_GPIO_FLOATING: u8 = 3; - -#[inline(always)] -pub unsafe fn gpio_set_direction(gpio_num: i32, direction: u8) { - if direction == gpio_mode_t_GPIO_MODE_DISABLE { - // Deinit - clear_peri_reg_mask( - RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, - RTC_IO_TOUCH_PAD0_MUX_SEL, - ); - return; - } else { - // Init - set_peri_reg_mask(SENS_SAR_IO_MUX_CONF_REG, SENS_IOMUX_CLK_GATE_EN_M); - set_peri_reg_mask( - RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, - RTC_IO_TOUCH_PAD0_MUX_SEL, - ); - reg_set_field( - RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, - RTC_IO_TOUCH_PAD0_FUN_SEL_S, - RTC_IO_TOUCH_PAD0_FUN_SEL_V, - 0, - ); - } - - let input = direction == gpio_mode_t_GPIO_MODE_INPUT - || direction == gpio_mode_t_GPIO_MODE_INPUT_OUTPUT - || direction == gpio_mode_t_GPIO_MODE_INPUT_OUTPUT_OD; - let output = direction == gpio_mode_t_GPIO_MODE_OUTPUT - || direction == gpio_mode_t_GPIO_MODE_OUTPUT_OD - || direction == gpio_mode_t_GPIO_MODE_INPUT_OUTPUT - || direction == gpio_mode_t_GPIO_MODE_INPUT_OUTPUT_OD; - let od = direction == gpio_mode_t_GPIO_MODE_OUTPUT_OD - || direction == gpio_mode_t_GPIO_MODE_INPUT_OUTPUT_OD; - - if input { - set_peri_reg_mask( - RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, - RTC_IO_TOUCH_PAD0_FUN_IE, - ); - } else { - clear_peri_reg_mask( - RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, - RTC_IO_TOUCH_PAD0_FUN_IE, - ); - } - - if output { - reg_set_field( - RTC_GPIO_ENABLE_W1TS_REG, - RTC_GPIO_ENABLE_W1TS_S, - RTC_GPIO_ENABLE_W1TS_V, - bit(gpio_num as u32), - ); - reg_set_field( - RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, - RTC_IO_TOUCH_PAD0_DRV_S, - RTC_IO_TOUCH_PAD0_DRV_V, - if od { 1 } else { 0 }, - ); - } else { - reg_set_field( - RTC_GPIO_ENABLE_W1TC_REG, - RTC_GPIO_ENABLE_W1TC_S, - RTC_GPIO_ENABLE_W1TC_V, - bit(gpio_num as u32), - ); - } -} - -#[inline(always)] -pub unsafe fn gpio_set_pull_mode(gpio_num: i32, mode: u8) { - let pullup = - mode == gpio_pull_mode_t_GPIO_PULLUP_ONLY || mode == gpio_pull_mode_t_GPIO_PULLUP_PULLDOWN; - let pulldown = mode == gpio_pull_mode_t_GPIO_PULLDOWN_ONLY - || mode == gpio_pull_mode_t_GPIO_PULLUP_PULLDOWN; - - if pullup { - set_peri_reg_mask( - RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, - RTC_IO_TOUCH_PAD0_RUE, - ); - } else { - clear_peri_reg_mask( - RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, - RTC_IO_TOUCH_PAD0_RUE, - ); - } - - if pulldown { - set_peri_reg_mask( - RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, - RTC_IO_TOUCH_PAD0_RDE, - ); - } else { - clear_peri_reg_mask( - RTC_IO_TOUCH_PAD0_REG + gpio_num as u32 * 4, - RTC_IO_TOUCH_PAD0_RDE, - ); - } -} - -#[inline(always)] -pub unsafe fn gpio_get_level(gpio_num: i32) -> u8 { - if (reg_get_field(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S, RTC_GPIO_IN_NEXT_V) - & bit(gpio_num as u32)) - != 0 - { - 1 - } else { - 0 - } -} - -#[inline(always)] -pub unsafe fn gpio_set_level(gpio_num: i32, level: u8) { - if level != 0 { - reg_set_field( - RTC_GPIO_OUT_W1TS_REG, - RTC_GPIO_OUT_DATA_W1TS_S, - RTC_GPIO_OUT_DATA_W1TS_V, - bit(gpio_num as u32), - ); - } else { - reg_set_field( - RTC_GPIO_OUT_W1TC_REG, - RTC_GPIO_OUT_DATA_W1TC_S, - RTC_GPIO_OUT_DATA_W1TC_V, - bit(gpio_num as u32), - ); - } -} - -#[inline(always)] -pub unsafe fn gpio_get_output_level(gpio_num: i32) -> u8 { - if (reg_get_field( - RTC_GPIO_OUT_W1TS_REG, - RTC_GPIO_OUT_DATA_W1TS_S, - RTC_GPIO_OUT_DATA_W1TS_V, - ) & bit(gpio_num as u32)) - != 0 - { - 1 - } else { - 0 - } -} diff --git a/src/rmt.rs b/src/rmt.rs index 005b74b910a..e723912a61a 100644 --- a/src/rmt.rs +++ b/src/rmt.rs @@ -11,7 +11,6 @@ //! //! Not supported: //! * Interrupts. -//! * Receiving. //! * Change of config after initialisation. //! //! # Example @@ -51,10 +50,7 @@ //! [VariableLengthSignal] allows you to use the heap and incrementally add pulse items without knowing the size //! ahead of time. -use core::cell::UnsafeCell; -use core::convert::{TryFrom, TryInto}; -use core::marker::PhantomData; -use core::ptr; +use core::slice; use core::time::Duration; #[cfg(feature = "alloc")] @@ -62,15 +58,15 @@ extern crate alloc; use esp_idf_sys::*; -use crate::gpio::InputPin; -use crate::gpio::OutputPin; -use crate::peripheral::Peripheral; use crate::units::Hertz; -use config::ReceiveConfig; -use config::TransmitConfig; - pub use chip::*; +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +pub use driver::*; + +// Might not always be available in the generated `esp-idf-sys` bindings +const ERR_ERANGE: esp_err_t = 34; +const ERR_EOVERFLOW: esp_err_t = 139; pub type RmtTransmitConfig = config::TransmitConfig; pub type RmtReceiveConfig = config::ReceiveConfig; @@ -174,7 +170,7 @@ impl PulseTicks { /// returned. pub fn new(ticks: u16) -> Result { if ticks > Self::MAX { - Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()) + Err(EspError::from_infallible::()) } else { Ok(Self(ticks)) } @@ -207,22 +203,22 @@ pub fn duration_to_ticks(ticks_hz: Hertz, duration: &Duration) -> Result Result { let duration = 1_000_000_000_u128 .checked_mul(ticks as u128) - .ok_or_else(|| EspError::from(EOVERFLOW as i32).unwrap())? + .ok_or_else(|| EspError::from(ERR_EOVERFLOW).unwrap())? / u32::from(ticks_hz) as u128; u64::try_from(duration) .map(Duration::from_nanos) - .map_err(|_| EspError::from(EOVERFLOW as i32).unwrap()) + .map_err(|_| EspError::from(ERR_EOVERFLOW).unwrap()) } pub type TxRmtConfig = config::TransmitConfig; @@ -246,10 +242,14 @@ pub type RxRmtConfig = config::ReceiveConfig; /// /// ``` pub mod config { + use enumset::EnumSet; use esp_idf_sys::{EspError, ESP_ERR_INVALID_ARG}; use super::PinState; - use crate::units::{FromValueType, Hertz}; + use crate::{ + interrupt::InterruptType, + units::{FromValueType, Hertz}, + }; /// A percentage from 0 to 100%, used to specify the duty percentage in [`CarrierConfig`]. #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -259,7 +259,7 @@ pub mod config { /// Must be between 0 and 100, otherwise an error is returned. pub fn new(v: u8) -> Result { if v > 100 { - Err(EspError::from(ESP_ERR_INVALID_ARG).unwrap()) + Err(EspError::from_infallible::()) } else { Ok(Self(v)) } @@ -288,16 +288,19 @@ pub mod config { } } + #[must_use] pub fn frequency(mut self, hz: Hertz) -> Self { self.frequency = hz; self } + #[must_use] pub fn carrier_level(mut self, state: PinState) -> Self { self.carrier_level = state; self } + #[must_use] pub fn duty_percent(mut self, duty: DutyPercent) -> Self { self.duty_percent = duty; self @@ -316,11 +319,16 @@ pub mod config { pub enum Loop { None, Endless, - #[cfg(not(any(esp32, esp32c2)))] + #[cfg(any( + all(not(esp_idf_version_major = "4"), not(esp_idf_version_major = "5")), + all(esp_idf_version_major = "5", not(esp_idf_version_minor = "0")), + not(esp32) + ))] Count(u32), } /// Used when creating a [`Transmit`][crate::rmt::Transmit] instance. + #[derive(Debug, Clone)] pub struct TransmitConfig { pub clock_divider: u8, pub mem_block_num: u8, @@ -336,6 +344,8 @@ pub mod config { /// When set, RMT channel will take REF_TICK or XTAL as source clock. The benefit is, RMT /// channel can continue work even when APB clock is changing. pub aware_dfs: bool, + + pub intr_flags: EnumSet, } impl TransmitConfig { @@ -347,38 +357,51 @@ pub mod config { looping: Loop::None, carrier: None, idle: Some(PinState::Low), + intr_flags: EnumSet::::empty(), } } + #[must_use] pub fn aware_dfs(mut self, enable: bool) -> Self { self.aware_dfs = enable; self } + #[must_use] pub fn mem_block_num(mut self, mem_block_num: u8) -> Self { self.mem_block_num = mem_block_num; self } + #[must_use] pub fn clock_divider(mut self, divider: u8) -> Self { self.clock_divider = divider; self } + #[must_use] pub fn looping(mut self, looping: Loop) -> Self { self.looping = looping; self } + #[must_use] pub fn carrier(mut self, carrier: Option) -> Self { self.carrier = carrier; self } + #[must_use] pub fn idle(mut self, idle: Option) -> Self { self.idle = idle; self } + + #[must_use] + pub fn intr_flags(mut self, flags: EnumSet) -> Self { + self.intr_flags = flags; + self + } } impl Default for TransmitConfig { @@ -389,6 +412,7 @@ pub mod config { } /// Used when creating a [`Receive`][crate::rmt::Receive] instance. + #[derive(Debug, Clone)] pub struct ReceiveConfig { pub clock_divider: u8, pub mem_block_num: u8, @@ -396,6 +420,7 @@ pub mod config { pub filter_ticks_thresh: u8, pub filter_en: bool, pub carrier: Option, + pub intr_flags: EnumSet, } impl ReceiveConfig { @@ -403,35 +428,47 @@ pub mod config { Self::default() } + #[must_use] pub fn clock_divider(mut self, divider: u8) -> Self { self.clock_divider = divider; self } + #[must_use] pub fn mem_block_num(mut self, mem_block_num: u8) -> Self { self.mem_block_num = mem_block_num; self } + #[must_use] pub fn idle_threshold(mut self, threshold: u16) -> Self { self.idle_threshold = threshold; self } + #[must_use] pub fn filter_ticks_thresh(mut self, threshold: u8) -> Self { self.filter_ticks_thresh = threshold; self } + #[must_use] pub fn filter_en(mut self, enable: bool) -> Self { self.filter_en = enable; self } + #[must_use] pub fn carrier(mut self, carrier: Option) -> Self { self.carrier = carrier; self } + + #[must_use] + pub fn intr_flags(mut self, flags: EnumSet) -> Self { + self.intr_flags = flags; + self + } } impl Default for ReceiveConfig { @@ -444,282 +481,58 @@ pub mod config { filter_ticks_thresh: 100, // 100 microseconds, pulses less than this will be ignored filter_en: true, carrier: None, + intr_flags: EnumSet::::empty(), } } } } -/// The RMT transmitter driver. -/// -/// Use [`TxRmtDriver::start()`] or [`TxRmtDriver::start_blocking()`] to transmit pulses. +/// Symbols /// -/// See the [rmt module][crate::rmt] for more information. - -pub struct TxRmtDriver<'d> { - channel: u8, - _p: PhantomData<&'d mut ()>, -} - -impl<'d> TxRmtDriver<'d> { - /// Initialise the rmt module with the specified pin, channel and configuration. - /// - /// To uninstall the driver just drop it. - /// - /// Internally this calls `rmt_config()` and `rmt_driver_install()`. - pub fn new( - _channel: impl Peripheral

+ 'd, - pin: impl Peripheral

+ 'd, - config: &TransmitConfig, - ) -> Result { - crate::into_ref!(pin); - - let mut flags = 0; - if config.aware_dfs { - flags |= RMT_CHANNEL_FLAGS_AWARE_DFS; - } - - let carrier_en = config.carrier.is_some(); - let carrier = config.carrier.unwrap_or_default(); - - let sys_config = rmt_config_t { - rmt_mode: rmt_mode_t_RMT_MODE_TX, - channel: C::channel(), - gpio_num: pin.pin(), - clk_div: config.clock_divider, - mem_block_num: config.mem_block_num, - flags, - __bindgen_anon_1: rmt_config_t__bindgen_ty_1 { - tx_config: rmt_tx_config_t { - carrier_en, - carrier_freq_hz: carrier.frequency.into(), - carrier_level: carrier.carrier_level as u32, - carrier_duty_percent: carrier.duty_percent.0, - idle_output_en: config.idle.is_some(), - idle_level: config.idle.map(|i| i as u32).unwrap_or(0), - loop_en: config.looping != config::Loop::None, - #[cfg(not(any(esp32, esp32c2)))] - loop_count: match config.looping { - config::Loop::Count(count) if count > 0 && count < 1024 => count, - _ => 0, - }, - }, - }, +/// Represents a single pulse cycle symbol comprised of mark (high) +/// and space (low) periods in either order or a fixed level if both +/// halves have the same [`PinState`]. This is just a newtype over the +/// IDF's `rmt_item32_t` or `rmt_symbol_word_t` type. +#[derive(Clone, Copy)] +pub struct Symbol(rmt_item32_t); + +impl Symbol { + /// Create a symbol from a pair of half-cycles. + pub fn new(level0: Pulse, level1: Pulse) -> Self { + let item = rmt_item32_t { + __bindgen_anon_1: rmt_item32_t__bindgen_ty_1 { val: 0 }, }; - - unsafe { - esp!(rmt_config(&sys_config))?; - esp!(rmt_driver_install(C::channel(), 0, 0))?; - } - - Ok(Self { - channel: C::channel() as _, - _p: PhantomData, - }) - } - - /// Get speed of the channel’s internal counter clock. - /// - /// This calls [rmt_get_counter_clock()][rmt_get_counter_clock] - /// internally. It is used for calculating the number of ticks per second for pulses. - /// - /// See [Pulse::new_with_duration()]. - /// - /// [rmt_get_counter_clock]: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/rmt.html#_CPPv421rmt_get_counter_clock13rmt_channel_tP8uint32_t - pub fn counter_clock(&self) -> Result { - let mut ticks_hz: u32 = 0; - esp!(unsafe { rmt_get_counter_clock(self.channel(), &mut ticks_hz) })?; - Ok(ticks_hz.into()) - } - - /// Start sending the given signal without blocking. - /// - /// `signal` is captured for safety so that the user can't change the data while transmitting. - pub fn start(&mut self, signal: S) -> Result<(), EspError> - where - S: Signal, - { - self.write_items(&signal, false) - } - - /// Start sending the given signal while blocking. - pub fn start_blocking(&mut self, signal: &S) -> Result<(), EspError> - where - S: Signal, - { - self.write_items(signal, true) - } - - fn write_items(&mut self, signal: &S, block: bool) -> Result<(), EspError> - where - S: Signal, - { - let items = signal.as_slice(); - esp!(unsafe { rmt_write_items(self.channel(), items.as_ptr(), items.len() as i32, block) }) - } - - /// Transmit all items in `iter` without blocking. - /// - /// Note that this requires `iter` to be [`Box`]ed for an allocation free version see [`Self::start_iter_blocking`]. - /// - /// ### Warning - /// - /// Iteration of `iter` happens inside an interrupt handler so beware of side-effects - /// that don't work in interrupt handlers. Iteration must also be fast so that there - /// are no time-gaps between successive transmissions where the perhipheral has to - /// wait for items. This can cause weird behavior and can be counteracted with - /// increasing [`Config::mem_block_num`] or making iteration more efficient. - #[cfg(feature = "alloc")] - pub fn start_iter(&mut self, iter: T) -> Result<(), EspError> - where - T: Iterator + Send + 'static, - { - let iter = alloc::boxed::Box::new(UnsafeCell::new(iter)); - unsafe { - esp!(rmt_translator_init( - self.channel(), - Some(Self::translate_iterator::), - ))?; - - esp!(rmt_write_sample( - self.channel(), - alloc::boxed::Box::leak(iter) as *const _ as _, - 1, - false - )) - } + let mut this = Self(item); + this.update(level0, level1); + this } - /// Transmit all items in `iter`, blocking until all items are transmitted. - /// - /// This method does not require any allocations since the thread is paused until all - /// items are transmitted. The iterator lives on the stack and will be dropped after - /// all items are written and before this method returns. - /// - /// ### Warning - /// - /// Iteration of `iter` happens inside an interrupt handler so beware of side-effects - /// that don't work in interrupt handlers. Iteration must also be fast so that there - /// are no time-gaps between successive transmissions where the perhipheral has to - /// wait for items. This can cause weird behavior and can be counteracted with - /// increasing [`Config::mem_block_num`] or making iteration more efficient. - - pub fn start_iter_blocking(&mut self, iter: T) -> Result<(), EspError> - where - T: Iterator + Send, - { - let iter = UnsafeCell::new(iter); - unsafe { - // TODO: maybe use a separate struct so that we don't have to do this when - // transmitting the same iterator type. - esp!(rmt_translator_init( - self.channel(), - Some(Self::translate_iterator::), - ))?; - esp!(rmt_write_sample( - self.channel(), - &iter as *const _ as _, - 24, - true - )) - } - } - - /// The translator that turns an iterator into `rmt_item32_t` elements. Most of the - /// magic happens here. - /// - /// The general idea is that we can fill a buffer (`dest`) of `rmt_item32_t` items of - /// length `wanted_num` with the items that we get from the iterator. Then we can tell - /// the peripheral driver how many items we filled in by setting `item_num`. The - /// driver will call this function over-and-over until `translated_size` is equal to - /// `src_size` so when the iterator returns [`None`] we set `translated_size` to - /// `src_size` to signal that there are no more items to translate. - /// - /// The compiler will generate this function for every different call to - /// [`Self::start_iter_blocking`] and [`Self::start_iter`] with different iterator - /// types because of the type parameter. This is done to avoid the double indirection - /// that we'd have to do when using a trait object since references to trait objects - /// are fat-pointers (2 `usize` wide) and we only get a narrow pointer (`src`). - /// Using a trait object has the addional overhead that every call to `Iterator::next` - /// would also be indirect (through the `vtable`) and couldn't be inlined. - unsafe extern "C" fn translate_iterator( - src: *const core::ffi::c_void, - mut dest: *mut rmt_item32_t, - src_size: usize, - wanted_num: usize, - translated_size: *mut usize, - item_num: *mut usize, - ) where - T: Iterator, - { - // An `UnsafeCell` is needed here because we're casting a `*const` to a `*mut`. - // Safe because this is the only existing reference. - let iter = &mut *UnsafeCell::raw_get(src as *const UnsafeCell); - - let mut i = 0; - let finished = loop { - if i >= wanted_num { - break 0; - } - - if let Some(item) = iter.next() { - *dest = item; - dest = dest.add(1); - i += 1; - } else { - // Only deallocate the iter if the const generics argument is `true` - // otherwise we could be deallocating stack memory. - #[cfg(feature = "alloc")] - if DEALLOC_ITER { - drop(alloc::boxed::Box::from_raw(iter)); - } - break src_size; - } - }; - - *item_num = i; - *translated_size = finished; - } - - /// Stop transmitting. - pub fn stop(&mut self) -> Result<(), EspError> { - esp!(unsafe { rmt_tx_stop(self.channel()) }) - } - - pub fn set_looping(&mut self, looping: config::Loop) -> Result<(), EspError> { - esp!(unsafe { rmt_set_tx_loop_mode(self.channel(), looping != config::Loop::None) })?; - - #[cfg(not(any(esp32, esp32c2)))] - esp!(unsafe { - rmt_set_tx_loop_count( - self.channel(), - match looping { - config::Loop::Count(count) if count > 0 && count < 1024 => count, - _ => 0, - }, - ) - })?; - - Ok(()) + /// Mutate this symbol to store a different pair of half-cycles. + pub fn update(&mut self, level0: Pulse, level1: Pulse) { + // SAFETY: We're overriding all 32 bits, so it doesn't matter what was here before. + let inner = unsafe { &mut self.0.__bindgen_anon_1.__bindgen_anon_1 }; + inner.set_level0(level0.pin_state as u32); + inner.set_duration0(level0.ticks.0 as u32); + inner.set_level1(level1.pin_state as u32); + inner.set_duration1(level1.ticks.0 as u32); } +} - pub fn channel(&self) -> rmt_channel_t { - self.channel as _ - } +/// Signal storage for [`Transmit`] in a format ready for the RMT driver. +pub trait Signal { + fn as_slice(&self) -> &[rmt_item32_t]; } -impl<'d> Drop for TxRmtDriver<'d> { - /// Stop transmitting and release the driver. - fn drop(&mut self) { - self.stop().unwrap(); - esp!(unsafe { rmt_driver_uninstall(self.channel()) }).unwrap(); +impl Signal for Symbol { + fn as_slice(&self) -> &[rmt_item32_t] { + slice::from_ref(&self.0) } } -unsafe impl<'d> Send for TxRmtDriver<'d> {} - -/// Signal storage for [`Transmit`] in a format ready for the RMT driver. -pub trait Signal { - fn as_slice(&self) -> &[rmt_item32_t]; +impl Signal for [rmt_item32_t] { + fn as_slice(&self) -> &[rmt_item32_t] { + self + } } /// Stack based signal storage for an RMT signal. @@ -769,15 +582,11 @@ impl FixedLengthSignal { let item = self .0 .get_mut(index) - .ok_or_else(|| EspError::from(ERANGE as i32).unwrap())?; - - // SAFETY: We're overriding all 32 bits, so it doesn't matter what was here before. - let inner = unsafe { &mut item.__bindgen_anon_1.__bindgen_anon_1 }; - inner.set_level0(pair.0.pin_state as u32); - inner.set_duration0(pair.0.ticks.0 as u32); - inner.set_level1(pair.1.pin_state as u32); - inner.set_duration1(pair.1.ticks.0 as u32); + .ok_or_else(|| EspError::from(ERR_ERANGE).unwrap())?; + let mut symbol = Symbol(*item); + symbol.update(pair.0, pair.1); + *item = symbol.0; Ok(()) } } @@ -807,7 +616,6 @@ impl Default for FixedLengthSignal { /// signal.push(Pulse::new(PinState::High, PulseTicks::new(10))); /// signal.push(Pulse::new(PinState::Low, PulseTicks::new(9))); /// ``` - #[cfg(feature = "alloc")] #[derive(Clone, Default)] pub struct VariableLengthSignal { @@ -834,7 +642,7 @@ impl VariableLengthSignal { /// - `capacity` is the number of [`Pulse`]s which can be pushes before reallocating pub fn with_capacity(capacity: usize) -> Self { // half the size, rounding up, because each entry in the [`Vec`] holds upto 2 pulses each - let vec_size = (capacity + 1) / 2; + let vec_size = capacity.div_ceil(2); Self { items: alloc::vec::Vec::with_capacity(vec_size), next_item_is_new: true, @@ -899,178 +707,483 @@ pub enum Receive { Timeout, } -/// The RMT receiver. -/// -/// Use [`RxRmtDriver::start()`] to receive pulses. -/// -/// See the [rmt module][crate::rmt] for more information. -pub struct RxRmtDriver<'d> { - channel: u8, - next_ringbuf_item: Option<(*mut rmt_item32_t, usize)>, - _p: PhantomData<&'d mut ()>, -} +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +mod driver { + use core::cell::UnsafeCell; + use core::marker::PhantomData; + use core::ptr; + + use config::{ReceiveConfig, TransmitConfig}; + use esp_idf_sys::{ + esp, rmt_config_t, rmt_config_t__bindgen_ty_1, rmt_item32_t, rmt_mode_t_RMT_MODE_TX, + rmt_set_tx_loop_mode, rmt_tx_config_t, rmt_tx_stop, vRingbufferReturnItem, EspError, + RMT_CHANNEL_FLAGS_AWARE_DFS, + }; + use esp_idf_sys::{rmt_channel_t, rmt_driver_uninstall}; + + use crate::gpio::InputPin; + use crate::interrupt::InterruptType; + use crate::{gpio::OutputPin, peripheral::Peripheral}; -impl<'d> RxRmtDriver<'d> { - /// Initialise the rmt module with the specified pin, channel and configuration. + use super::RmtChannel; + + use super::*; + + /// The RMT transmitter driver. /// - /// To uninstall the driver just drop it. + /// Use [`TxRmtDriver::start()`] or [`TxRmtDriver::start_blocking()`] to transmit pulses. /// - /// Internally this calls `rmt_config()` and `rmt_driver_install()`. + /// See the [rmt module][crate::rmt] for more information. + pub struct TxRmtDriver<'d> { + channel: u8, + _p: PhantomData<&'d mut ()>, + } - pub fn new( - _channel: impl Peripheral

+ 'd, - pin: impl Peripheral

+ 'd, - config: &ReceiveConfig, - ring_buf_size: usize, - ) -> Result { - crate::into_ref!(pin); - - #[cfg(not(any(esp32, esp32c2)))] - let carrier_en = config.carrier.is_some(); - - #[cfg(not(any(esp32, esp32c2)))] - let carrier = config.carrier.unwrap_or_default(); - - let config = rmt_config_t { - rmt_mode: rmt_mode_t_RMT_MODE_RX, - channel: C::channel(), - gpio_num: pin.pin(), - clk_div: config.clock_divider, - mem_block_num: config.mem_block_num, - flags: 0, - __bindgen_anon_1: rmt_config_t__bindgen_ty_1 { - rx_config: rmt_rx_config_t { - idle_threshold: config.idle_threshold, - filter_ticks_thresh: config.filter_ticks_thresh, - filter_en: config.filter_en, - #[cfg(not(any(esp32, esp32c2)))] - rm_carrier: carrier_en, - #[cfg(not(any(esp32, esp32c2)))] - carrier_freq_hz: carrier.frequency.into(), - #[cfg(not(any(esp32, esp32c2)))] - carrier_level: carrier.carrier_level as u32, - #[cfg(not(any(esp32, esp32c2)))] - carrier_duty_percent: carrier.duty_percent.0, + impl<'d> TxRmtDriver<'d> { + /// Initialise the rmt module with the specified pin, channel and configuration. + /// + /// To uninstall the driver just drop it. + /// + /// Internally this calls `rmt_config()` and `rmt_driver_install()`. + pub fn new( + _channel: impl Peripheral

+ 'd, + pin: impl Peripheral

+ 'd, + config: &TransmitConfig, + ) -> Result { + crate::into_ref!(pin); + + let mut flags = 0; + if config.aware_dfs { + flags |= RMT_CHANNEL_FLAGS_AWARE_DFS; + } + + let carrier_en = config.carrier.is_some(); + let carrier = config.carrier.unwrap_or_default(); + + let sys_config = rmt_config_t { + rmt_mode: rmt_mode_t_RMT_MODE_TX, + channel: C::channel(), + gpio_num: pin.pin(), + clk_div: config.clock_divider, + mem_block_num: config.mem_block_num, + flags, + __bindgen_anon_1: rmt_config_t__bindgen_ty_1 { + tx_config: rmt_tx_config_t { + carrier_en, + carrier_freq_hz: carrier.frequency.into(), + carrier_level: carrier.carrier_level as u32, + carrier_duty_percent: carrier.duty_percent.0, + idle_output_en: config.idle.is_some(), + idle_level: config.idle.map(|i| i as u32).unwrap_or(0), + loop_en: config.looping != config::Loop::None, + #[cfg(any( + all( + not(esp_idf_version_major = "4"), + not(esp_idf_version_major = "5") + ), + all(esp_idf_version_major = "5", not(esp_idf_version_minor = "0")), + not(esp32) + ))] + loop_count: match config.looping { + config::Loop::Count(count) if count > 0 && count < 1024 => count, + _ => 0, + }, + }, }, - }, - }; + }; + + unsafe { + esp!(rmt_config(&sys_config))?; + esp!(rmt_driver_install( + C::channel(), + 0, + InterruptType::to_native(config.intr_flags) as _ + ))?; + } - unsafe { - esp!(rmt_config(&config))?; - esp!(rmt_driver_install(C::channel(), ring_buf_size * 4, 0))?; + Ok(Self { + channel: C::channel() as _, + _p: PhantomData, + }) } - Ok(Self { - channel: C::channel() as _, - next_ringbuf_item: None, - _p: PhantomData, - }) - } + /// Get speed of the channel’s internal counter clock. + /// + /// This calls [rmt_get_counter_clock()][rmt_get_counter_clock] + /// internally. It is used for calculating the number of ticks per second for pulses. + /// + /// See [Pulse::new_with_duration()]. + /// + /// [rmt_get_counter_clock]: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/rmt.html#_CPPv421rmt_get_counter_clock13rmt_channel_tP8uint32_t + pub fn counter_clock(&self) -> Result { + let mut ticks_hz: u32 = 0; + esp!(unsafe { rmt_get_counter_clock(self.channel(), &mut ticks_hz) })?; + Ok(ticks_hz.into()) + } - pub fn channel(&self) -> rmt_channel_t { - self.channel as _ - } + /// Start sending the given signal without blocking. + /// + /// `signal` is captured for safety so that the user can't change the data while transmitting. + pub fn start(&mut self, signal: S) -> Result<(), EspError> + where + S: Signal, + { + self.write_items(&signal, false) + } - /// Start receiving - pub fn start(&self) -> Result<(), EspError> { - esp!(unsafe { rmt_rx_start(self.channel(), true) }) - } + /// Start sending the given signal while blocking. + pub fn start_blocking(&mut self, signal: &S) -> Result<(), EspError> + where + S: Signal + ?Sized, + { + self.write_items(signal, true) + } - /// Stop receiving - pub fn stop(&self) -> Result<(), EspError> { - esp!(unsafe { rmt_rx_stop(self.channel()) }) - } + fn write_items(&mut self, signal: &S, block: bool) -> Result<(), EspError> + where + S: Signal + ?Sized, + { + let items = signal.as_slice(); + esp!(unsafe { + rmt_write_items(self.channel(), items.as_ptr(), items.len() as i32, block) + }) + } - pub fn receive( - &mut self, - buf: &mut [(Pulse, Pulse)], - ticks_to_wait: TickType_t, - ) -> Result { - if let Some(items) = self.fetch_ringbuf_next_item(ticks_to_wait)? { - if items.len() <= buf.len() { - for (index, item) in items.iter().enumerate() { - let item = unsafe { item.__bindgen_anon_1.__bindgen_anon_1 }; - - buf[index] = ( - Pulse::new( - item.level0().into(), - PulseTicks::new(item.duration0().try_into().unwrap()).unwrap(), - ), - Pulse::new( - item.level1().into(), - PulseTicks::new(item.duration1().try_into().unwrap()).unwrap(), - ), - ); + /// Transmit all items in `iter` without blocking. + /// + /// Note that this requires `iter` to be [`Box`]ed for an allocation free version see [`Self::start_iter_blocking`]. + /// + /// ### Warning + /// + /// Iteration of `iter` happens inside an interrupt handler so beware of side-effects + /// that don't work in interrupt handlers. Iteration must also be fast so that there + /// are no time-gaps between successive transmissions where the perhipheral has to + /// wait for items. This can cause weird behavior and can be counteracted with + /// increasing [`Config::mem_block_num`] or making iteration more efficient. + #[cfg(feature = "alloc")] + pub fn start_iter(&mut self, iter: T) -> Result<(), EspError> + where + T: Iterator + Send + 'static, + { + let iter = alloc::boxed::Box::new(UnsafeCell::new(iter)); + unsafe { + esp!(rmt_translator_init( + self.channel(), + Some(Self::translate_iterator::), + ))?; + + esp!(rmt_write_sample( + self.channel(), + alloc::boxed::Box::leak(iter) as *const _ as _, + 1, + false + )) + } + } + + /// Transmit all items in `iter`, blocking until all items are transmitted. + /// + /// This method does not require any allocations since the thread is paused until all + /// items are transmitted. The iterator lives on the stack and will be dropped after + /// all items are written and before this method returns. + /// + /// ### Warning + /// + /// Iteration of `iter` happens inside an interrupt handler so beware of side-effects + /// that don't work in interrupt handlers. Iteration must also be fast so that there + /// are no time-gaps between successive transmissions where the perhipheral has to + /// wait for items. This can cause weird behavior and can be counteracted with + /// increasing [`Config::mem_block_num`] or making iteration more efficient. + pub fn start_iter_blocking(&mut self, iter: T) -> Result<(), EspError> + where + T: Iterator + Send, + { + let iter = UnsafeCell::new(iter); + unsafe { + // TODO: maybe use a separate struct so that we don't have to do this when + // transmitting the same iterator type. + esp!(rmt_translator_init( + self.channel(), + Some(Self::translate_iterator::), + ))?; + esp!(rmt_write_sample( + self.channel(), + &iter as *const _ as _, + 24, + true + )) + } + } + + /// The translator that turns an iterator into `rmt_item32_t` elements. Most of the + /// magic happens here. + /// + /// The general idea is that we can fill a buffer (`dest`) of `rmt_item32_t` items of + /// length `wanted_num` with the items that we get from the iterator. Then we can tell + /// the peripheral driver how many items we filled in by setting `item_num`. The + /// driver will call this function over-and-over until `translated_size` is equal to + /// `src_size` so when the iterator returns [`None`] we set `translated_size` to + /// `src_size` to signal that there are no more items to translate. + /// + /// The compiler will generate this function for every different call to + /// [`Self::start_iter_blocking`] and [`Self::start_iter`] with different iterator + /// types because of the type parameter. This is done to avoid the double indirection + /// that we'd have to do when using a trait object since references to trait objects + /// are fat-pointers (2 `usize` wide) and we only get a narrow pointer (`src`). + /// Using a trait object has the addional overhead that every call to `Iterator::next` + /// would also be indirect (through the `vtable`) and couldn't be inlined. + unsafe extern "C" fn translate_iterator( + src: *const core::ffi::c_void, + mut dest: *mut rmt_item32_t, + src_size: usize, + wanted_num: usize, + translated_size: *mut usize, + item_num: *mut usize, + ) where + T: Iterator, + { + // An `UnsafeCell` is needed here because we're casting a `*const` to a `*mut`. + // Safe because this is the only existing reference. + let iter = &mut *UnsafeCell::raw_get(src as *const UnsafeCell); + + let mut i = 0; + let finished = loop { + if i >= wanted_num { + break 0; } - let len = items.len(); + if let Some(item) = iter.next() { + *dest = item.0; + dest = dest.add(1); + i += 1; + } else { + // Only deallocate the iter if the const generics argument is `true` + // otherwise we could be deallocating stack memory. + #[cfg(feature = "alloc")] + if DEALLOC_ITER { + drop(alloc::boxed::Box::from_raw(iter)); + } + break src_size; + } + }; - self.return_ringbuf_item()?; + *item_num = i; + *translated_size = finished; + } - Ok(Receive::Read(len)) - } else { - Ok(Receive::Overflow(items.len())) - } - } else { - Ok(Receive::Timeout) + /// Stop transmitting. + pub fn stop(&mut self) -> Result<(), EspError> { + esp!(unsafe { rmt_tx_stop(self.channel()) }) + } + + pub fn set_looping(&mut self, looping: config::Loop) -> Result<(), EspError> { + esp!(unsafe { rmt_set_tx_loop_mode(self.channel(), looping != config::Loop::None) })?; + + #[cfg(not(any(esp32, esp32c2)))] + esp!(unsafe { + rmt_set_tx_loop_count( + self.channel(), + match looping { + config::Loop::Count(count) if count > 0 && count < 1024 => count, + _ => 0, + }, + ) + })?; + + Ok(()) + } + + pub fn channel(&self) -> rmt_channel_t { + self.channel as _ } } - fn fetch_ringbuf_next_item( - &mut self, - ticks_to_wait: TickType_t, - ) -> Result, EspError> { - if let Some((rmt_items, length)) = self.next_ringbuf_item { - Ok(Some(unsafe { - core::slice::from_raw_parts(rmt_items, length) - })) - } else { - let mut ringbuf_handle = ptr::null_mut(); - esp!(unsafe { rmt_get_ringbuf_handle(self.channel(), &mut ringbuf_handle) })?; + impl Drop for TxRmtDriver<'_> { + /// Stop transmitting and release the driver. + fn drop(&mut self) { + self.stop().unwrap(); + esp!(unsafe { rmt_driver_uninstall(self.channel()) }).unwrap(); + } + } + + unsafe impl Send for TxRmtDriver<'_> {} + + /// The RMT receiver. + /// + /// Use [`RxRmtDriver::start()`] to receive pulses. + /// + /// See the [rmt module][crate::rmt] for more information. + pub struct RxRmtDriver<'d> { + channel: u8, + next_ringbuf_item: Option<(*mut rmt_item32_t, usize)>, + _p: PhantomData<&'d mut ()>, + } - let mut length = 0; - let rmt_items: *mut rmt_item32_t = unsafe { - xRingbufferReceive(ringbuf_handle.cast(), &mut length, ticks_to_wait).cast() + impl<'d> RxRmtDriver<'d> { + /// Initialise the rmt module with the specified pin, channel and configuration. + /// + /// To uninstall the driver just drop it. + /// + /// Internally this calls `rmt_config()` and `rmt_driver_install()`. + pub fn new( + _channel: impl Peripheral

+ 'd, + pin: impl Peripheral

+ 'd, + config: &ReceiveConfig, + ring_buf_size: usize, + ) -> Result { + crate::into_ref!(pin); + + #[cfg(not(any(esp32, esp32c2)))] + let carrier_en = config.carrier.is_some(); + + #[cfg(not(any(esp32, esp32c2)))] + let carrier = config.carrier.unwrap_or_default(); + + let sys_config = rmt_config_t { + rmt_mode: rmt_mode_t_RMT_MODE_RX, + channel: C::channel(), + gpio_num: pin.pin(), + clk_div: config.clock_divider, + mem_block_num: config.mem_block_num, + flags: 0, + __bindgen_anon_1: rmt_config_t__bindgen_ty_1 { + rx_config: rmt_rx_config_t { + idle_threshold: config.idle_threshold, + filter_ticks_thresh: config.filter_ticks_thresh, + filter_en: config.filter_en, + #[cfg(not(any(esp32, esp32c2)))] + rm_carrier: carrier_en, + #[cfg(not(any(esp32, esp32c2)))] + carrier_freq_hz: carrier.frequency.into(), + #[cfg(not(any(esp32, esp32c2)))] + carrier_level: carrier.carrier_level as u32, + #[cfg(not(any(esp32, esp32c2)))] + carrier_duty_percent: carrier.duty_percent.0, + }, + }, }; - if rmt_items.is_null() { - Ok(None) + unsafe { + esp!(rmt_config(&sys_config))?; + esp!(rmt_driver_install( + C::channel(), + ring_buf_size * 4, + InterruptType::to_native(config.intr_flags) as _ + ))?; + } + + Ok(Self { + channel: C::channel() as _, + next_ringbuf_item: None, + _p: PhantomData, + }) + } + + pub fn channel(&self) -> rmt_channel_t { + self.channel as _ + } + + /// Start receiving + pub fn start(&self) -> Result<(), EspError> { + esp!(unsafe { rmt_rx_start(self.channel(), true) }) + } + + /// Stop receiving + pub fn stop(&self) -> Result<(), EspError> { + esp!(unsafe { rmt_rx_stop(self.channel()) }) + } + + pub fn receive( + &mut self, + buf: &mut [(Pulse, Pulse)], + ticks_to_wait: TickType_t, + ) -> Result { + if let Some(items) = self.fetch_ringbuf_next_item(ticks_to_wait)? { + if items.len() <= buf.len() { + for (index, item) in items.iter().enumerate() { + let item = unsafe { item.__bindgen_anon_1.__bindgen_anon_1 }; + + buf[index] = ( + Pulse::new( + item.level0().into(), + PulseTicks::new(item.duration0().try_into().unwrap()).unwrap(), + ), + Pulse::new( + item.level1().into(), + PulseTicks::new(item.duration1().try_into().unwrap()).unwrap(), + ), + ); + } + + let len = items.len(); + + self.return_ringbuf_item()?; + + Ok(Receive::Read(len)) + } else { + Ok(Receive::Overflow(items.len())) + } } else { - let length = length / 4; - self.next_ringbuf_item = Some((rmt_items, length)); + Ok(Receive::Timeout) + } + } + fn fetch_ringbuf_next_item( + &mut self, + ticks_to_wait: TickType_t, + ) -> Result, EspError> { + if let Some((rmt_items, length)) = self.next_ringbuf_item { Ok(Some(unsafe { core::slice::from_raw_parts(rmt_items, length) })) + } else { + let mut ringbuf_handle = ptr::null_mut(); + esp!(unsafe { rmt_get_ringbuf_handle(self.channel(), &mut ringbuf_handle) })?; + + let mut length = 0; + let rmt_items: *mut rmt_item32_t = unsafe { + xRingbufferReceive(ringbuf_handle.cast(), &mut length, ticks_to_wait).cast() + }; + + if rmt_items.is_null() { + Ok(None) + } else { + let length = length / 4; + self.next_ringbuf_item = Some((rmt_items, length)); + + Ok(Some(unsafe { + core::slice::from_raw_parts(rmt_items, length) + })) + } } } - } - fn return_ringbuf_item(&mut self) -> Result<(), EspError> { - let mut ringbuf_handle = ptr::null_mut(); - esp!(unsafe { rmt_get_ringbuf_handle(self.channel(), &mut ringbuf_handle) })?; + fn return_ringbuf_item(&mut self) -> Result<(), EspError> { + let mut ringbuf_handle = ptr::null_mut(); + esp!(unsafe { rmt_get_ringbuf_handle(self.channel(), &mut ringbuf_handle) })?; - if let Some((rmt_items, _)) = core::mem::replace(&mut self.next_ringbuf_item, None) { - unsafe { - vRingbufferReturnItem(ringbuf_handle, rmt_items.cast()); + if let Some((rmt_items, _)) = self.next_ringbuf_item.take() { + unsafe { + vRingbufferReturnItem(ringbuf_handle, rmt_items.cast()); + } + } else { + unreachable!(); } - } else { - unreachable!(); - } - Ok(()) + Ok(()) + } } -} -impl<'d> Drop for RxRmtDriver<'d> { - /// Stop receiving and release the driver. - fn drop(&mut self) { - self.stop().unwrap(); - esp!(unsafe { rmt_driver_uninstall(self.channel()) }).unwrap(); + impl Drop for RxRmtDriver<'_> { + /// Stop receiving and release the driver. + fn drop(&mut self) { + self.stop().unwrap(); + esp!(unsafe { rmt_driver_uninstall(self.channel()) }).unwrap(); + } } -} -unsafe impl<'d> Send for RxRmtDriver<'d> {} + unsafe impl Send for RxRmtDriver<'_> {} +} mod chip { use esp_idf_sys::*; diff --git a/src/rom.rs b/src/rom.rs new file mode 100644 index 00000000000..80eb1f1633b --- /dev/null +++ b/src/rom.rs @@ -0,0 +1,116 @@ +//! ESP ROM libraries +//! +//! Safe abstractions to the additional libraries provided in the ESP's +//! read-only memory. + +pub mod crc { + //! Cyclic Redundancy Check + //! + //! These are safe abstractions to the CRC functions in the ESP32 ROM. + //! Some chips may not include all of these functions so they will be compiled + //! into the program binary in those cases. + //! + //! # Parameters + //! + //! The ROM provides the following polynomials for each CRC width: + //! + //! | CRC Width | Polynomial | + //! | --------- | ----------- | + //! | CRC-8 | 0x07 | + //! | CRC-16 | 0x1021 | + //! | CRC-32 | 0x04c11db7 | + //! + //! The "big-endian" `*_be()` functions are left-shifting algorithms to be used + //! when input and output reflection are **not** needed. If input and output + //! reflection **are** needed, the right-shifting "little-endian" `*_le()` + //! functions should be used. + //! + //! These functions are designed to compute a CRC over a single buffer or as an + //! ongoing calculation over multiple buffers. To do this, the initial value + //! passed in and the final value returned are one's complemented. + //! + //! ``` + //! // CRC-32/MPEG-2 + //! const CRC_INITIAL = 0xffffffff; // "init" or "xorin" of all ones + //! let mut crc = crc32_be(!CRC_INITIAL, &data0); // start + //! crc = crc32_be(crc, &data1); + //! crc = !crc32_be(crc, &data2); // finish + //! ``` + //! + //! # Examples + //! + //! A catalogue of these parameters can be found at + //! + //! + //! CRC-32/ISO-HDLC poly=0x04c11db7 init=0xffffffff refin=true refout=true xorout=0xffffffff + //! + //! ``` + //! let crc = crc32_le(!0xffffffff, &data); + //! ``` + //! + //! CRC-32/BZIP2 poly=0x04c11db7 init=0xffffffff refin=false refout=false xorout=0xffffffff + //! + //! ``` + //! let crc = crc32_be(!0xffffffff, &data); + //! ``` + //! + //! CRC-32/MPEG-2 poly=0x04c11db7 init=0xffffffff refin=false refout=false xorout=0x00000000 + //! + //! ``` + //! let crc = !crc32_be(!0xffffffff, &data); + //! ``` + //! + //! CRC-32/CKSUM poly=0x04c11db7 init=0x00000000 refin=false refout=false xorout=0xffffffff + //! + //! ``` + //! let crc = crc32_be(!0, &data); + //! ``` + //! + //! CRC-16/KERMIT poly=0x1021 init=0x0000 refin=true refout=true xorout=0x0000 + //! + //! ``` + //! let crc = !crc16_le(!0, &data); + //! ``` + + use esp_idf_sys::*; + + // SAFETY: These functions are all implemented as table lookups. No locking is + // needed to access them, they are all referentially transparent, and the size + // and alignment of `usize` and `u32` are identical on all ESP32 chips. + + /// Right-shifting CRC-32 with polynomial 0x04c11db7 + #[inline(always)] + pub fn crc32_le(crc: u32, buf: &[u8]) -> u32 { + unsafe { esp_rom_crc32_le(crc, buf.as_ptr(), buf.len() as u32) } + } + + /// Left-shifting CRC-32 with polynomial 0x04c11db7 + #[inline(always)] + pub fn crc32_be(crc: u32, buf: &[u8]) -> u32 { + unsafe { esp_rom_crc32_be(crc, buf.as_ptr(), buf.len() as u32) } + } + + /// Right-shifting CRC-16 with polynomial 0x1021 + #[inline(always)] + pub fn crc16_le(crc: u16, buf: &[u8]) -> u16 { + unsafe { esp_rom_crc16_le(crc, buf.as_ptr(), buf.len() as u32) } + } + + /// Left-shifting CRC-16 with polynomial 0x1021 + #[inline(always)] + pub fn crc16_be(crc: u16, buf: &[u8]) -> u16 { + unsafe { esp_rom_crc16_be(crc, buf.as_ptr(), buf.len() as u32) } + } + + /// Right-shifting CRC-8 with polynomial 0x07 + #[inline(always)] + pub fn crc8_le(crc: u8, buf: &[u8]) -> u8 { + unsafe { esp_rom_crc8_le(crc, buf.as_ptr(), buf.len() as u32) } + } + + /// Left-shifting CRC-8 with polynomial 0x07 + #[inline(always)] + pub fn crc8_be(crc: u8, buf: &[u8]) -> u8 { + unsafe { esp_rom_crc8_be(crc, buf.as_ptr(), buf.len() as u32) } + } +} diff --git a/src/sd.rs b/src/sd.rs new file mode 100755 index 00000000000..5bfa50f6c3e --- /dev/null +++ b/src/sd.rs @@ -0,0 +1,449 @@ +#[cfg(feature = "alloc")] +pub use sdcard::*; + +#[cfg(esp_idf_soc_sdmmc_host_supported)] +pub mod mmc; +pub mod spi; + +#[cfg(feature = "alloc")] +mod sdcard { + #[cfg(esp_idf_soc_sdmmc_host_supported)] + use super::mmc::SdMmcHostDriver; + use super::spi::SdSpiHostDriver; + + use core::borrow::Borrow; + + use crate::spi::SpiDriver; + use crate::sys::*; + + extern crate alloc; + + const _SDMMC_HOST_FLAG_SPI: u32 = 1 << 3; + const _SDMMC_HOST_FLAG_DDR: u32 = 1 << 4; + const _SDMMC_HOST_FLAG_DEINIT_ARG: u32 = 1 << 5; + + pub type SdCardConfiguration = config::Configuration; + + pub mod config { + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + )))] // For ESP-IDF v5.2 and later + use crate::sys::*; + + /// (SD-MMC only): Input delay phase + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + )))] // For ESP-IDF v5.2 and later + #[non_exhaustive] + #[derive(Debug, Copy, Clone, Eq, PartialEq)] + pub enum DelayPhase { + Phase0 = sdmmc_delay_phase_t_SDMMC_DELAY_PHASE_0 as isize, + Phase1 = sdmmc_delay_phase_t_SDMMC_DELAY_PHASE_1 as isize, + Phase2 = sdmmc_delay_phase_t_SDMMC_DELAY_PHASE_2 as isize, + Phase3 = sdmmc_delay_phase_t_SDMMC_DELAY_PHASE_3 as isize, + } + + // #[cfg(not(any( + // esp_idf_version_major = "4", + // all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + // all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + // )))] // For ESP-IDF v5.2 and later + // impl From for DelayPhase { + // fn from(phase: sdmmc_delay_phase_t) -> Self { + // #[allow(non_upper_case_globals)] + // match phase { + // sdmmc_delay_phase_t_SDMMC_DELAY_PHASE_0 => Self::Phase0, + // sdmmc_delay_phase_t_SDMMC_DELAY_PHASE_1 => Self::Phase1, + // sdmmc_delay_phase_t_SDMMC_DELAY_PHASE_2 => Self::Phase2, + // sdmmc_delay_phase_t_SDMMC_DELAY_PHASE_3 => Self::Phase3, + // _ => panic!("Invalid delay phase"), + // } + // } + // } + + /// SD-Card voltage + #[non_exhaustive] + #[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] + pub enum Voltage { + /// (ESP32P4 only) + /// Use 1.8V IO voltage for UHS-I speed + /// This means the user has to provide an external LDO power supply + /// or to enable and configure an internal LDO via the `sdkconfig` setting + /// "SD/MMC Example Configuration -> SD power supply comes from internal LDO IO" + V1P8, + /// Use 3.3V IO voltage for speeds below UHS-I + /// The only supported conf for MCUs other than ESP32P4 + #[default] + V3P3, + } + + impl Voltage { + pub(crate) const fn as_native(&self) -> f32 { + match self { + Self::V1P8 => 1.8, + Self::V3P3 => 3.3, + } + } + } + + /// (SD-MMC only): Driver Strength + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "3"), + )))] // For ESP-IDF v5.4 and later + #[non_exhaustive] + #[derive(Debug, Copy, Clone, Eq, PartialEq)] + pub enum DriverStrength { + /// Type B + StrengthB = sdmmc_driver_strength_t_SDMMC_DRIVER_STRENGTH_B as isize, + /// Type A + StrengthA = sdmmc_driver_strength_t_SDMMC_DRIVER_STRENGTH_A as isize, + /// Type C + StrengthC = sdmmc_driver_strength_t_SDMMC_DRIVER_STRENGTH_C as isize, + /// Type D + StrengthD = sdmmc_driver_strength_t_SDMMC_DRIVER_STRENGTH_D as isize, + } + + /// (SD-MMC only): Driver Strength + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "3"), + )))] // For ESP-IDF v5.4 and later + #[non_exhaustive] + #[derive(Debug, Copy, Clone, Eq, PartialEq)] + pub enum CurrentLimit { + /// < 200mA + Limit200mA = sdmmc_current_limit_t_SDMMC_CURRENT_LIMIT_200MA as isize, + /// < 400mA + Limit400mA = sdmmc_current_limit_t_SDMMC_CURRENT_LIMIT_400MA as isize, + /// < 600mA + Limit600mA = sdmmc_current_limit_t_SDMMC_CURRENT_LIMIT_600MA as isize, + /// < 800mA + Limit800mA = sdmmc_current_limit_t_SDMMC_CURRENT_LIMIT_800MA as isize, + } + + /// Configuration for the SD-Card driver + #[non_exhaustive] + pub struct Configuration { + /// Command timeout in milliseconds. Default is 0 (no timeout) + pub command_timeout_ms: u32, + /// SD-Card IO voltage. Default is 3.3V; + /// 1.8V might only be necessary for ESP32P4 and UHS-I speeds + pub io_voltage: Voltage, + /// SD-Card speed in kHz. Default is 20000 kHz (20 MHz) + /// Maximum speed is usually 40000 kHz (40 MHz) + /// Speeds lower than 20000 kHz can also be used + pub speed_khz: u32, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + )))] // For ESP-IDF v5.2 and later + pub input_delay_phase: DelayPhase, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "3"), + )))] // For ESP-IDF v5.4 and later + pub driver_strength: DriverStrength, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "3"), + )))] // For ESP-IDF v5.4 and later + pub current_limit: CurrentLimit, + } + + impl Configuration { + /// Create a new configuration with default values + pub const fn new() -> Self { + Self { + command_timeout_ms: 0, + io_voltage: Voltage::V3P3, + speed_khz: 20000, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + )))] // For ESP-IDF v5.2 and later + input_delay_phase: DelayPhase::Phase0, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "3"), + )))] // For ESP-IDF v5.4 and later + driver_strength: DriverStrength::StrengthB, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "3"), + )))] // For ESP-IDF v5.4 and later + current_limit: CurrentLimit::Limit200mA, + } + } + } + + impl Default for Configuration { + fn default() -> Self { + Self::new() + } + } + } + + /// A high-level SD-Card driver. + /// + /// This driver is used to interface with an SD-Card by wrapping one of the two SD Host drivers: + /// - SD-SPI Host driver (`SdSpiHostDriver`) + /// - SD-MMC Host driver (`SdMmcHostDriver`) - on MCUs that do have an SD-MMC peripheral (ESP32, ESP32S3 and ESP32P4) + /// + /// The interface allows reading, writing and erasing sectors, as well as reading and writing arbitrary-length bytes. + /// + /// Currently, all interaction with the SD-Card driver is via the native, unsafe `sys::sdmmc_*` functions. + pub struct SdCardDriver { + _host: T, + card: alloc::boxed::Box, + } + + impl SdCardDriver { + /// Get a reference to the SD-Card native structure. + pub fn card(&self) -> &sdmmc_card_t { + &self.card + } + + // TODO: Implement the SD-Card API here, i.e. read/write/erase sectors, as well as + // read/write of arbitrary-length bytes. + } + + impl<'d, T> SdCardDriver> + where + T: Borrow>, + { + /// Create a new SD-Card driver using the SD-SPI host driver instantiated with one of the SPI peripherals + pub fn new_spi( + host: SdSpiHostDriver<'d, T>, + configuration: &config::Configuration, + ) -> Result { + let configuration = sdmmc_host_t { + flags: _SDMMC_HOST_FLAG_SPI | _SDMMC_HOST_FLAG_DEINIT_ARG, + slot: host.handle() as _, + max_freq_khz: configuration.speed_khz as _, + io_voltage: configuration.io_voltage.as_native(), + init: Some(sdspi_host_init), + set_bus_width: None, + get_bus_width: None, + set_bus_ddr_mode: None, + set_card_clk: Some(sdspi_host_set_card_clk), + set_cclk_always_on: None, + do_transaction: Some(sdspi_host_do_transaction), + __bindgen_anon_1: sdmmc_host_t__bindgen_ty_1 { + deinit_p: Some(sdspi_host_remove_device), + }, + io_int_enable: Some(sdspi_host_io_int_enable), + io_int_wait: Some(sdspi_host_io_int_wait), + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + )))] // For ESP-IDF v5.1 and later + get_real_freq: Some(sdspi_host_get_real_freq), + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + )))] // For ESP-IDF v5.2 and later + input_delay_phase: configuration.input_delay_phase as _, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + )))] // For ESP-IDF v5.2 and later + set_input_delay: None, + command_timeout_ms: configuration.command_timeout_ms as _, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + )))] // For ESP-IDF v5.3 and later + dma_aligned_buffer: core::ptr::null_mut(), + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + )))] // For ESP-IDF v5.3 and later + get_dma_info: Some(sdspi_host_get_dma_info), + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "3"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "4"), + )))] // For ESP-IDF v5.5 and later + check_buffer_alignment: Some(sdspi_host_check_buffer_alignment), + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + )))] // For ESP-IDF v5.3 and later + pwr_ctrl_handle: core::ptr::null_mut() as _, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "3"), + )))] // For ESP-IDF v5.4 and later + driver_strength: configuration.driver_strength as _, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "3"), + )))] // For ESP-IDF v5.4 and later + current_limit: configuration.current_limit as _, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "3"), + )))] // For ESP-IDF v5.4 and later + is_slot_set_to_uhs1: None, + }; + + let mut card: alloc::boxed::Box = Default::default(); + + esp!(unsafe { sdmmc_card_init(&configuration, &mut *card) })?; + + Ok(Self { _host: host, card }) + } + } + + #[cfg(esp_idf_soc_sdmmc_host_supported)] + impl<'d> SdCardDriver> { + /// Create a new SD-Card driver using the SD-MMC Host driver instantiated with one of the two SD-MMC peripheral slots + pub fn new_mmc( + host: SdMmcHostDriver<'d>, + configuration: &config::Configuration, + ) -> Result { + let configuration = sdmmc_host_t { + flags: _SDMMC_HOST_FLAG_DEINIT_ARG + | _SDMMC_HOST_FLAG_DDR + // Bits 0 - 2 are flags for data widths 1, 4 and 8 respectively + // Set the bit corresponding to our width and all smaller widths + // in case the card does not support our width, but a smaller one only + | (1 | (host.width() - 1)) as u32, + slot: host.slot() as _, + max_freq_khz: configuration.speed_khz as _, + io_voltage: configuration.io_voltage.as_native(), + init: Some(sdmmc_host_init), + set_bus_width: Some(sdmmc_host_set_bus_width), + get_bus_width: Some(sdmmc_host_get_slot_width), + set_bus_ddr_mode: Some(sdmmc_host_set_bus_ddr_mode), + set_card_clk: Some(sdmmc_host_set_card_clk), + set_cclk_always_on: Some(sdmmc_host_set_cclk_always_on), + do_transaction: Some(sdmmc_host_do_transaction), + __bindgen_anon_1: sdmmc_host_t__bindgen_ty_1 { + deinit: Some(sdmmc_host_deinit), + }, + io_int_enable: Some(sdmmc_host_io_int_enable), + io_int_wait: Some(sdmmc_host_io_int_wait), + get_real_freq: Some(sdmmc_host_get_real_freq), + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + )))] // For ESP-IDF v5.2 and later + input_delay_phase: configuration.input_delay_phase as _, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + )))] // For ESP-IDF v5.2 and later + set_input_delay: Some(sdmmc_host_set_input_delay), + command_timeout_ms: configuration.command_timeout_ms as _, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + )))] // For ESP-IDF v5.3 and later + dma_aligned_buffer: core::ptr::null_mut(), + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + )))] // For ESP-IDF v5.3 and later + get_dma_info: Some(sdmmc_host_get_dma_info), + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "3"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "4"), + )))] // For ESP-IDF v5.5 and later + check_buffer_alignment: Some(sdmmc_host_check_buffer_alignment), + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + )))] // For ESP-IDF v5.3 and later + pwr_ctrl_handle: core::ptr::null_mut() as _, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "3"), + )))] // For ESP-IDF v5.4 and later + driver_strength: configuration.driver_strength as _, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "3"), + )))] // For ESP-IDF v5.4 and later + current_limit: configuration.current_limit as _, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "3"), + )))] // For ESP-IDF v5.4 and later + is_slot_set_to_uhs1: None, + }; + + let mut card: alloc::boxed::Box = Default::default(); + + esp!(unsafe { sdmmc_card_init(&configuration, &mut *card) })?; + + Ok(Self { _host: host, card }) + } + } +} diff --git a/src/sd/mmc.rs b/src/sd/mmc.rs new file mode 100644 index 00000000000..00ced8d8674 --- /dev/null +++ b/src/sd/mmc.rs @@ -0,0 +1,558 @@ +use core::marker::PhantomData; +use core::ops::Deref; +use core::sync::atomic::{AtomicU8, Ordering}; + +use crate::gpio::{self, InputPin, OutputPin}; +use crate::peripheral::Peripheral; +use crate::sys::*; + +/// Indicates that card detect line is not used +const SDMMC_SLOT_NO_CD: i32 = -1; +/// Indicates that write protect line is not used +const SDMMC_SLOT_NO_WP: i32 = -1; +/// Bit indicating that internal pullups should be enabled +const SDMMC_INTERNAL_PULLUPS_ENABLE_FLAG: u32 = 1; + +static USED_SLOTS: AtomicU8 = AtomicU8::new(0); +static USED_SLOTS_CS: crate::task::CriticalSection = crate::task::CriticalSection::new(); + +pub type SdMmcHostConfiguration = config::Configuration; + +pub mod config { + /// Configuration for the SD-MMC Host driver + #[non_exhaustive] + pub struct Configuration { + /// Enable internal pullups on the data lines. + /// + /// Pullups (either internal or external) MUST be enabled for the data lines + /// so as the driver to operate correctly. + /// + /// Espressif recommends using external pullups (10k each) - yet - for + /// demo/debugging purposes internal pullups should be fine + /// + /// Set this to `false` only when external pullups are used. + /// When using external pullups note that those should be set even on the pins + /// which are not actually used (i.e. on pins d1, d2 and d3 for slot-1 of ESP32 + /// 1-bit mode) + pub enable_internal_pullups: bool, + } + + impl Configuration { + /// Create a new configuration with default values + pub const fn new() -> Self { + Self { + enable_internal_pullups: true, + } + } + } + + impl Default for Configuration { + fn default() -> Self { + Self::new() + } + } +} + +/// SDMMC host slot peripheral +pub trait SdMmc { + fn slot() -> u8; +} + +/// SD-MMC Host driver (per slot) for SD Cards supporting the MMC protocol. +pub struct SdMmcHostDriver<'d> { + slot: u8, + width: u8, + _p: PhantomData<&'d mut ()>, +} + +impl<'d> SdMmcHostDriver<'d> { + /// Create a new driver for the provided slot peripheral with data line width 1. + #[cfg(esp_idf_soc_sdmmc_use_gpio_matrix)] + #[allow(clippy::too_many_arguments)] + pub fn new_1bit( + slot: impl Peripheral

+ 'd, + cmd: impl Peripheral

+ 'd, + clk: impl Peripheral

+ 'd, + d0: impl Peripheral

+ 'd, + cd: Option + 'd>, + wp: Option + 'd>, + config: &config::Configuration, + ) -> Result { + Self::new_internal( + 1, + config.enable_internal_pullups, + slot, + cmd, + clk, + d0, + gpio::AnyIOPin::none(), + gpio::AnyIOPin::none(), + gpio::AnyIOPin::none(), + gpio::AnyIOPin::none(), + gpio::AnyIOPin::none(), + gpio::AnyIOPin::none(), + gpio::AnyIOPin::none(), + cd, + wp, + ) + } + + /// Create a new driver for the provided slot peripheral with data line width 4. + #[cfg(esp_idf_soc_sdmmc_use_gpio_matrix)] + #[allow(clippy::too_many_arguments)] + pub fn new_4bits( + slot: impl Peripheral

+ 'd, + cmd: impl Peripheral

+ 'd, + clk: impl Peripheral

+ 'd, + d0: impl Peripheral

+ 'd, + d1: impl Peripheral

+ 'd, + d2: impl Peripheral

+ 'd, + d3: impl Peripheral

+ 'd, + cd: Option + 'd>, + wp: Option + 'd>, + config: &config::Configuration, + ) -> Result { + Self::new_internal( + 4, + config.enable_internal_pullups, + slot, + cmd, + clk, + d0, + Some(d1), + Some(d2), + Some(d3), + gpio::AnyIOPin::none(), + gpio::AnyIOPin::none(), + gpio::AnyIOPin::none(), + gpio::AnyIOPin::none(), + cd, + wp, + ) + } + + /// Create a new driver for the provided slot peripheral with data line width 8. + #[cfg(esp_idf_soc_sdmmc_use_gpio_matrix)] + #[allow(clippy::too_many_arguments)] + pub fn new_8bits( + slot: impl Peripheral

+ 'd, + cmd: impl Peripheral

+ 'd, + clk: impl Peripheral

+ 'd, + d0: impl Peripheral

+ 'd, + d1: impl Peripheral

+ 'd, + d2: impl Peripheral

+ 'd, + d3: impl Peripheral

+ 'd, + d4: impl Peripheral

+ 'd, + d5: impl Peripheral

+ 'd, + d6: impl Peripheral

+ 'd, + d7: impl Peripheral

+ 'd, + cd: Option + 'd>, + wp: Option + 'd>, + config: &config::Configuration, + ) -> Result { + Self::new_internal( + 8, + config.enable_internal_pullups, + slot, + cmd, + clk, + d0, + Some(d1), + Some(d2), + Some(d3), + Some(d4), + Some(d5), + Some(d6), + Some(d7), + cd, + wp, + ) + } + + /// Create a new driver for slot 0 of the SD-MMC peripheral with data line width 1. + #[cfg(not(esp_idf_soc_sdmmc_use_gpio_matrix))] + #[allow(clippy::too_many_arguments)] + pub fn new_slot0_1bit( + slot0: impl Peripheral

+ 'd, + cmd: impl Peripheral

+ 'd, + clk: impl Peripheral

+ 'd, + d0: impl Peripheral

+ 'd, + cd: Option + 'd>, + wp: Option + 'd>, + config: &config::Configuration, + ) -> Result { + Self::new_internal( + 1, + config.enable_internal_pullups, + slot0, + cmd, + clk, + d0, + Option::::None, + Option::::None, + Option::::None, + Option::::None, + Option::::None, + Option::::None, + Option::::None, + cd, + wp, + ) + } + + /// Create a new driver for slot 0 of the SD-MMC peripheral with data line width 4. + #[cfg(not(esp_idf_soc_sdmmc_use_gpio_matrix))] + #[allow(clippy::too_many_arguments)] + pub fn new_slot0_4bits( + slot0: impl Peripheral

+ 'd, + cmd: impl Peripheral

+ 'd, + clk: impl Peripheral

+ 'd, + d0: impl Peripheral

+ 'd, + d1: impl Peripheral

+ 'd, + d2: impl Peripheral

+ 'd, + d3: impl Peripheral

+ 'd, + cd: Option + 'd>, + wp: Option + 'd>, + config: &config::Configuration, + ) -> Result { + Self::new_internal( + 4, + config.enable_internal_pullups, + slot0, + cmd, + clk, + d0, + Some(d1), + Some(d2), + Some(d3), + Option::::None, + Option::::None, + Option::::None, + Option::::None, + cd, + wp, + ) + } + + /// Create a new driver for slot 0 of the SD-MMC peripheral with data line width 8. + #[cfg(not(esp_idf_soc_sdmmc_use_gpio_matrix))] + #[allow(clippy::too_many_arguments)] + pub fn new_slot0_8bits( + slot0: impl Peripheral

+ 'd, + cmd: impl Peripheral

+ 'd, + clk: impl Peripheral

+ 'd, + d0: impl Peripheral

+ 'd, + d1: impl Peripheral

+ 'd, + d2: impl Peripheral

+ 'd, + d3: impl Peripheral

+ 'd, + d4: impl Peripheral

+ 'd, + d5: impl Peripheral

+ 'd, + d6: impl Peripheral

+ 'd, + d7: impl Peripheral

+ 'd, + cd: Option + 'd>, + wp: Option + 'd>, + config: &config::Configuration, + ) -> Result { + Self::new_internal( + 8, + config.enable_internal_pullups, + slot0, + cmd, + clk, + d0, + Some(d1), + Some(d2), + Some(d3), + Some(d4), + Some(d5), + Some(d6), + Some(d7), + cd, + wp, + ) + } + + /// Create a new driver for slot 1 of the SD-MMC peripheral with data line width 1. + #[cfg(not(esp_idf_soc_sdmmc_use_gpio_matrix))] + #[allow(clippy::too_many_arguments)] + pub fn new_slot1_1bit( + slot1: impl Peripheral

+ 'd, + cmd: impl Peripheral

+ 'd, + clk: impl Peripheral

+ 'd, + d0: impl Peripheral

+ 'd, + cd: Option + 'd>, + wp: Option + 'd>, + config: &config::Configuration, + ) -> Result { + Self::new_internal( + 1, + config.enable_internal_pullups, + slot1, + cmd, + clk, + d0, + Option::::None, + Option::::None, + Option::::None, + Option::::None, + Option::::None, + Option::::None, + Option::::None, + cd, + wp, + ) + } + + /// Create a new driver for slot 1 of the SD-MMC peripheral with data line width 4. + #[cfg(not(esp_idf_soc_sdmmc_use_gpio_matrix))] + #[allow(clippy::too_many_arguments)] + pub fn new_slot1_4bits( + slot1: impl Peripheral

+ 'd, + cmd: impl Peripheral

+ 'd, + clk: impl Peripheral

+ 'd, + d0: impl Peripheral

+ 'd, + d1: impl Peripheral

+ 'd, + d2: impl Peripheral

+ 'd, + d3: impl Peripheral

+ 'd, + cd: Option + 'd>, + wp: Option + 'd>, + config: &config::Configuration, + ) -> Result { + Self::new_internal( + 4, + config.enable_internal_pullups, + slot1, + cmd, + clk, + d0, + Some(d1), + Some(d2), + Some(d3), + Option::::None, + Option::::None, + Option::::None, + Option::::None, + cd, + wp, + ) + } + + #[allow(clippy::too_many_arguments)] + fn new_internal( + width: u8, + internal_pullups: bool, + _slot: impl Peripheral

+ 'd, + _cmd: impl Peripheral

+ 'd, + _clk: impl Peripheral

+ 'd, + _d0: impl Peripheral

+ 'd, + _d1: Option + 'd>, + _d2: Option + 'd>, + _d3: Option + 'd>, + _d4: Option + 'd>, + _d5: Option + 'd>, + _d6: Option + 'd>, + _d7: Option + 'd>, + cd: Option + 'd>, + wp: Option + 'd>, + ) -> Result { + let slot_config = sdmmc_slot_config_t { + width: width as _, + flags: if internal_pullups { + SDMMC_INTERNAL_PULLUPS_ENABLE_FLAG + } else { + 0 + }, + __bindgen_anon_1: sdmmc_slot_config_t__bindgen_ty_1 { + cd: cd + .map(|cd| cd.into_ref().deref().pin()) + .unwrap_or(SDMMC_SLOT_NO_CD), + }, + __bindgen_anon_2: sdmmc_slot_config_t__bindgen_ty_2 { + wp: wp + .map(|wp| wp.into_ref().deref().pin()) + .unwrap_or(SDMMC_SLOT_NO_WP), + }, + #[cfg(any( + esp_idf_soc_sdmmc_use_gpio_matrix, + not(any( + esp_idf_version_major = "4", + esp_idf_version = "5.0", + esp_idf_version = "5.1", + esp_idf_version = "5.2", + esp_idf_version_full = "5.3.0", + esp_idf_version_full = "5.3.1", + esp_idf_version_full = "5.3.2" + )) + ))] + clk: _clk.into_ref().deref().pin(), + #[cfg(any( + esp_idf_soc_sdmmc_use_gpio_matrix, + not(any( + esp_idf_version_major = "4", + esp_idf_version = "5.0", + esp_idf_version = "5.1", + esp_idf_version = "5.2", + esp_idf_version_full = "5.3.0", + esp_idf_version_full = "5.3.1", + esp_idf_version_full = "5.3.2" + )) + ))] + cmd: _cmd.into_ref().deref().pin(), + #[cfg(any( + esp_idf_soc_sdmmc_use_gpio_matrix, + not(any( + esp_idf_version_major = "4", + esp_idf_version = "5.0", + esp_idf_version = "5.1", + esp_idf_version = "5.2", + esp_idf_version_full = "5.3.0", + esp_idf_version_full = "5.3.1", + esp_idf_version_full = "5.3.2" + )) + ))] + d0: _d0.into_ref().deref().pin(), + #[cfg(any( + esp_idf_soc_sdmmc_use_gpio_matrix, + not(any( + esp_idf_version_major = "4", + esp_idf_version = "5.0", + esp_idf_version = "5.1", + esp_idf_version = "5.2", + esp_idf_version_full = "5.3.0", + esp_idf_version_full = "5.3.1", + esp_idf_version_full = "5.3.2" + )) + ))] + d1: _d1.map(|d1| d1.into_ref().deref().pin()).unwrap_or(-1), + #[cfg(any( + esp_idf_soc_sdmmc_use_gpio_matrix, + not(any( + esp_idf_version_major = "4", + esp_idf_version = "5.0", + esp_idf_version = "5.1", + esp_idf_version = "5.2", + esp_idf_version_full = "5.3.0", + esp_idf_version_full = "5.3.1", + esp_idf_version_full = "5.3.2" + )) + ))] + d2: _d2.map(|d2| d2.into_ref().deref().pin()).unwrap_or(-1), + #[cfg(any( + esp_idf_soc_sdmmc_use_gpio_matrix, + not(any( + esp_idf_version_major = "4", + esp_idf_version = "5.0", + esp_idf_version = "5.1", + esp_idf_version = "5.2", + esp_idf_version_full = "5.3.0", + esp_idf_version_full = "5.3.1", + esp_idf_version_full = "5.3.2" + )) + ))] + d3: _d3.map(|d3| d3.into_ref().deref().pin()).unwrap_or(-1), + #[cfg(any( + esp_idf_soc_sdmmc_use_gpio_matrix, + not(any( + esp_idf_version_major = "4", + esp_idf_version = "5.0", + esp_idf_version = "5.1", + esp_idf_version = "5.2", + esp_idf_version_full = "5.3.0", + esp_idf_version_full = "5.3.1", + esp_idf_version_full = "5.3.2" + )) + ))] + d4: _d4.map(|d4| d4.into_ref().deref().pin()).unwrap_or(-1), + #[cfg(any( + esp_idf_soc_sdmmc_use_gpio_matrix, + not(any( + esp_idf_version_major = "4", + esp_idf_version = "5.0", + esp_idf_version = "5.1", + esp_idf_version = "5.2", + esp_idf_version_full = "5.3.0", + esp_idf_version_full = "5.3.1", + esp_idf_version_full = "5.3.2" + )) + ))] + d5: _d5.map(|d5| d5.into_ref().deref().pin()).unwrap_or(-1), + #[cfg(any( + esp_idf_soc_sdmmc_use_gpio_matrix, + not(any( + esp_idf_version_major = "4", + esp_idf_version = "5.0", + esp_idf_version = "5.1", + esp_idf_version = "5.2", + esp_idf_version_full = "5.3.0", + esp_idf_version_full = "5.3.1", + esp_idf_version_full = "5.3.2" + )) + ))] + d6: _d6.map(|d6| d6.into_ref().deref().pin()).unwrap_or(-1), + #[cfg(any( + esp_idf_soc_sdmmc_use_gpio_matrix, + not(any( + esp_idf_version_major = "4", + esp_idf_version = "5.0", + esp_idf_version = "5.1", + esp_idf_version = "5.2", + esp_idf_version_full = "5.3.0", + esp_idf_version_full = "5.3.1", + esp_idf_version_full = "5.3.2" + )) + ))] + d7: _d7.map(|d7| d7.into_ref().deref().pin()).unwrap_or(-1), + }; + + { + let _cs = USED_SLOTS_CS.enter(); + + if USED_SLOTS.load(Ordering::SeqCst) == 0 { + esp!(unsafe { sdmmc_host_init() })?; + + USED_SLOTS.fetch_add(1, Ordering::SeqCst); + } + } + + esp!(unsafe { sdmmc_host_init_slot(S::slot() as _, &slot_config) })?; + + Ok(Self { + slot: S::slot(), + width, + _p: PhantomData, + }) + } + + pub(crate) fn slot(&self) -> u8 { + self.slot + } + + pub(crate) fn width(&self) -> u8 { + self.width + } +} + +impl Drop for SdMmcHostDriver<'_> { + fn drop(&mut self) { + let _cs = USED_SLOTS_CS.enter(); + + if USED_SLOTS.fetch_sub(1, Ordering::SeqCst) == 1 { + esp!(unsafe { sdmmc_host_deinit() }).unwrap(); + } + } +} + +macro_rules! impl_slot { + ($instance:ident: $slot:expr) => { + crate::impl_peripheral!($instance); + + impl SdMmc for $instance { + fn slot() -> u8 { + $slot + } + } + }; +} + +impl_slot!(SDMMC0: 0); +impl_slot!(SDMMC1: 1); diff --git a/src/sd/spi.rs b/src/sd/spi.rs new file mode 100755 index 00000000000..98c83f8680f --- /dev/null +++ b/src/sd/spi.rs @@ -0,0 +1,104 @@ +use core::borrow::Borrow; +use core::marker::PhantomData; +use core::ops::Deref; +use core::sync::atomic::{AtomicU8, Ordering}; + +use crate::gpio::{InputPin, OutputPin}; +use crate::peripheral::Peripheral; +use crate::spi::SpiDriver; +use crate::sys::*; + +static USED: AtomicU8 = AtomicU8::new(0); +static USED_CS: crate::task::CriticalSection = crate::task::CriticalSection::new(); + +/// SPI Host driver for SD cards supporting the SPI protocol. +pub struct SdSpiHostDriver<'d, T> { + _spi_driver: T, + handle: sdspi_dev_handle_t, + _p: PhantomData<&'d mut ()>, +} + +impl<'d, T> SdSpiHostDriver<'d, T> +where + T: Borrow>, +{ + /// Create a new SPI host driver for SD cards + /// + /// # Arguments + /// - spi_driver: SPI peripheral driver + /// - cs: Chip Select pin (optional) + /// - cd: Card Detect pin (optional) + /// - wp: Write Protect pin (optional) + /// - int: Interrupt pin (optional) + /// - wp_active_high: Write Protect active when high (optional, default = `false`) + pub fn new( + spi_driver: T, + cs: Option + 'd>, + cd: Option + 'd>, + wp: Option + 'd>, + int: Option + 'd>, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + )))] // For ESP-IDF v5.2 and later + wp_active_high: Option, + ) -> Result + where + T: Borrow>, + { + #[allow(clippy::needless_update)] + let dev_config = sdspi_device_config_t { + host_id: spi_driver.borrow().host(), + gpio_cs: cs.map(|cs| cs.into_ref().deref().pin()).unwrap_or(-1), + gpio_cd: cd.map(|cd| cd.into_ref().deref().pin()).unwrap_or(-1), + gpio_wp: wp.map(|wp| wp.into_ref().deref().pin()).unwrap_or(-1), + gpio_int: int.map(|int| int.into_ref().deref().pin()).unwrap_or(-1), + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + )))] // For ESP-IDF v5.2 and later + gpio_wp_polarity: wp_active_high.unwrap_or(false), // `false` = active when low + ..Default::default() // `duty_cycle_pos` = 128 (or 0 which is equivalent) - since ESP-IDF V5.4 + }; + + { + let _cs = USED_CS.enter(); + + if USED.load(Ordering::SeqCst) == 0 { + esp!(unsafe { sdspi_host_init() })?; + + USED.fetch_add(1, Ordering::SeqCst); + } + } + + let mut handle = 0; + + esp!(unsafe { sdspi_host_init_device(&dev_config, &mut handle) })?; + + Ok(Self { + _spi_driver: spi_driver, + handle, + _p: PhantomData, + }) + } + + pub fn handle(&self) -> sdspi_dev_handle_t { + self.handle + } +} + +impl Drop for SdSpiHostDriver<'_, T> { + fn drop(&mut self) { + esp!(unsafe { sdspi_host_remove_device(self.handle) }).unwrap(); + + { + let _cs = USED_CS.enter(); + + if USED.fetch_sub(1, Ordering::SeqCst) == 1 { + esp!(unsafe { sdspi_host_deinit() }).unwrap(); + } + } + } +} diff --git a/src/spi.rs b/src/spi.rs index d0c6fa9a558..47fae5c892f 100644 --- a/src/spi.rs +++ b/src/spi.rs @@ -9,17 +9,20 @@ //! //! Look at the following table to determine which driver best suits your requirements: //! -//! | | | SpiDeviceDriver::new | SpiDeviceDriver::new (no CS) | SpiSoftCsDeviceDriver::new | | -//! |---|------------------|----------------------|------------------------------|----------------------------|---| -//! | | managed cs | hardware | - | software triggerd | | -//! | | 1 device | x | x | x | | -//! | | 1-3 devices | x | - | x | | -//! | | 4-6 devices | only on esp32c* | - | x | | -//! | | more than 6 | - | - | x | | -//! | | Dma | - | - | - | | -//! | | polling transmit | x | x | x | | -//! | | isr transmit | - | - | - | | -//! | | async ready | - | - | - | | +//! | | | SpiDeviceDriver::new | SpiDeviceDriver::new (no CS) | SpiSoftCsDeviceDriver::new | SpiBusDriver::new | +//! |---|------------------|----------------------|------------------------------|----------------------------|-------------------| +//! | | Managed CS | Hardware | N | Software triggered | N | +//! | | 1 device | Y | Y | Y | Y | +//! | | 1-3 devices | Y | N | Y | N | +//! | | 4-6 devices | Only on esp32CX | N | Y | N | +//! | | More than 6 | N | N | Y | N | +//! | | DMA | Y | Y | Y | Y | +//! | | Polling transmit | Y | Y | Y | Y | +//! | | ISR transmit | Y | Y | Y | Y | +//! | | Async support* | Y | Y | Y | Y | +//! +//! * True non-blocking async possible only when all devices attached to the SPI bus are used in async mode (i.e. calling methods `xxx_async()` +//! instead of their blocking `xxx()` counterparts) //! //! The [Transfer::transfer], [Write::write] and [WriteIter::write_iter] functions lock the //! APB frequency and therefore the requests are always run at the requested baudrate. @@ -27,22 +30,29 @@ //! therefore may run at a different frequency. //! //! # TODO -//! - Quad SPI //! - Slave SPI -use core::borrow::Borrow; +use core::borrow::{Borrow, BorrowMut}; +use core::cell::Cell; use core::cell::UnsafeCell; use core::cmp::{max, min, Ordering}; +use core::future::Future; +use core::iter::once; use core::marker::PhantomData; use core::ptr; -use embedded_hal::spi::{SpiBus, SpiBusFlush, SpiBusRead, SpiBusWrite, SpiDevice}; +use embassy_sync::mutex::Mutex; +use embedded_hal::spi::{SpiBus, SpiDevice}; use esp_idf_sys::*; +use heapless::Deque; -use crate::delay::{Ets, BLOCK}; -use crate::gpio::{AnyOutputPin, InputPin, Level, Output, OutputPin, PinDriver}; +use crate::delay::{self, Ets, BLOCK}; +use crate::gpio::{AnyOutputPin, IOPin, InputPin, Level, Output, OutputMode, OutputPin, PinDriver}; +use crate::interrupt::asynch::HalIsrNotification; +use crate::interrupt::InterruptType; use crate::peripheral::Peripheral; +use crate::task::embassy_sync::EspRawMutex; use crate::task::CriticalSection; crate::embedded_hal_error!( @@ -51,6 +61,8 @@ crate::embedded_hal_error!( embedded_hal::spi::ErrorKind ); +use config::{Duplex, LineWidth}; + pub trait Spi: Send { fn device() -> spi_host_device_t; } @@ -84,48 +96,52 @@ impl Dma { Dma::Disabled => TRANS_LEN, Dma::Channel1(size) | Dma::Channel2(size) | Dma::Auto(size) => *size, }; - if max_transfer_size % 4 != 0 { - panic!("The max transfer size must a multiple of 4") - } else if max_transfer_size > 4096 { - 4096 - } else { - max_transfer_size + match max_transfer_size { + 0 => panic!("The max transfer size must be greater than 0"), + x if x % 4 != 0 => panic!("The max transfer size must be a multiple of 4"), + _ => max_transfer_size, } } } +pub type SpiDriverConfig = config::DriverConfig; pub type SpiConfig = config::Config; /// SPI configuration pub mod config { - use crate::units::*; + use crate::{interrupt::InterruptType, units::*}; + use enumset::EnumSet; use esp_idf_sys::*; + use super::Dma; + + pub use embedded_hal::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; + pub struct V02Type(pub T); - impl From> for embedded_hal::spi::Polarity { + impl From> for Polarity { fn from(polarity: V02Type) -> Self { match polarity.0 { - embedded_hal_0_2::spi::Polarity::IdleHigh => embedded_hal::spi::Polarity::IdleHigh, - embedded_hal_0_2::spi::Polarity::IdleLow => embedded_hal::spi::Polarity::IdleLow, + embedded_hal_0_2::spi::Polarity::IdleHigh => Polarity::IdleHigh, + embedded_hal_0_2::spi::Polarity::IdleLow => Polarity::IdleLow, } } } - impl From> for embedded_hal::spi::Phase { + impl From> for Phase { fn from(phase: V02Type) -> Self { match phase.0 { embedded_hal_0_2::spi::Phase::CaptureOnFirstTransition => { - embedded_hal::spi::Phase::CaptureOnFirstTransition + Phase::CaptureOnFirstTransition } embedded_hal_0_2::spi::Phase::CaptureOnSecondTransition => { - embedded_hal::spi::Phase::CaptureOnSecondTransition + Phase::CaptureOnSecondTransition } } } } - impl From> for embedded_hal::spi::Mode { + impl From> for Mode { fn from(mode: V02Type) -> Self { Self { polarity: V02Type(mode.0.polarity).into(), @@ -135,7 +151,7 @@ pub mod config { } /// Specify the communication mode with the device - #[derive(Copy, Clone)] + #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Duplex { /// Full duplex is the default Full, @@ -155,18 +171,96 @@ pub mod config { } } + /// Specifies the order in which the bits of data should be transfered/received + #[derive(Debug, Copy, Clone, Eq, PartialEq)] + pub enum BitOrder { + /// Most significant bit first (default) + MsbFirst, + /// Least significant bit first + LsbFirst, + /// Least significant bit first, when sending + TxLsbFirst, + /// Least significant bit first, when receiving + RxLsbFirst, + } + + impl BitOrder { + pub fn as_flags(&self) -> u32 { + match self { + Self::MsbFirst => 0, + Self::LsbFirst => SPI_DEVICE_BIT_LSBFIRST, + Self::TxLsbFirst => SPI_DEVICE_TXBIT_LSBFIRST, + Self::RxLsbFirst => SPI_DEVICE_RXBIT_LSBFIRST, + } + } + } + + #[derive(Debug, Copy, Clone, Eq, PartialEq)] + pub enum LineWidth { + /// 1-bit, 2 wire duplex or 1 wire half-duplex + Single, + /// 2-bit, 2 wire half-duplex + Dual, + /// 4-bit, 4 wire half-duplex + Quad, + } + + /// SPI Driver configuration + #[derive(Debug, Clone)] + pub struct DriverConfig { + pub dma: Dma, + pub intr_flags: EnumSet, + } + + impl DriverConfig { + pub fn new() -> Self { + Default::default() + } + + #[must_use] + pub fn dma(mut self, dma: Dma) -> Self { + self.dma = dma; + self + } + + #[must_use] + pub fn intr_flags(mut self, intr_flags: EnumSet) -> Self { + self.intr_flags = intr_flags; + self + } + } + + impl Default for DriverConfig { + fn default() -> Self { + Self { + dma: Dma::Disabled, + intr_flags: EnumSet::::empty(), + } + } + } + /// SPI Device configuration - #[derive(Copy, Clone)] + #[derive(Debug, Clone)] pub struct Config { pub baudrate: Hertz, - pub data_mode: embedded_hal::spi::Mode, + pub data_mode: Mode, /// This property can be set to configure a SPI Device for being write only /// Thus the flag SPI_DEVICE_NO_DUMMY will be passed on initialization and /// it will unlock the possibility of using 80Mhz as the bus freq /// See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_master.html#timing-considerations pub write_only: bool, pub duplex: Duplex, + pub bit_order: BitOrder, pub cs_active_high: bool, + /// On Half-Duplex transactions: `cs_pre_delay_us % 16` corresponds to the number of SPI bit-cycles cs should be activated before the transmission. + /// On Full-Duplex transactions: `cs_pre_delay_us != 0` will add 1 microsecond of cs activation before transmission + pub cs_pre_delay_us: Option, // u16 as per the C struct has a uint16_t, cf: esp-idf/components/driver/spi/include/driver/spi_master.h spi_device_interface_config_t + ///< Amount of SPI bit-cycles the cs should stay active after the transmission (0-16) + pub cs_post_delay_us: Option, // u8 as per the C struct had a uint8_t, cf: esp-idf/components/driver/spi/include/driver/spi_master.h spi_device_interface_config_t + pub input_delay_ns: i32, + pub polling: bool, + pub allow_pre_post_delays: bool, + pub queue_size: usize, } impl Config { @@ -181,168 +275,153 @@ pub mod config { } #[must_use] - pub fn data_mode(mut self, data_mode: embedded_hal::spi::Mode) -> Self { + pub fn data_mode(mut self, data_mode: Mode) -> Self { self.data_mode = data_mode; self } + #[must_use] pub fn write_only(mut self, write_only: bool) -> Self { self.write_only = write_only; self } + #[must_use] pub fn duplex(mut self, duplex: Duplex) -> Self { self.duplex = duplex; self } + #[must_use] + pub fn bit_order(mut self, bit_order: BitOrder) -> Self { + self.bit_order = bit_order; + self + } + + #[must_use] pub fn cs_active_high(mut self) -> Self { self.cs_active_high = true; self } - } - impl Default for Config { - fn default() -> Self { - Self { - baudrate: Hertz(1_000_000), - data_mode: embedded_hal::spi::MODE_0, - write_only: false, - cs_active_high: false, - duplex: Duplex::Full, - } + /// On Half-Duplex transactions: `cs_pre_delay_us % 16` corresponds to the number of SPI bit-cycles cs should be activated before the transmission + /// On Full-Duplex transactions: `cs_pre_delay_us != 0` will add 1 microsecond of cs activation before transmission + #[must_use] + pub fn cs_pre_delay_us(mut self, delay_us: u16) -> Self { + self.cs_pre_delay_us = Some(delay_us); + self } - } -} - -pub struct SpiBusDriver<'d> { - handle: spi_device_handle_t, - trans_len: usize, - hardware_cs: bool, - _p: PhantomData<&'d mut ()>, -} -impl<'d> SpiBusDriver<'d> { - pub fn read(&mut self, words: &mut [u8]) -> Result<(), EspError> { - for chunk in words.chunks_mut(self.trans_len) { - self.polling_transmit(chunk.as_mut_ptr(), ptr::null(), chunk.len(), chunk.len())?; + /// Add an aditional Amount of SPI bit-cycles the cs should be activated after the transmission (0-16). + /// This only works on half-duplex transactions. + #[must_use] + pub fn cs_post_delay_us(mut self, delay_us: u8) -> Self { + self.cs_post_delay_us = Some(delay_us); + self } - Ok(()) - } - - pub fn write(&mut self, words: &[u8]) -> Result<(), EspError> { - for chunk in words.chunks(self.trans_len) { - self.polling_transmit(ptr::null_mut(), chunk.as_ptr(), chunk.len(), 0)?; + #[must_use] + pub fn input_delay_ns(mut self, input_delay_ns: i32) -> Self { + self.input_delay_ns = input_delay_ns; + self } - Ok(()) - } - - pub fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), EspError> { - let common_length = min(read.len(), write.len()); - let common_read = read[0..common_length].chunks_mut(self.trans_len); - let common_write = write[0..common_length].chunks(self.trans_len); - - for (read_chunk, write_chunk) in common_read.zip(common_write) { - self.polling_transmit( - read_chunk.as_mut_ptr(), - write_chunk.as_ptr(), - max(read_chunk.len(), write_chunk.len()), - read_chunk.len(), - )?; + #[must_use] + pub fn polling(mut self, polling: bool) -> Self { + self.polling = polling; + self } - match read.len().cmp(&write.len()) { - Ordering::Equal => { /* Nothing left to do */ } - Ordering::Greater => { - // Read remainder - self.read(&mut read[write.len()..])?; - } - Ordering::Less => { - // Write remainder - self.write(&write[read.len()..])?; - } + #[must_use] + pub fn allow_pre_post_delays(mut self, allow_pre_post_delays: bool) -> Self { + self.allow_pre_post_delays = allow_pre_post_delays; + self } - Ok(()) - } - - pub fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), EspError> { - for chunk in words.chunks_mut(self.trans_len) { - let ptr = chunk.as_mut_ptr(); - let len = chunk.len(); - self.polling_transmit(ptr, ptr, len, len)?; + #[must_use] + pub fn queue_size(mut self, queue_size: usize) -> Self { + self.queue_size = queue_size; + self } - - Ok(()) - } - - pub fn flush(&mut self) -> Result<(), EspError> { - // Since we use polling transactions, flushing isn't required. - // In future, when DMA is available spi_device_get_trans_result - // will be called here. - Ok(()) - } - - fn polling_transmit( - &mut self, - read: *mut u8, - write: *const u8, - transaction_length: usize, - rx_length: usize, - ) -> Result<(), EspError> { - polling_transmit( - self.handle, - read, - write, - transaction_length, - rx_length, - self.hardware_cs, - ) } - /// Empty transaction to de-assert CS. - fn finish(&mut self) -> Result<(), EspError> { - polling_transmit(self.handle, ptr::null_mut(), ptr::null(), 0, 0, false) - } -} - -impl<'d> embedded_hal::spi::ErrorType for SpiBusDriver<'d> { - type Error = SpiError; -} - -impl<'d> SpiBusFlush for SpiBusDriver<'d> { - fn flush(&mut self) -> Result<(), Self::Error> { - SpiBusDriver::flush(self).map_err(to_spi_err) + impl Default for Config { + fn default() -> Self { + Self { + baudrate: Hertz(1_000_000), + data_mode: embedded_hal::spi::MODE_0, + write_only: false, + cs_active_high: false, + duplex: Duplex::Full, + bit_order: BitOrder::MsbFirst, + cs_pre_delay_us: None, + cs_post_delay_us: None, + input_delay_ns: 0, + polling: true, + allow_pre_post_delays: false, + queue_size: 1, + } + } } -} -impl<'d> SpiBusRead for SpiBusDriver<'d> { - fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - SpiBusDriver::read(self, words).map_err(to_spi_err) + impl From<&Config> for spi_device_interface_config_t { + fn from(config: &Config) -> Self { + Self { + spics_io_num: -1, + clock_speed_hz: config.baudrate.0 as i32, + mode: data_mode_to_u8(config.data_mode), + queue_size: config.queue_size as i32, + flags: if config.write_only { + SPI_DEVICE_NO_DUMMY + } else { + 0_u32 + } | if config.cs_active_high { + SPI_DEVICE_POSITIVE_CS + } else { + 0_u32 + } | config.duplex.as_flags() + | config.bit_order.as_flags(), + cs_ena_pretrans: config.cs_pre_delay_us.unwrap_or(0), + cs_ena_posttrans: config.cs_post_delay_us.unwrap_or(0), + ..Default::default() + } + } } -} -impl<'d> SpiBusWrite for SpiBusDriver<'d> { - fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { - SpiBusDriver::write(self, words).map_err(to_spi_err) + fn data_mode_to_u8(data_mode: Mode) -> u8 { + (((data_mode.polarity == Polarity::IdleHigh) as u8) << 1) + | ((data_mode.phase == Phase::CaptureOnSecondTransition) as u8) } } -impl<'d> SpiBus for SpiBusDriver<'d> { - fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { - SpiBusDriver::transfer(self, read, write).map_err(to_spi_err) - } - - fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { - SpiBusDriver::transfer_in_place(self, words).map_err(to_spi_err) - } +/// SPI transaction operation. +/// +/// This allows composition of SPI operations into a single bus transaction. +#[non_exhaustive] +#[derive(Debug, PartialEq, Eq)] +pub enum Operation<'a> { + /// Read data into the provided buffer. + Read(&'a mut [u8]), + /// Read data into the provided buffer with the provided line width in half-duplex mode. + ReadWithWidth(&'a mut [u8], LineWidth), + /// Write data from the provided buffer, discarding read data. + Write(&'a [u8]), + /// Write data from the provided buffer, using the provided line width in half-duplex mode, + /// discarding read data. + WriteWithWidth(&'a [u8], LineWidth), + /// Read data into the first buffer, while writing data from the second buffer. + Transfer(&'a mut [u8], &'a [u8]), + /// Write data out while reading data into the provided buffer. + TransferInPlace(&'a mut [u8]), + /// Delay for at least the specified number of nanoseconds. + DelayNs(u32), } pub struct SpiDriver<'d> { host: u8, max_transfer_size: usize, + #[allow(dead_code)] + bus_async_lock: Mutex, _p: PhantomData<&'d mut ()>, } @@ -356,13 +435,24 @@ impl<'d> SpiDriver<'d> { sclk: impl Peripheral

+ 'd, sdo: impl Peripheral

+ 'd, sdi: Option + 'd>, - dma: Dma, + config: &config::DriverConfig, ) -> Result { - let max_transfer_size = Self::new_internal(SPI1::device(), sclk, sdo, sdi, dma)?; + use crate::gpio::Pin; + + let max_transfer_size = Self::new_internal( + SPI1::device(), + Some(sclk.into_ref().pin()), + Some(sdo.into_ref().pin()), + sdi.map(|p| p.into_ref().pin()), + None, + None, + config, + )?; Ok(Self { host: SPI1::device() as _, max_transfer_size, + bus_async_lock: Mutex::new(()), _p: PhantomData, }) } @@ -372,14 +462,99 @@ impl<'d> SpiDriver<'d> { _spi: impl Peripheral

+ 'd, sclk: impl Peripheral

+ 'd, sdo: impl Peripheral

+ 'd, - sdi: Option + 'd>, - dma: Dma, + sdi: Option + 'd>, + config: &config::DriverConfig, + ) -> Result { + let max_transfer_size = Self::new_internal( + SPI::device(), + Some(sclk.into_ref().pin()), + Some(sdo.into_ref().pin()), + sdi.map(|p| p.into_ref().pin()), + None, + None, + config, + )?; + + Ok(Self { + host: SPI::device() as _, + max_transfer_size, + bus_async_lock: Mutex::new(()), + _p: PhantomData, + }) + } + + pub fn new_without_sclk( + _spi: impl Peripheral

+ 'd, + sdo: impl Peripheral

+ 'd, + sdi: Option + 'd>, + config: &config::DriverConfig, + ) -> Result { + let max_transfer_size = Self::new_internal( + SPI::device(), + None, + Some(sdo.into_ref().pin()), + sdi.map(|p| p.into_ref().pin()), + None, + None, + config, + )?; + + Ok(Self { + host: SPI::device() as _, + max_transfer_size, + bus_async_lock: Mutex::new(()), + _p: PhantomData, + }) + } + + pub fn new_dual( + _spi: impl Peripheral

+ 'd, + sclk: impl Peripheral

+ 'd, + data0: impl Peripheral

+ 'd, + data1: impl Peripheral

+ 'd, + config: &config::DriverConfig, + ) -> Result { + let max_transfer_size = Self::new_internal( + SPI::device(), + Some(sclk.into_ref().pin()), + Some(data0.into_ref().pin()), + Some(data1.into_ref().pin()), + None, + None, + config, + )?; + + Ok(Self { + host: SPI::device() as _, + max_transfer_size, + bus_async_lock: Mutex::new(()), + _p: PhantomData, + }) + } + + pub fn new_quad( + _spi: impl Peripheral

+ 'd, + sclk: impl Peripheral

+ 'd, + data0: impl Peripheral

+ 'd, + data1: impl Peripheral

+ 'd, + data2: impl Peripheral

+ 'd, + data3: impl Peripheral

+ 'd, + config: &config::DriverConfig, ) -> Result { - let max_transfer_size = Self::new_internal(SPI::device(), sclk, sdo, sdi, dma)?; + let max_transfer_size = Self::new_internal( + SPI::device(), + Some(sclk.into_ref().pin()), + Some(data0.into_ref().pin()), + Some(data1.into_ref().pin()), + Some(data2.into_ref().pin()), + Some(data3.into_ref().pin()), + config, + )?; Ok(Self { host: SPI::device() as _, max_transfer_size, + bus_async_lock: Mutex::new(()), _p: PhantomData, }) } @@ -390,57 +565,43 @@ impl<'d> SpiDriver<'d> { fn new_internal( host: spi_host_device_t, - sclk: impl Peripheral

+ 'd, - sdo: impl Peripheral

+ 'd, - sdi: Option + 'd>, - dma: Dma, + sclk: Option, + sdo: Option, + sdi: Option, + data2: Option, + data3: Option, + config: &config::DriverConfig, ) -> Result { - crate::into_ref!(sclk, sdo); - let sdi = sdi.map(|sdi| sdi.into_ref()); + let max_transfer_sz = config.dma.max_transfer_size(); + let dma_chan: spi_dma_chan_t = config.dma.into(); - let max_transfer_sz = dma.max_transfer_size(); - let dma_chan: spi_dma_chan_t = dma.into(); - - #[cfg(not(esp_idf_version = "4.3"))] + #[allow(clippy::needless_update)] let bus_config = spi_bus_config_t { flags: SPICOMMON_BUSFLAG_MASTER, - sclk_io_num: sclk.pin(), + sclk_io_num: sclk.unwrap_or(-1), data4_io_num: -1, data5_io_num: -1, data6_io_num: -1, data7_io_num: -1, __bindgen_anon_1: spi_bus_config_t__bindgen_ty_1 { - mosi_io_num: sdo.pin(), + mosi_io_num: sdo.unwrap_or(-1), //data0_io_num: -1, }, __bindgen_anon_2: spi_bus_config_t__bindgen_ty_2 { - miso_io_num: sdi.as_ref().map_or(-1, |p| p.pin()), + miso_io_num: sdi.unwrap_or(-1), //data1_io_num: -1, }, __bindgen_anon_3: spi_bus_config_t__bindgen_ty_3 { - quadwp_io_num: -1, + quadwp_io_num: data2.unwrap_or(-1), //data2_io_num: -1, }, __bindgen_anon_4: spi_bus_config_t__bindgen_ty_4 { - quadhd_io_num: -1, + quadhd_io_num: data3.unwrap_or(-1), //data3_io_num: -1, }, max_transfer_sz: max_transfer_sz as i32, - ..Default::default() - }; - - #[cfg(esp_idf_version = "4.3")] - let bus_config = spi_bus_config_t { - flags: SPICOMMON_BUSFLAG_MASTER, - sclk_io_num: sclk.pin(), - - mosi_io_num: sdo.pin(), - miso_io_num: sdi.as_ref().map_or(-1, |p| p.pin()), - quadwp_io_num: -1, - quadhd_io_num: -1, - - max_transfer_sz: max_transfer_sz as i32, + intr_flags: InterruptType::to_native(config.intr_flags) as _, ..Default::default() }; @@ -450,227 +611,699 @@ impl<'d> SpiDriver<'d> { } } -impl<'d> Drop for SpiDriver<'d> { +impl Drop for SpiDriver<'_> { fn drop(&mut self) { esp!(unsafe { spi_bus_free(self.host()) }).unwrap(); } } -unsafe impl<'d> Send for SpiDriver<'d> {} - -pub type SpiSingleDeviceDriver<'d> = SpiDeviceDriver<'d, SpiDriver<'d>>; +unsafe impl Send for SpiDriver<'_> {} -pub struct SpiDeviceDriver<'d, T> { +pub struct SpiBusDriver<'d, T> +where + T: BorrowMut>, +{ + lock: Option, handle: spi_device_handle_t, driver: T, - with_cs_pin: bool, - _p: PhantomData<&'d ()>, -} - -impl<'d> SpiDeviceDriver<'d, SpiDriver<'d>> { - #[cfg(esp32)] - pub fn new_single_spi1( - spi: impl Peripheral

+ 'd, - sclk: impl Peripheral

+ 'd, - sdo: impl Peripheral

+ 'd, - sdi: Option + 'd>, - dma: Dma, - cs: Option + 'd>, - config: &config::Config, - ) -> Result { - Self::new(SpiDriver::new_spi1(spi, sclk, sdo, sdi, dma)?, cs, config) - } - - pub fn new_single( - spi: impl Peripheral

+ 'd, - sclk: impl Peripheral

+ 'd, - sdo: impl Peripheral

+ 'd, - sdi: Option + 'd>, - dma: Dma, - cs: Option + 'd>, - config: &config::Config, - ) -> Result { - Self::new(SpiDriver::new(spi, sclk, sdo, sdi, dma)?, cs, config) - } + duplex: Duplex, + polling: bool, + queue_size: usize, + _d: PhantomData<&'d ()>, } -impl<'d, T> SpiDeviceDriver<'d, T> +impl<'d, T> SpiBusDriver<'d, T> where - T: Borrow> + 'd, + T: BorrowMut>, { - pub fn new( - driver: T, - cs: Option + 'd>, - config: &config::Config, - ) -> Result { - let cs = cs.map(|cs| cs.into_ref().pin()).unwrap_or(-1); - - let conf = spi_device_interface_config_t { - spics_io_num: cs, - clock_speed_hz: config.baudrate.0 as i32, - mode: (((config.data_mode.polarity == embedded_hal::spi::Polarity::IdleHigh) as u8) - << 1) - | ((config.data_mode.phase == embedded_hal::spi::Phase::CaptureOnSecondTransition) - as u8), - queue_size: 64, - flags: if config.write_only { - SPI_DEVICE_NO_DUMMY - } else { - 0_u32 - } | if config.cs_active_high { - SPI_DEVICE_POSITIVE_CS - } else { - 0_u32 - } | config.duplex.as_flags(), - ..Default::default() - }; + pub fn new(driver: T, config: &config::Config) -> Result { + let mut conf: spi_device_interface_config_t = config.into(); + conf.post_cb = Some(spi_notify); let mut handle: spi_device_handle_t = ptr::null_mut(); esp!(unsafe { spi_bus_add_device(driver.borrow().host(), &conf, &mut handle as *mut _) })?; + let lock = BusLock::new(handle)?; + Ok(Self { + lock: Some(lock), handle, driver, - with_cs_pin: cs >= 0, - _p: PhantomData, + duplex: config.duplex, + polling: config.polling, + queue_size: config.queue_size, + _d: PhantomData, }) } - pub fn device(&self) -> spi_device_handle_t { - self.handle - } + pub fn read(&mut self, words: &mut [u8]) -> Result<(), EspError> { + // Full-Duplex Mode: + // The internal hardware 16*4 u8 FIFO buffer (shared for read/write) is not cleared + // between transactions (read/write/transfer) + // This can lead to rewriting the internal buffer to MOSI on a read call - pub fn transaction( - &mut self, - f: impl FnOnce(&mut SpiBusDriver<'d>) -> Result, - ) -> Result - where - E: From, - { - // if DMA used -> get trans length info from driver - let trans_len = self.driver.borrow().max_transfer_size; + let chunk_size = self.driver.borrow().max_transfer_size; - let mut bus = SpiBusDriver { - handle: self.handle, - trans_len, - hardware_cs: self.with_cs_pin, - _p: PhantomData, - }; + let transactions = spi_read_transactions(words, chunk_size, self.duplex, LineWidth::Single); + spi_transmit(self.handle, transactions, self.polling, self.queue_size)?; + + Ok(()) + } - let lock = self.lock_bus()?; + #[cfg(not(esp_idf_spi_master_isr_in_iram))] + pub async fn read_async(&mut self, words: &mut [u8]) -> Result<(), EspError> { + let chunk_size = self.driver.borrow().max_transfer_size; - let trans_result = f(&mut bus); + let transactions = spi_read_transactions(words, chunk_size, self.duplex, LineWidth::Single); + core::pin::pin!(spi_transmit_async( + self.handle, + transactions, + self.queue_size + )) + .await?; - // #99 is partially resolved by allowing software CS to ignore this bus.finish() work around - let finish_result = if self.with_cs_pin { - bus.finish() - } else { - Ok(()) - }; + Ok(()) + } - // Flush whatever is pending. - // Note that this is done even when an error is returned from the transaction. - let flush_result = bus.flush(); + pub fn write(&mut self, words: &[u8]) -> Result<(), EspError> { + // Full-Duplex Mode: + // The internal hardware 16*4 u8 FIFO buffer (shared for read/write) is not cleared + // between transactions ( read/write/transfer) + // This can lead to re-reading the last internal buffer MOSI msg, in case the Slave fails to send a msg - core::mem::drop(lock); + let chunk_size = self.driver.borrow().max_transfer_size; - let result = trans_result?; - finish_result?; - flush_result?; + let transactions = spi_write_transactions(words, chunk_size, LineWidth::Single); + spi_transmit(self.handle, transactions, self.polling, self.queue_size)?; - Ok(result) + Ok(()) } - pub fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), EspError> { - self.transaction(|bus| bus.transfer(read, write)) - } + #[cfg(not(esp_idf_spi_master_isr_in_iram))] + pub async fn write_async(&mut self, words: &[u8]) -> Result<(), EspError> { + let chunk_size = self.driver.borrow().max_transfer_size; - pub fn write(&mut self, write: &[u8]) -> Result<(), EspError> { - self.transaction(|bus| bus.write(write)) - } + let transactions = spi_write_transactions(words, chunk_size, LineWidth::Single); + core::pin::pin!(spi_transmit_async( + self.handle, + transactions, + self.queue_size + )) + .await?; - pub fn read(&mut self, read: &mut [u8]) -> Result<(), EspError> { - self.transaction(|bus| bus.read(read)) + Ok(()) } - pub fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), EspError> { - self.transaction(|bus| bus.transfer_in_place(buf)) + pub fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), EspError> { + // In non-DMA mode, it will internally split the transfers every 64 bytes (max_transf_len). + // - If the read and write buffers are not of the same length, it will first transfer the common buffer length + // and then (separately aligned) the remaining buffer. + // - Expect a delay time between every internally split (64-byte or remainder) package. + + // Half-Duplex & Half-3-Duplex Mode: + // Data will be split into 64-byte write/read sections. + // Example: write: [u8;96] - read [u8; 160] + // Package 1: write 64, read 64 -> Package 2: write 32, read 32 -> Package 3: write 0, read 64. + // Note that the first "package" is a 128-byte clock out while the later are respectively 64 bytes. + + let chunk_size = self.driver.borrow().max_transfer_size; + + let transactions = spi_transfer_transactions(read, write, chunk_size, self.duplex); + spi_transmit(self.handle, transactions, self.polling, self.queue_size)?; + + Ok(()) + } + + #[cfg(not(esp_idf_spi_master_isr_in_iram))] + pub async fn transfer_async(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), EspError> { + let chunk_size = self.driver.borrow().max_transfer_size; + + let transactions = spi_transfer_transactions(read, write, chunk_size, self.duplex); + core::pin::pin!(spi_transmit_async( + self.handle, + transactions, + self.queue_size + )) + .await?; + + Ok(()) + } + + pub fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), EspError> { + let chunk_size = self.driver.borrow().max_transfer_size; + + let transactions = spi_transfer_in_place_transactions(words, chunk_size); + spi_transmit(self.handle, transactions, self.polling, self.queue_size)?; + + Ok(()) } - fn lock_bus(&self) -> Result { - Lock::new(self.handle) + #[cfg(not(esp_idf_spi_master_isr_in_iram))] + pub async fn transfer_in_place_async(&mut self, words: &mut [u8]) -> Result<(), EspError> { + let chunk_size = self.driver.borrow().max_transfer_size; + + let transactions = spi_transfer_in_place_transactions(words, chunk_size); + core::pin::pin!(spi_transmit_async( + self.handle, + transactions, + self.queue_size + )) + .await?; + + Ok(()) + } + + pub fn flush(&mut self) -> Result<(), EspError> { + Ok(()) + } + + /// Run the provided [`Operation`] on the bus. + /// + /// Only Operations that result in a transfer are supported. For example, + /// passing an [`Operation::DelayNs`] will return an error. + pub fn operation(&mut self, operation: Operation<'_>) -> Result<(), EspError> { + if let Operation::DelayNs(_) = operation { + return Err(EspError::from_infallible::()); + } + + let chunk_size = self.driver.borrow().max_transfer_size; + let transactions = spi_operations(once(operation), chunk_size, self.duplex) + .filter_map(|t| t.transaction()); + + spi_transmit(self.handle, transactions, self.polling, self.queue_size)?; + + Ok(()) + } + + /// Run the provided [`Operation`] on the bus. + /// + /// Only Operations that result in a transfer are supported. For example, + /// passing an [`Operation::DelayNs`] will return an error. + #[cfg(not(esp_idf_spi_master_isr_in_iram))] + pub async fn operation_async(&mut self, operation: Operation<'_>) -> Result<(), EspError> { + if let Operation::DelayNs(_) = operation { + return Err(EspError::from_infallible::()); + } + + let chunk_size = self.driver.borrow().max_transfer_size; + let transactions = spi_operations(once(operation), chunk_size, self.duplex) + .filter_map(|t| t.transaction()); + + core::pin::pin!(spi_transmit_async( + self.handle, + transactions, + self.queue_size + )) + .await?; + + Ok(()) } } -unsafe impl<'d, T> Send for SpiDeviceDriver<'d, T> where T: Send {} +impl<'d, T> Drop for SpiBusDriver<'d, T> +where + T: BorrowMut>, +{ + fn drop(&mut self) { + // Need to drop the lock first, because it holds the device + // we are about to remove below + self.lock = None; + + esp!(unsafe { spi_bus_remove_device(self.handle) }).unwrap(); + } +} -impl<'d, T> embedded_hal::spi::ErrorType for SpiDeviceDriver<'d, T> { +impl<'d, T> embedded_hal::spi::ErrorType for SpiBusDriver<'d, T> +where + T: BorrowMut>, +{ type Error = SpiError; } -impl<'d, T> SpiDevice for SpiDeviceDriver<'d, T> +impl<'d, T> SpiBus for SpiBusDriver<'d, T> +where + T: BorrowMut>, +{ + fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + SpiBusDriver::read(self, words).map_err(to_spi_err) + } + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + SpiBusDriver::write(self, words).map_err(to_spi_err) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + SpiBusDriver::flush(self).map_err(to_spi_err) + } + + fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + SpiBusDriver::transfer(self, read, write).map_err(to_spi_err) + } + + fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + SpiBusDriver::transfer_in_place(self, words).map_err(to_spi_err) + } +} + +#[cfg(not(esp_idf_spi_master_isr_in_iram))] +impl<'d, T> embedded_hal_async::spi::SpiBus for SpiBusDriver<'d, T> +where + T: BorrowMut>, +{ + async fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { + SpiBusDriver::read_async(self, buf) + .await + .map_err(to_spi_err) + } + + async fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> { + SpiBusDriver::write_async(self, buf) + .await + .map_err(to_spi_err) + } + + async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + SpiBusDriver::transfer_async(self, read, write) + .await + .map_err(to_spi_err) + } + + async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + SpiBusDriver::transfer_in_place_async(self, words) + .await + .map_err(to_spi_err) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + SpiBusDriver::flush(self).map_err(to_spi_err) + } +} + +enum SpiOperation { + Transaction(spi_transaction_t), + Delay(u32), +} + +impl SpiOperation { + pub fn transaction(self) -> Option { + if let Self::Transaction(transaction) = self { + Some(transaction) + } else { + None + } + } +} + +pub type SpiSingleDeviceDriver<'d> = SpiDeviceDriver<'d, SpiDriver<'d>>; + +pub struct SpiDeviceDriver<'d, T> where T: Borrow> + 'd, { - type Bus = SpiBusDriver<'d>; + handle: spi_device_handle_t, + driver: T, + cs_pin_configured: bool, + duplex: Duplex, + polling: bool, + allow_pre_post_delays: bool, + queue_size: usize, + _d: PhantomData<&'d ()>, +} - fn transaction( - &mut self, - f: impl FnOnce(&mut Self::Bus) -> Result::Error>, - ) -> Result { - Self::transaction(self, f) +impl<'d> SpiDeviceDriver<'d, SpiDriver<'d>> { + #[cfg(esp32)] + pub fn new_single_spi1( + spi: impl Peripheral

+ 'd, + sclk: impl Peripheral

+ 'd, + sdo: impl Peripheral

+ 'd, + sdi: Option + 'd>, + cs: Option + 'd>, + bus_config: &config::DriverConfig, + config: &config::Config, + ) -> Result { + Self::new( + SpiDriver::new_spi1(spi, sclk, sdo, sdi, bus_config)?, + cs, + config, + ) + } + + pub fn new_single( + spi: impl Peripheral

+ 'd, + sclk: impl Peripheral

+ 'd, + sdo: impl Peripheral

+ 'd, + sdi: Option + 'd>, + cs: Option + 'd>, + bus_config: &config::DriverConfig, + config: &config::Config, + ) -> Result { + Self::new(SpiDriver::new(spi, sclk, sdo, sdi, bus_config)?, cs, config) } } -impl<'d, T> embedded_hal_0_2::blocking::spi::Transfer for SpiDeviceDriver<'d, T> +impl<'d, T> SpiDeviceDriver<'d, T> where T: Borrow> + 'd, { - type Error = SpiError; + pub fn new( + driver: T, + cs: Option + 'd>, + config: &config::Config, + ) -> Result { + let cs = cs.map(|cs| cs.into_ref().pin()).unwrap_or(-1); - fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { - let _lock = self.lock_bus()?; - let mut chunks = words - .chunks_mut(self.driver.borrow().max_transfer_size) + let mut conf: spi_device_interface_config_t = config.into(); + conf.spics_io_num = cs; + conf.post_cb = Some(spi_notify); + + let mut handle: spi_device_handle_t = ptr::null_mut(); + esp!(unsafe { spi_bus_add_device(driver.borrow().host(), &conf, &mut handle as *mut _) })?; + + Ok(Self { + handle, + driver, + cs_pin_configured: cs >= 0, + duplex: config.duplex, + polling: config.polling, + allow_pre_post_delays: config.allow_pre_post_delays, + queue_size: config.queue_size, + _d: PhantomData, + }) + } + + pub fn device(&self) -> spi_device_handle_t { + self.handle + } + + pub fn transaction(&mut self, operations: &mut [Operation<'_>]) -> Result<(), EspError> { + self.run( + self.hardware_cs_ctl(operations.iter_mut().map(copy_operation))?, + operations.iter_mut().map(copy_operation), + ) + } + + #[cfg(not(esp_idf_spi_master_isr_in_iram))] + pub async fn transaction_async( + &mut self, + operations: &mut [Operation<'_>], + ) -> Result<(), EspError> { + core::pin::pin!(self.run_async( + self.hardware_cs_ctl(operations.iter_mut().map(copy_operation))?, + operations.iter_mut().map(copy_operation), + )) + .await + } + + pub fn read(&mut self, read: &mut [u8]) -> Result<(), EspError> { + self.transaction(&mut [Operation::Read(read)]) + } + + #[cfg(not(esp_idf_spi_master_isr_in_iram))] + pub async fn read_async(&mut self, read: &mut [u8]) -> Result<(), EspError> { + let mut operation = [Operation::Read(read)]; + let work = core::pin::pin!(self.transaction_async(&mut operation)); + work.await + } + + pub fn write(&mut self, write: &[u8]) -> Result<(), EspError> { + self.transaction(&mut [Operation::Write(write)]) + } + + #[cfg(not(esp_idf_spi_master_isr_in_iram))] + pub async fn write_async(&mut self, write: &[u8]) -> Result<(), EspError> { + let mut operation = [Operation::Write(write)]; + let work = core::pin::pin!(self.transaction_async(&mut operation)); + work.await + } + + pub fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), EspError> { + self.transaction(&mut [Operation::TransferInPlace(buf)]) + } + + #[cfg(not(esp_idf_spi_master_isr_in_iram))] + pub async fn transfer_in_place_async(&mut self, buf: &mut [u8]) -> Result<(), EspError> { + let mut operation = [Operation::TransferInPlace(buf)]; + let work = core::pin::pin!(self.transaction_async(&mut operation)); + work.await + } + + pub fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), EspError> { + self.transaction(&mut [Operation::Transfer(read, write)]) + } + + #[cfg(not(esp_idf_spi_master_isr_in_iram))] + pub async fn transfer_async(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), EspError> { + let mut operation = [Operation::Transfer(read, write)]; + let work = core::pin::pin!(self.transaction_async(&mut operation)); + work.await + } + + fn run<'a, 'c, 'p, P, M>( + &mut self, + mut cs_pin: CsCtl<'c, 'p, P, M>, + operations: impl Iterator> + 'a, + ) -> Result<(), EspError> + where + P: OutputPin, + M: OutputMode, + { + let _lock = if cs_pin.needs_bus_lock() { + Some(BusLock::new(self.device())?) + } else { + None + }; + + cs_pin.raise_cs()?; + + let mut spi_operations = self + .spi_operations(operations) + .enumerate() + .map(|(index, mut operation)| { + cs_pin.configure(&mut operation, index); + operation + }) .peekable(); - while let Some(chunk) = chunks.next() { - let ptr = chunk.as_mut_ptr(); - let len = chunk.len(); - polling_transmit(self.handle, ptr, ptr, len, len, chunks.peek().is_some())?; + let delay_impl = crate::delay::Delay::new_default(); + let mut result = Ok(()); + + while spi_operations.peek().is_some() { + if let Some(SpiOperation::Delay(delay)) = spi_operations.peek() { + delay_impl.delay_us(*delay / 1000); + spi_operations.next(); + } else { + let transactions = core::iter::from_fn(|| { + spi_operations + .next_if(|operation| matches!(operation, SpiOperation::Transaction(_))) + }) + .fuse() + .filter_map(|operation| operation.transaction()); + + result = spi_transmit(self.handle, transactions, self.polling, self.queue_size); + + if result.is_err() { + break; + } + } } - Ok(words) + cs_pin.lower_cs()?; + + result + } + + #[allow(dead_code)] + async fn run_async<'a, 'c, 'p, P, M>( + &self, + mut cs_pin: CsCtl<'c, 'p, P, M>, + operations: impl Iterator> + 'a, + ) -> Result<(), EspError> + where + P: OutputPin, + M: OutputMode, + { + let _async_bus_lock = if cs_pin.needs_bus_lock() { + Some(self.driver.borrow().bus_async_lock.lock().await) + } else { + None + }; + + let _lock = if cs_pin.needs_bus_lock() { + Some(BusLock::new(self.device())?) + } else { + None + }; + + cs_pin.raise_cs()?; + + let delay_impl = crate::delay::Delay::new_default(); // TODO: Need to wait asnchronously if in async mode + let mut result = Ok(()); + + let mut spi_operations = self + .spi_operations(operations) + .enumerate() + .map(|(index, mut operation)| { + cs_pin.configure(&mut operation, index); + operation + }) + .peekable(); + + while spi_operations.peek().is_some() { + if let Some(SpiOperation::Delay(delay)) = spi_operations.peek() { + delay_impl.delay_us(*delay); + spi_operations.next(); + } else { + let transactions = core::iter::from_fn(|| { + spi_operations + .next_if(|operation| matches!(operation, SpiOperation::Transaction(_))) + }) + .fuse() + .filter_map(|operation| operation.transaction()); + + result = core::pin::pin!(spi_transmit_async( + self.handle, + transactions, + self.queue_size + )) + .await; + + if result.is_err() { + break; + } + } + } + + cs_pin.lower_cs()?; + + result + } + + fn hardware_cs_ctl<'a, 'c, 'p>( + &self, + operations: impl Iterator> + 'a, + ) -> Result, EspError> { + let (total_count, transactions_count, first_transaction, last_transaction) = + self.spi_operations_stats(operations); + + if !self.allow_pre_post_delays + && self.cs_pin_configured + && transactions_count > 0 + && (first_transaction != Some(0) || last_transaction != Some(total_count - 1)) + { + Err(EspError::from_infallible::())?; + } + + Ok(CsCtl::Hardware { + enabled: self.cs_pin_configured, + transactions_count, + last_transaction, + }) + } + + fn spi_operations_stats<'a>( + &self, + operations: impl Iterator> + 'a, + ) -> (usize, usize, Option, Option) { + self.spi_operations(operations).enumerate().fold( + (0, 0, None, None), + |(total_count, transactions_count, first_transaction, last_transaction), + (index, operation)| { + if matches!(operation, SpiOperation::Transaction(_)) { + ( + total_count + 1, + transactions_count + 1, + Some(first_transaction.unwrap_or(index)), + Some(index), + ) + } else { + ( + total_count + 1, + transactions_count, + first_transaction, + last_transaction, + ) + } + }, + ) + } + + fn spi_operations<'a>( + &self, + operations: impl Iterator> + 'a, + ) -> impl Iterator + 'a { + let chunk_size = self.driver.borrow().max_transfer_size; + let duplex = self.duplex; + spi_operations(operations, chunk_size, duplex) } } -impl<'d, T> embedded_hal_0_2::blocking::spi::Write for SpiDeviceDriver<'d, T> +impl<'d, T> Drop for SpiDeviceDriver<'d, T> +where + T: Borrow> + 'd, +{ + fn drop(&mut self) { + esp!(unsafe { spi_bus_remove_device(self.handle) }).unwrap(); + } +} + +unsafe impl<'d, T> Send for SpiDeviceDriver<'d, T> where T: Send + Borrow> + 'd {} + +impl<'d, T> embedded_hal::spi::ErrorType for SpiDeviceDriver<'d, T> where T: Borrow> + 'd, { type Error = SpiError; +} - fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { - let _lock = self.lock_bus()?; - let mut chunks = words - .chunks(self.driver.borrow().max_transfer_size) - .peekable(); +impl<'d, T> SpiDevice for SpiDeviceDriver<'d, T> +where + T: Borrow> + 'd, +{ + fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { + Self::read(self, buf).map_err(to_spi_err) + } - while let Some(chunk) = chunks.next() { - polling_transmit( - self.handle, - ptr::null_mut(), - chunk.as_ptr(), - chunk.len(), - 0, - chunks.peek().is_some(), - )?; - } + fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> { + Self::write(self, buf).map_err(to_spi_err) + } + + fn transaction( + &mut self, + operations: &mut [embedded_hal::spi::Operation<'_, u8>], + ) -> Result<(), Self::Error> { + self.run( + self.hardware_cs_ctl(operations.iter_mut().map(copy_ehal_operation))?, + operations.iter_mut().map(copy_ehal_operation), + ) + .map_err(to_spi_err) + } +} + +impl<'d, T> embedded_hal_0_2::blocking::spi::Transfer for SpiDeviceDriver<'d, T> +where + T: Borrow> + 'd, +{ + type Error = SpiError; + + fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { + self.transfer_in_place(words)?; + + Ok(words) + } +} + +impl<'d, T> embedded_hal_0_2::blocking::spi::Write for SpiDeviceDriver<'d, T> +where + T: Borrow> + 'd, +{ + type Error = SpiError; - Ok(()) + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.write(words).map_err(to_spi_err) } } +/// All data is chunked into max(iter.len(), 64) impl<'d, T> embedded_hal_0_2::blocking::spi::WriteIter for SpiDeviceDriver<'d, T> where T: Borrow> + 'd, @@ -681,31 +1314,53 @@ where where WI: IntoIterator, { - let mut words = words.into_iter(); + let mut lock = None; + + let mut words = words.into_iter().peekable(); let mut buf = [0_u8; TRANS_LEN]; - self.transaction(|bus| { - loop { - let mut offset = 0_usize; - - while offset < buf.len() { - if let Some(word) = words.next() { - buf[offset] = word; - offset += 1; - } else { - break; - } - } + loop { + let mut offset = 0_usize; - if offset == 0 { + while offset < buf.len() { + if let Some(word) = words.next() { + buf[offset] = word; + offset += 1; + } else { break; } + } - bus.write(&buf[..offset])?; + if offset == 0 { + break; } - Ok(()) - }) + let mut transaction = spi_create_transaction( + core::ptr::null_mut(), + buf[..offset].as_ptr(), + offset, + 0, + LineWidth::Single, + ); + + if lock.is_none() && words.peek().is_some() { + lock = Some(BusLock::new(self.handle)?); + } + + set_keep_cs_active( + &mut transaction, + self.cs_pin_configured && words.peek().is_some(), + ); + + spi_transmit( + self.handle, + once(transaction), + self.polling, + self.queue_size, + )?; + } + + Ok(()) } } @@ -719,30 +1374,58 @@ where &mut self, operations: &mut [embedded_hal_0_2::blocking::spi::Operation<'_, u8>], ) -> Result<(), Self::Error> { - self.transaction(|bus| { - for operation in operations { - match operation { - embedded_hal_0_2::blocking::spi::Operation::Write(write) => bus.write(write), - embedded_hal_0_2::blocking::spi::Operation::Transfer(words) => { - bus.transfer_in_place(words) - } - }?; - } - - Ok(()) - }) + self.run( + self.hardware_cs_ctl(operations.iter_mut().map(|op| match op { + embedded_hal_0_2::blocking::spi::Operation::Write(words) => Operation::Write(words), + embedded_hal_0_2::blocking::spi::Operation::Transfer(words) => { + Operation::TransferInPlace(words) + } + }))?, + operations.iter_mut().map(|op| match op { + embedded_hal_0_2::blocking::spi::Operation::Write(words) => Operation::Write(words), + embedded_hal_0_2::blocking::spi::Operation::Transfer(words) => { + Operation::TransferInPlace(words) + } + }), + ) + .map_err(to_spi_err) } } -impl<'d, T> Drop for SpiDeviceDriver<'d, T> { - fn drop(&mut self) { - esp!(unsafe { spi_bus_remove_device(self.handle) }).unwrap(); +#[cfg(not(esp_idf_spi_master_isr_in_iram))] +impl<'d, T> embedded_hal_async::spi::SpiDevice for SpiDeviceDriver<'d, T> +where + T: Borrow> + 'd, +{ + async fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { + Self::read_async(self, buf).await.map_err(to_spi_err) + } + + async fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> { + Self::write_async(self, buf).await.map_err(to_spi_err) + } + + async fn transaction( + &mut self, + operations: &mut [embedded_hal::spi::Operation<'_, u8>], + ) -> Result<(), Self::Error> { + core::pin::pin!(self.run_async( + self.hardware_cs_ctl(operations.iter_mut().map(copy_ehal_operation))?, + operations.iter_mut().map(copy_ehal_operation), + )) + .await + .map_err(to_spi_err) } } -pub struct SpiSharedDeviceDriver<'d, T> { +pub struct SpiSharedDeviceDriver<'d, T> +where + T: Borrow> + 'd, +{ driver: UnsafeCell>, - cs: CriticalSection, + lock: CriticalSection, + #[allow(dead_code)] + async_lock: Mutex, } impl<'d, T> SpiSharedDeviceDriver<'d, T> @@ -760,14 +1443,15 @@ where pub const fn wrap(device: SpiDeviceDriver<'d, T>) -> Self { Self { driver: UnsafeCell::new(device), - cs: CriticalSection::new(), + lock: CriticalSection::new(), + async_lock: Mutex::new(()), } } pub fn lock(&self, f: impl FnOnce(&mut SpiDeviceDriver<'d, T>) -> R) -> R { - let _guard = self.cs.enter(); + let _guard = self.lock.enter(); - let device = unsafe { &mut *self.driver.get() }; + let device = unsafe { self.driver_mut() }; f(device) } @@ -775,6 +1459,11 @@ where pub fn release(self) -> SpiDeviceDriver<'d, T> { self.driver.into_inner() } + + #[allow(clippy::mut_from_ref)] + unsafe fn driver_mut(&self) -> &mut SpiDeviceDriver<'d, T> { + &mut *self.driver.get() + } } pub struct SpiSoftCsDeviceDriver<'d, DEVICE, DRIVER> { @@ -810,8 +1499,8 @@ where }) } - /// Add an aditional delay of x in uSeconds before transaction - /// between chip select and first clk out + /// Add an aditional Amount of SPI bit-cycles the cs should be activated before the transmission (0-16). + /// This only works on half-duplex transactions. pub fn cs_pre_delay_us(&mut self, delay_us: u32) -> &mut Self { self.pre_delay_us = Some(delay_us); @@ -826,50 +1515,96 @@ where self } - pub fn transaction( + pub fn transaction(&mut self, operations: &mut [Operation<'_>]) -> Result<(), EspError> { + self.run(operations.iter_mut().map(copy_operation)) + } + + #[cfg(not(esp_idf_spi_master_isr_in_iram))] + pub async fn transaction_async( &mut self, - f: impl FnOnce(&mut SpiBusDriver<'d>) -> Result, - ) -> Result - where - E: From, - { - let cs_pin = &mut self.cs_pin; - let pre_delay_us = self.pre_delay_us; - let post_delay_us = self.post_delay_us; + operations: &mut [Operation<'_>], + ) -> Result<(), EspError> { + core::pin::pin!(self.run_async(operations.iter_mut().map(copy_operation))).await + } - self.shared_device.borrow().lock(|device| { - cs_pin.toggle()?; + pub fn read(&mut self, read: &mut [u8]) -> Result<(), EspError> { + self.transaction(&mut [Operation::Read(read)]) + } - if let Some(delay) = pre_delay_us { - Ets::delay_us(delay); - } + #[cfg(not(esp_idf_spi_master_isr_in_iram))] + pub async fn read_async(&mut self, read: &mut [u8]) -> Result<(), EspError> { + let mut operation = [Operation::Read(read)]; + let work = core::pin::pin!(self.transaction_async(&mut operation)); + work.await + } - let trans_result = device.transaction(f); + pub fn write(&mut self, write: &[u8]) -> Result<(), EspError> { + self.transaction(&mut [Operation::Write(write)]) + } - if let Some(delay) = post_delay_us { - Ets::delay_us(delay); - } + #[cfg(not(esp_idf_spi_master_isr_in_iram))] + pub async fn write_async(&mut self, write: &[u8]) -> Result<(), EspError> { + let mut operation = [Operation::Write(write)]; + let work = core::pin::pin!(self.transaction_async(&mut operation)); + work.await + } - cs_pin.toggle()?; + pub fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), EspError> { + self.transaction(&mut [Operation::TransferInPlace(buf)]) + } - trans_result - }) + #[cfg(not(esp_idf_spi_master_isr_in_iram))] + pub async fn transfer_in_place_async(&mut self, buf: &mut [u8]) -> Result<(), EspError> { + let mut operation = [Operation::TransferInPlace(buf)]; + let work = core::pin::pin!(self.transaction_async(&mut operation)); + work.await } pub fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), EspError> { - self.transaction(|bus| bus.transfer(read, write)) + self.transaction(&mut [Operation::Transfer(read, write)]) } - pub fn write(&mut self, write: &[u8]) -> Result<(), EspError> { - self.transaction(|bus| bus.write(write)) + #[cfg(not(esp_idf_spi_master_isr_in_iram))] + pub async fn transfer_async(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), EspError> { + let mut operation = [Operation::Transfer(read, write)]; + let work = core::pin::pin!(self.transaction_async(&mut operation)); + work.await } - pub fn read(&mut self, read: &mut [u8]) -> Result<(), EspError> { - self.transaction(|bus| bus.read(read)) + fn run<'a>( + &mut self, + operations: impl Iterator> + 'a, + ) -> Result<(), EspError> { + let cs_pin = CsCtl::Software { + cs: &mut self.cs_pin, + pre_delay: self.pre_delay_us, + post_delay: self.post_delay_us, + }; + + self.shared_device + .borrow() + .lock(move |device| device.run(cs_pin, operations)) } - pub fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), EspError> { - self.transaction(|bus| bus.transfer_in_place(buf)) + #[allow(dead_code)] + async fn run_async<'a>( + &mut self, + operations: impl Iterator> + 'a, + ) -> Result<(), EspError> { + let cs_pin = CsCtl::Software { + cs: &mut self.cs_pin, + pre_delay: self.pre_delay_us, + post_delay: self.post_delay_us, + }; + + let device = self.shared_device.borrow(); + + let _async_guard = device.async_lock.lock().await; + let _guard = device.lock.enter(); + + let driver = unsafe { device.driver_mut() }; + + driver.run_async(cs_pin, operations).await } } @@ -886,13 +1621,45 @@ where DEVICE: Borrow> + 'd, DRIVER: Borrow> + 'd, { - type Bus = SpiBusDriver<'d>; + fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { + Self::read(self, buf).map_err(to_spi_err) + } + + fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> { + Self::write(self, buf).map_err(to_spi_err) + } + + fn transaction( + &mut self, + operations: &mut [embedded_hal::spi::Operation<'_, u8>], + ) -> Result<(), Self::Error> { + self.run(operations.iter_mut().map(copy_ehal_operation)) + .map_err(to_spi_err) + } +} + +#[cfg(not(esp_idf_spi_master_isr_in_iram))] +impl<'d, DEVICE, DRIVER> embedded_hal_async::spi::SpiDevice + for SpiSoftCsDeviceDriver<'d, DEVICE, DRIVER> +where + DEVICE: Borrow> + 'd, + DRIVER: Borrow> + 'd, +{ + async fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { + Self::read_async(self, buf).await.map_err(to_spi_err) + } + + async fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> { + Self::write_async(self, buf).await.map_err(to_spi_err) + } - fn transaction( + async fn transaction( &mut self, - f: impl FnOnce(&mut Self::Bus) -> Result::Error>, - ) -> Result { - Self::transaction(self, f) + operations: &mut [embedded_hal::spi::Operation<'_, u8>], + ) -> Result<(), Self::Error> { + core::pin::pin!(self.run_async(operations.iter_mut().map(copy_ehal_operation))) + .await + .map_err(to_spi_err) } } @@ -908,9 +1675,16 @@ const TRANS_LEN: usize = if SOC_SPI_MAXIMUM_BUFFER_SIZE < 64_u32 { 64_usize }; -struct Lock(spi_device_handle_t); +// Whilst ESP-IDF doesn't have a documented maximum for queued transactions, we need a compile time +// max to be able to place the transactions on the stack (without recursion hacks) and not be +// forced to use box. Perhaps this is something the user can inject in, via generics or slice. +// This means a spi_device_interface_config_t.queue_size higher than this constant will be clamped +// down in practice. +const MAX_QUEUED_TRANSACTIONS: usize = 6; -impl Lock { +struct BusLock(spi_device_handle_t); + +impl BusLock { fn new(device: spi_device_handle_t) -> Result { esp!(unsafe { spi_device_acquire_bus(device, BLOCK) })?; @@ -918,7 +1692,7 @@ impl Lock { } } -impl Drop for Lock { +impl Drop for BusLock { fn drop(&mut self) { unsafe { spi_device_release_bus(self.0); @@ -926,28 +1700,282 @@ impl Drop for Lock { } } +enum CsCtl<'c, 'p, P, M> +where + P: OutputPin, + M: OutputMode, +{ + Hardware { + enabled: bool, + transactions_count: usize, + last_transaction: Option, + }, + Software { + cs: &'c mut PinDriver<'p, P, M>, + pre_delay: Option, + post_delay: Option, + }, +} + +impl CsCtl<'_, '_, P, M> +where + P: OutputPin, + M: OutputMode, +{ + fn needs_bus_lock(&self) -> bool { + match self { + Self::Hardware { + transactions_count, .. + } => *transactions_count > 1, + Self::Software { .. } => true, + } + } + + fn raise_cs(&mut self) -> Result<(), EspError> { + if let CsCtl::Software { cs, pre_delay, .. } = self { + cs.toggle()?; + + // TODO: Need to wait asnchronously if in async mode + if let Some(delay) = pre_delay { + Ets::delay_us(*delay); + } + } + + Ok(()) + } + + fn lower_cs(&mut self) -> Result<(), EspError> { + if let CsCtl::Software { cs, post_delay, .. } = self { + cs.toggle()?; + + // TODO: Need to wait asnchronously if in async mode + if let Some(delay) = post_delay { + Ets::delay_us(*delay); + } + } + + Ok(()) + } + + fn configure(&self, operation: &mut SpiOperation, index: usize) { + if let SpiOperation::Transaction(transaction) = operation { + self.configure_transaction(transaction, index) + } + } + + fn configure_transaction(&self, transaction: &mut spi_transaction_t, index: usize) { + if let Self::Hardware { + enabled, + last_transaction, + .. + } = self + { + set_keep_cs_active(transaction, *enabled && Some(index) != *last_transaction); + } + } +} + +fn spi_operations<'a>( + operations: impl Iterator> + 'a, + chunk_size: usize, + duplex: Duplex, +) -> impl Iterator + 'a { + enum OperationsIter { + Read(R), + Write(W), + Transfer(T), + TransferInPlace(I), + Delay(D), + } + + impl Iterator for OperationsIter + where + R: Iterator, + W: Iterator, + T: Iterator, + I: Iterator, + D: Iterator, + { + type Item = SpiOperation; + + fn next(&mut self) -> Option { + match self { + Self::Read(iter) => iter.next(), + Self::Write(iter) => iter.next(), + Self::Transfer(iter) => iter.next(), + Self::TransferInPlace(iter) => iter.next(), + Self::Delay(iter) => iter.next(), + } + } + } + + operations.flat_map(move |op| match op { + Operation::Read(words) => OperationsIter::Read( + spi_read_transactions(words, chunk_size, duplex, LineWidth::Single) + .map(SpiOperation::Transaction), + ), + Operation::ReadWithWidth(words, line_width) => OperationsIter::Read( + spi_read_transactions(words, chunk_size, duplex, line_width) + .map(SpiOperation::Transaction), + ), + Operation::Write(words) => OperationsIter::Write( + spi_write_transactions(words, chunk_size, LineWidth::Single) + .map(SpiOperation::Transaction), + ), + Operation::WriteWithWidth(words, line_width) => OperationsIter::Write( + spi_write_transactions(words, chunk_size, line_width).map(SpiOperation::Transaction), + ), + Operation::Transfer(read, write) => OperationsIter::Transfer( + spi_transfer_transactions(read, write, chunk_size, duplex) + .map(SpiOperation::Transaction), + ), + Operation::TransferInPlace(words) => OperationsIter::TransferInPlace( + spi_transfer_in_place_transactions(words, chunk_size).map(SpiOperation::Transaction), + ), + Operation::DelayNs(delay) => { + OperationsIter::Delay(core::iter::once(SpiOperation::Delay(delay))) + } + }) +} + +fn spi_read_transactions( + words: &mut [u8], + chunk_size: usize, + duplex: Duplex, + line_width: LineWidth, +) -> impl Iterator + '_ { + words.chunks_mut(chunk_size).map(move |chunk| { + spi_create_transaction( + chunk.as_mut_ptr(), + core::ptr::null(), + if duplex == Duplex::Full { + chunk.len() + } else { + 0 + }, + chunk.len(), + line_width, + ) + }) +} + +fn spi_write_transactions( + words: &[u8], + chunk_size: usize, + line_width: LineWidth, +) -> impl Iterator + '_ { + words.chunks(chunk_size).map(move |chunk| { + spi_create_transaction( + core::ptr::null_mut(), + chunk.as_ptr(), + chunk.len(), + 0, + line_width, + ) + }) +} + +fn spi_transfer_in_place_transactions( + words: &mut [u8], + chunk_size: usize, +) -> impl Iterator + '_ { + words.chunks_mut(chunk_size).map(|chunk| { + spi_create_transaction( + chunk.as_mut_ptr(), + chunk.as_mut_ptr(), + chunk.len(), + chunk.len(), + LineWidth::Single, + ) + }) +} + +fn spi_transfer_transactions<'a>( + read: &'a mut [u8], + write: &'a [u8], + chunk_size: usize, + duplex: Duplex, +) -> impl Iterator + 'a { + enum OperationsIter { + Equal(E), + ReadLonger(R), + WriteLonger(W), + } + + impl Iterator for OperationsIter + where + E: Iterator, + R: Iterator, + W: Iterator, + { + type Item = spi_transaction_t; + + fn next(&mut self) -> Option { + match self { + Self::Equal(iter) => iter.next(), + Self::ReadLonger(iter) => iter.next(), + Self::WriteLonger(iter) => iter.next(), + } + } + } + + match read.len().cmp(&write.len()) { + Ordering::Equal => { + OperationsIter::Equal(spi_transfer_equal_transactions(read, write, chunk_size)) + } + Ordering::Greater => { + let (read, read_trail) = read.split_at_mut(write.len()); + + OperationsIter::ReadLonger( + spi_transfer_equal_transactions(read, write, chunk_size).chain( + spi_read_transactions(read_trail, chunk_size, duplex, LineWidth::Single), + ), + ) + } + Ordering::Less => { + let (write, write_trail) = write.split_at(read.len()); + + OperationsIter::WriteLonger( + spi_transfer_equal_transactions(read, write, chunk_size).chain( + spi_write_transactions(write_trail, chunk_size, LineWidth::Single), + ), + ) + } + } +} + +fn spi_transfer_equal_transactions<'a>( + read: &'a mut [u8], + write: &'a [u8], + chunk_size: usize, +) -> impl Iterator + 'a { + read.chunks_mut(chunk_size) + .zip(write.chunks(chunk_size)) + .map(|(read_chunk, write_chunk)| { + spi_create_transaction( + read_chunk.as_mut_ptr(), + write_chunk.as_ptr(), + max(read_chunk.len(), write_chunk.len()), + read_chunk.len(), + LineWidth::Single, + ) + }) +} + // These parameters assume full duplex. -fn polling_transmit( - handle: spi_device_handle_t, +fn spi_create_transaction( read: *mut u8, write: *const u8, transaction_length: usize, rx_length: usize, - _keep_cs_active: bool, -) -> Result<(), EspError> { - #[cfg(esp_idf_version = "4.3")] - let flags = 0; - - // This unfortunately means that this implementation is incorrect for esp-idf < 4.4. - // The CS pin should be kept active through transactions. - #[cfg(not(esp_idf_version = "4.3"))] - let flags = if _keep_cs_active { - SPI_TRANS_CS_KEEP_ACTIVE - } else { - 0 + line_width: LineWidth, +) -> spi_transaction_t { + let flags = match line_width { + LineWidth::Single => 0, + LineWidth::Dual => SPI_TRANS_MODE_DIO, + LineWidth::Quad => SPI_TRANS_MODE_QIO, }; - - let mut transaction = spi_transaction_t { + spi_transaction_t { flags, __bindgen_anon_1: spi_transaction_t__bindgen_ty_1 { tx_buffer: write as *const _, @@ -958,9 +1986,225 @@ fn polling_transmit( length: (transaction_length * 8) as _, rxlength: (rx_length * 8) as _, ..Default::default() + } +} + +fn set_keep_cs_active(transaction: &mut spi_transaction_t, _keep_cs_active: bool) { + if _keep_cs_active { + transaction.flags |= SPI_TRANS_CS_KEEP_ACTIVE + } +} + +fn spi_transmit( + handle: spi_device_handle_t, + transactions: impl Iterator, + polling: bool, + queue_size: usize, +) -> Result<(), EspError> { + if polling { + for mut transaction in transactions { + esp!(unsafe { spi_device_polling_transmit(handle, &mut transaction as *mut _) })?; + } + } else { + pub type Queue = Deque; + + let mut queue = Queue::new(); + let queue_size = min(MAX_QUEUED_TRANSACTIONS, queue_size); + + let push = |queue: &mut Queue, transaction| { + let _ = queue.push_back(transaction); + esp!(unsafe { spi_device_queue_trans(handle, queue.back_mut().unwrap(), delay::BLOCK) }) + }; + + let pop = |queue: &mut Queue| { + let mut rtrans = ptr::null_mut(); + esp!(unsafe { spi_device_get_trans_result(handle, &mut rtrans, delay::BLOCK) })?; + + if rtrans != queue.front_mut().unwrap() { + unreachable!(); + } + queue.pop_front().unwrap(); + + Ok(()) + }; + + let pop_all = |queue: &mut Queue| { + while !queue.is_empty() { + pop(queue)?; + } + + Ok(()) + }; + + for transaction in transactions { + if queue.len() == queue_size { + // If the queue is full, we wait for the first transaction in the queue + pop(&mut queue)?; + } + + // Write transaction to a stable memory location + push(&mut queue, transaction)?; + } + + pop_all(&mut queue)?; + } + + Ok(()) +} + +#[allow(dead_code)] +async fn spi_transmit_async( + handle: spi_device_handle_t, + transactions: impl Iterator, + queue_size: usize, +) -> Result<(), EspError> { + pub type Queue = Deque<(spi_transaction_t, HalIsrNotification), MAX_QUEUED_TRANSACTIONS>; + + let mut queue = Queue::new(); + let queue = &mut queue; + + let queue_size = min(MAX_QUEUED_TRANSACTIONS, queue_size); + let queued = Cell::new(0_usize); + + let fut = &mut core::pin::pin!(async { + let push = |queue: &mut Queue, transaction| { + queue + .push_back((transaction, HalIsrNotification::new())) + .map_err(|_| EspError::from_infallible::<{ ESP_ERR_INVALID_STATE }>())?; + queued.set(queue.len()); + + let last = queue.back_mut().unwrap(); + last.0.user = &last.1 as *const _ as *mut _; + match esp!(unsafe { spi_device_queue_trans(handle, &mut last.0, delay::BLOCK) }) { + Err(e) if e.code() == ESP_ERR_TIMEOUT => unreachable!(), + other => other, + } + }; + + let pop = |queue: &mut Queue| { + let mut rtrans = ptr::null_mut(); + match esp!(unsafe { + spi_device_get_trans_result(handle, &mut rtrans, delay::NON_BLOCK) + }) { + Err(e) if e.code() == ESP_ERR_TIMEOUT => return Ok(false), + Err(e) => Err(e)?, + Ok(()) => (), + }; + + if rtrans != &mut queue.front_mut().unwrap().0 { + unreachable!(); + } + + queue.pop_front().unwrap(); + queued.set(queue.len()); + + Ok(true) + }; + + for transaction in transactions { + while queue.len() == queue_size { + if pop(queue)? { + break; + } + + // If the queue is full, we wait for the first transaction in the queue + queue.front_mut().unwrap().1.wait().await; + } + + // Write transaction to a stable memory location + push(queue, transaction)?; + } + + while !queue.is_empty() { + if !pop(queue)? { + queue.front_mut().unwrap().1.wait().await; + } + } + + Ok(()) + }); + + with_completion(fut, |completed| { + if !completed { + for _ in 0..queued.get() { + let mut rtrans = ptr::null_mut(); + esp!(unsafe { spi_device_get_trans_result(handle, &mut rtrans, delay::BLOCK) }) + .unwrap(); + } + } + }) + .await +} + +extern "C" fn spi_notify(transaction: *mut spi_transaction_t) { + if let Some(transaction) = unsafe { transaction.as_ref() } { + if let Some(notification) = unsafe { + (transaction.user as *mut HalIsrNotification as *const HalIsrNotification).as_ref() + } { + notification.notify_lsb(); + } + } +} + +fn copy_operation<'b>(operation: &'b mut Operation<'_>) -> Operation<'b> { + match operation { + Operation::Read(read) => Operation::Read(read), + Operation::ReadWithWidth(read, line_width) => Operation::ReadWithWidth(read, *line_width), + Operation::Write(write) => Operation::Write(write), + Operation::WriteWithWidth(write, line_width) => { + Operation::WriteWithWidth(write, *line_width) + } + Operation::Transfer(read, write) => Operation::Transfer(read, write), + Operation::TransferInPlace(write) => Operation::TransferInPlace(write), + Operation::DelayNs(delay) => Operation::DelayNs(*delay), + } +} + +fn copy_ehal_operation<'b>( + operation: &'b mut embedded_hal::spi::Operation<'_, u8>, +) -> Operation<'b> { + match operation { + embedded_hal::spi::Operation::Read(read) => Operation::Read(read), + embedded_hal::spi::Operation::Write(write) => Operation::Write(write), + embedded_hal::spi::Operation::Transfer(read, write) => Operation::Transfer(read, write), + embedded_hal::spi::Operation::TransferInPlace(write) => Operation::TransferInPlace(write), + embedded_hal::spi::Operation::DelayNs(delay) => Operation::DelayNs(*delay), + } +} + +#[allow(dead_code)] +async fn with_completion(fut: F, dtor: D) -> F::Output +where + F: Future, + D: FnMut(bool), +{ + struct Completion + where + D: FnMut(bool), + { + dtor: D, + completed: bool, + } + + impl Drop for Completion + where + D: FnMut(bool), + { + fn drop(&mut self) { + (self.dtor)(self.completed); + } + } + + let mut completion = Completion { + dtor, + completed: false, }; - esp!(unsafe { spi_device_polling_transmit(handle, &mut transaction as *mut _) }) + let result = fut.await; + + completion.completed = true; + + result } macro_rules! impl_spi { @@ -984,9 +2228,9 @@ macro_rules! impl_spi_any_pins { impl_spi!(SPI1: spi_host_device_t_SPI1_HOST); impl_spi!(SPI2: spi_host_device_t_SPI2_HOST); -#[cfg(not(esp32c3))] +#[cfg(any(esp32, esp32s2, esp32s3))] impl_spi!(SPI3: spi_host_device_t_SPI3_HOST); impl_spi_any_pins!(SPI2); -#[cfg(not(esp32c3))] +#[cfg(any(esp32, esp32s2, esp32s3))] impl_spi_any_pins!(SPI3); diff --git a/src/sys.rs b/src/sys.rs new file mode 100644 index 00000000000..bc4cb823d1c --- /dev/null +++ b/src/sys.rs @@ -0,0 +1 @@ +pub use esp_idf_sys::*; diff --git a/src/task.rs b/src/task.rs index 5e7c5d45b95..8d78a8e010a 100644 --- a/src/task.rs +++ b/src/task.rs @@ -1,13 +1,88 @@ use core::cell::Cell; +use core::future::Future; +use core::num::NonZeroU32; +use core::pin::Pin; use core::ptr::{self, NonNull}; use core::sync::atomic::{AtomicBool, Ordering}; -use core::time::Duration; +use core::task::{Context, Poll}; + +#[cfg(feature = "alloc")] +extern crate alloc; use esp_idf_sys::*; -use crate::delay::TickType; +use crate::cpu::Core; use crate::interrupt; +#[cfg(not(any( + esp_idf_version_major = "4", + esp_idf_version = "5.0", + esp_idf_version = "5.1" +)))] +const NO_AFFINITY: core::ffi::c_int = CONFIG_FREERTOS_NO_AFFINITY as _; + +#[cfg(any( + esp_idf_version_major = "4", + esp_idf_version = "5.0", + esp_idf_version = "5.1" +))] +const NO_AFFINITY: core::ffi::c_uint = tskNO_AFFINITY; + +/// Creates a FreeRTOS task. +/// +/// This API is to be used only for niche use cases like where the `std` feature is not enabled, or one absolutely +/// needs to create a raw FreeRTOS task. +/// +/// In all other cases, the standard, safe Rust `std::thread` API should be utilized, as it is anyway +/// a thin wrapper around the FreeRTOS task API. +/// +/// # Safety +/// +/// Only marked as unsafe for symmetry with `destroy` and to discourage users from leaning on it in favor of `std::thread`. +/// Otherwise, this function is actually safe. +pub unsafe fn create( + task_handler: extern "C" fn(*mut core::ffi::c_void), + task_name: &core::ffi::CStr, + stack_size: usize, + task_arg: *mut core::ffi::c_void, + priority: u8, + pin_to_core: Option, +) -> Result { + let mut task: TaskHandle_t = core::ptr::null_mut(); + + let created = xTaskCreatePinnedToCore( + Some(task_handler), + task_name.as_ptr(), + stack_size as _, + task_arg, + priority as _, + &mut task, + pin_to_core.map(Into::into).unwrap_or(NO_AFFINITY as _), + ); + + if created == 0 { + Err(EspError::from_infallible::()) + } else { + Ok(task) + } +} + +/// Deletes a FreeRTOS task. +/// +/// This API is to be used only for niche use cases like where the `std` feature is not enabled, or one absolutely +/// needs to create a raw FreeRTOS task. +/// +/// In all other cases, the standard, safe Rust `std::thread` API should be utilized, as it is anyway +/// a thin wrapper around the FreeRTOS task API. +/// +/// # Safety +/// +/// A valid `TaskHandle_t` instance of an existing task should be provided. +/// Providing a `TaskHandle_t` of a task which was already destroyed is an undefined behavior. +pub unsafe fn destroy(task: TaskHandle_t) { + vTaskDelete(task) +} + #[inline(always)] #[link_section = ".iram1.interrupt_task_do_yield"] pub fn do_yield() { @@ -16,13 +91,19 @@ pub fn do_yield() { if let Some((yielder, arg)) = interrupt::get_isr_yielder() { yielder(arg); } else { - #[cfg(esp32c3)] + #[cfg(any(esp32c3, esp32c2, esp32h2, esp32c5, esp32c6))] vPortYieldFromISR(); - #[cfg(all(not(esp32c3), esp_idf_version_major = "4"))] + #[cfg(all( + not(any(esp32c3, esp32c2, esp32h2, esp32c5, esp32c6)), + esp_idf_version_major = "4" + ))] vPortEvaluateYieldFromISR(0); - #[cfg(all(not(esp32c3), not(esp_idf_version_major = "4")))] + #[cfg(all( + not(any(esp32c3, esp32c2, esp32h2, esp32c5, esp32c6)), + not(esp_idf_version_major = "4") + ))] _frxt_setup_switch(); } } @@ -43,36 +124,14 @@ pub fn current() -> Option { } } -pub fn wait_any_notification() { - loop { - if let Some(notification) = wait_notification(None) { - if notification != 0 { - break; - } - } - } -} - -pub fn wait_notification(duration: Option) -> Option { +pub fn wait_notification(timeout: TickType_t) -> Option { let mut notification = 0_u32; - #[cfg(esp_idf_version = "4.3")] let notified = - unsafe { xTaskNotifyWait(0, u32::MAX, &mut notification, TickType::from(duration).0) } != 0; - - #[cfg(not(esp_idf_version = "4.3"))] - let notified = unsafe { - xTaskGenericNotifyWait( - 0, - 0, - u32::MAX, - &mut notification, - TickType::from(duration).0, - ) - } != 0; + unsafe { xTaskGenericNotifyWait(0, 0, u32::MAX, &mut notification, timeout) } != 0; if notified { - Some(notification) + NonZeroU32::new(notification) } else { None } @@ -83,60 +142,213 @@ pub fn wait_notification(duration: Option) -> Option { /// When calling this function care should be taken to pass a valid /// FreeRTOS task handle. Moreover, the FreeRTOS task should be valid /// when this function is being called. -pub unsafe fn notify(task: TaskHandle_t, notification: u32) -> bool { - let notified = if interrupt::active() { +pub unsafe fn notify_and_yield(task: TaskHandle_t, notification: NonZeroU32) -> bool { + let (notified, higher_prio_task_woken) = notify(task, notification); + + if higher_prio_task_woken { + do_yield(); + } + + notified +} + +/// # Safety +/// +/// When calling this function care should be taken to pass a valid +/// FreeRTOS task handle. Moreover, the FreeRTOS task should be valid +/// when this function is being called. +pub unsafe fn notify(task: TaskHandle_t, notification: NonZeroU32) -> (bool, bool) { + let (notified, higher_prio_task_woken) = if interrupt::active() { let mut higher_prio_task_woken: BaseType_t = Default::default(); - #[cfg(esp_idf_version = "4.3")] let notified = xTaskGenericNotifyFromISR( task, - notification, + 0, + notification.into(), eNotifyAction_eSetBits, ptr::null_mut(), &mut higher_prio_task_woken, ); - #[cfg(not(esp_idf_version = "4.3"))] - let notified = xTaskGenericNotifyFromISR( + (notified, higher_prio_task_woken) + } else { + let notified = xTaskGenericNotify( task, 0, - notification, + notification.into(), eNotifyAction_eSetBits, ptr::null_mut(), - &mut higher_prio_task_woken, ); - if higher_prio_task_woken != 0 { - do_yield(); + (notified, 0) + }; + + (notified != 0, higher_prio_task_woken != 0) +} + +pub fn get_idle_task(core: crate::cpu::Core) -> TaskHandle_t { + #[cfg(any(esp32c3, esp32c2, esp32h2, esp32c5, esp32c6))] + { + if matches!(core, crate::cpu::Core::Core0) { + unsafe { xTaskGetIdleTaskHandle() } + } else { + unreachable!() } + } - notified - } else { - #[cfg(esp_idf_version = "4.3")] - let notified = - xTaskGenericNotify(task, notification, eNotifyAction_eSetBits, ptr::null_mut()); + #[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5, esp32c6)))] + #[cfg(any( + esp_idf_version_major = "4", + esp_idf_version = "5.0", + esp_idf_version = "5.1" + ))] + unsafe { + xTaskGetIdleTaskHandleForCPU(core as u32) + } - #[cfg(not(esp_idf_version = "4.3"))] - let notified = xTaskGenericNotify( - task, - 0, - notification, - eNotifyAction_eSetBits, - ptr::null_mut(), - ); + #[cfg(not(any(esp32c3, esp32c2, esp32h2, esp32c5, esp32c6)))] + #[cfg(not(any( + esp_idf_version_major = "4", + esp_idf_version = "5.0", + esp_idf_version = "5.1" + )))] + unsafe { + xTaskGetIdleTaskHandleForCore(core as i32) + } +} + +/// Executes the supplied future on the current thread, thus blocking it until the future becomes ready. +#[cfg(feature = "alloc")] +pub fn block_on(mut fut: F) -> F::Output +where + F: Future, +{ + ::log::trace!("block_on(): started"); + + let notification = notification::Notification::new(); + + let mut fut = core::pin::pin!(fut); + + let waker = notification.notifier().into(); - notified + let mut cx = Context::from_waker(&waker); + + let res = loop { + match fut.as_mut().poll(&mut cx) { + Poll::Ready(res) => break res, + Poll::Pending => notification.wait_any(), + } }; - notified != 0 + ::log::trace!("block_on(): finished"); + + res +} + +/// Yield from the current task once, allowing other tasks to run. +/// +/// This can be used to easily and quickly implement simple async primitives +/// without using wakers. The following snippet will wait for a condition to +/// hold, while still allowing other tasks to run concurrently (not monopolizing +/// the executor thread). +/// +/// ```rust,no_run +/// while !some_condition() { +/// yield_now().await; +/// } +/// ``` +/// +/// The downside is this will spin in a busy loop, using 100% of the CPU, while +/// using wakers correctly would allow the CPU to sleep while waiting. +/// +/// The internal implementation is: on first poll the future wakes itself and +/// returns `Poll::Pending`. On second poll, it returns `Poll::Ready`. +pub fn yield_now() -> impl Future { + YieldNowFuture { yielded: false } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct YieldNowFuture { + yielded: bool, +} + +impl Future for YieldNowFuture { + type Output = (); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.yielded { + Poll::Ready(()) + } else { + self.yielded = true; + cx.waker().wake_by_ref(); + Poll::Pending + } + } } #[cfg(esp_idf_comp_pthread_enabled)] pub mod thread { + use core::ffi::CStr; + + use enumset::EnumSetType; + + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + )))] // ESP-IDF 5.3 and later + use enumset::EnumSet; + use esp_idf_sys::*; + use super::NO_AFFINITY; + use crate::cpu::Core; + /// Flags to indicate the capabilities of the various memo + /// + /// Used together with EnumSet + /// `let flags = MallocCap:Default | MallocCap:Cap_8bit` + #[derive(Debug, EnumSetType)] + #[enumset(repr = "u32")] // Note: following value variants represent the bitposition **not** a literal u32 value in an EnumSet + pub enum MallocCap { + // Memory must be able to run executable code + Exec = 0, + // Memory must allow for aligned 32-bit data accesses + Cap32bit = 1, + // Memory must allow for 8/16/...-bit data accesses + Cap8bit = 2, + // Memory must be able to accessed by DMA + Dma = 3, + // Memory must be mapped to PID2 memory space (PIDs are not currently used) + Pid2 = 4, + // Memory must be mapped to PID3 memory space (PIDs are not currently used) + Pid3 = 5, + // Memory must be mapped to PID4 memory space (PIDs are not currently used) + Pid4 = 6, + // Memory must be mapped to PID5 memory space (PIDs are not currently used) + Pid5 = 7, + // Memory must be mapped to PID6 memory space (PIDs are not currently used) + Pid6 = 8, + // Memory must be mapped to PID7 memory space (PIDs are not currently used) + Pid7 = 9, + // Memory must be in SPI RAM + Spiram = 10, + // Memory must be internal; specifically it should not disappear when flash/spiram cache is switched off + Internal = 11, + // Memory can be returned in a non-capability-specific memory allocation (e.g. malloc(), calloc()) call + Default = 12, + // Memory must be in IRAM and allow unaligned access + Iram8bit = 13, + // Memory must be able to accessed by retention DMA + Retention = 14, + // Memory must be in RTC fast memory + Rtcram = 15, + // Memory must be in TCM memory + Tcm = 16, + // Memory can't be used / list end marker + Invalid = 31, + } #[derive(Debug)] pub struct ThreadSpawnConfiguration { pub name: Option<&'static [u8]>, @@ -144,6 +356,13 @@ pub mod thread { pub priority: u8, pub inherit: bool, pub pin_to_core: Option, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + )))] // ESP-IDF 5.3 and later + pub stack_alloc_caps: EnumSet, } impl ThreadSpawnConfiguration { @@ -172,10 +391,14 @@ pub mod thread { stack_size: conf.stack_size as _, prio: conf.priority as _, inherit_cfg: conf.inherit, - pin_to_core: conf - .pin_to_core - .map(Into::into) - .unwrap_or(tskNO_AFFINITY as _), + pin_to_core: conf.pin_to_core.map(Into::into).unwrap_or(NO_AFFINITY as _), + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + )))] // ESP-IDF 5.3 and later + stack_alloc_caps: conf.stack_alloc_caps.as_u32(), } } } @@ -196,11 +419,18 @@ pub mod thread { stack_size: conf.stack_size as _, priority: conf.prio as _, inherit: conf.inherit_cfg, - pin_to_core: if conf.pin_to_core == tskNO_AFFINITY as _ { + pin_to_core: if conf.pin_to_core == NO_AFFINITY as _ { None } else { Some(conf.pin_to_core.into()) }, + #[cfg(not(any( + esp_idf_version_major = "4", + all(esp_idf_version_major = "5", esp_idf_version_minor = "0"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "1"), + all(esp_idf_version_major = "5", esp_idf_version_minor = "2"), + )))] // ESP-IDF 5.3 and later + stack_alloc_caps: EnumSet::::from_u32(conf.stack_alloc_caps), } } } @@ -222,6 +452,15 @@ pub mod thread { } fn set_conf(conf: &ThreadSpawnConfiguration) -> Result<(), EspError> { + if let Some(name) = conf.name { + let _str = CStr::from_bytes_with_nul(name) + .map_err(|_e| panic! {"Missing null byte in provided Thread-Name"}); + } + + if conf.priority < 1 || conf.priority as u32 >= configMAX_PRIORITIES { + panic!("Thread priority {} has to be [1 - 24]", conf.priority); + } + esp!(unsafe { esp_pthread_set_cfg(&conf.into()) })?; Ok(()) @@ -320,7 +559,7 @@ unsafe impl Sync for CriticalSection {} pub struct CriticalSectionGuard<'a>(&'a CriticalSection); -impl<'a> Drop for CriticalSectionGuard<'a> { +impl Drop for CriticalSectionGuard<'_> { #[inline(always)] #[link_section = ".iram1.csg_drop"] fn drop(&mut self) { @@ -328,6 +567,248 @@ impl<'a> Drop for CriticalSectionGuard<'a> { } } +pub mod watchdog { + //! ## Example + //! + //! ```rust, ignore + //! # fn main() -> Result<()> { + //! let peripherals = Peripherals::take().unwrap(); + //! + //! let config = TWDTConfig { + //! duration: Duration::from_secs(2), + //! panic_on_trigger: true, + //! subscribed_idle_tasks: enum_set!(Core::Core0) + //! }; + //! let mut driver = esp_idf_hal::task::watchdog::TWDTDriver::new( + //! peripherals.twdt, + //! &config, + //! )?; + //! + //! let mut watchdog = driver.watch_current_task()?; + //! + //! loop { + //! watchdog.feed(); + //! unsafe { vTaskDelay(1) }; + //! } + //! # } + //! ``` + + use core::{ + marker::PhantomData, + sync::atomic::{AtomicUsize, Ordering}, + }; + + use esp_idf_sys::*; + + use crate::peripheral::Peripheral; + + pub type TWDTConfig = config::Config; + + pub mod config { + + #[cfg(not(esp_idf_version_major = "4"))] + use esp_idf_sys::*; + + #[derive(Clone)] + pub struct Config { + pub duration: core::time::Duration, + pub panic_on_trigger: bool, + pub subscribed_idle_tasks: enumset::EnumSet, + } + + impl Config { + // Could be const if enumset operations are const + pub fn new() -> Self { + #[cfg(esp_idf_esp_task_wdt)] + let duration = core::time::Duration::from_secs( + esp_idf_sys::CONFIG_ESP_TASK_WDT_TIMEOUT_S as u64, + ); + #[cfg(not(esp_idf_esp_task_wdt))] + let duration = core::time::Duration::from_secs(5); + Self { + duration, + panic_on_trigger: cfg!(esp_idf_esp_task_wdt_panic), + subscribed_idle_tasks: { + let mut subscribed_idle_tasks = enumset::EnumSet::empty(); + if cfg!(esp_idf_esp_task_wdt_check_idle_task_cpu0) { + subscribed_idle_tasks |= crate::cpu::Core::Core0; + } + #[cfg(any(esp32, esp32s3))] + if cfg!(esp_idf_esp_task_wdt_check_idle_task_cpu1) { + subscribed_idle_tasks |= crate::cpu::Core::Core1; + } + subscribed_idle_tasks + }, + } + } + } + + impl Default for Config { + fn default() -> Self { + Self::new() + } + } + + #[cfg(not(esp_idf_version_major = "4"))] + impl From<&Config> for esp_task_wdt_config_t { + fn from(config: &Config) -> Self { + esp_task_wdt_config_t { + timeout_ms: config.duration.as_millis() as u32, + trigger_panic: config.panic_on_trigger, + idle_core_mask: config.subscribed_idle_tasks.as_u32(), + } + } + } + } + + pub struct TWDTDriver<'d> { + init_by_idf: bool, + _marker: PhantomData<&'d mut ()>, + } + + static TWDT_DRIVER_REF_COUNT: AtomicUsize = AtomicUsize::new(0); + + impl<'d> TWDTDriver<'d> { + pub fn new( + _twdt: impl Peripheral

+ 'd, + config: &config::Config, + ) -> Result { + TWDT_DRIVER_REF_COUNT.fetch_add(1, Ordering::SeqCst); + let init_by_idf = Self::watchdog_is_init_by_idf(); + + #[cfg(not(esp_idf_version_major = "4"))] + if !init_by_idf { + esp!(unsafe { esp_task_wdt_init(&config.into() as *const esp_task_wdt_config_t) })?; + } else { + esp!(unsafe { + esp_task_wdt_reconfigure(&config.into() as *const esp_task_wdt_config_t) + })?; + } + + #[cfg(esp_idf_version_major = "4")] + esp!(unsafe { + esp_task_wdt_init(config.duration.as_secs() as u32, config.panic_on_trigger) + })?; + + #[cfg(esp_idf_version_major = "4")] + if let Err(e) = Self::subscribe_idle_tasks(config.subscribed_idle_tasks) { + // error task already subscribed could occur but it's ok (not checking if tasks already subscribed before) + if e.code() != ESP_ERR_INVALID_ARG { + return Err(e); + } + } + + Ok(Self { + init_by_idf, + _marker: Default::default(), + }) + } + + pub fn watch_current_task(&mut self) -> Result, EspError> { + esp!(unsafe { esp_task_wdt_add(core::ptr::null_mut()) })?; + Ok(WatchdogSubscription::new()) + } + + #[cfg(esp_idf_version_major = "4")] + fn subscribe_idle_tasks(cores: enumset::EnumSet) -> Result<(), EspError> { + for core in cores { + let task = super::get_idle_task(core); + esp!(unsafe { esp_task_wdt_add(task) })?; + } + + Ok(()) + } + + #[cfg(esp_idf_version_major = "4")] + fn unsubscribe_idle_tasks() -> Result<(), EspError> { + for core in enumset::EnumSet::::all() { + let task = super::get_idle_task(core); + esp!(unsafe { esp_task_wdt_delete(task) })?; + } + + Ok(()) + } + + fn watchdog_is_init_by_idf() -> bool { + if cfg!(not(any( + esp_idf_version_major = "4", + esp_idf_version = "5.0" + ))) { + cfg!(esp_idf_esp_task_wdt_init) + } else { + !matches!( + unsafe { esp_task_wdt_status(core::ptr::null_mut()) }, + ESP_ERR_INVALID_STATE + ) + } + } + + fn deinit(&self) -> Result<(), EspError> { + if !self.init_by_idf { + #[cfg(esp_idf_version_major = "4")] + if let Err(e) = Self::unsubscribe_idle_tasks() { + // error task not subscribed could occur but it's ok (not checking if tasks subscribed before) + if e.code() != ESP_ERR_INVALID_ARG { + return Err(e); + } + } + esp!(unsafe { esp_task_wdt_deinit() }).unwrap(); + } + + Ok(()) + } + } + + impl Clone for TWDTDriver<'_> { + fn clone(&self) -> Self { + TWDT_DRIVER_REF_COUNT.fetch_add(1, Ordering::SeqCst); + Self { + init_by_idf: self.init_by_idf, + _marker: Default::default(), + } + } + } + + impl Drop for TWDTDriver<'_> { + fn drop(&mut self) { + let refcnt = TWDT_DRIVER_REF_COUNT.fetch_sub(1, Ordering::SeqCst); + match refcnt { + 1 => self.deinit().unwrap(), + r if r < 1 => unreachable!(), // Bug, should never happen + _ => (), + } + } + } + + unsafe impl Send for TWDTDriver<'_> {} + + pub struct WatchdogSubscription<'s>(PhantomData<&'s mut ()>); + + impl WatchdogSubscription<'_> { + fn new() -> Self { + Self(Default::default()) + } + + pub fn feed(&mut self) -> Result<(), EspError> { + esp!(unsafe { esp_task_wdt_reset() }) + } + } + + impl embedded_hal_0_2::watchdog::Watchdog for WatchdogSubscription<'_> { + fn feed(&mut self) { + Self::feed(self).unwrap() + } + } + + impl Drop for WatchdogSubscription<'_> { + fn drop(&mut self) { + esp!(unsafe { esp_task_wdt_delete(core::ptr::null_mut()) }).unwrap(); + } + } + + crate::impl_peripheral!(TWDT); +} + #[cfg(feature = "critical-section")] pub mod critical_section { static CS: super::CriticalSection = super::CriticalSection::new(); @@ -358,7 +839,6 @@ pub mod critical_section { critical_section::set_impl!(EspCriticalSection); } -#[cfg(feature = "embassy-sync")] pub mod embassy_sync { use embassy_sync::blocking_mutex::raw::RawMutex; @@ -372,6 +852,12 @@ pub mod embassy_sync { unsafe impl Send for EspRawMutex {} unsafe impl Sync for EspRawMutex {} + impl Default for EspRawMutex { + fn default() -> Self { + Self::new() + } + } + impl EspRawMutex { /// Create a new `EspRawMutex`. pub const fn new() -> Self { @@ -391,84 +877,455 @@ pub mod embassy_sync { } } -#[cfg(all( - feature = "edge-executor", - feature = "alloc", - target_has_atomic = "ptr" -))] -pub mod executor { +#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] +pub mod notification { + use core::marker::PhantomData; + use core::num::NonZeroU32; use core::sync::atomic::{AtomicPtr, Ordering}; - use core::{mem, ptr}; extern crate alloc; - use alloc::sync::{Arc, Weak}; + use alloc::sync::Arc; + use alloc::task::Wake; + + use esp_idf_sys::TickType_t; use crate::task; - pub use edge_executor::*; + #[cfg(esp_idf_version_major = "4")] + type Task = core::ffi::c_void; - pub type EspExecutor<'a, const C: usize, S> = Executor<'a, C, FreeRtosMonitor, S>; - pub type EspBlocker = Blocker; + #[cfg(not(esp_idf_version_major = "4"))] + type Task = esp_idf_sys::tskTaskControlBlock; - pub struct FreeRtosMonitor(Arc>, *const ()); + pub struct Notification(Arc, PhantomData<*const ()>); - impl FreeRtosMonitor { + impl Notification { pub fn new() -> Self { Self( - Arc::new(AtomicPtr::new(task::current().unwrap())), - ptr::null(), + Arc::new(Notifier(AtomicPtr::new(task::current().unwrap()))), + PhantomData, ) } + + pub fn notifier(&self) -> Arc { + self.0.clone() + } + + pub fn wait_any(&self) { + loop { + if task::wait_notification(crate::delay::BLOCK).is_some() { + break; + } + } + } + + pub fn wait(&self, timeout: TickType_t) -> Option { + task::wait_notification(timeout) + } } - impl Default for FreeRtosMonitor { + impl Default for Notification { fn default() -> Self { Self::new() } } - impl Drop for FreeRtosMonitor { - fn drop(&mut self) { - let mut arc = mem::replace(&mut self.0, Arc::new(AtomicPtr::new(ptr::null_mut()))); + pub struct Notifier(AtomicPtr); + + impl Notifier { + /// # Safety + /// + /// Care should be taken to ensure that `Notifier` does not outlive the task + /// in which the `Notification` that produced it was created. + /// + /// If that happens, a dangling pointer instead of proper task handle will be passed to `task::notify`, + /// which will result in memory corruption. + pub unsafe fn notify(&self, notification: NonZeroU32) -> (bool, bool) { + let freertos_task = self.0.load(Ordering::SeqCst); + + if !freertos_task.is_null() { + return unsafe { task::notify(freertos_task, notification) }; + } - // Busy loop until we can destroy the Arc - which means that nobody is actively holding a strong reference to it - // and thus trying to notify our FreeRtos task, which will likely be destroyed afterwards - loop { - arc = match Arc::try_unwrap(arc) { - Ok(_) => break, - Err(a) => a, - } + (false, false) + } + + /// # Safety + /// + /// Care should be taken to ensure that `Notifier` does not outlive the task + /// in which the `Notification` that produced it was created. + /// + /// If that happens, a dangling pointer instead of proper task handle will be passed to `task::notify_and_yield`, + /// which will result in memory corruption. + pub unsafe fn notify_and_yield(&self, notification: NonZeroU32) -> bool { + let freertos_task = self.0.load(Ordering::SeqCst); + + if !freertos_task.is_null() { + unsafe { task::notify_and_yield(freertos_task, notification) } + } else { + false } } } - impl Monitor for FreeRtosMonitor { - type Notify = FreeRtosMonitorNotify; + impl Wake for Notifier { + fn wake(self: Arc) { + unsafe { + self.notify_and_yield(NonZeroU32::new(1).unwrap()); + } + } + } +} + +pub mod queue { + use core::{ + marker::PhantomData, + mem::{size_of, MaybeUninit}, + }; + + use esp_idf_sys::{EspError, TickType_t, ESP_FAIL}; + + use crate::sys; + + /// Thin wrapper on top of the FreeRTOS queue. + /// + /// This may be preferable over a Rust channel + /// in cases where an ISR needs to send or receive + /// data as it is safe to use in ISR contexts. + pub struct Queue { + ptr: sys::QueueHandle_t, + is_owned: bool, + _marker: PhantomData, + } + + unsafe impl Send for Queue where T: Send + Sync {} + unsafe impl Sync for Queue where T: Send + Sync {} + + impl Queue + where + // ensures the contained elements are not `Drop` + // might be able to lift restriction in the future + T: Copy, + { + /// Allocate a new queue on the heap. + pub fn new(size: usize) -> Self { + Queue { + ptr: unsafe { sys::xQueueGenericCreate(size as u32, size_of::() as u32, 0) }, + is_owned: true, + _marker: PhantomData, + } + } + + /// Create a new queue which is not deleted on `Drop`, but owned by somebody else. + /// + /// # Safety + /// + /// Care must be taken that the queue is valid for the constructed + /// lifetime. + pub unsafe fn new_borrowed(ptr: sys::QueueHandle_t) -> Self { + assert!(!ptr.is_null()); + + Queue { + ptr, + is_owned: false, + _marker: PhantomData, + } + } + + /// Retrieves the underlying FreeRTOS handle. + #[inline] + #[link_section = "iram1.queue_as_raw"] + pub fn as_raw(&self) -> sys::QueueHandle_t { + self.ptr + } + + /// Copy item to back of queue, blocking for `timeout` ticks if full. + /// + /// # ISR safety + /// + /// This function is safe to call in ISR contexts. + /// + /// # Parameters + /// + /// * `item` the item to push onto the back of the queue + /// * `timeout` specifies how long to block. Ignored in ISR context. + /// + /// # Returns + /// + /// Will return an error if queue is full. + /// If this function is executed in an ISR context, + /// it will return true if a higher priority task was awoken. + /// In non-ISR contexts, the function will always return `false`. + /// In this case the interrupt should call [`crate::task::do_yield`]. + #[inline] + #[link_section = "iram1.queue_send_back"] + pub fn send_back(&self, item: T, timeout: TickType_t) -> Result { + self.send_generic(item, timeout, 0) + } + + /// Copy item to front of queue, blocking for `timeout` ticks if full. + /// This can be used for hight priority messages which should be processed + /// sooner. + /// + /// # ISR safety + /// + /// This function is safe to call in ISR contexts. + /// + /// # Parameters + /// + /// * `item` the item to push to front of the queue + /// * `timeout` specifies how long to block. Ignored in ISR context. + /// + /// # Returns + /// + /// Will return an error if queue is full. + /// If this function is executed in an ISR context, + /// it will return true if a higher priority task was awoken. + /// In non-ISR contexts, the function will always return `false`. + /// In this case the interrupt should call [`crate::task::do_yield`]. + #[inline] + #[link_section = "iram1.queue_send_front"] + pub fn send_front(&self, item: T, timeout: TickType_t) -> Result { + self.send_generic(item, timeout, 1) + } + + /// Copy item to queue, blocking for `timeout` ticks if full. + /// + /// # ISR safety + /// + /// This function is safe to call in ISR contexts. + /// + /// # Parameters + /// + /// * `item` the item to push to the queue + /// * `timeout` specifies how long to block. Ignored in ISR context. + /// * `copy_position` 0 to push onto back, 1 to push to front + /// + /// # Returns + /// + /// Will return an error if queue is full. + /// If this function is executed in an ISR context, + /// it will return true if a higher priority task was awoken. + /// In non-ISR contexts, the function will always return `false`. + /// In this case the interrupt should call [`crate::task::do_yield`]. + #[inline] + #[link_section = "iram1.queue_send_generic"] + fn send_generic( + &self, + item: T, + timeout: TickType_t, + copy_position: i32, + ) -> Result { + let mut hp_task_awoken: i32 = false as i32; + let success = unsafe { + if crate::interrupt::active() { + sys::xQueueGenericSendFromISR( + self.ptr, + &item as *const T as *const _, + &mut hp_task_awoken, + copy_position, + ) + } else { + sys::xQueueGenericSend( + self.ptr, + &item as *const T as *const _, + timeout, + copy_position, + ) + } + }; + let success = success == 1; + let hp_task_awoken = hp_task_awoken == 1; - fn notifier(&self) -> Self::Notify { - FreeRtosMonitorNotify(Arc::downgrade(&self.0)) + match success { + true => Ok(hp_task_awoken), + false => Err(EspError::from_infallible::()), + } + } + + /// Receive a message from the queue and remove it. + /// + /// # ISR safety + /// + /// This function is safe to use in ISR contexts + /// + /// # Parameters + /// + /// * `timeout` specifies how long to block. Ignored in ISR contexts. + /// + /// # Returns + /// + /// * `None` if no message could be received in time + /// * `Some((message, higher_priority_task_awoken))` otherwise + /// + /// The boolean is used for ISRs and indicates if a higher priority task was awoken. + /// In this case the interrupt should call [`crate::task::do_yield`]. + /// In non-ISR contexts, the function will always return `false`. + #[inline] + #[link_section = "iram1.queue_recv_front"] + pub fn recv_front(&self, timeout: TickType_t) -> Option<(T, bool)> { + let mut buf = MaybeUninit::uninit(); + let mut hp_task_awoken = false as i32; + + unsafe { + let success = if crate::interrupt::active() { + sys::xQueueReceiveFromISR( + self.ptr, + buf.as_mut_ptr() as *mut _, + &mut hp_task_awoken, + ) + } else { + sys::xQueueReceive(self.ptr, buf.as_mut_ptr() as *mut _, timeout) + }; + if success == 1 { + Some((buf.assume_init(), hp_task_awoken == 1)) + } else { + None + } + } + } + + /// Copy the first message from the queue without removing it. + /// + /// # ISR safety + /// + /// This function is safe to use in ISR contexts + /// + /// # Parameters + /// + /// * `timeout` specifies how long to block. Ignored in ISR contexts. + /// + /// # Returns + /// + /// * `None` if no message could be received in time + /// * `Some(message)` otherwise + /// + /// This function does not return a boolean to indicate if + /// a higher priority task was awoken since we don't free + /// up space in the queue and thus cannot unblock anyone. + #[inline] + #[link_section = "iram1.queue_peek_front"] + pub fn peek_front(&self, timeout: TickType_t) -> Option { + let mut buf = MaybeUninit::uninit(); + + unsafe { + let success = if crate::interrupt::active() { + sys::xQueuePeekFromISR(self.ptr, buf.as_mut_ptr() as *mut _) + } else { + sys::xQueuePeek(self.ptr, buf.as_mut_ptr() as *mut _, timeout) + }; + if success == 1 { + Some(buf.assume_init()) + } else { + None + } + } } } - impl Wait for FreeRtosMonitor { - fn wait(&self) { - task::wait_any_notification(); + impl Drop for Queue { + fn drop(&mut self) { + if self.is_owned { + unsafe { sys::vQueueDelete(self.ptr) } + } } } +} - pub struct FreeRtosMonitorNotify(Weak>); +pub mod asynch { + use core::future::Future; + use core::num::NonZeroU32; + use core::sync::atomic::{AtomicU32, Ordering}; + use core::task::{Context, Poll, Waker}; - impl Notify for FreeRtosMonitorNotify { - fn notify(&self) { - if let Some(notify) = self.0.upgrade() { - let freertos_task = notify.load(Ordering::SeqCst); + use atomic_waker::AtomicWaker; - if !freertos_task.is_null() { - unsafe { - task::notify(freertos_task, 1); - } - } + /// Single-slot lock-free signaling primitive supporting signalling with a `u32` bit-set. + /// + /// It is useful for sending data between tasks when the receiver only cares about + /// the latest data, and therefore it's fine to "lose" messages. This is often the case for "state" + /// updates. + /// + /// The sending part of the primitive is non-blocking, so it is also useful for notifying asynchronous tasks + /// from contexts where blocking or async wait is not possible. + /// + /// Similar in spirit to the ESP-IDF FreeRTOS task notifications in that it is light-weight and operates on bit-sets, + /// but for synchronization between an asynchronous task, and another one, which might be blocking or asynchronous. + pub struct Notification { + waker: AtomicWaker, + notified: AtomicU32, + } + + impl Default for Notification { + fn default() -> Self { + Self::new() + } + } + + impl Notification { + /// Creates a new `Notification`. + pub const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + notified: AtomicU32::new(0), + } + } + + /// Marks the least significant bit (bit 0) in this `IsrNotification` as nofified. + /// Returns `true` if there was a registered waker which got awoken. + pub fn notify_lsb(&self) -> bool { + self.notify(NonZeroU32::new(1).unwrap()) + } + + /// Marks the supplied bits in this `Notification` as notified. + /// Returns `true` if there was a registered waker which got awoken. + pub fn notify(&self, bits: NonZeroU32) -> bool { + if let Some(waker) = self.notify_waker(bits) { + waker.wake(); + + true + } else { + false } } + + /// A utility to help in implementing a custom `wait` logic: + /// Adds the supplied bits as notified in the notification instance and returns the registered waker (if any). + pub fn notify_waker(&self, bits: NonZeroU32) -> Option { + self.notified.fetch_or(bits.into(), Ordering::SeqCst); + + self.waker.take() + } + + /// Clears the state of this notification by removing any registered waker and setting all bits to 0. + pub fn reset(&self) { + self.waker.take(); + self.notified.store(0, Ordering::SeqCst); + } + + /// Future that completes when this `Notification` has been notified. + #[allow(unused)] + pub fn wait(&self) -> impl Future + '_ { + core::future::poll_fn(move |cx| self.poll_wait(cx)) + } + + /// Non-blocking method to check whether this notification has been notified. + pub fn poll_wait(&self, cx: &Context<'_>) -> Poll { + self.waker.register(cx.waker()); + + let bits = self.notified.swap(0, Ordering::SeqCst); + + if let Some(bits) = NonZeroU32::new(bits) { + Poll::Ready(bits) + } else { + Poll::Pending + } + } + } + + impl Drop for Notification { + fn drop(&mut self) { + self.reset(); + } } } diff --git a/src/temp_sensor.rs b/src/temp_sensor.rs new file mode 100644 index 00000000000..08acb40b678 --- /dev/null +++ b/src/temp_sensor.rs @@ -0,0 +1,192 @@ +use esp_idf_sys::{ + esp, soc_periph_temperature_sensor_clk_src_t_TEMPERATURE_SENSOR_CLK_SRC_DEFAULT, + temperature_sensor_clk_src_t, temperature_sensor_config_t, temperature_sensor_disable, + temperature_sensor_enable, temperature_sensor_get_celsius, temperature_sensor_handle_t, + temperature_sensor_install, temperature_sensor_uninstall, EspError, +}; + +#[cfg(esp32p4)] +use esp_idf_sys::soc_periph_temperature_sensor_clk_src_t_TEMPERATURE_SENSOR_CLK_SRC_LP_PERI; +#[cfg(any( + esp32c2, esp32c3, esp32c5, esp32c6, esp32c61, esp32h2, esp32s2, esp32s3 +))] +use esp_idf_sys::soc_periph_temperature_sensor_clk_src_t_TEMPERATURE_SENSOR_CLK_SRC_RC_FAST; +#[cfg(any(esp32c2, esp32c3, esp32c5, esp32c6, esp32c61, esp32h2))] +use esp_idf_sys::soc_periph_temperature_sensor_clk_src_t_TEMPERATURE_SENSOR_CLK_SRC_XTAL; + +use crate::peripheral::Peripheral; +use core::marker::PhantomData; + +// -- TempSensorClockSource -- + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +/// Rust translation of `temperature_sensor_clk_src_t` +pub enum TempSensorClockSource { + Default, + #[cfg(any( + esp32c2, esp32c3, esp32c5, esp32c6, esp32c61, esp32h2, esp32s2, esp32s3 + ))] + RcFast, + #[cfg(any(esp32c2, esp32c3, esp32c5, esp32c6, esp32c61, esp32h2))] + XTAL, + #[cfg(esp32p4)] + LpPeri, +} + +impl From for temperature_sensor_clk_src_t { + fn from(value: TempSensorClockSource) -> Self { + match value { + #[cfg(any( + esp32c2, esp32c3, esp32c5, esp32c6, esp32c61, esp32h2, esp32s2, esp32s3 + ))] + TempSensorClockSource::RcFast => { + soc_periph_temperature_sensor_clk_src_t_TEMPERATURE_SENSOR_CLK_SRC_RC_FAST + } + #[cfg(any(esp32c2, esp32c3, esp32c5, esp32c6, esp32c61, esp32h2))] + TempSensorClockSource::XTAL => { + soc_periph_temperature_sensor_clk_src_t_TEMPERATURE_SENSOR_CLK_SRC_XTAL + } + #[cfg(esp32p4)] + TempSensorClockSource::LpPeri => { + soc_periph_temperature_sensor_clk_src_t_TEMPERATURE_SENSOR_CLK_SRC_LP_PERI + } + TempSensorClockSource::Default => { + soc_periph_temperature_sensor_clk_src_t_TEMPERATURE_SENSOR_CLK_SRC_DEFAULT + } + } + } +} + +impl From for TempSensorClockSource { + fn from(value: temperature_sensor_clk_src_t) -> Self { + match value { + #[cfg(any( + esp32c2, esp32c3, esp32c5, esp32c6, esp32c61, esp32h2, esp32s2, esp32s3 + ))] + #[allow(non_upper_case_globals)] + soc_periph_temperature_sensor_clk_src_t_TEMPERATURE_SENSOR_CLK_SRC_RC_FAST => { + Self::RcFast + } + #[cfg(any(esp32c2, esp32c3, esp32c5, esp32c6, esp32c61, esp32h2))] + #[allow(non_upper_case_globals)] + soc_periph_temperature_sensor_clk_src_t_TEMPERATURE_SENSOR_CLK_SRC_XTAL => Self::XTAL, + #[cfg(esp32p4)] + #[allow(non_upper_case_globals)] + soc_periph_temperature_sensor_clk_src_t_TEMPERATURE_SENSOR_CLK_SRC_LP_PERI => { + Self::LpPeri + } + // TODO: Perhaps the default value should be mapped explicitly + // and all other (u32) values should cause a failure + _ => Self::Default, + } + } +} + +// -- TempSensorConfig -- +pub type TempSensorConfig = config::Config; +pub mod config { + use super::*; + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + #[non_exhaustive] + /// Rust wrapper for `temperature_sensor_config_t` + pub struct Config { + // TODO: check int size + pub range_min: i32, + pub range_max: i32, + pub clk_src: TempSensorClockSource, + } + + impl From for Config { + fn from(value: temperature_sensor_config_t) -> Self { + Config { + range_min: value.range_min, + range_max: value.range_max, + clk_src: value.clk_src.into(), + } + } + } + + #[allow(clippy::needless_update)] + impl From<&Config> for temperature_sensor_config_t { + fn from(value: &Config) -> Self { + temperature_sensor_config_t { + clk_src: value.clk_src.into(), + range_max: value.range_max, + range_min: value.range_min, + ..Default::default() + } + } + } + + impl Default for Config { + fn default() -> Self { + Config::new() + } + } + + impl Config { + pub const fn new() -> Self { + Self { + range_min: -10, + range_max: 80, + clk_src: TempSensorClockSource::Default, + } + } + } +} + +// -- TempSensorDriver -- + +pub struct TempSensorDriver<'d> { + ptr: temperature_sensor_handle_t, + _p: PhantomData<&'d mut ()>, +} + +impl<'d> TempSensorDriver<'d> { + pub fn new( + config: &TempSensorConfig, + _sensor: impl Peripheral

+ 'd, + ) -> Result { + let mut sensor = core::ptr::null_mut(); + esp!(unsafe { temperature_sensor_install(&config.into(), &mut sensor) })?; + Ok(TempSensorDriver { + ptr: sensor, + _p: PhantomData, + }) + } + + pub fn enable(&mut self) -> Result<(), EspError> { + esp!(unsafe { temperature_sensor_enable(self.ptr) }) + } + + pub fn disable(&mut self) -> Result<(), EspError> { + esp!(unsafe { temperature_sensor_disable(self.ptr) }) + } + + pub fn get_celsius(&self) -> Result { + let mut val = 0.0; + esp!(unsafe { temperature_sensor_get_celsius(self.ptr, &mut val) })?; + Ok(val) + } + + pub fn get_fahrenheit(&self) -> Result { + let celsius = self.get_celsius()?; + Ok((celsius * 1.8) + 32.0) + } + + pub fn get_kelvin(&self) -> Result { + let celsius = self.get_celsius()?; + Ok(celsius + 273.15) + } +} + +impl Drop for TempSensorDriver<'_> { + fn drop(&mut self) { + let _ = self.disable(); + esp!(unsafe { temperature_sensor_uninstall(self.ptr) }).unwrap(); + } +} + +unsafe impl Send for TempSensorDriver<'_> {} + +crate::impl_peripheral!(TempSensor); diff --git a/src/timer.rs b/src/timer.rs index 2bca2f9cb8c..c19ff1903dc 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -1,4 +1,4 @@ -use core::{ffi::c_void, marker::PhantomData}; +use core::marker::PhantomData; use esp_idf_sys::*; @@ -14,11 +14,20 @@ pub type TimerConfig = config::Config; /// Timer configuration pub mod config { + #[derive(Copy, Clone)] pub struct Config { pub divider: u32, - #[cfg(any(esp32s2, esp32s3, esp32c3))] + #[cfg(not(esp32))] pub xtal: bool, + + /// Enable or disable counter reload function when alarm event occurs. + /// + /// Enabling this makes the hardware automatically reset the counter + /// to the value set by [`TimerDriver::set_counter`](super::TimerDriver::set_counter) when the alarm is fired. + /// This allows creating timers that automatically fire at a given interval + /// without the software having to do anything after the timer setup. + pub auto_reload: bool, } impl Config { @@ -33,19 +42,71 @@ pub mod config { } #[must_use] - #[cfg(any(esp32s2, esp32s3, esp32c3))] + #[cfg(not(esp32))] pub fn xtal(mut self, xtal: bool) -> Self { self.xtal = xtal; self } + + #[must_use] + pub fn auto_reload(mut self, auto_reload: bool) -> Self { + self.auto_reload = auto_reload; + self + } } impl Default for Config { fn default() -> Self { Self { divider: 80, - #[cfg(any(esp32s2, esp32s3, esp32c3))] + #[cfg(not(esp32))] xtal: false, + auto_reload: false, + } + } + } + + #[cfg(not(esp_idf_version_major = "4"))] + #[allow(clippy::upper_case_acronyms)] + #[derive(Default)] + pub(crate) enum ClockSource { + #[cfg(any(esp32, esp32s2, esp32s3, esp32c3))] + #[default] + APB, + #[cfg(esp32c2)] + #[default] + PLL40, + #[cfg(esp32h2)] + #[default] + PLL48, + #[cfg(esp32c6)] + #[default] + PLL80, + #[cfg(not(esp32))] + XTAL, + } + + #[cfg(not(esp_idf_version_major = "4"))] + #[allow(clippy::from_over_into)] + impl Into for ClockSource { + fn into(self) -> esp_idf_sys::soc_periph_tg_clk_src_legacy_t { + match self { + #[cfg(any(esp32, esp32s2, esp32s3, esp32c3))] + ClockSource::APB => esp_idf_sys::soc_periph_tg_clk_src_legacy_t_TIMER_SRC_CLK_APB, + #[cfg(esp32c2)] + ClockSource::PLL40 => { + esp_idf_sys::soc_periph_tg_clk_src_legacy_t_TIMER_SRC_CLK_PLL_F40M + } + #[cfg(esp32h2)] + ClockSource::PLL48 => { + esp_idf_sys::soc_periph_tg_clk_src_legacy_t_TIMER_SRC_CLK_PLL_F48M + } + #[cfg(esp32c6)] + ClockSource::PLL80 => { + esp_idf_sys::soc_periph_tg_clk_src_legacy_t_TIMER_SRC_CLK_PLL_F80M + } + #[cfg(not(esp32))] + ClockSource::XTAL => esp_idf_sys::soc_periph_tg_clk_src_legacy_t_TIMER_SRC_CLK_XTAL, } } } @@ -58,6 +119,10 @@ pub trait Timer: Send { pub struct TimerDriver<'d> { timer: u8, + divider: u32, + #[cfg(all(not(esp32), not(esp_idf_version_major = "4")))] + xtal: bool, + isr_registered: bool, _p: PhantomData<&'d mut ()>, } @@ -74,27 +139,107 @@ impl<'d> TimerDriver<'d> { alarm_en: timer_alarm_t_TIMER_ALARM_DIS, counter_en: timer_start_t_TIMER_PAUSE, counter_dir: timer_count_dir_t_TIMER_COUNT_UP, - auto_reload: timer_autoreload_t_TIMER_AUTORELOAD_DIS, + auto_reload: if config.auto_reload { + timer_autoreload_t_TIMER_AUTORELOAD_EN + } else { + timer_autoreload_t_TIMER_AUTORELOAD_DIS + }, intr_type: timer_intr_mode_t_TIMER_INTR_LEVEL, divider: config.divider, - #[cfg(all(any(esp32s2, esp32s3, esp32c3), esp_idf_version_major = "4"))] + #[cfg(all(not(esp32), esp_idf_version_major = "4"))] clk_src: if config.xtal { timer_src_clk_t_TIMER_SRC_CLK_XTAL } else { timer_src_clk_t_TIMER_SRC_CLK_APB }, - #[cfg(not(esp_idf_version_major = "4"))] - clk_src: 0, + #[cfg(all(not(esp32), not(esp_idf_version_major = "4")))] + clk_src: if config.xtal { + config::ClockSource::XTAL.into() + } else { + config::ClockSource::default().into() + }, + #[cfg(all(esp32, not(esp_idf_version_major = "4")))] + clk_src: config::ClockSource::default().into(), }, ) })?; - Ok(TimerDriver { + Ok(Self { timer: ((TIMER::group() as u8) << 4) | (TIMER::index() as u8), + divider: config.divider, + #[cfg(all(not(esp32), not(esp_idf_version_major = "4")))] + xtal: config.xtal, + isr_registered: false, _p: PhantomData, }) } + /// + /// Returns the tick rate of the timer. + /// + pub fn tick_hz(&self) -> u64 { + let hz; + + #[cfg(esp_idf_version_major = "4")] + { + hz = TIMER_BASE_CLK / self.divider; + } + + #[cfg(not(esp_idf_version_major = "4"))] + { + #[cfg(not(esp32))] + if self.xtal { + #[cfg(esp_idf_xtal_freq_24)] + { + hz = 24_000_000 / self.divider; + } + #[cfg(esp_idf_xtal_freq_26)] + { + hz = 26_000_000 / self.divider; + } + #[cfg(esp_idf_xtal_freq_32)] + { + hz = 32_000_000 / self.divider; + } + #[cfg(esp_idf_xtal_freq_40)] + { + hz = 40_000_000 / self.divider; + } + } else { + #[cfg(any(esp32, esp32s2, esp32s3, esp32c3))] + { + hz = APB_CLK_FREQ / self.divider; + } + #[cfg(esp32c2)] //PLL40 + { + hz = 40_000_000 / self.divider; + } + #[cfg(esp32h2)] //PLL48 + { + hz = 48_000_000 / self.divider; + } + #[cfg(esp32c6)] //PLL80 + { + hz = 80_000_000 / self.divider; + } + } + #[cfg(esp32)] + { + hz = APB_CLK_FREQ / self.divider; + } + } + + hz as _ + } + + /// + /// Enable or disable the timer. + /// + /// Enabling the timer causes it to begin counting + /// up from the current counter. + /// + /// Disabling the timer effectively pauses the counter. + /// pub fn enable(&mut self, enable: bool) -> Result<(), EspError> { self.check(); @@ -107,6 +252,9 @@ impl<'d> TimerDriver<'d> { Ok(()) } + /// + /// Returns the current counter value of the timer + /// pub fn counter(&self) -> Result { let value = if crate::interrupt::active() { unsafe { timer_group_get_counter_value_in_isr(self.group(), self.index()) } @@ -121,6 +269,11 @@ impl<'d> TimerDriver<'d> { Ok(value) } + /// + /// Manually set the current counter value of the timer. + /// + /// This does not enable or disable the timer. + /// pub fn set_counter(&mut self, value: u64) -> Result<(), EspError> { self.check(); @@ -129,6 +282,13 @@ impl<'d> TimerDriver<'d> { Ok(()) } + /// + /// Enable or disable the alarm. + /// + /// Enabling the alarm activates the following behaviors once it is triggered: + /// - The counter will reset to 0, if auto-reload is set + /// - An interrupt will be triggered, if configured + /// pub fn enable_alarm(&mut self, enable: bool) -> Result<(), EspError> { if crate::interrupt::active() { if enable { @@ -155,6 +315,9 @@ impl<'d> TimerDriver<'d> { Ok(()) } + /// + /// Returns the configured alarm value + /// pub fn alarm(&self) -> Result { self.check(); @@ -165,6 +328,15 @@ impl<'d> TimerDriver<'d> { Ok(value) } + /// + /// Set the alarm value of the timer. + /// + /// NOTE: The alarm must be activated with enable_alarm for this value to take effect + /// + /// Once the counter exceeds this value: + /// - The counter will reset to 0, if auto-reload is set + /// - An interrupt will be triggered, if configured + /// pub fn set_alarm(&mut self, value: u64) -> Result<(), EspError> { if crate::interrupt::active() { unsafe { @@ -180,7 +352,20 @@ impl<'d> TimerDriver<'d> { pub fn enable_interrupt(&mut self) -> Result<(), EspError> { self.check(); - esp!(unsafe { timer_enable_intr(self.group(), self.index()) })?; + if !self.isr_registered { + // Driver will complain if we try to register when ISR CB is already registered + esp!(unsafe { + timer_isr_callback_add( + self.group(), + self.index(), + Some(Self::handle_isr), + (self.group() * timer_idx_t_TIMER_MAX + self.index()) as *mut core::ffi::c_void, + 0, + ) + })?; + + self.isr_registered = true; + } Ok(()) } @@ -188,41 +373,131 @@ impl<'d> TimerDriver<'d> { pub fn disable_interrupt(&mut self) -> Result<(), EspError> { self.check(); - esp!(unsafe { timer_disable_intr(self.group(), self.index()) })?; + if self.isr_registered { + // Driver will complain if we try to deregister when ISR callback is not registered + esp!(unsafe { timer_isr_callback_remove(self.group(), self.index()) })?; + + self.isr_registered = false; + } Ok(()) } + /// + /// Delays for `counter` ticks + /// + /// NOTE: This function resets the counter + /// + /// + pub async fn delay(&mut self, counter: u64) -> Result<(), EspError> { + self.enable(false)?; + self.enable_alarm(false)?; + self.set_counter(0)?; + self.set_alarm(counter)?; + + self.reset_wait(); + + self.enable_interrupt()?; + self.enable_alarm(true)?; + self.enable(true)?; + + self.wait().await + } + + /// + /// Resets the internal wait notification + /// + pub fn reset_wait(&mut self) { + let notif = &PIN_NOTIF[(self.group() * timer_idx_t_TIMER_MAX + self.index()) as usize]; + notif.reset(); + } + + /// + /// Wait for an alarm interrupt to occur + /// + /// + /// NOTE: This requires interrupts to be enabled to work + /// + pub async fn wait(&mut self) -> Result<(), EspError> { + let notif = &PIN_NOTIF[(self.group() * timer_idx_t_TIMER_MAX + self.index()) as usize]; + + notif.wait().await; + + Ok(()) + } + + /// Subscribes the provided callback for ISR notifications. + /// As a side effect, interrupts will be disabled, so to receive a notification, one has + /// to also call `TimerDriver::enable_interrupt` after calling this method. + /// /// # Safety /// /// Care should be taken not to call STD, libc or FreeRTOS APIs (except for a few allowed ones) /// in the callback passed to this function, as it is executed in an ISR context. #[cfg(feature = "alloc")] - pub unsafe fn subscribe(&mut self, callback: impl FnMut() + 'static) -> Result<(), EspError> { - self.check(); + pub unsafe fn subscribe(&mut self, callback: F) -> Result<(), EspError> + where + F: FnMut() + Send + 'static, + { + self.internal_subscribe(callback) + } - self.unsubscribe()?; + /// Subscribes the provided callback for ISR notifications. + /// As a side effect, interrupts will be disabled, so to receive a notification, one has + /// to also call `TimerDriver::enable_interrupt` after calling this method. + /// + /// # Safety + /// + /// Care should be taken not to call STD, libc or FreeRTOS APIs (except for a few allowed ones) + /// in the callback passed to this function, as it is executed in an ISR context. + /// + /// Additionally, this method - in contrast to method `subscribe` - allows + /// the passed-in callback/closure to be non-`'static`. This enables users to borrow + /// - in the closure - variables that live on the stack - or more generally - in the same + /// scope where the driver is created. + /// + /// HOWEVER: care should be taken NOT to call `core::mem::forget()` on the driver, + /// as that would immediately lead to an UB (crash). + /// Also note that forgetting the driver might happen with `Rc` and `Arc` + /// when circular references are introduced: https://github.com/rust-lang/rust/issues/24456 + /// + /// The reason is that the closure is actually sent and owned by an ISR routine, + /// which means that if the driver is forgotten, Rust is free to e.g. unwind the stack + /// and the ISR routine will end up with references to variables that no longer exist. + /// + /// The destructor of the driver takes care - prior to the driver being dropped and e.g. + /// the stack being unwind - to unsubscribe the ISR routine. + /// Unfortunately, when the driver is forgotten, the un-subscription does not happen + /// and invalid references are left dangling. + /// + /// This "local borrowing" will only be possible to express in a safe way once/if `!Leak` types + /// are introduced to Rust (i.e. the impossibility to "forget" a type and thus not call its destructor). + #[cfg(feature = "alloc")] + pub unsafe fn subscribe_nonstatic(&mut self, callback: F) -> Result<(), EspError> + where + F: FnMut() + Send + 'd, + { + self.internal_subscribe(callback) + } - let callback: Box = Box::new(callback); + #[cfg(feature = "alloc")] + fn internal_subscribe(&mut self, callback: F) -> Result<(), EspError> + where + F: FnMut() + Send + 'd, + { + self.check(); - ISR_HANDLERS[(self.group() * timer_group_t_TIMER_GROUP_MAX + self.index()) as usize] = - Some(Box::new(callback)); + self.disable_interrupt()?; - esp!(timer_isr_callback_add( - self.group(), - self.index(), - Some(Self::handle_isr), - UnsafeCallback::from( - ISR_HANDLERS - [(self.group() * timer_group_t_TIMER_GROUP_MAX + self.index()) as usize] - .as_mut() - .unwrap(), - ) - .as_ptr(), - 0 - ))?; + let callback: Box = Box::new(callback); - self.enable_interrupt()?; + unsafe { + ISR_HANDLERS[(self.group() * timer_idx_t_TIMER_MAX + self.index()) as usize] = + Some(core::mem::transmute::< + Box, + Box, + >(callback)); + } Ok(()) } @@ -231,18 +506,10 @@ impl<'d> TimerDriver<'d> { pub fn unsubscribe(&mut self) -> Result<(), EspError> { self.check(); - unsafe { - let subscribed = ISR_HANDLERS - [(self.group() * timer_group_t_TIMER_GROUP_MAX + self.index()) as usize] - .is_some(); - - if subscribed { - esp!(timer_disable_intr(self.group(), self.index()))?; - esp!(timer_isr_callback_remove(self.group(), self.index()))?; + self.disable_interrupt()?; - ISR_HANDLERS - [(self.group() * timer_group_t_TIMER_GROUP_MAX + self.index()) as usize] = None; - } + unsafe { + ISR_HANDLERS[(self.group() * timer_idx_t_TIMER_MAX + self.index()) as usize] = None; } Ok(()) @@ -254,10 +521,20 @@ impl<'d> TimerDriver<'d> { } } - #[cfg(feature = "alloc")] - unsafe extern "C" fn handle_isr(unsafe_callback: *mut c_void) -> bool { + unsafe extern "C" fn handle_isr(index: *mut core::ffi::c_void) -> bool { + use core::num::NonZeroU32; + + let index = index as usize; + crate::interrupt::with_isr_yield_signal(move || { - UnsafeCallback::from_ptr(unsafe_callback).call(); + #[cfg(feature = "alloc")] + { + if let Some(handler) = ISR_HANDLERS[index].as_mut() { + handler(); + } + } + + PIN_NOTIF[index].notify(NonZeroU32::new(1).unwrap()); }) } @@ -270,41 +547,34 @@ impl<'d> TimerDriver<'d> { } } -impl<'d> Drop for TimerDriver<'d> { +impl Drop for TimerDriver<'_> { fn drop(&mut self) { + self.disable_interrupt().unwrap(); + #[cfg(feature = "alloc")] - { - self.unsubscribe().unwrap(); + unsafe { + ISR_HANDLERS[(self.group() * timer_idx_t_TIMER_MAX + self.index()) as usize] = None; } + PIN_NOTIF[(self.group() * timer_idx_t_TIMER_MAX + self.index()) as usize].reset(); + esp!(unsafe { timer_deinit(self.group(), self.index()) }).unwrap(); } } -unsafe impl<'d> Send for TimerDriver<'d> {} +unsafe impl Send for TimerDriver<'_> {} -#[cfg(feature = "alloc")] -struct UnsafeCallback(*mut Box); +impl embedded_hal_async::delay::DelayNs for TimerDriver<'_> { + async fn delay_ns(&mut self, ns: u32) { + let counter = core::cmp::max((self.tick_hz() * ns as u64) / 1000000, 1); -#[cfg(feature = "alloc")] -impl UnsafeCallback { - #[allow(clippy::type_complexity)] - pub fn from(boxed: &mut Box>) -> Self { - Self(boxed.as_mut()) + self.delay(counter).await.unwrap(); } - pub unsafe fn from_ptr(ptr: *mut c_void) -> Self { - Self(ptr.cast()) - } - - pub fn as_ptr(&self) -> *mut c_void { - self.0.cast() - } - - pub unsafe fn call(&mut self) { - let reference = self.0.as_mut().unwrap(); + async fn delay_ms(&mut self, ms: u32) { + let counter = core::cmp::max((self.tick_hz() * ms as u64) / 1000, 1); - (reference)(); + self.delay(counter).await.unwrap(); } } @@ -327,18 +597,35 @@ macro_rules! impl_timer { } #[allow(clippy::type_complexity)] -#[cfg(esp32c3)] +#[cfg(not(any(esp32, esp32s2, esp32s3)))] #[cfg(feature = "alloc")] -static mut ISR_HANDLERS: [Option>>; 2] = [None, None]; +static mut ISR_HANDLERS: [Option>; 2] = [None, None]; #[allow(clippy::type_complexity)] -#[cfg(not(esp32c3))] +#[cfg(not(any(esp32, esp32s2, esp32s3)))] +pub(crate) static PIN_NOTIF: [crate::interrupt::asynch::HalIsrNotification; 2] = [ + crate::interrupt::asynch::HalIsrNotification::new(), + crate::interrupt::asynch::HalIsrNotification::new(), +]; + +#[allow(clippy::type_complexity)] +#[cfg(any(esp32, esp32s2, esp32s3))] #[cfg(feature = "alloc")] -static mut ISR_HANDLERS: [Option>>; 4] = [None, None, None, None]; +static mut ISR_HANDLERS: [Option>; 4] = [None, None, None, None]; + +#[allow(clippy::type_complexity)] +#[cfg(any(esp32, esp32s2, esp32s3))] +pub(crate) static PIN_NOTIF: [crate::interrupt::asynch::HalIsrNotification; 4] = [ + crate::interrupt::asynch::HalIsrNotification::new(), + crate::interrupt::asynch::HalIsrNotification::new(), + crate::interrupt::asynch::HalIsrNotification::new(), + crate::interrupt::asynch::HalIsrNotification::new(), +]; impl_timer!(TIMER00: timer_group_t_TIMER_GROUP_0, timer_idx_t_TIMER_0); -#[cfg(not(esp32c3))] +#[cfg(any(esp32, esp32s2, esp32s3))] impl_timer!(TIMER01: timer_group_t_TIMER_GROUP_0, timer_idx_t_TIMER_1); +#[cfg(not(esp32c2))] impl_timer!(TIMER10: timer_group_t_TIMER_GROUP_1, timer_idx_t_TIMER_0); -#[cfg(not(esp32c3))] -impl_timer!(TIMER11: timer_group_t_TIMER_GROUP_1, timer_idx_t_TIMER_0); +#[cfg(any(esp32, esp32s2, esp32s3))] +impl_timer!(TIMER11: timer_group_t_TIMER_GROUP_1, timer_idx_t_TIMER_1); diff --git a/src/uart.rs b/src/uart.rs index d493d23acad..9673a9108d4 100644 --- a/src/uart.rs +++ b/src/uart.rs @@ -1,6 +1,6 @@ -//! UART peripheral control -//! +//: QueueHandle_t ! UART peripheral control //! Controls UART peripherals (UART0, UART1, UART2). +//! //! Notice that UART0 is typically already used for loading firmware and logging. //! Therefore use UART1 and UART2 in your application. //! Any pin can be used for `rx` and `tx`. @@ -11,7 +11,7 @@ //! ``` //! use std::fmt::Write; //! use esp_idf_hal::prelude::*; -//! use esp_idf_hal::serial; +//! use esp_idf_hal::uart; //! //! let peripherals = Peripherals::take().unwrap(); //! let pins = peripherals.pins; @@ -22,8 +22,8 @@ //! peripherals.uart1, //! pins.gpio1, //! pins.gpio3, -//! None, -//! None, +//! Option::::None, +//! Option::::None, //! &config //! ).unwrap(); //! @@ -33,32 +33,70 @@ //! ``` //! //! # TODO -//! - Add all extra features esp32 supports (eg rs485, etc. etc.) +//! - Add all extra features esp32 supports //! - Free APB lock when TX is idle (and no RX used) //! - Address errata 3.17: UART fifo_cnt is inconsistent with FIFO pointer +use core::borrow::BorrowMut; +use core::ffi::CStr; use core::marker::PhantomData; use core::mem::ManuallyDrop; use core::ptr; use core::sync::atomic::{AtomicU8, Ordering}; -use crate::delay::NON_BLOCK; -use crate::gpio::*; +use crate::cpu::Core; +use crate::delay::{self, NON_BLOCK}; +use crate::interrupt::InterruptType; +use crate::io::EspIOError; +use crate::task::asynch::Notification; +use crate::task::queue::Queue; use crate::units::*; +use crate::{gpio::*, task}; +use embedded_hal_nb::serial::ErrorKind; use esp_idf_sys::*; use crate::peripheral::Peripheral; -const UART_FIFO_SIZE: i32 = SOC_UART_FIFO_LEN as i32; +const UART_FIFO_SIZE: usize = SOC_UART_FIFO_LEN as usize; pub type UartConfig = config::Config; /// UART configuration pub mod config { - use crate::units::*; + use crate::{interrupt::InterruptType, units::*}; + use enumset::{enum_set, EnumSet, EnumSetType}; use esp_idf_sys::*; + /// Mode + #[derive(PartialEq, Eq, Copy, Clone, Debug)] + pub enum Mode { + /// regular UART mode + UART, + /// half duplex RS485 UART mode control by RTS pin + RS485HalfDuplex, + } + + impl From for uart_mode_t { + fn from(mode: Mode) -> Self { + match mode { + Mode::UART => uart_mode_t_UART_MODE_UART, + Mode::RS485HalfDuplex => uart_mode_t_UART_MODE_RS485_HALF_DUPLEX, + } + } + } + + impl From for Mode { + #[allow(non_upper_case_globals)] + fn from(uart_mode: uart_mode_t) -> Self { + match uart_mode { + uart_mode_t_UART_MODE_UART => Mode::UART, + uart_mode_t_UART_MODE_RS485_HALF_DUPLEX => Mode::RS485HalfDuplex, + _ => unreachable!(), + } + } + } + /// Number of data bits #[derive(PartialEq, Eq, Copy, Clone, Debug)] pub enum DataBits { @@ -92,7 +130,7 @@ pub mod config { } } - /// Number of data bits + /// Flow control #[derive(PartialEq, Eq, Copy, Clone, Debug)] pub enum FlowControl { None, @@ -207,12 +245,44 @@ pub mod config { /// UART source clock from `XTAL` #[cfg(esp_idf_soc_uart_support_xtal_clk)] Crystal, + /// UART source clock from `XTAL` + #[allow(non_camel_case_types)] + #[cfg(esp_idf_soc_uart_support_pll_f80m_clk)] + PLL_F80M, /// UART source clock from `REF_TICK` #[cfg(esp_idf_soc_uart_support_ref_tick)] RefTick, } impl SourceClock { + pub const fn default() -> Self { + #[cfg(not(esp_idf_version_major = "4"))] + const DEFAULT: uart_sclk_t = soc_periph_uart_clk_src_legacy_t_UART_SCLK_DEFAULT; + #[cfg(esp_idf_version_major = "4")] + const DEFAULT: uart_sclk_t = uart_sclk_t_UART_SCLK_APB; + Self::from_raw(DEFAULT) + } + + pub const fn from_raw(source_clock: uart_sclk_t) -> Self { + match source_clock { + #[cfg(any( + esp_idf_soc_uart_support_apb_clk, + esp_idf_soc_uart_support_pll_f40m_clk, + esp_idf_version_major = "4", + ))] + APB_SCLK => SourceClock::APB, + #[cfg(esp_idf_soc_uart_support_rtc_clk)] + RTC_SCLK => SourceClock::RTC, + #[cfg(esp_idf_soc_uart_support_xtal_clk)] + XTAL_SCLK => SourceClock::Crystal, + #[cfg(esp_idf_soc_uart_support_pll_f80m_clk)] + PLL_F80M_SCLK => SourceClock::PLL_F80M, + #[cfg(esp_idf_soc_uart_support_ref_tick)] + REF_TICK_SCLK => SourceClock::RefTick, + _ => unreachable!(), + } + } + #[cfg(not(esp_idf_version_major = "4"))] pub fn frequency(self) -> Result { let mut frequency: u32 = 0; @@ -244,6 +314,14 @@ pub mod config { #[cfg(all(esp_idf_version_major = "4", esp_idf_soc_uart_support_xtal_clk))] const XTAL_SCLK: uart_sclk_t = uart_sclk_t_UART_SCLK_XTAL; + #[cfg(all( + not(esp_idf_version_major = "4"), + esp_idf_soc_uart_support_pll_f80m_clk + ))] + const PLL_F80M_SCLK: uart_sclk_t = soc_periph_uart_clk_src_legacy_t_UART_SCLK_PLL_F80M; + #[cfg(all(esp_idf_version_major = "4", esp_idf_soc_uart_support_pll_f80m_clk))] + const PLL_F80M_SCLK: uart_sclk_t = uart_sclk_t_UART_SCLK_PLL_F80M; + #[cfg(all(not(esp_idf_version_major = "4"), esp_idf_soc_uart_support_ref_tick))] const REF_TICK_SCLK: uart_sclk_t = soc_periph_uart_clk_src_legacy_t_UART_SCLK_REF_TICK; #[cfg(all(esp_idf_version_major = "4", esp_idf_soc_uart_support_ref_tick))] @@ -251,11 +329,7 @@ pub mod config { impl Default for SourceClock { fn default() -> Self { - #[cfg(not(esp_idf_version_major = "4"))] - const DEFAULT: uart_sclk_t = soc_periph_uart_clk_src_legacy_t_UART_SCLK_DEFAULT; - #[cfg(esp_idf_version_major = "4")] - const DEFAULT: uart_sclk_t = uart_sclk_t_UART_SCLK_APB; - Self::from(DEFAULT) + SourceClock::default() } } @@ -272,6 +346,8 @@ pub mod config { SourceClock::RTC => RTC_SCLK, #[cfg(esp_idf_soc_uart_support_xtal_clk)] SourceClock::Crystal => XTAL_SCLK, + #[cfg(esp_idf_soc_uart_support_pll_f80m_clk)] + SourceClock::PLL_F80M => PLL_F80M_SCLK, #[cfg(esp_idf_soc_uart_support_ref_tick)] SourceClock::RefTick => REF_TICK_SCLK, } @@ -280,27 +356,130 @@ pub mod config { impl From for SourceClock { fn from(source_clock: uart_sclk_t) -> Self { - match source_clock { - #[cfg(any( - esp_idf_soc_uart_support_apb_clk, - esp_idf_soc_uart_support_pll_f40m_clk, - esp_idf_version_major = "4", - ))] - APB_SCLK => SourceClock::APB, - #[cfg(esp_idf_soc_uart_support_rtc_clk)] - RTC_SCLK => SourceClock::RTC, - #[cfg(esp_idf_soc_uart_support_xtal_clk)] - XTAL_SCLK => SourceClock::Crystal, - #[cfg(esp_idf_soc_uart_support_ref_tick)] - REF_TICK_SCLK => SourceClock::RefTick, - _ => unreachable!(), + Self::from_raw(source_clock) + } + } + + /// Configures the interrupts the UART driver should enable + /// in order to be able to quickly inform us about the + /// related event. + #[derive(Debug, Clone)] + pub struct EventConfig { + /// If `Some(number_of_words)`, an interrupt will trigger + /// after `number_of_words` could have been transmitted + /// (unit is baudrate dependant). + /// + /// If `None` or `Some(0)` interrupt will be disabled. + pub receive_timeout: Option, + /// Sets the threshold at which an interrupt will + /// be generated (the hardware receive FIFO contains more words than + /// this number). + /// + /// If set to `None` interrupt will be disabled. + pub rx_fifo_full: Option, + /// Sets the threshold **below** which an interrupt will + /// be generated (the hardware transmit FIFO contains less words than + /// this number). + /// + /// If set to `None` interrupt will be disabled. + /// Should not be set to `0` as the interrupt will trigger constantly. + pub tx_fifo_empty: Option, + /// Other interrupts to enable + pub flags: EnumSet, + /// Allow using struct syntax, + /// but signal users other fields may be added + /// so `..Default::default()` should be used. + #[doc(hidden)] + pub _non_exhaustive: (), + } + + impl EventConfig { + pub const fn new() -> Self { + EventConfig { + receive_timeout: Some(10), + rx_fifo_full: Some(120), + tx_fifo_empty: Some(10), + flags: enum_set!( + EventFlags::RxFifoFull + | EventFlags::RxFifoTimeout + | EventFlags::RxFifoOverflow + | EventFlags::BreakDetected + | EventFlags::ParityError + ), + _non_exhaustive: (), } } } + impl Default for EventConfig { + fn default() -> Self { + EventConfig::new() + } + } + + impl From for crate::sys::uart_intr_config_t { + fn from(cfg: EventConfig) -> Self { + let mut intr_enable_mask = cfg.flags; + + if cfg.receive_timeout.map(|to| to > 0).unwrap_or(false) { + intr_enable_mask.insert(EventFlags::RxFifoTimeout); + } else { + intr_enable_mask.remove(EventFlags::RxFifoTimeout); + } + + if cfg.rx_fifo_full.is_some() { + intr_enable_mask.insert(EventFlags::RxFifoFull); + } else { + intr_enable_mask.remove(EventFlags::RxFifoFull); + } + + if cfg.tx_fifo_empty.is_some() { + intr_enable_mask.insert(EventFlags::TxFifoEmpty); + } else { + intr_enable_mask.remove(EventFlags::TxFifoEmpty); + } + + crate::sys::uart_intr_config_t { + intr_enable_mask: intr_enable_mask.as_repr(), + rx_timeout_thresh: cfg.receive_timeout.unwrap_or(0), + txfifo_empty_intr_thresh: cfg.tx_fifo_empty.unwrap_or(0), + rxfifo_full_thresh: cfg.rx_fifo_full.unwrap_or(0), + } + } + } + + #[derive(Debug, EnumSetType)] + #[enumset(repr = "u32")] + #[non_exhaustive] + pub enum EventFlags { + #[doc(hidden)] + RxFifoFull = 0, + #[doc(hidden)] + TxFifoEmpty = 1, + ParityError = 2, + FrameError = 3, + RxFifoOverflow = 4, + DsrChange = 5, + CtsChange = 6, + BreakDetected = 7, + #[doc(hidden)] + RxFifoTimeout = 8, + SwXon = 9, + SwXoff = 10, + GlitchDetected = 11, + TxBreakDone = 12, + TxBreakIdle = 13, + TxDone = 14, + Rs485ParityError = 15, + Rs485FrameError = 16, + Rs485Clash = 17, + CmdCharDetected = 18, + } + /// UART configuration - #[derive(Debug, Copy, Clone)] + #[derive(Debug, Clone)] pub struct Config { + pub mode: Mode, pub baudrate: Hertz, pub data_bits: DataBits, pub parity: Parity, @@ -308,11 +487,83 @@ pub mod config { pub flow_control: FlowControl, pub flow_control_rts_threshold: u8, pub source_clock: SourceClock, + /// Configures the flags to use for interrupt allocation, + /// e.g. priority to use for the interrupt. + /// + /// Note that you should not set `Iram` here, because it will + /// be automatically set depending on the value of `CONFIG_UART_ISR_IN_IRAM`. + pub intr_flags: EnumSet, + /// Configures the interrupts the driver should enable. + pub event_config: EventConfig, + /// The size of the software rx buffer. Must be bigger than the hardware FIFO. + pub rx_fifo_size: usize, + /// The size of the software tx buffer. Must be bigger than the hardware FIFO + /// or 0 to disable transmit buffering (note that this will make write operations + /// block until data has been sent out). + pub tx_fifo_size: usize, + /// Number of events that should fit into the event queue. + /// Specify 0 to prevent the creation of an event queue. + pub queue_size: usize, + /// Allow using struct syntax, + /// but signal users other fields may be added + /// so `..Default::default()` should be used. + #[doc(hidden)] + pub _non_exhaustive: (), + } + + impl From<&Config> for uart_config_t { + fn from(config: &Config) -> Self { + #[allow(clippy::needless_update)] + Self { + baud_rate: config.baudrate.0 as i32, + data_bits: config.data_bits.into(), + parity: config.parity.into(), + stop_bits: config.stop_bits.into(), + flow_ctrl: config.flow_control.into(), + rx_flow_ctrl_thresh: config.flow_control_rts_threshold, + // ESP-IDF 5.0 and 5.1 + #[cfg(all( + esp_idf_version_major = "5", + any(esp_idf_version_minor = "0", esp_idf_version_minor = "1") + ))] + source_clk: config.source_clock.into(), + // All others + #[cfg(not(all( + esp_idf_version_major = "5", + any(esp_idf_version_minor = "0", esp_idf_version_minor = "1") + )))] + __bindgen_anon_1: uart_config_t__bindgen_ty_1 { + source_clk: config.source_clock.into(), + }, + ..Default::default() + } + } } impl Config { - pub fn new() -> Self { - Default::default() + pub const fn new() -> Config { + Config { + mode: Mode::UART, + baudrate: Hertz(115_200), + data_bits: DataBits::DataBits8, + parity: Parity::ParityNone, + stop_bits: StopBits::STOP1, + flow_control: FlowControl::None, + flow_control_rts_threshold: 122, + source_clock: SourceClock::default(), + intr_flags: EnumSet::empty(), + event_config: EventConfig::new(), + rx_fifo_size: super::UART_FIFO_SIZE * 2, + tx_fifo_size: super::UART_FIFO_SIZE * 2, + queue_size: 10, + _non_exhaustive: (), + } + } + + #[must_use] + pub fn mode(mut self, mode: Mode) -> Self { + self.mode = mode; + self } #[must_use] @@ -371,19 +622,29 @@ pub mod config { self.source_clock = source_clock; self } + + #[must_use] + pub fn tx_fifo_size(mut self, tx_fifo_size: usize) -> Self { + self.tx_fifo_size = tx_fifo_size; + self + } + + #[must_use] + pub fn rx_fifo_size(mut self, rx_fifo_size: usize) -> Self { + self.rx_fifo_size = rx_fifo_size; + self + } + + #[must_use] + pub fn queue_size(mut self, queue_size: usize) -> Self { + self.queue_size = queue_size; + self + } } impl Default for Config { fn default() -> Config { - Config { - baudrate: Hertz(19_200), - data_bits: DataBits::DataBits8, - parity: Parity::ParityNone, - stop_bits: StopBits::STOP1, - flow_control: FlowControl::None, - flow_control_rts_threshold: 122, - source_clock: SourceClock::default(), - } + Config::new() } } } @@ -394,20 +655,75 @@ pub trait Uart { crate::embedded_hal_error!( SerialError, - embedded_hal::serial::Error, - embedded_hal::serial::ErrorKind + embedded_hal_nb::serial::Error, + embedded_hal_nb::serial::ErrorKind ); +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct UartEvent { + raw: uart_event_t, +} + +impl UartEvent { + pub fn payload(&self) -> UartEventPayload { + #[allow(non_upper_case_globals)] + match self.raw.type_ { + uart_event_type_t_UART_DATA => UartEventPayload::Data { + size: self.raw.size, + timeout: self.raw.timeout_flag, + }, + uart_event_type_t_UART_BREAK => UartEventPayload::Break, + uart_event_type_t_UART_BUFFER_FULL => UartEventPayload::RxBufferFull, + uart_event_type_t_UART_FIFO_OVF => UartEventPayload::RxFifoOverflow, + uart_event_type_t_UART_FRAME_ERR => UartEventPayload::FrameError, + uart_event_type_t_UART_PARITY_ERR => UartEventPayload::ParityError, + uart_event_type_t_UART_DATA_BREAK => UartEventPayload::DataBreak, + uart_event_type_t_UART_PATTERN_DET => UartEventPayload::PatternDetected, + _ => UartEventPayload::Unknown, + } + } +} + +#[derive(Clone, Copy, Debug)] +#[non_exhaustive] +pub enum UartEventPayload { + /// UART data was received and/or a timeout was triggered + Data { + /// The number of bytes received + size: usize, + /// Whether a timeout has occurred. + /// It is possible that bytes have been received + /// and this is set to `true` in case the driver + /// processed both interrupts at the same time. + timeout: bool, + }, + /// Represents DATA event with timeout_flag set + Break, + RxBufferFull, + RxFifoOverflow, + FrameError, + ParityError, + DataBreak, + PatternDetected, + Unknown, +} + /// Serial abstraction pub struct UartDriver<'d> { port: u8, + queue: Option>, _p: PhantomData<&'d mut ()>, } +unsafe impl Send for UartDriver<'_> {} +unsafe impl Sync for UartDriver<'_> {} + /// Serial receiver pub struct UartRxDriver<'d> { port: u8, owner: Owner, + queue: Option>, _p: PhantomData<&'d mut ()>, } @@ -415,6 +731,7 @@ pub struct UartRxDriver<'d> { pub struct UartTxDriver<'d> { port: u8, owner: Owner, + queue: Option>, _p: PhantomData<&'d mut ()>, } @@ -428,14 +745,34 @@ impl<'d> UartDriver<'d> { rts: Option + 'd>, config: &config::Config, ) -> Result { - new_common(uart, Some(tx), Some(rx), cts, rts, config)?; + let mut q_handle_raw = ptr::null_mut(); + let q_handle = if config.queue_size > 0 { + Some(&mut q_handle_raw) + } else { + None + }; + new_common(uart, Some(tx), Some(rx), cts, rts, config, q_handle)?; + + // SAFTEY: okay because Queue borrows self + // SAFETY: we can safely use UartEvent instead of uart_event_t because of repr(transparent) + let queue = match q_handle_raw.is_null() { + false => Some(unsafe { Queue::new_borrowed(q_handle_raw) }), + true => None, + }; Ok(Self { port: UART::port() as _, + queue, _p: PhantomData, }) } + /// Retrieves the event queue for this UART. Returns `None` if + /// the config specified 0 for `queue_size`. + pub fn event_queue(&self) -> Option<&Queue> { + self.queue.as_ref() + } + /// Change the number of stop bits pub fn change_stop_bits(&self, stop_bits: config::StopBits) -> Result<&Self, EspError> { change_stop_bits(self.port(), stop_bits).map(|_| self) @@ -483,16 +820,24 @@ impl<'d> UartDriver<'d> { } /// Split the serial driver in separate TX and RX drivers - pub fn split(&self) -> (UartTxDriver<'_>, UartRxDriver<'_>) { + pub fn split(&mut self) -> (UartTxDriver<'_>, UartRxDriver<'_>) { ( UartTxDriver { port: self.port, owner: Owner::Borrowed, + queue: self + .queue + .as_ref() + .map(|queue| unsafe { Queue::new_borrowed(queue.as_raw()) }), _p: PhantomData, }, UartRxDriver { port: self.port, owner: Owner::Borrowed, + queue: self + .queue + .as_ref() + .map(|queue| unsafe { Queue::new_borrowed(queue.as_raw()) }), _p: PhantomData, }, ) @@ -503,38 +848,75 @@ impl<'d> UartDriver<'d> { /// Unlike [`split`], the halves are owned and reference counted. pub fn into_split(self) -> (UartTxDriver<'d>, UartRxDriver<'d>) { let port = self.port; + let tx_queue = self + .queue + .as_ref() + .map(|queue| unsafe { Queue::new_borrowed(queue.as_raw()) }); + let rx_queue = self + .queue + .as_ref() + .map(|queue| unsafe { Queue::new_borrowed(queue.as_raw()) }); let _ = ManuallyDrop::new(self); REFS[port as usize].fetch_add(2, Ordering::SeqCst); ( UartTxDriver { port, owner: Owner::Shared, + queue: tx_queue, _p: PhantomData, }, UartRxDriver { port, owner: Owner::Shared, + queue: rx_queue, _p: PhantomData, }, ) } /// Read multiple bytes into a slice - pub fn read(&self, buf: &mut [u8], delay: TickType_t) -> Result { - self.rx().read(buf, delay) + pub fn read(&self, buf: &mut [u8], timeout: TickType_t) -> Result { + self.rx().read(buf, timeout) } /// Write multiple bytes from a slice - pub fn write(&self, buf: &[u8]) -> Result { - self.tx().write(buf) + pub fn write(&self, bytes: &[u8]) -> Result { + self.tx().write(bytes) } + /// Write multiple bytes from a slice directly to the TX FIFO hardware. + /// Returns the number of bytes written, where 0 would mean that the TX FIFO is full. + /// + /// NOTE: In case the UART TX buffer is enabled, this method might have unpredictable results + /// when used together with method `write`, as the latter will push the data to be sent to the + /// TX buffer first. + /// + /// To avoid this, always call `wait_done` after the last call to `write` and before + /// calling this method. + pub fn write_nb(&self, bytes: &[u8]) -> Result { + self.tx().write_nb(bytes) + } + + /// Clears the receive buffer. + #[deprecated(since = "0.41.3", note = "Use UartDriver::clear_rx instead")] pub fn flush_read(&self) -> Result<(), EspError> { - self.rx().flush() + self.rx().clear() + } + + /// Clears the receive buffer. + pub fn clear_rx(&self) -> Result<(), EspError> { + self.rx().clear() } + /// Waits for the transmission to complete. + #[deprecated(since = "0.41.3", note = "Use UartDriver::wait_tx_done instead")] pub fn flush_write(&self) -> Result<(), EspError> { - self.tx().flush() + self.tx().wait_done(delay::BLOCK) + } + + /// Waits until the transmission is complete or until the specified timeout expires. + pub fn wait_tx_done(&self, timeout: TickType_t) -> Result<(), EspError> { + self.tx().wait_done(timeout) } pub fn port(&self) -> uart_port_t { @@ -562,6 +944,10 @@ impl<'d> UartDriver<'d> { ManuallyDrop::new(UartRxDriver { port: self.port, owner: Owner::Borrowed, + queue: self + .queue + .as_ref() + .map(|queue| unsafe { Queue::new_borrowed(queue.as_raw()) }), _p: PhantomData, }) } @@ -570,36 +956,50 @@ impl<'d> UartDriver<'d> { ManuallyDrop::new(UartTxDriver { port: self.port, owner: Owner::Borrowed, + queue: self + .queue + .as_ref() + .map(|queue| unsafe { Queue::new_borrowed(queue.as_raw()) }), _p: PhantomData, }) } } -impl<'d> Drop for UartDriver<'d> { +impl Drop for UartDriver<'_> { fn drop(&mut self) { delete_driver(self.port()).unwrap(); } } -impl<'d> embedded_hal::serial::ErrorType for UartDriver<'d> { - type Error = SerialError; +impl embedded_io::ErrorType for UartDriver<'_> { + type Error = EspIOError; } -impl<'d> embedded_hal_0_2::serial::Read for UartDriver<'d> { - type Error = SerialError; +impl embedded_io::Read for UartDriver<'_> { + fn read(&mut self, buf: &mut [u8]) -> Result { + UartDriver::read(self, buf, delay::BLOCK).map_err(EspIOError) + } +} - fn read(&mut self) -> nb::Result { - embedded_hal_0_2::serial::Read::read(&mut *self.rx()) +impl embedded_io::Write for UartDriver<'_> { + fn write(&mut self, buf: &[u8]) -> Result { + UartDriver::write(self, buf).map_err(EspIOError) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + UartDriver::wait_tx_done(self, delay::BLOCK).map_err(EspIOError) } } -impl<'d> embedded_hal_nb::serial::Read for UartDriver<'d> { +impl embedded_hal_0_2::serial::Read for UartDriver<'_> { + type Error = SerialError; + fn read(&mut self) -> nb::Result { - embedded_hal_nb::serial::Read::read(&mut *self.rx()) + embedded_hal_0_2::serial::Read::read(&mut *self.rx()) } } -impl<'d> embedded_hal_0_2::serial::Write for UartDriver<'d> { +impl embedded_hal_0_2::serial::Write for UartDriver<'_> { type Error = SerialError; fn flush(&mut self) -> nb::Result<(), Self::Error> { @@ -611,26 +1011,32 @@ impl<'d> embedded_hal_0_2::serial::Write for UartDriver<'d> { } } -impl<'d> embedded_hal_nb::serial::Write for UartDriver<'d> { - fn flush(&mut self) -> nb::Result<(), Self::Error> { - embedded_hal_nb::serial::Write::flush(&mut *self.tx()) +impl embedded_hal_nb::serial::ErrorType for UartDriver<'_> { + type Error = SerialError; +} + +impl embedded_hal_nb::serial::Read for UartDriver<'_> { + fn read(&mut self) -> nb::Result { + embedded_hal_nb::serial::Read::read(&mut *self.rx()) } +} +impl embedded_hal_nb::serial::Write for UartDriver<'_> { fn write(&mut self, byte: u8) -> nb::Result<(), Self::Error> { embedded_hal_nb::serial::Write::write(&mut *self.tx(), byte) } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + embedded_hal_nb::serial::Write::flush(&mut *self.tx()) + } } -impl<'d> core::fmt::Write for UartDriver<'d> { +impl core::fmt::Write for UartDriver<'_> { fn write_str(&mut self, s: &str) -> core::fmt::Result { self.tx().write_str(s) } } -impl<'d> embedded_hal::serial::ErrorType for UartRxDriver<'d> { - type Error = SerialError; -} - impl<'d> UartRxDriver<'d> { /// Create a new serial receiver pub fn new( @@ -640,15 +1046,43 @@ impl<'d> UartRxDriver<'d> { rts: Option + 'd>, config: &config::Config, ) -> Result { - new_common(uart, None::, Some(rx), cts, rts, config)?; + let mut q_handle_raw = ptr::null_mut(); + let q_handle = if config.queue_size > 0 { + Some(&mut q_handle_raw) + } else { + None + }; + new_common( + uart, + None::, + Some(rx), + cts, + rts, + config, + q_handle, + )?; + + // SAFTEY: okay because Queue borrows self + // SAFETY: we can safely use UartEvent instead of uart_event_t because of repr(transparent) + let queue = match q_handle_raw.is_null() { + false => Some(unsafe { Queue::new_borrowed(q_handle_raw) }), + true => None, + }; Ok(Self { port: UART::port() as _, owner: Owner::Owned, + queue, _p: PhantomData, }) } + /// Retrieves the event queue for this UART. Returns `None` if + /// the config specified 0 for `queue_size`. + pub fn event_queue(&self) -> Option<&Queue> { + self.queue.as_ref() + } + /// Change the number of stop bits pub fn change_stop_bits(&self, stop_bits: config::StopBits) -> Result<&Self, EspError> { change_stop_bits(self.port(), stop_bits).map(|_| self) @@ -696,26 +1130,81 @@ impl<'d> UartRxDriver<'d> { } /// Read multiple bytes into a slice; block until specified timeout + /// Returns: + /// - `Ok(0)` if the buffer is of length 0 + /// - `Ok(n)` if `n` bytes were read, where n is > 0 + /// - `Err(EspError::Timeout)` if no bytes were read within the specified timeout pub fn read(&self, buf: &mut [u8], delay: TickType_t) -> Result { - // uart_read_bytes() returns error (-1) or how many bytes were read out - // 0 means timeout and nothing is yet read out + // `uart_read_bytes` has a WEIRD semantics: + // - If the data in the internal ring-buffer is LESS than the passed `length` + // **it will wait (with a `delay` timeout) UNTIL it can return up to `length` bytes** + // (and if the timeout had expired, it will return whatever it was able to read - possibly nothing too) + // - This is not matching the typical `read` syscall semantics where it only + // returns what is available in the internal buffer and does not wait for more; + // and only blocks if the internal buffer is empty, and only until _some_ data becomes available + // but NOT until `buf.len()` data is available. + // + // Therefore - and to avoid confusion - we will implement the typical `read` syscall + // semantics here + + // Passing an empty buffer is valid, but it means we'll always read 0 bytes + if buf.is_empty() { + return Ok(0); + } + + // First try to read without blocking let len = unsafe { uart_read_bytes( self.port(), buf.as_mut_ptr().cast(), buf.len() as u32, - delay, + delay::NON_BLOCK, ) }; - if len >= 0 { - Ok(len as usize) - } else { - Err(EspError::from(ESP_ERR_INVALID_STATE).unwrap()) + if len > 0 || delay == delay::NON_BLOCK { + // Some data was read, or the user requested a non-blocking read anyway + return match len { + -1 | 0 => Err(EspError::from_infallible::()), + len => Ok(len as usize), + }; + } + + // Now block until at least one byte is available + let mut len = + unsafe { uart_read_bytes(self.port(), buf.as_mut_ptr().cast(), 1_u32, delay) }; + + if len > 0 && buf.len() > 1 { + // Try to read more than that one byte in a non-blocking way + // just because we can, and this lowers the latency of `read`. + // To comply with the `read` syscall semantics we don't have to necessarily do this + let extra_len = unsafe { + uart_read_bytes( + self.port(), + buf[1..].as_mut_ptr().cast(), + (buf.len() - 1) as u32, + delay::NON_BLOCK, + ) + }; + + if extra_len > 0 { + len += extra_len; + } + } + + match len { + -1 | 0 => Err(EspError::from_infallible::()), + len => Ok(len as usize), } } + /// Clears the receive buffer. + #[deprecated(since = "0.41.3", note = "Use `UartRxDriver::clear` instead")] pub fn flush(&self) -> Result<(), EspError> { + self.clear() + } + + pub fn clear(&self) -> Result<(), EspError> { esp!(unsafe { uart_flush_input(self.port()) })?; Ok(()) @@ -731,13 +1220,23 @@ impl<'d> UartRxDriver<'d> { } } -impl<'d> Drop for UartRxDriver<'d> { +impl Drop for UartRxDriver<'_> { fn drop(&mut self) { self.owner.drop_impl(self.port()).unwrap() } } -impl<'d> embedded_hal_0_2::serial::Read for UartRxDriver<'d> { +impl embedded_io::ErrorType for UartRxDriver<'_> { + type Error = EspIOError; +} + +impl embedded_io::Read for UartRxDriver<'_> { + fn read(&mut self, buf: &mut [u8]) -> Result { + UartRxDriver::read(self, buf, delay::BLOCK).map_err(EspIOError) + } +} + +impl embedded_hal_0_2::serial::Read for UartRxDriver<'_> { type Error = SerialError; fn read(&mut self) -> nb::Result { @@ -749,7 +1248,11 @@ impl<'d> embedded_hal_0_2::serial::Read for UartRxDriver<'d> { } } -impl<'d> embedded_hal_nb::serial::Read for UartRxDriver<'d> { +impl embedded_hal_nb::serial::ErrorType for UartRxDriver<'_> { + type Error = SerialError; +} + +impl embedded_hal_nb::serial::Read for UartRxDriver<'_> { fn read(&mut self) -> nb::Result { let mut buf = [0_u8]; @@ -768,15 +1271,43 @@ impl<'d> UartTxDriver<'d> { rts: Option + 'd>, config: &config::Config, ) -> Result { - new_common(uart, Some(tx), None::, cts, rts, config)?; + let mut q_handle_raw = ptr::null_mut(); + let q_handle = if config.queue_size > 0 { + Some(&mut q_handle_raw) + } else { + None + }; + new_common( + uart, + Some(tx), + None::, + cts, + rts, + config, + q_handle, + )?; + + // SAFTEY: okay because Queue borrows self + // SAFETY: we can safely use UartEvent instead of uart_event_t because of repr(transparent) + let queue = match q_handle_raw.is_null() { + false => Some(unsafe { Queue::new_borrowed(q_handle_raw) }), + true => None, + }; Ok(Self { port: UART::port() as _, owner: Owner::Owned, + queue, _p: PhantomData, }) } + /// Retrieves the event queue for this UART. Returns `None` if + /// the config specified 0 for `queue_size`. + pub fn event_queue(&self) -> Option<&Queue> { + self.queue.as_ref() + } + /// Change the number of stop bits pub fn change_stop_bits(&self, stop_bits: config::StopBits) -> Result<&Self, EspError> { change_stop_bits(self.port(), stop_bits).map(|_| self) @@ -831,16 +1362,42 @@ impl<'d> UartTxDriver<'d> { if len >= 0 { Ok(len as usize) } else { - Err(EspError::from(ESP_ERR_INVALID_STATE).unwrap()) + Err(EspError::from_infallible::()) } } - pub fn flush(&mut self) -> Result<(), EspError> { - esp!(unsafe { uart_wait_tx_done(self.port(), 0) })?; + /// Write multiple bytes from a slice directly to the TX FIFO hardware. + /// Returns the number of bytes written, where 0 would mean that the TX FIFO is full. + /// + /// NOTE: In case the UART TX buffer is enabled, this method might have unpredictable results + /// when used together with method `write`, as the latter will push the data to be sent to the + /// TX buffer first. + /// + /// To avoid this, always call `wait_done` after the last call to `write` and before + /// calling this method. + pub fn write_nb(&self, bytes: &[u8]) -> Result { + let ret = unsafe { uart_tx_chars(self.port(), bytes.as_ptr().cast(), bytes.len() as _) }; + + if ret < 0 { + esp!(ret)?; + } + + Ok(ret as usize) + } + + /// Waits until the transmission is complete or until the specified timeout expires. + pub fn wait_done(&self, timeout: TickType_t) -> Result<(), EspError> { + esp!(unsafe { uart_wait_tx_done(self.port(), timeout) })?; Ok(()) } + /// Waits until the transmission is complete. + #[deprecated(since = "0.41.3", note = "Use `UartTxDriver::wait_done` instead")] + pub fn flush(&mut self) -> Result<(), EspError> { + self.wait_done(delay::BLOCK) + } + pub fn port(&self) -> uart_port_t { self.port as _ } @@ -858,39 +1415,53 @@ impl<'d> UartTxDriver<'d> { } } -impl<'d> Drop for UartTxDriver<'d> { +impl Drop for UartTxDriver<'_> { fn drop(&mut self) { self.owner.drop_impl(self.port()).unwrap() } } -impl<'d> embedded_hal::serial::ErrorType for UartTxDriver<'d> { - type Error = SerialError; +impl embedded_io::Write for UartTxDriver<'_> { + fn write(&mut self, buf: &[u8]) -> Result { + UartTxDriver::write(self, buf).map_err(EspIOError) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + UartTxDriver::wait_done(self, delay::BLOCK).map_err(EspIOError) + } +} + +impl embedded_io::ErrorType for UartTxDriver<'_> { + type Error = EspIOError; } -impl<'d> embedded_hal_0_2::serial::Write for UartTxDriver<'d> { +impl embedded_hal_0_2::serial::Write for UartTxDriver<'_> { type Error = SerialError; fn flush(&mut self) -> nb::Result<(), Self::Error> { - UartTxDriver::flush(self).map_err(to_nb_err) + check_nb_timeout(UartTxDriver::wait_done(self, delay::NON_BLOCK)) } fn write(&mut self, byte: u8) -> nb::Result<(), Self::Error> { - check_nb(UartTxDriver::write(self, &[byte]), ()) + check_nb(UartTxDriver::write_nb(self, &[byte]), ()) } } -impl<'d> embedded_hal_nb::serial::Write for UartTxDriver<'d> { +impl embedded_hal_nb::serial::ErrorType for UartTxDriver<'_> { + type Error = SerialError; +} + +impl embedded_hal_nb::serial::Write for UartTxDriver<'_> { fn flush(&mut self) -> nb::Result<(), Self::Error> { - UartTxDriver::flush(self).map_err(to_nb_err) + check_nb_timeout(UartTxDriver::wait_done(self, delay::NON_BLOCK)) } fn write(&mut self, byte: u8) -> nb::Result<(), Self::Error> { - check_nb(UartTxDriver::write(self, &[byte]), ()) + check_nb(UartTxDriver::write_nb(self, &[byte]), ()) } } -impl<'d> core::fmt::Write for UartTxDriver<'d> { +impl core::fmt::Write for UartTxDriver<'_> { fn write_str(&mut self, s: &str) -> core::fmt::Result { let buf = s.as_bytes(); let mut offset = 0; @@ -903,6 +1474,491 @@ impl<'d> core::fmt::Write for UartTxDriver<'d> { } } +pub struct AsyncUartDriver<'d, T> +where + T: BorrowMut>, +{ + driver: T, + task: TaskHandle_t, + _data: PhantomData<&'d ()>, +} + +impl<'d> AsyncUartDriver<'d, UartDriver<'d>> { + pub fn new( + uart: impl Peripheral

+ 'd, + tx: impl Peripheral

+ 'd, + rx: impl Peripheral

+ 'd, + cts: Option + 'd>, + rts: Option + 'd>, + config: &config::Config, + ) -> Result { + Self::wrap(UartDriver::new(uart, tx, rx, cts, rts, config)?) + } +} + +impl<'d, T> AsyncUartDriver<'d, T> +where + T: BorrowMut>, +{ + pub fn wrap(driver: T) -> Result { + Self::wrap_custom(driver, None, None) + } + + pub fn wrap_custom( + driver: T, + priority: Option, + pin_to_core: Option, + ) -> Result { + let task = new_task_common( + driver.borrow().port, + driver.borrow().event_queue(), + priority, + pin_to_core, + )?; + + Ok(Self { + driver, + task, + _data: PhantomData, + }) + } + + pub fn driver(&self) -> &UartDriver<'d> { + self.driver.borrow() + } + + pub fn driver_mut(&mut self) -> &mut UartDriver<'d> { + self.driver.borrow_mut() + } + + /// Split the serial driver in separate TX and RX drivers + pub fn split( + &mut self, + ) -> ( + AsyncUartTxDriver<'_, UartTxDriver<'_>>, + AsyncUartRxDriver<'_, UartRxDriver<'_>>, + ) { + let (tx, rx) = self.driver_mut().split(); + + ( + AsyncUartTxDriver { + driver: tx, + task: None, + _data: PhantomData, + }, + AsyncUartRxDriver { + driver: rx, + task: None, + _data: PhantomData, + }, + ) + } + + pub async fn read(&self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + Ok(0) + } else { + loop { + let res = self.driver.borrow().read(buf, delay::NON_BLOCK); + + match res { + Ok(len) if len > 0 => return Ok(len), + Err(e) if e.code() != ESP_ERR_TIMEOUT => return Err(e), + _ => (), + } + + let port = self.driver.borrow().port as usize; + READ_NOTIFS[port].wait().await; + } + } + } + + pub async fn write(&self, bytes: &[u8]) -> Result { + if bytes.is_empty() { + Ok(0) + } else { + loop { + let res = self.driver.borrow().write_nb(bytes); + + match res { + Ok(len) if len > 0 => return Ok(len), + Err(e) => return Err(e), + _ => (), + } + + // We cannot properly wait for the TX FIFO queue to become non-full + // because the ESP IDF UART ISR does not notify us on that + // + // Instead, spin a busy loop, however still allowing other futures to be polled too. + crate::task::yield_now().await; + } + } + } + + pub async fn wait_tx_done(&self) -> Result<(), EspError> { + loop { + let res = self.driver.borrow().wait_tx_done(delay::NON_BLOCK); + + match res { + Ok(()) => return Ok(()), + Err(e) if e.code() != ESP_ERR_TIMEOUT => return Err(e), + _ => (), + } + + // We cannot properly wait for the TX FIFO queue to become empty + // because the ESP IDF UART ISR does not notify us on that + // + // Instead, spin a busy loop, however still allowing other futures to be polled too. + crate::task::yield_now().await; + } + } +} + +unsafe impl<'d, T> Send for AsyncUartDriver<'d, T> where T: BorrowMut> + Send {} +unsafe impl<'d, T> Sync for AsyncUartDriver<'d, T> where T: BorrowMut> + Send + Sync {} + +impl<'d, T> Drop for AsyncUartDriver<'d, T> +where + T: BorrowMut>, +{ + fn drop(&mut self) { + drop_task_common(self.task, self.driver.borrow().port); + } +} + +impl<'d, T> embedded_io::ErrorType for AsyncUartDriver<'d, T> +where + T: BorrowMut>, +{ + type Error = EspIOError; +} + +impl<'d, T> embedded_io_async::Read for AsyncUartDriver<'d, T> +where + T: BorrowMut>, +{ + async fn read(&mut self, buf: &mut [u8]) -> Result { + AsyncUartDriver::read(self, buf).await.map_err(EspIOError) + } +} + +impl<'d, T> embedded_io_async::Write for AsyncUartDriver<'d, T> +where + T: BorrowMut>, +{ + async fn write(&mut self, buf: &[u8]) -> Result { + AsyncUartDriver::write(self, buf).await.map_err(EspIOError) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + AsyncUartDriver::wait_tx_done(self) + .await + .map_err(EspIOError) + } +} + +pub struct AsyncUartRxDriver<'d, T> +where + T: BorrowMut>, +{ + driver: T, + task: Option, + _data: PhantomData<&'d ()>, +} + +impl<'d> AsyncUartRxDriver<'d, UartRxDriver<'d>> { + pub fn new( + uart: impl Peripheral

+ 'd, + rx: impl Peripheral

+ 'd, + cts: Option + 'd>, + rts: Option + 'd>, + config: &config::Config, + ) -> Result { + Self::wrap(UartRxDriver::new(uart, rx, cts, rts, config)?) + } +} + +impl<'d, T> AsyncUartRxDriver<'d, T> +where + T: BorrowMut>, +{ + pub fn wrap(driver: T) -> Result { + Self::wrap_custom(driver, None, None) + } + + pub fn wrap_custom( + driver: T, + priority: Option, + pin_to_core: Option, + ) -> Result { + let task = new_task_common( + driver.borrow().port, + driver.borrow().event_queue(), + priority, + pin_to_core, + )?; + + Ok(Self { + driver, + task: Some(task), + _data: PhantomData, + }) + } + + pub fn driver(&self) -> &UartRxDriver<'d> { + self.driver.borrow() + } + + pub fn driver_mut(&mut self) -> &mut UartRxDriver<'d> { + self.driver.borrow_mut() + } + + pub async fn read(&self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + Ok(0) + } else { + loop { + let res = self.driver.borrow().read(buf, delay::NON_BLOCK); + + match res { + Ok(len) if len > 0 => return Ok(len), + Err(e) if e.code() != ESP_ERR_TIMEOUT => return Err(e), + _ => (), + } + + let port = self.driver.borrow().port as usize; + READ_NOTIFS[port].wait().await; + } + } + } +} + +impl<'d, T> Drop for AsyncUartRxDriver<'d, T> +where + T: BorrowMut>, +{ + fn drop(&mut self) { + if let Some(task) = self.task { + drop_task_common(task, self.driver.borrow().port); + } + } +} + +impl<'d, T> embedded_io::ErrorType for AsyncUartRxDriver<'d, T> +where + T: BorrowMut>, +{ + type Error = EspIOError; +} + +impl<'d, T> embedded_io_async::Read for AsyncUartRxDriver<'d, T> +where + T: BorrowMut>, +{ + async fn read(&mut self, buf: &mut [u8]) -> Result { + AsyncUartRxDriver::read(self, buf).await.map_err(EspIOError) + } +} + +pub struct AsyncUartTxDriver<'d, T> +where + T: BorrowMut>, +{ + driver: T, + task: Option, + _data: PhantomData<&'d ()>, +} + +impl<'d> AsyncUartTxDriver<'d, UartTxDriver<'d>> { + pub fn new( + uart: impl Peripheral

+ 'd, + tx: impl Peripheral

+ 'd, + cts: Option + 'd>, + rts: Option + 'd>, + config: &config::Config, + ) -> Result { + Self::wrap(UartTxDriver::new(uart, tx, cts, rts, config)?) + } +} + +impl<'d, T> AsyncUartTxDriver<'d, T> +where + T: BorrowMut>, +{ + pub fn wrap(driver: T) -> Result { + Self::wrap_custom(driver, None, None) + } + + pub fn wrap_custom( + driver: T, + priority: Option, + pin_to_core: Option, + ) -> Result { + let task = new_task_common( + driver.borrow().port, + driver.borrow().event_queue(), + priority, + pin_to_core, + )?; + + Ok(Self { + driver, + task: Some(task), + _data: PhantomData, + }) + } + + pub fn driver(&self) -> &UartTxDriver<'d> { + self.driver.borrow() + } + + pub fn driver_mut(&mut self) -> &mut UartTxDriver<'d> { + self.driver.borrow_mut() + } + + pub async fn write(&self, bytes: &[u8]) -> Result { + if bytes.is_empty() { + Ok(0) + } else { + loop { + let res = self.driver.borrow().write_nb(bytes); + + match res { + Ok(len) if len > 0 => return Ok(len), + Err(e) => return Err(e), + _ => (), + } + + // We cannot properly wait for the TX FIFO queue to become non-full + // because the ESP IDF UART ISR does not notify us on that + // + // Instead, spin a busy loop, however still allowing other futures to be polled too. + crate::task::yield_now().await; + } + } + } + + pub async fn wait_done(&self) -> Result<(), EspError> { + loop { + let res = self.driver.borrow().wait_done(delay::NON_BLOCK); + + match res { + Ok(()) => return Ok(()), + Err(e) if e.code() != ESP_ERR_TIMEOUT => return Err(e), + _ => (), + } + + // We cannot properly wait for the TX FIFO queue to become empty + // because the ESP IDF UART ISR does not notify us on that + // + // Instead, spin a busy loop, however still allowing other futures to be polled too. + crate::task::yield_now().await; + } + } +} + +impl<'d, T> Drop for AsyncUartTxDriver<'d, T> +where + T: BorrowMut>, +{ + fn drop(&mut self) { + if let Some(task) = self.task { + drop_task_common(task, self.driver.borrow().port); + } + } +} + +impl<'d, T> embedded_io::ErrorType for AsyncUartTxDriver<'d, T> +where + T: BorrowMut>, +{ + type Error = EspIOError; +} + +impl<'d, T> embedded_io_async::Write for AsyncUartTxDriver<'d, T> +where + T: BorrowMut>, +{ + async fn write(&mut self, buf: &[u8]) -> Result { + AsyncUartTxDriver::write(self, buf) + .await + .map_err(EspIOError) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + AsyncUartTxDriver::wait_done(self).await.map_err(EspIOError) + } +} + +fn new_task_common( + port: u8, + queue: Option<&Queue>, + priority: Option, + pin_to_core: Option, +) -> Result { + if let Some(queue) = queue { + let port = port as usize; + + unsafe { + QUEUES[port] = queue.as_raw() as _; + } + + let res = unsafe { + task::create( + process_events, + CStr::from_bytes_until_nul(b"UART - Events task\0").unwrap(), + 2048, + port as _, + priority.unwrap_or(6), + pin_to_core, + ) + }; + + if res.is_err() { + unsafe { + QUEUES[port] = core::ptr::null(); + } + } + + res + } else { + Err(EspError::from_infallible::()) + } +} + +fn drop_task_common(task: TaskHandle_t, port: u8) { + unsafe { + task::destroy(task); + QUEUES[port as usize] = core::ptr::null_mut(); + + READ_NOTIFS[port as usize].reset(); + WRITE_NOTIFS[port as usize].reset(); + TX_NOTIFS[port as usize].reset(); + } +} + +extern "C" fn process_events(arg: *mut core::ffi::c_void) { + let port: usize = arg as _; + let queue: Queue = unsafe { Queue::new_borrowed(QUEUES[port] as _) }; + + loop { + if let Some((event, _)) = queue.recv_front(delay::BLOCK) { + match event.payload() { + UartEventPayload::Data { .. } + | UartEventPayload::RxBufferFull + | UartEventPayload::RxFifoOverflow => { + READ_NOTIFS[port].notify_lsb(); + } + UartEventPayload::Break | UartEventPayload::DataBreak => { + WRITE_NOTIFS[port].notify_lsb(); + TX_NOTIFS[port].notify_lsb(); + } + _ => (), + } + } + } +} + fn new_common( _uart: impl Peripheral

, tx: Option>, @@ -910,28 +1966,14 @@ fn new_common( cts: Option>, rts: Option>, config: &config::Config, + queue: Option<&mut QueueHandle_t>, ) -> Result<(), EspError> { let tx = tx.map(|tx| tx.into_ref()); let rx = rx.map(|rx| rx.into_ref()); let cts = cts.map(|cts| cts.into_ref()); let rts = rts.map(|rts| rts.into_ref()); - #[allow(clippy::needless_update)] - let uart_config = uart_config_t { - baud_rate: config.baudrate.0 as i32, - data_bits: config.data_bits.into(), - parity: config.parity.into(), - stop_bits: config.stop_bits.into(), - flow_ctrl: config.flow_control.into(), - rx_flow_ctrl_thresh: config.flow_control_rts_threshold, - #[cfg(not(esp_idf_version_major = "4"))] - source_clk: config.source_clock.into(), - #[cfg(esp_idf_version_major = "4")] - __bindgen_anon_1: uart_config_t__bindgen_ty_1 { - source_clk: config.source_clock.into(), - }, - ..Default::default() - }; + let uart_config = config.into(); esp!(unsafe { uart_param_config(UART::port(), &uart_config) })?; @@ -948,14 +1990,29 @@ fn new_common( esp!(unsafe { uart_driver_install( UART::port(), - UART_FIFO_SIZE * 2, - if tx.is_some() { UART_FIFO_SIZE * 2 } else { 0 }, - 0, - ptr::null_mut(), - 0, + if rx.is_some() { + config.rx_fifo_size as _ + } else { + 0 + }, + if tx.is_some() { + config.tx_fifo_size as _ + } else { + 0 + }, + config.queue_size as _, + queue.map(|q| q as *mut _).unwrap_or(ptr::null_mut()), + InterruptType::to_native(config.intr_flags) as i32, ) })?; + esp!(unsafe { uart_set_mode(UART::port(), config.mode.into()) })?; + + // Configure interrupts after installing the driver + // so it won't get overwritten. + let usr_intrs = config.event_config.clone().into(); + esp!(unsafe { uart_intr_config(UART::port(), &usr_intrs as *const _) })?; + Ok(()) } @@ -1041,7 +2098,12 @@ impl Owner { Owner::Borrowed => false, Owner::Shared => REFS[port as usize].fetch_sub(1, Ordering::SeqCst) == 0, }; - needs_drop.then(|| delete_driver(port)).unwrap_or(Ok(())) + + if needs_drop { + delete_driver(port) + } else { + Ok(()) + } } } @@ -1057,20 +2119,25 @@ macro_rules! impl_uart { }; } -fn to_nb_err(err: EspError) -> nb::Error { - if err.code() == ESP_ERR_TIMEOUT { - nb::Error::WouldBlock - } else { - nb::Error::Other(SerialError::from(err)) +fn check_nb(result: Result, value: T) -> nb::Result { + match result { + Ok(len) => { + if len > 0 { + Ok(value) + } else { + Err(nb::Error::WouldBlock) + } + } + Err(err) if err.code() == ESP_ERR_TIMEOUT => Err(nb::Error::WouldBlock), + Err(err) => Err(nb::Error::Other(SerialError::new(ErrorKind::Other, err))), } } -fn check_nb(result: Result, value: T) -> nb::Result { +fn check_nb_timeout(result: Result<(), EspError>) -> nb::Result<(), SerialError> { match result { - Ok(1) => Ok(value), - Ok(0) => Err(nb::Error::WouldBlock), - Ok(_) => unreachable!(), - Err(err) => Err(nb::Error::Other(SerialError::other(err))), + Ok(()) => Ok(()), + Err(err) if err.code() == ESP_ERR_TIMEOUT => Err(nb::Error::WouldBlock), + Err(err) => Err(nb::Error::Other(SerialError::new(ErrorKind::Other, err))), } } @@ -1081,4 +2148,12 @@ impl_uart!(UART2: 2); #[allow(clippy::declare_interior_mutable_const)] const NO_REFS: AtomicU8 = AtomicU8::new(0); -static REFS: [AtomicU8; UART_NUM_MAX as usize] = [NO_REFS; UART_NUM_MAX as usize]; +static REFS: [AtomicU8; SOC_UART_NUM as usize] = [NO_REFS; SOC_UART_NUM as usize]; + +#[allow(clippy::declare_interior_mutable_const)] +const NOTIF: Notification = Notification::new(); +static READ_NOTIFS: [Notification; SOC_UART_NUM as usize] = [NOTIF; SOC_UART_NUM as usize]; +static WRITE_NOTIFS: [Notification; SOC_UART_NUM as usize] = [NOTIF; SOC_UART_NUM as usize]; +static TX_NOTIFS: [Notification; SOC_UART_NUM as usize] = [NOTIF; SOC_UART_NUM as usize]; +static mut QUEUES: [*const core::ffi::c_void; SOC_UART_NUM as usize] = + [core::ptr::null(); SOC_UART_NUM as usize]; diff --git a/src/ulp.rs b/src/ulp.rs index c68f376d833..c808356ac09 100644 --- a/src/ulp.rs +++ b/src/ulp.rs @@ -21,15 +21,13 @@ impl Default for SleepTimer { esp_idf_ulp_coproc_enabled, esp_idf_ulp_coproc_type_fsm ), - all(esp_idf_version_major = "4", esp32, esp_idf_esp32_ulp_coproc_enabled), + all(esp32, esp_idf_esp32_ulp_coproc_enabled), all( - esp_idf_version_major = "4", esp32s2, esp_idf_esp32s2_ulp_coproc_enabled, not(esp_idf_esp32s2_ulp_coproc_riscv) ), all( - esp_idf_version_major = "4", esp32s3, esp_idf_esp32s3_ulp_coproc_enabled, not(esp_idf_esp32s3_ulp_coproc_riscv) @@ -47,15 +45,13 @@ pub struct Word { esp_idf_ulp_coproc_enabled, esp_idf_ulp_coproc_type_fsm ), - all(esp_idf_version_major = "4", esp32, esp_idf_esp32_ulp_coproc_enabled), + all(esp32, esp_idf_esp32_ulp_coproc_enabled), all( - esp_idf_version_major = "4", esp32s2, esp_idf_esp32s2_ulp_coproc_enabled, not(esp_idf_esp32s2_ulp_coproc_riscv) ), all( - esp_idf_version_major = "4", esp32s3, esp_idf_esp32s3_ulp_coproc_enabled, not(esp_idf_esp32s3_ulp_coproc_riscv) @@ -77,49 +73,25 @@ impl Word { #[cfg(any( all(not(esp_idf_version_major = "4"), esp_idf_ulp_coproc_enabled), - all(esp_idf_version_major = "4", esp32, esp_idf_esp32_ulp_coproc_enabled), - all( - esp_idf_version_major = "4", - esp32s2, - esp_idf_esp32s2_ulp_coproc_enabled - ), - all( - esp_idf_version_major = "4", - esp32s3, - esp_idf_esp32s3_ulp_coproc_enabled - ) + all(esp32, esp_idf_esp32_ulp_coproc_enabled), + all(esp32s2, esp_idf_esp32s2_ulp_coproc_enabled), + all(esp32s3, esp_idf_esp32s3_ulp_coproc_enabled) ))] pub struct UlpDriver<'d>(crate::peripheral::PeripheralRef<'d, ULP>); #[cfg(any( all(not(esp_idf_version_major = "4"), esp_idf_ulp_coproc_enabled), - all(esp_idf_version_major = "4", esp32, esp_idf_esp32_ulp_coproc_enabled), - all( - esp_idf_version_major = "4", - esp32s2, - esp_idf_esp32s2_ulp_coproc_enabled - ), - all( - esp_idf_version_major = "4", - esp32s3, - esp_idf_esp32s3_ulp_coproc_enabled - ) + all(esp32, esp_idf_esp32_ulp_coproc_enabled), + all(esp32s2, esp_idf_esp32s2_ulp_coproc_enabled), + all(esp32s3, esp_idf_esp32s3_ulp_coproc_enabled) ))] unsafe impl<'d> Send for UlpDriver<'d> {} #[cfg(any( all(not(esp_idf_version_major = "4"), esp_idf_ulp_coproc_enabled), - all(esp_idf_version_major = "4", esp32, esp_idf_esp32_ulp_coproc_enabled), - all( - esp_idf_version_major = "4", - esp32s2, - esp_idf_esp32s2_ulp_coproc_enabled - ), - all( - esp_idf_version_major = "4", - esp32s3, - esp_idf_esp32s3_ulp_coproc_enabled - ) + all(esp32, esp_idf_esp32_ulp_coproc_enabled), + all(esp32s2, esp_idf_esp32s2_ulp_coproc_enabled), + all(esp32s3, esp_idf_esp32s3_ulp_coproc_enabled) ))] impl<'d> UlpDriver<'d> { pub fn new( @@ -180,7 +152,9 @@ impl<'d> UlpDriver<'d> { if ptr < mem_start || ptr.offset(core::mem::size_of::() as _) > mem_start.offset(ULP::MEM_SIZE as _) { - esp_idf_sys::esp!(esp_idf_sys::ESP_ERR_INVALID_SIZE)?; + return Err(esp_idf_sys::EspError::from_infallible::< + { esp_idf_sys::ESP_ERR_INVALID_SIZE }, + >()); } } @@ -194,15 +168,13 @@ impl<'d> UlpDriver<'d> { esp_idf_ulp_coproc_enabled, esp_idf_ulp_coproc_type_fsm ), - all(esp_idf_version_major = "4", esp32, esp_idf_esp32_ulp_coproc_enabled), + all(esp32, esp_idf_esp32_ulp_coproc_enabled), all( - esp_idf_version_major = "4", esp32s2, esp_idf_esp32s2_ulp_coproc_enabled, not(esp_idf_esp32s2_ulp_coproc_riscv) ), all( - esp_idf_version_major = "4", esp32s3, esp_idf_esp32s3_ulp_coproc_enabled, not(esp_idf_esp32s3_ulp_coproc_riscv) @@ -216,11 +188,15 @@ impl<'d> UlpDriver<'d> { ) -> Result<(), esp_idf_sys::EspError> { let address: usize = core::mem::transmute(address); if address % core::mem::size_of::() != 0 { - esp_idf_sys::esp!(esp_idf_sys::ESP_ERR_INVALID_ARG)?; + return Err(esp_idf_sys::EspError::from_infallible::< + { esp_idf_sys::ESP_ERR_INVALID_ARG }, + >()); } if program.len() % core::mem::size_of::() != 0 { - esp_idf_sys::esp!(esp_idf_sys::ESP_ERR_INVALID_SIZE)?; + return Err(esp_idf_sys::EspError::from_infallible::< + { esp_idf_sys::ESP_ERR_INVALID_SIZE }, + >()); } esp_idf_sys::esp!(esp_idf_sys::ulp_load_binary( @@ -283,7 +259,9 @@ impl<'d> UlpDriver<'d> { let ptr_usize: usize = unsafe { core::mem::transmute(ptr) }; if ptr_usize % core::mem::size_of::() != 0 { - esp_idf_sys::esp!(esp_idf_sys::ESP_ERR_INVALID_SIZE)?; + return Err(esp_idf_sys::EspError::from_infallible::< + { esp_idf_sys::ESP_ERR_INVALID_SIZE }, + >()); } Ok(()) @@ -297,17 +275,15 @@ impl<'d> UlpDriver<'d> { not(esp_idf_ulp_coproc_type_fsm) ), all( - esp_idf_version_major = "4", esp32s2, esp_idf_esp32s2_ulp_coproc_enabled, esp_idf_esp32s2_ulp_coproc_riscv ), all( - esp_idf_version_major = "4", esp32s3, esp_idf_esp32s3_ulp_coproc_enabled, esp_idf_esp32s3_ulp_coproc_riscv - ) + ), ))] impl<'d> UlpDriver<'d> { pub unsafe fn load(&mut self, program: &[u8]) -> Result<(), esp_idf_sys::EspError> { @@ -356,17 +332,9 @@ crate::impl_peripheral!(ULP); #[cfg(any( all(not(esp_idf_version_major = "4"), esp_idf_ulp_coproc_enabled), - all(esp_idf_version_major = "4", esp32, esp_idf_esp32_ulp_coproc_enabled), - all( - esp_idf_version_major = "4", - esp32s2, - esp_idf_esp32s2_ulp_coproc_enabled - ), - all( - esp_idf_version_major = "4", - esp32s3, - esp_idf_esp32s3_ulp_coproc_enabled - ) + all(esp32, esp_idf_esp32_ulp_coproc_enabled), + all(esp32s2, esp_idf_esp32s2_ulp_coproc_enabled), + all(esp32s3, esp_idf_esp32s3_ulp_coproc_enabled) ))] impl ULP { const RTC_SLOW_MEM: u32 = 0x5000_0000_u32; diff --git a/src/units.rs b/src/units.rs index 4df4d753368..bf257e70d67 100644 --- a/src/units.rs +++ b/src/units.rs @@ -13,8 +13,6 @@ //! let frequency_hz_1 = 1.Hz() + frequency_khz_3.into(), //! ``` -use core::convert::TryFrom; -use core::convert::TryInto; use core::fmt; pub type ValueType = u32; diff --git a/src/usb_serial.rs b/src/usb_serial.rs new file mode 100644 index 00000000000..a367c23792a --- /dev/null +++ b/src/usb_serial.rs @@ -0,0 +1,225 @@ +//! USB Serial / JTAG peripheral and driver +//! +//! Communication through a virtualized UART-like USB-CDC interface. +#![allow(non_camel_case_types)] + +use crate::io::EspIOError; +use crate::peripheral::{Peripheral, PeripheralRef}; +use crate::sys::{ + esp, usb_serial_jtag_driver_config_t, usb_serial_jtag_driver_install, + usb_serial_jtag_driver_uninstall, usb_serial_jtag_is_connected, usb_serial_jtag_read_bytes, + usb_serial_jtag_write_bytes, EspError, TickType_t, +}; +use crate::{delay, gpio}; + +/// A type alias for the USB Serial driver configuration +pub type UsbSerialConfig = config::Config; + +/// USB D- GPIO pin +#[cfg(esp32c3)] +pub type UsbDMinGpio = gpio::Gpio18; +/// USB D+ GPIO pin +#[cfg(esp32c3)] +pub type UsbDPlusGpio = gpio::Gpio19; +/// USB D- GPIO pin +#[cfg(esp32c5)] +pub type UsbDMinGpio = gpio::Gpio13; +/// USB D+ GPIO pin +#[cfg(esp32c5)] +pub type UsbDPlusGpio = gpio::Gpio14; +/// USB D- GPIO pin +#[cfg(esp32c6)] +pub type UsbDMinGpio = gpio::Gpio12; +/// USB D+ GPIO pin +#[cfg(esp32c6)] +pub type UsbDPlusGpio = gpio::Gpio13; +/// USB D- GPIO pin +#[cfg(esp32h2)] +pub type UsbDMinGpio = gpio::Gpio26; +/// USB D+ GPIO pin +#[cfg(esp32h2)] +pub type UsbDPlusGpio = gpio::Gpio27; +/// USB D- GPIO pin +#[cfg(esp32p4)] +pub type UsbDMinGpio = gpio::Gpio24; +/// USB D+ GPIO pin +#[cfg(esp32p4)] +pub type UsbDPlusGpio = gpio::Gpio25; +// TODO +// #[cfg(esp32p4)] +// pub type UsbDMinGpio2 = gpio::Gpio26; +// #[cfg(esp32p4)] +// pub type UsbDPlusGpio2 = gpio::Gpio27; +/// USB D- GPIO pin +#[cfg(esp32s3)] +pub type UsbDMinGpio = gpio::Gpio19; +/// USB D+ GPIO pin +#[cfg(esp32s3)] +pub type UsbDPlusGpio = gpio::Gpio20; + +/// USB Serial driver configuration +pub mod config { + /// USB Serial driver configuration + #[derive(Debug, Clone)] + #[non_exhaustive] + pub struct Config { + pub tx_buffer_size: usize, + pub rx_buffer_size: usize, + } + + impl Config { + /// Create a new configuration with default values + pub const fn new() -> Self { + Self { + tx_buffer_size: 256, + rx_buffer_size: 256, + } + } + + /// Set the transmit buffer size + #[must_use] + pub fn tx_buffer_size(mut self, tx_buffer_size: usize) -> Self { + self.tx_buffer_size = tx_buffer_size; + self + } + + /// Set the receive buffer size + #[must_use] + pub fn rx_buffer_size(mut self, rx_buffer_size: usize) -> Self { + self.rx_buffer_size = rx_buffer_size; + self + } + } + + impl Default for Config { + fn default() -> Self { + Self::new() + } + } +} + +/// USB-SERIAL driver +pub struct UsbSerialDriver<'d>(PeripheralRef<'d, USB_SERIAL>); + +impl<'d> UsbSerialDriver<'d> { + /// Create a new USB Serial driver + /// + /// # Arguments + /// - `usb_serial`: The USB Serial peripheral + /// - `config`: The driver configuration + /// - `usb_d_min`: The USB D- GPIO pin + /// - `usb_d_plus`: The USB D+ GPIO pin + pub fn new( + usb_serial: impl Peripheral

+ 'd, + _usb_d_min: impl Peripheral

, + _usb_d_plus: impl Peripheral

, + config: &config::Config, + ) -> Result { + crate::into_ref!(usb_serial); + + let mut config = usb_serial_jtag_driver_config_t { + tx_buffer_size: config.tx_buffer_size as _, + rx_buffer_size: config.rx_buffer_size as _, + }; + + esp!(unsafe { usb_serial_jtag_driver_install(&mut config) })?; + + Ok(Self(usb_serial)) + } + + /// Check if the USB Serial is connected + pub fn is_connected(&self) -> bool { + unsafe { usb_serial_jtag_is_connected() } + } + + /// Read bytes into a slice + /// + /// # Arguments + /// - `buf`: The buffer to read into + /// - `timeout`: The timeout in ticks + /// + /// # Returns + /// The number of bytes read or an error if the operation failed or the timeout was reached + pub fn read(&mut self, buf: &mut [u8], timeout: TickType_t) -> Result { + let len = unsafe { + usb_serial_jtag_read_bytes(buf.as_mut_ptr() as *mut _, buf.len() as _, timeout) + }; + + Ok(len as _) + } + + /// Write bytes from a slice + /// + /// # Arguments + /// - `bytes`: The bytes to write + /// - `timeout`: The timeout in ticks + /// + /// # Returns + /// The number of bytes written or an error if the operation failed or the timeout was reached + pub fn write(&mut self, bytes: &[u8], timeout: TickType_t) -> Result { + let len = unsafe { + usb_serial_jtag_write_bytes(bytes.as_ptr() as *const _, bytes.len() as _, timeout) + }; + + Ok(len as _) + } +} + +impl Drop for UsbSerialDriver<'_> { + fn drop(&mut self) { + esp!(unsafe { usb_serial_jtag_driver_uninstall() }).unwrap(); + } +} + +unsafe impl Send for UsbSerialDriver<'_> {} + +impl embedded_io::ErrorType for UsbSerialDriver<'_> { + type Error = EspIOError; +} + +impl embedded_io::Read for UsbSerialDriver<'_> { + fn read(&mut self, buf: &mut [u8]) -> Result { + UsbSerialDriver::read(self, buf, delay::BLOCK).map_err(EspIOError) + } +} + +impl embedded_io::Write for UsbSerialDriver<'_> { + fn write(&mut self, buf: &[u8]) -> Result { + UsbSerialDriver::write(self, buf, delay::BLOCK).map_err(EspIOError) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl embedded_hal_0_2::serial::Write for UsbSerialDriver<'_> { + type Error = EspError; + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + Ok(()) + } + + fn write(&mut self, byte: u8) -> nb::Result<(), Self::Error> { + UsbSerialDriver::write(self, &[byte], delay::BLOCK)?; + + Ok(()) + } +} + +impl core::fmt::Write for UsbSerialDriver<'_> { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + let buf = s.as_bytes(); + let mut offset = 0; + + while offset < buf.len() { + offset += self + .write(buf, delay::BLOCK) + .map_err(|_| core::fmt::Error)? + } + + Ok(()) + } +} + +crate::impl_peripheral!(USB_SERIAL);